So in an event-driven server, how do you ...

Chris Gray cg at ami-cg.GraySage.Edmonton.AB.CA
Mon Apr 7 19:05:43 New Zealand Standard Time 1997


:It looks like you guys hate polling.  (Not that I blame you, I don't
:really like it myself. :)  But if you've got a system that just sits
:until the next "ripening", how do you do your IO with players?  Do you 
:use an endless loop to poll the connections?  Or do you use the SIGIO
:signal?

No, just use the BSD 'select' call. It simultaneously allows for a whole
mess of sockets, and a timeout. Here is more detail than you ever wanted
(its the entire 'main' from my ToyMUD system):

int
main(int argc, char *argv[])
{
    struct sockaddr_in myAddr;
    int hostPort;
    Variable_t *globalContext;

    hostPort = DEFAULT_PORT;
    DbPath = DEFAULT_DB;
    if (argc >= 2) {
	hostPort = atoi(argv[1]);
	if (argc >= 3) {
	    DbPath = argv[2];
	}
    }

    Clients = NULL;
    Actions = NULL;
    PlayerCount = 0;
    globalContext = readDB(DbPath);
    Socket = socket(AF_INET, SOCK_STREAM, 0);
    if (Socket < 0) {
	perror("socket");
	exit(1);
    }
    memset(&myAddr, 0, sizeof(struct sockaddr_in));
    myAddr.sin_port = htons(hostPort);
    if (bind(Socket, (struct sockaddr *) &myAddr,
	     sizeof(struct sockaddr_in)) < 0)
    {
	perror("bind");
	exit(1);
    }
    if (listen(Socket, 5) < 0) {
	perror("listen");
	exit(1);
    }

    FD_ZERO(&AllPlayerBits);
    initInterpreter(globalContext);
    WantQuit = false;
    CurrentDelay = MAX_DELAY;
    LastTime = time(NULL);
    srand(LastTime);
    signal(SIGINT, interruptHandler);
#ifndef __SASC
    signal(SIGPIPE, SIG_IGN);
#endif
    startMachines();
    while (! WantQuit || PlayerCount != 0) {
	fd_set readFds, exceptFds;
	struct sockaddr_in from;
	Client_t *cl;
	int res;
	struct timeval timeval;

	cleanupMachines();
	readFds = AllPlayerBits;
	FD_SET(Socket, &readFds);
	exceptFds = readFds;
	timeval.tv_sec = CurrentDelay;
	timeval.tv_usec = 0;
	res = select(FD_SETSIZE, &readFds, (fd_set *) NULL, &exceptFds,
		     &timeval);
	if (res < 0) {
	    if (errno != EINTR) {
		perror("select");
		mudAbort(NULL);
	    }
	    WantQuit = true;
	} else if (res == 0) {
	    doDelayedActions();
	} else {
	    if (FD_ISSET(Socket, &exceptFds)) {
		perror("select");
		mudAbort(NULL);
	    }
	    if (FD_ISSET(Socket, &readFds)) {
		int clientFd;
#ifdef __SASC
		LONG fromLen;
#else
		int fromLen;
#endif

		fromLen = sizeof(struct sockaddr_in);
		clientFd = accept(Socket, (struct sockaddr *) &from, &fromLen);
		if (clientFd < 0) {
		    if (errno != EINTR) {
			perror("accept");
			mudAbort(NULL);
		    }
		} else {
		    struct hostent *hp;

		    cl = (Client_t *) mudAlloc(sizeof(Client_t));
		    cl->cl_fd = clientFd;
		    hp = gethostbyaddr((const char *) &from,
				       sizeof(struct in_addr), AF_INET);
		    if (hp == NULL) {
			sprintf(&PrintMessage[0], "%08lx",
				from.sin_addr.s_addr);
		    } else {
			strcpy(&PrintMessage[0], hp->h_name);
		    }
		    cl->cl_hostName = strdup(&PrintMessage[0]);
		    cl->cl_state = cs_name;
		    cl->cl_player = NULL;
		    cl->cl_next = Clients;
		    if (Clients != NULL) {
			Clients->cl_prev = cl;
		    }
		    cl->cl_prev = NULL;
		    cl->cl_inputLength = 0;
		    cl->cl_exiting = false;
		    cl->cl_isMachine = false;
		    cl->cl_deleted = false;
		    Clients = cl;
		    PlayerCount += 1;
		    FD_SET(clientFd, &AllPlayerBits);
		    cprintf(cl, "Enter player name: ");
		}
	    }
	    cl = Clients;
	    while (cl != NULL) {
		Client_t *nextClient;

		nextClient = cl->cl_next;
		if (FD_ISSET(cl->cl_fd, &exceptFds)) {
		    if (cl->cl_state == cs_createQuery ||
			cl->cl_state == cs_createPW1 ||
			cl->cl_state == cs_createPW2)
		    {
			deletePlayer(cl->cl_player);
			cl->cl_player = NULL;
		    } else {
			runWithPlayer(cl, cl->cl_player, "QUIT_ACTION");
		    }
		    killPlayer(cl);
		} else if (FD_ISSET(cl->cl_fd, &readFds)) {
		    processInput(cl);
		}
		cl = nextClient;
	    }
	    doDelayedActions();
	}
    }

    shutdown(Socket, 2);
    writeDB(DbPath, getGlobals());
    return(0);
}


Wheeeee!

Activity on the main socket, 'Socket', is only connection requests from
new clients. 'killPlayer' shutsdown the client. 'processInput' reads input
data from the client socket, and if full lines are found, processes them
as MUD commands (or program segments, since this MUD allows everyone to
program). This code runs on SunOS4, Solaris, AIX, HALOS, HP-UX and Amiga,
and probably more that I haven't personally tested. You *might* encounter
a non-BSD system that requires you to use 'poll' instead of 'select', but
you can mix'n'match with conditional compilation.

In AmigaMUD its a bit different, since its not UNIX, and there everything
is done via a slightly extended 'SelectWait' call, which combines the
normal 'select' call with the Amiga 'Wait' call, which waits for any
Amiga-style events, one of which is my MUD-event timer.

--
Chris Gray   cg at ami-cg.GraySage.Edmonton.AB.CA



More information about the MUD-Dev mailing list