tNon-blocking network handling abstracted out into NetworkBuffer datatype - vaccinewars - be a doctor and try to vaccinate the world
 (HTM) git clone git://src.adamsgaard.dk/vaccinewars
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 7d0b10dce7f72780b488b3df2800347731565a73
 (DIR) parent e694fa9cbc3214dbf38465474571e2d9209f7d23
 (HTM) Author: Ben Webb <ben@salilab.org>
       Date:   Tue,  5 Jun 2001 02:45:07 +0000
       
       Non-blocking network handling abstracted out into NetworkBuffer datatype
       
       
       Diffstat:
         M TODO                                |       9 ++-------
         M src/AIPlayer.c                      |      32 +++++++++++++++----------------
         M src/curses_client.c                 |      31 +++++++++++++------------------
         M src/dopewars.c                      |      17 +++++++----------
         M src/dopewars.h                      |      11 +++++++++--
         M src/gtk_client.c                    |      36 +++++++++++++------------------
         M src/message.c                       |     170 +++++++++++++++++++++++++------
         M src/message.h                       |      24 ++++++++++++++++++++----
         M src/serverside.c                    |      61 ++++++++++---------------------
       
       9 files changed, 240 insertions(+), 151 deletions(-)
       ---
 (DIR) diff --git a/TODO b/TODO
       t@@ -1,13 +1,8 @@
       -DONE - Tidy up display of high scores in GUI client
       -- Revamp player-player fighting; use same system for fighting the cops and
       -  for fighting other players (perhaps the cops can intervene in fights);
       -  add SWAT teams, soldiers, etc. as dealers get more and more guns
       -- Increase cops' toughness - they should kill a bitch in 50-70% of encounters
       -  (and damage should be cumulative)
       +- Fix problem with dialogs popping up while menus are open
       +- Fix problem with Jet dialog during fights
        - Increase difficulty of escaping from another player - impose penalty on
          running (lose drugs, free shot, destination revealed)
        - Alliances/cartels - several players share cash
       -- Graphical mode server? (would avoid select() problems under Win32)
        - Introduce minimum/maximum players options - AI players automatically
          spawned/killed to "fill the gaps" when humans leave/enter
        - "Deal" option when meeting players?
 (DIR) diff --git a/src/AIPlayer.c b/src/AIPlayer.c
       t@@ -54,7 +54,8 @@ void AIPlayerLoop() {
           gchar *pt;
           Player *AIPlay;
           fd_set readfs,writefs;
       -   gboolean ReadOK,QuitRequest;
       +   gboolean DataWaiting,QuitRequest;
       +   int MaxSock;
        
           AIPlay=g_new(Player,1);
           FirstClient=AddPlayer(0,AIPlay,FirstClient);
       t@@ -67,7 +68,7 @@ void AIPlayerLoop() {
                      "AI Player terminating abnormally."),_(pt));
              return;
           }
       -   AIPlay->fd=ClientSock;
       +   BindNetworkBufferToSocket(&AIPlay->NetBuf,ClientSock);
        
           InitAbilities(AIPlay);
           SendAbilities(AIPlay);
       t@@ -81,31 +82,28 @@ void AIPlayerLoop() {
           while (1) {
              FD_ZERO(&readfs);
              FD_ZERO(&writefs);
       -      FD_SET(ClientSock,&readfs);
       -      if (AIPlay->WriteBuf.DataPresent) FD_SET(ClientSock,&writefs);
       -      if (bselect(ClientSock+1,&readfs,&writefs,NULL,NULL)==-1) {
       +      MaxSock=0;
       +
       +      SetSelectForNetworkBuffer(&AIPlay->NetBuf,&readfs,&writefs,NULL,&MaxSock);
       +
       +      if (bselect(MaxSock,&readfs,&writefs,NULL,NULL)==-1) {
                 if (errno==EINTR) continue;
                 printf("Error in select\n"); exit(1);
              }
       -      if (FD_ISSET(ClientSock,&writefs)) {
       -         WriteConnectionBufferToWire(AIPlay);
       -      }
       -      if (FD_ISSET(ClientSock,&readfs)) {
       -         QuitRequest=FALSE;
       -         ReadOK=ReadConnectionBufferFromWire(AIPlay);
        
       -         while ((pt=ReadFromConnectionBuffer(AIPlay))!=NULL) {
       +      if (!RespondToSelect(&AIPlay->NetBuf,&readfs,&writefs,
       +                           NULL,&DataWaiting)) {
       +         g_print(_("Connection to server lost!\n"));
       +         break;
       +      } else if (DataWaiting) {
       +         QuitRequest=FALSE;
       +         while ((pt=GetWaitingPlayerMessage(AIPlay))!=NULL) {
                    if (HandleAIMessage(pt,AIPlay)) {
                       QuitRequest=TRUE;
                       break;
                    }
                 }
                 if (QuitRequest) break;
       -
       -         if (!ReadOK) {
       -            g_print(_("Connection to server lost!\n"));
       -            break;
       -         }
              }
           }
           ShutdownNetwork();
 (DIR) diff --git a/src/curses_client.c b/src/curses_client.c
       t@@ -1507,7 +1507,7 @@ static void Curses_DoGame(Player *Play) {
           char HaveWorthless;
           Player *tmp;
           struct sigaction sact;
       -   gboolean ReadOK;
       +   gboolean DataWaiting;
        
           DisplayMode=DM_NONE;
           QuitRequest=FALSE;
       t@@ -1535,7 +1535,7 @@ static void Curses_DoGame(Player *Play) {
        #if NETWORKING
           if (WantNetwork) {
              if (!ConnectToServer(Play)) { end_curses(); exit(1); }
       -      Play->fd=ClientSock;
       +      BindNetworkBufferToSocket(&Play->NetBuf,ClientSock);
           }
        #endif /* NETWORKING */
           print_status(Play,TRUE);
       t@@ -1642,9 +1642,8 @@ static void Curses_DoGame(Player *Play) {
              FD_ZERO(&writefs);
              FD_SET(0,&readfs); MaxSock=1;
              if (Client) {
       -         FD_SET(Play->fd,&readfs);
       -         if (Play->WriteBuf.DataPresent) FD_SET(Play->fd,&writefs);
       -         MaxSock=ClientSock+2;
       +         SetSelectForNetworkBuffer(&Play->NetBuf,&readfs,&writefs,
       +                                   NULL,&MaxSock);
              }
              if (bselect(MaxSock,&readfs,&writefs,NULL,NULL)==-1) {
                 if (errno==EINTR) {
       t@@ -1653,16 +1652,9 @@ static void Curses_DoGame(Player *Play) {
                 }
                 perror("bselect"); exit(1);
              }
       -      if (Client && FD_ISSET(Play->fd,&readfs)) {
       -         ReadOK=ReadConnectionBufferFromWire(Play);
       -
       -         while ((pt=ReadFromConnectionBuffer(Play))!=NULL) {
       -            HandleClientMessage(pt,Play);
       -            g_free(pt);
       -         }
       -         if (QuitRequest) return;
       -
       -         if (!ReadOK) {
       +      if (Client) {
       +         if (!RespondToSelect(&Play->NetBuf,&readfs,&writefs,
       +                              NULL,&DataWaiting)) {
                    attrset(TextAttr);
                    clear_line(22);
                    mvaddstr(22,0,_("Connection to server lost! "
       t@@ -1670,11 +1662,14 @@ static void Curses_DoGame(Player *Play) {
                    nice_wait();
                    SwitchToSinglePlayer(Play);
                    print_status(Play,TRUE);
       +         } else if (DataWaiting) {
       +            while ((pt=GetWaitingPlayerMessage(Play))!=NULL) {
       +               HandleClientMessage(pt,Play);
       +               g_free(pt);
       +            }
       +            if (QuitRequest) return;
                 }
              }
       -      if (Client && FD_ISSET(Play->fd,&writefs)) {
       -         WriteConnectionBufferToWire(Play);
       -      }
              if (FD_ISSET(0,&readfs)) {
        #elif HAVE_SELECT
              FD_ZERO(&readfs);
 (DIR) diff --git a/src/dopewars.c b/src/dopewars.c
       t@@ -600,7 +600,6 @@ GSList *AddPlayer(int fd,Player *NewPlayer,GSList *First) {
                 list=g_slist_next(list);
              }
           }
       -   NewPlayer->fd=-1;
           NewPlayer->Name=NULL;
           SetPlayerName(NewPlayer,NULL);
           NewPlayer->IsAt=0;
       t@@ -619,11 +618,11 @@ GSList *AddPlayer(int fd,Player *NewPlayer,GSList *First) {
           NewPlayer->Health=100;
           NewPlayer->CoatSize=100;
           NewPlayer->Flags=0;
       -   NewPlayer->ReadBuf.Data=NewPlayer->WriteBuf.Data=NULL;
       -   NewPlayer->ReadBuf.Length=NewPlayer->WriteBuf.Length=0;
       -   NewPlayer->ReadBuf.DataPresent=NewPlayer->WriteBuf.DataPresent=0;
       +#if NETWORKING
       +   InitNetworkBuffer(&NewPlayer->NetBuf,'\n');
       +   if (Server) BindNetworkBufferToSocket(&NewPlayer->NetBuf,fd);
       +#endif
           InitAbilities(NewPlayer);
       -   if (Server) NewPlayer->fd=fd;
           NewPlayer->FightArray=NULL;
           NewPlayer->Attacking=NULL;
           return g_slist_append(First,(gpointer)NewPlayer);
       t@@ -643,13 +642,11 @@ GSList *RemovePlayer(Player *Play,GSList *First) {
           g_assert(First);
        
           First=g_slist_remove(First,(gpointer)Play);
       -   if (Server && !IsCop(Play) && Play->fd>=0) {
       -      CloseSocket(Play->fd);
       -   }
       +#if NETWORKING
       +   if (!IsCop(Play)) ShutdownNetworkBuffer(&Play->NetBuf);
       +#endif
           ClearList(&(Play->SpyList));
           ClearList(&(Play->TipList));
       -   g_free(Play->ReadBuf.Data);
       -   g_free(Play->WriteBuf.Data);
           g_free(Play->Name);
           g_free(Play);
           return First;
 (DIR) diff --git a/src/dopewars.h b/src/dopewars.h
       t@@ -277,6 +277,14 @@ typedef struct tagConnBuf {
           int DataPresent; /* number of bytes currently in "Data"   */
        } ConnBuf;            
        
       +/* Handles reading and writing messages from/to a network connection */
       +typedef struct tagNetworkBuffer {
       +   int fd;              /* File descriptor of the socket */
       +   char Terminator;     /* Character that separates messages */
       +   ConnBuf ReadBuf;     /* New data, waiting for the application */
       +   ConnBuf WriteBuf;    /* Data waiting to be written to the wire */
       +} NetworkBuffer;
       +
        struct PLAYER_T {
           guint ID;
           int Turn;
       t@@ -287,13 +295,12 @@ struct PLAYER_T {
           char Flags;
           gchar *Name;
           Inventory *Guns,*Drugs,Bitches;
       -   int fd;
           int EventNum,ResyncNum;
           time_t FightTimeout,IdleTimeout,ConnectTimeout;
           price_t DocPrice;
           DopeList SpyList,TipList;
           Player *OnBehalfOf;
       -   ConnBuf ReadBuf,WriteBuf;
       +   NetworkBuffer NetBuf;
           Abilities Abil;
           gint InputTag;
           GPtrArray *FightArray; /* If non-NULL, a list of players in a fight */
 (DIR) diff --git a/src/gtk_client.c b/src/gtk_client.c
       t@@ -257,26 +257,20 @@ void ListInventory(GtkWidget *widget,gpointer data) {
        void GetClientMessage(gpointer data,gint socket,
                              GdkInputCondition condition) {
           gchar *pt;
       -   gboolean ReadOK;
       -   if (condition&GDK_INPUT_WRITE) {
       -      WriteConnectionBufferToWire(ClientData.Play);
       -      if (ClientData.Play->WriteBuf.DataPresent==0) {
       -         SetSocketWriteTest(ClientData.Play,FALSE);
       -      }
       -   }
       -   if (condition&GDK_INPUT_READ) {
       -      ReadOK=ReadConnectionBufferFromWire(ClientData.Play);
       -      while ((pt=ReadFromConnectionBuffer(ClientData.Play))!=NULL) {
       -         HandleClientMessage(pt,ClientData.Play); g_free(pt);
       -      }
       -      if (!ReadOK) {
       -         if (Network) gdk_input_remove(ClientData.GdkInputTag);
       -         if (InGame) {
       +   gboolean DataWaiting;
       +   if (!PlayerHandleNetwork(ClientData.Play,condition&GDK_INPUT_READ,
       +                            condition&GDK_INPUT_WRITE,&DataWaiting)) {
       +      if (Network) gdk_input_remove(ClientData.GdkInputTag);
       +      if (InGame) {
        /* The network connection to the server was dropped unexpectedly */
       -            g_warning(_("Connection to server lost - switching to "
       -                      "single player mode"));
       -            SwitchToSinglePlayer(ClientData.Play);
       -         }
       +         g_warning(_("Connection to server lost - switching to "
       +                   "single player mode"));
       +         SwitchToSinglePlayer(ClientData.Play);
       +      }
       +   } else if (DataWaiting) {
       +      while ((pt=GetWaitingPlayerMessage(ClientData.Play))!=NULL) {
       +         HandleClientMessage(pt,ClientData.Play);
       +         g_free(pt);
              }
           }
        }
       t@@ -284,7 +278,7 @@ void GetClientMessage(gpointer data,gint socket,
        void SetSocketWriteTest(Player *Play,gboolean WriteTest) {
           if (Network) {
              if (ClientData.GdkInputTag) gdk_input_remove(ClientData.GdkInputTag);
       -      ClientData.GdkInputTag=gdk_input_add(Play->fd,
       +      ClientData.GdkInputTag=gdk_input_add(Play->NetBuf.fd,
                                     GDK_INPUT_READ|(WriteTest ? GDK_INPUT_WRITE : 0),
                                     GetClientMessage,NULL);
           }
       t@@ -1513,7 +1507,7 @@ void StartGame() {
           Player *Play;
           Play=ClientData.Play=g_new(Player,1);
           FirstClient=AddPlayer(0,Play,FirstClient);
       -   Play->fd=ClientSock;
       +   BindNetworkBufferToSocket(&Play->NetBuf,ClientSock);
           InitAbilities(Play);
           SendAbilities(Play);
           SetPlayerName(Play,ClientData.PlayerName);
 (DIR) diff --git a/src/message.c b/src/message.c
       t@@ -147,7 +147,7 @@ void DoSendClientMessage(Player *From,char AICode,char Code,
              HandleServerMessage(text->str,ServerFrom);
        #if NETWORKING
           } else {
       -      WriteToConnectionBuffer(BufOwn,text->str);
       +      QueuePlayerMessageForSend(BufOwn,text->str);
              if (SocketWriteTestPt) (*SocketWriteTestPt)(BufOwn,TRUE);
           }
        #endif /* NETWORKING */
       t@@ -193,7 +193,7 @@ void SendServerMessage(Player *From,char AICode,char Code,
              }
        #if NETWORKING
           } else {
       -      WriteToConnectionBuffer(To,text->str);
       +      QueuePlayerMessageForSend(To,text->str);
              if (SocketWriteTestPt) (*SocketWriteTestPt)(To,TRUE);
           }
        #endif
       t@@ -279,19 +279,117 @@ gboolean HaveAbility(Player *Play,gint Type) {
        }
        
        #if NETWORKING
       -gchar *ReadFromConnectionBuffer(Player *Play) {
       -/* Reads a newline-terminated message from "Play"'s read buffer. The message */
       -/* is removed from the buffer, and returned as a null-terminated string (the */
       -/* terminating newline is removed). If no complete message is waiting, NULL  */
       -/* is returned. The string is dynamically allocated, and must be g_free'd by */
       -/* the caller.                                                               */
       +void InitNetworkBuffer(NetworkBuffer *NetBuf,char Terminator) {
       +/* Initialises the passed network buffer, ready for use. Messages sent */
       +/* or received on the buffered connection will be terminated by the    */
       +/* given character.                                                    */
       +   NetBuf->fd=-1;
       +   NetBuf->Terminator=Terminator;
       +   NetBuf->ReadBuf.Data=NetBuf->WriteBuf.Data=NULL;
       +   NetBuf->ReadBuf.Length=NetBuf->WriteBuf.Length=0;
       +   NetBuf->ReadBuf.DataPresent=NetBuf->WriteBuf.DataPresent=0;
       +}
       +
       +void BindNetworkBufferToSocket(NetworkBuffer *NetBuf,int fd) {
       +/* Sets up the given network buffer to handle data being sent/received */
       +/* through the given socket                                            */
       +   NetBuf->fd=fd;
       +}
       +
       +void ShutdownNetworkBuffer(NetworkBuffer *NetBuf) {
       +/* Frees the network buffer's data structures (leaving it in the  */
       +/* 'initialised' state) and closes the accompanying socket.       */
       +   if (NetBuf->fd>0) CloseSocket(NetBuf->fd);
       +
       +   g_free(NetBuf->ReadBuf.Data);
       +   g_free(NetBuf->WriteBuf.Data);
       +   InitNetworkBuffer(NetBuf,NetBuf->Terminator);
       +}
       +
       +void SetSelectForNetworkBuffer(NetworkBuffer *NetBuf,fd_set *readfds,
       +                               fd_set *writefds,fd_set *errorfds,int *MaxSock) {
       +/* Updates the sets of read and write file descriptors to monitor    */
       +/* input to/output from the given network buffer. MaxSock is updated */
       +/* with the highest-numbered file descriptor (plus 1) for use in a   */
       +/* later select() call.                                              */
       +   if (!NetBuf || NetBuf->fd<=0) return;
       +   FD_SET(NetBuf->fd,readfds);
       +   if (errorfds) FD_SET(NetBuf->fd,errorfds);
       +   if (NetBuf->fd >= *MaxSock) *MaxSock=NetBuf->fd+1;
       +   if (NetBuf->WriteBuf.DataPresent) FD_SET(NetBuf->fd,writefds);
       +}
       +
       +static gboolean DoNetworkBufferStuff(NetworkBuffer *NetBuf,gboolean ReadReady,
       +                                     gboolean WriteReady,gboolean ErrorReady,
       +                                     gboolean *ReadOK,gboolean *WriteOK,
       +                                     gboolean *ErrorOK) {
       +/* Reads and writes data if the network connection is ready. Sets the  */
       +/* various OK variables to TRUE if no errors occurred in the relevant  */
       +/* operations, and returns TRUE if data was read and is waiting for    */
       +/* processing.                                                         */
       +   gboolean DataWaiting=FALSE;
       +   *ReadOK=*WriteOK=*ErrorOK=TRUE;
       +
       +   if (ErrorReady) *ErrorOK=FALSE;
       +
       +   if (WriteReady) *WriteOK=WriteDataToWire(NetBuf);
       +
       +   if (ReadReady) {
       +      *ReadOK=ReadDataFromWire(NetBuf);
       +      if (ReadOK) DataWaiting=TRUE;
       +   }
       +   return DataWaiting;
       +}
       +
       +gboolean RespondToSelect(NetworkBuffer *NetBuf,fd_set *readfds,
       +                         fd_set *writefds,fd_set *errorfds,
       +                         gboolean *DataWaiting) {
       +/* Responds to a select() call by reading/writing data as necessary.   */
       +/* If any data were read, DataWaiting is set TRUE. Returns TRUE unless */
       +/* a fatal error (i.e. the connection was broken) occurred.            */
       +   gboolean ReadOK,WriteOK,ErrorOK;
       +   *DataWaiting=DoNetworkBufferStuff(NetBuf,FD_ISSET(NetBuf->fd,readfds),
       +                        FD_ISSET(NetBuf->fd,writefds),
       +                        errorfds ? FD_ISSET(NetBuf->fd,errorfds) : FALSE,
       +                        &ReadOK,&WriteOK,&ErrorOK);
       +   return (WriteOK && ErrorOK && ReadOK);
       +}
       +
       +gboolean PlayerHandleNetwork(Player *Play,gboolean ReadReady,
       +                             gboolean WriteReady,gboolean *DataWaiting) {
       +/* Reads and writes player data from/to the network if it is ready.    */
       +/* If any data were read, DataWaiting is set TRUE. Returns TRUE unless */
       +/* a fatal error (i.e. the connection was broken) occurred.            */
       +   gboolean ReadOK,WriteOK,ErrorOK;
       +   *DataWaiting=DoNetworkBufferStuff(&Play->NetBuf,ReadReady,WriteReady,FALSE,
       +                                     &ReadOK,&WriteOK,&ErrorOK);
       +
       +/* If we've written out everything, then ask not to be notified of
       +   socket write-ready status in future */
       +   if (WriteReady && Play->NetBuf.WriteBuf.DataPresent==0 &&
       +       SocketWriteTestPt) {
       +      (*SocketWriteTestPt)(Play,FALSE);
       +   }
       +   return (WriteOK && ErrorOK && ReadOK);
       +}
       +
       +gchar *GetWaitingPlayerMessage(Player *Play) {
       +   return GetWaitingMessage(&Play->NetBuf);
       +}
       +
       +gchar *GetWaitingMessage(NetworkBuffer *NetBuf) {
       +/* Reads a complete (terminated) message from the network buffer. The    */
       +/* message is removed from the buffer, and returned as a null-terminated */
       +/* string (the network terminator is removed). If no complete message is */
       +/* waiting, NULL is returned. The string is dynamically allocated, and   */
       +/* so must be g_free'd by the caller.                                    */
           ConnBuf *conn;
           int MessageLen;
           char *SepPt;
           gchar *NewMessage;
       -   conn=&Play->ReadBuf;
       +   conn=&NetBuf->ReadBuf;
           if (!conn->Data || !conn->DataPresent) return NULL;
       -   SepPt=memchr(conn->Data,'\n',conn->DataPresent);
       +   SepPt=memchr(conn->Data,NetBuf->Terminator,conn->DataPresent);
           if (!SepPt) return NULL;
           *SepPt='\0';
           MessageLen=SepPt-conn->Data+1;
       t@@ -305,13 +403,17 @@ gchar *ReadFromConnectionBuffer(Player *Play) {
           return NewMessage;
        }
        
       -gboolean ReadConnectionBufferFromWire(Player *Play) {
       -/* Reads any waiting data on the TCP/IP connection for player "Play" into */
       -/* the player's read buffer. Returns FALSE if the connection was closed,  */
       -/* or if the read buffer's maximum size was reached.                      */
       +gboolean ReadPlayerDataFromWire(Player *Play) {
       +  return ReadDataFromWire(&Play->NetBuf);
       +}
       +
       +gboolean ReadDataFromWire(NetworkBuffer *NetBuf) {
       +/* Reads any waiting data on the given network buffer's TCP/IP connection */
       +/* into the read buffer. Returns FALSE if the connection was closed, or   */
       +/* if the read buffer's maximum size was reached.                         */
           ConnBuf *conn;
           int CurrentPosition,BytesRead;
       -   conn=&Play->ReadBuf;
       +   conn=&NetBuf->ReadBuf;
           CurrentPosition=conn->DataPresent;
           while(1) {
              if (CurrentPosition>=conn->Length) {
       t@@ -322,7 +424,7 @@ gboolean ReadConnectionBufferFromWire(Player *Play) {
                 if (conn->Length>MAXREADBUF) conn->Length=MAXREADBUF;
                 conn->Data=g_realloc(conn->Data,conn->Length);
              }
       -      BytesRead=recv(Play->fd,&conn->Data[CurrentPosition],
       +      BytesRead=recv(NetBuf->fd,&conn->Data[CurrentPosition],
                             conn->Length-CurrentPosition,0);
              if (BytesRead==SOCKET_ERROR) {
        #ifdef CYGWIN
       t@@ -340,15 +442,19 @@ gboolean ReadConnectionBufferFromWire(Player *Play) {
           return TRUE;
        }
        
       -void WriteToConnectionBuffer(Player *Play,gchar *data) {
       -/* Writes the null-terminated string "data" to "Play"'s connection buffer. */
       -/* The message is automatically newline-terminated. Fails to write the     */
       -/* message without error if the buffer reaches its maximum size (although  */
       -/* this error will be detected when the buffer is attempted to be written  */
       -/* to the wire, below)                                                     */
       +void QueuePlayerMessageForSend(Player *Play,gchar *data) {
       +   QueueMessageForSend(&Play->NetBuf,data);
       +}
       +
       +void QueueMessageForSend(NetworkBuffer *NetBuf,gchar *data) {
       +/* Writes the null-terminated string "data" to the network buffer, ready   */
       +/* to be sent to the wire when the network connection becomes free. The    */
       +/* message is automatically terminated. Fails to write the message without */
       +/* error if the buffer reaches its maximum size (although this error will  */
       +/* be detected when an attempt is made to write the buffer to the wire).   */
           int AddLength,NewLength;
           ConnBuf *conn;
       -   conn=&Play->WriteBuf;
       +   conn=&NetBuf->WriteBuf;
           AddLength=strlen(data)+1;
           NewLength=conn->DataPresent+AddLength;
           if (NewLength > conn->Length) {
       t@@ -360,21 +466,25 @@ void WriteToConnectionBuffer(Player *Play,gchar *data) {
           }
           memcpy(&conn->Data[conn->DataPresent],data,AddLength);
           conn->DataPresent=NewLength;
       -   conn->Data[NewLength-1]='\n';
       +   conn->Data[NewLength-1]=NetBuf->Terminator;
       +}
       +
       +gboolean WritePlayerDataToWire(Player *Play) {
       +   return WriteDataToWire(&Play->NetBuf);
        }
        
       -gboolean WriteConnectionBufferToWire(Player *Play) {
       -/* Writes any waiting data in "Play"'s connection buffer to the wire.  */
       -/* Returns TRUE on success, or FALSE if the buffer's maximum length is */
       -/* reached, or the remote end has closed the connection.               */
       +gboolean WriteDataToWire(NetworkBuffer *NetBuf) {
       +/* Writes any waiting data in the network buffer to the wire. Returns */
       +/* TRUE on success, or FALSE if the buffer's maximum length is        */
       +/* reached, or the remote end has closed the connection.              */
           ConnBuf *conn;
           int CurrentPosition,BytesSent;
       -   conn=&Play->WriteBuf;
       +   conn=&NetBuf->WriteBuf;
           if (!conn->Data || !conn->DataPresent) return TRUE;
           if (conn->Length==MAXWRITEBUF) return FALSE;
           CurrentPosition=0;
           while (CurrentPosition<conn->DataPresent) {
       -      BytesSent=send(Play->fd,&conn->Data[CurrentPosition],
       +      BytesSent=send(NetBuf->fd,&conn->Data[CurrentPosition],
                             conn->DataPresent-CurrentPosition,0);
              if (BytesSent==SOCKET_ERROR) {
        #ifdef CYGWIN
 (DIR) diff --git a/src/message.h b/src/message.h
       t@@ -115,10 +115,26 @@ void SendPrintMessage(Player *From,char AICode,Player *To,char *Data);
        void SendQuestion(Player *From,char AICode,Player *To,char *Data);
        
        #if NETWORKING
       -gchar *ReadFromConnectionBuffer(Player *Play);
       -gboolean ReadConnectionBufferFromWire(Player *Play);
       -void WriteToConnectionBuffer(Player *Play,gchar *data);
       -gboolean WriteConnectionBufferToWire(Player *Play);
       +void InitNetworkBuffer(NetworkBuffer *NetBuf,char Terminator);
       +void BindNetworkBufferToSocket(NetworkBuffer *NetBuf,int fd);
       +void ShutdownNetworkBuffer(NetworkBuffer *NetBuf);
       +void SetSelectForNetworkBuffer(NetworkBuffer *NetBuf,fd_set *readfds,
       +                               fd_set *writefds,fd_set *errorfds,int *MaxSock);
       +gboolean RespondToSelect(NetworkBuffer *NetBuf,fd_set *readfds,
       +                         fd_set *writefds,fd_set *errorfds,
       +                         gboolean *DataWaiting);
       +gboolean PlayerHandleNetwork(Player *Play,gboolean ReadReady,
       +                             gboolean WriteReady,gboolean *DataWaiting);
       +gboolean ReadPlayerDataFromWire(Player *Play);
       +void QueuePlayerMessageForSend(Player *Play,gchar *data);
       +gboolean WritePlayerDataToWire(Player *Play);
       +gchar *GetWaitingPlayerMessage(Player *Play);
       +
       +gboolean ReadDataFromWire(NetworkBuffer *NetBuf);
       +gboolean WriteDataToWire(NetworkBuffer *NetBuf);
       +void QueueMessageForSend(NetworkBuffer *NetBuf,gchar *data);
       +gchar *GetWaitingMessage(NetworkBuffer *NetBuf);
       +
        gchar *bgets(int fd);
        #endif /* NETWORKING */
        
 (DIR) diff --git a/src/serverside.c b/src/serverside.c
       t@@ -195,7 +195,7 @@ void RegisterWithMetaServer(char Up,char SendData) {
        void HandleServerPlayer(Player *Play) {
           gchar *buf;
           gboolean MessageRead=FALSE;
       -   while ((buf=ReadFromConnectionBuffer(Play))!=NULL) {
       +   while ((buf=GetWaitingPlayerMessage(Play))!=NULL) {
              MessageRead=TRUE;
              HandleServerMessage(buf,Play);
              g_free(buf);
       t@@ -772,7 +772,7 @@ void ServerLoop() {
           struct timeval timeout;
           int MinTimeout;
           GString *LineBuf;
       -   gboolean EndOfLine;
       +   gboolean EndOfLine,DataWaiting;
        
           StartServer();
        
       t@@ -787,11 +787,9 @@ void ServerLoop() {
              topsock=ListenSock+1;
              for (list=FirstServer;list;list=g_slist_next(list)) {
                 tmp=(Player *)list->data;
       -         if (!IsCop(tmp) && tmp->fd>0) {
       -            FD_SET(tmp->fd,&readfs);
       -            if (tmp->WriteBuf.DataPresent) FD_SET(tmp->fd,&writefs);
       -            FD_SET(tmp->fd,&errorfs);
       -            if (tmp->fd>=topsock) topsock=tmp->fd+1;
       +         if (!IsCop(tmp)) {
       +            SetSelectForNetworkBuffer(&tmp->NetBuf,&readfs,&writefs,
       +                                      &errorfs,&topsock);
                 }
              }
              MinTimeout=GetMinimumTimeout(FirstServer);
       t@@ -831,28 +829,14 @@ void ServerLoop() {
              while (list) {
                 nextlist=g_slist_next(list);
                 tmp=(Player *)list->data;
       -         if (tmp && FD_ISSET(tmp->fd,&errorfs)) {
       -            g_warning("socket error from client: %d",tmp->fd);
       -            CleanUpServer(); bgetch(); break;
       -         }
       -         if (tmp && FD_ISSET(tmp->fd,&writefs)) {
       -/* Try and empty the player's write buffer */
       -            if (!WriteConnectionBufferToWire(tmp)) {
       +         if (tmp && !RespondToSelect(&tmp->NetBuf,&readfs,&writefs,&errorfs,
       +                                     &DataWaiting)) {
        /* The socket has been shut down, or the buffer was filled - remove player */
       -               if (RemovePlayerFromServer(tmp,WantQuit)) break;
       -               tmp=NULL;
       -            }
       -         }
       -         if (tmp && FD_ISSET(tmp->fd,&readfs)) {
       -/* Read any waiting data into the player's read buffer */
       -            if (!ReadConnectionBufferFromWire(tmp)) {
       -/* remove player! */
       -               if (RemovePlayerFromServer(tmp,WantQuit)) break;
       -               tmp=NULL;
       -            } else {
       +            if (RemovePlayerFromServer(tmp,WantQuit)) break;
       +            tmp=NULL;
       +         } else if (tmp && DataWaiting) {
        /* If any complete messages were read, process them */
       -               HandleServerPlayer(tmp);
       -            }
       +            HandleServerPlayer(tmp);
                 }
                 list=nextlist;
              }
       t@@ -933,31 +917,24 @@ static void GuiDoCommand(GtkWidget *widget,gpointer data) {
        static void GuiHandleSocket(gpointer data,gint socket,
                                    GdkInputCondition condition) {
           Player *Play;
       +   gboolean DataWaiting;
           Play = (Player *)data;
        
           /* Sanity check - is the player still around? */
           if (!g_slist_find(FirstServer,(gpointer)Play)) return;
        
       -   if (condition&GDK_INPUT_WRITE) {
       -      if (!WriteConnectionBufferToWire(Play)) {
       -         if (RemovePlayerFromServer(Play,WantQuit)) GuiQuitServer();
       -      } else if (Play->WriteBuf.DataPresent==0) {
       -         SetSocketWriteTest(Play,FALSE);
       -      }
       -   }
       -   if (condition&GDK_INPUT_READ) {
       -      if (!ReadConnectionBufferFromWire(Play)) {
       -         if (RemovePlayerFromServer(Play,WantQuit)) GuiQuitServer();
       -      } else {
       -         HandleServerPlayer(Play);
       -         GuiSetTimeouts();  /* We may have set some new timeouts */
       -      }
       +   if (!PlayerHandleNetwork(Play,condition&GDK_INPUT_READ,
       +                            condition&GDK_INPUT_WRITE,&DataWaiting)) {
       +      if (RemovePlayerFromServer(Play,WantQuit)) GuiQuitServer();
       +   } else if (DataWaiting) {
       +      HandleServerPlayer(Play);
       +      GuiSetTimeouts();  /* We may have set some new timeouts */
           }
        }
        
        void SetSocketWriteTest(Player *Play,gboolean WriteTest) {
           if (Play->InputTag) gdk_input_remove(Play->InputTag);
       -   Play->InputTag=gdk_input_add(Play->fd,
       +   Play->InputTag=gdk_input_add(Play->NetBuf.fd,
                              GDK_INPUT_READ|(WriteTest ? GDK_INPUT_WRITE : 0),
                              GuiHandleSocket,(gpointer)Play);
        }