Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
109 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

The Receiving Buffer

Now that we have a string class, it should be relatively easy to add in more string related functions. You may notice that I have changed a couple of functions over to use AString. This is not a big deal as the functionality is the same.

The reason we need a receiving buffer is because some clients will send us one or several characters at a time, rather than one line at a time. Namely this client would be telnet, and this is important, because we will be making our MUD telnet compliant.

Under normal operation, the buffer will just add any data that is received by the socket to its end. You don't want people talking one letter at a time, so the buffer is only "executed" (put into action) when it contain a carriage return (enter) character ('/r'). When this happens, all the text up until the enter is taken and executed, and then the buffer is reset to contain whatever was left at its beginning.

If the case occurs that the buffer fills up before a enter character is added, then we must handle it. As the buffer is full, there is no way for the user to add in a enter character, so a suggested way to handle this situation is to clear the buffer entirely, and give the user a message about their "flooding" (sending excess text).

It is possible to have a buffer of infinite length, but our variable that keeps track of buffer would overflow well before we reached infinity, and also, we don't want the user to be able to generate out of memory errors (that can bring down the server or the machine itself) by having multi-megabyte buffers in use.

For our buffer, add the following to CUser:

bool ProcessText();
char* buffer;
unsigned int buffersize;

And we also throw in a:

#define MUD_LINE_MAX 1024

The function ProcessText is to handle getting text from the socket and appending it to the buffer. It is called whenever the user class receives a message that text is waiting on the socket. Note that the buffer is not one of our shiny new AString classes, this is because we do fancy stuff directly to the memory that the buffer involves, and we don't need to add that kind of functionality to AString. Also note that defiantly isn't the fastest way of handling this, but it is simple so it can be understood easily. If you want to get really fancy, then you could go and use a specialized string library.

Note that buffer is initialized in the constructor and is always an array of MUD_LINE_MAX characters (although it is reinitialized throughout the code).

Now let me step you through the ProcessText function:

First of all, we need to grab the incoming text from

buffersize += socket.GetText(buffer+buffersize, MUD_LINE_MAX - buffersize);

We then check if the updated buffer contains an enter character. The function strchr returns the position of that character, or NULL if it does not exist. If the buffer does not contain an enter character, and it is full, then reject it and create a new one.

char* enterpos = strchr(buffer, '\r');
if(enterpos == NULL)
{
  if(buffersize >= MUD_LINE_MAX-1 )
  {
  delete[] buffer;
  buffer = new char[MUD_LINE_MAX];
  buffersize = 0;
  return false;
  }
}

On the other hand, if we do have an enter character we can execute the buffer.

else
{
  AString execbuf;
  do
  {
  // ...

  Execute(execbuf);
  }
  while( (enterpos = strchr(buffer, '\r')) != NULL ); 
}

We need to fill in that do statement. We need to add functionality to get out the data that we want to execute, and also to remove that same data from the existing buffer. The loop above will keep doing it until there are no more enter characters. This is because as well as some clients being able to send less than a line at once, some can send more than a line at once.

So consider what we have at the moment. We have a pointer to the beginning of the string and a pointer to the location of the enter character. Between these two pointers is the string we want to get at.

enterpos[0] = '\0';

This changes the character to a null terminator. We can then use those string functions mentioned before (that search up until a null terminator) to extract the string. Our AString class already does this in one of its constructors, which is good because we want our execute buffer to be an AString.

execbuf = AString(buffer);

We want to make a new buffer that has the end of the old one in it. This is where we make it, and then fill it from enterpos onwards. We increment the pointer by one for avoiding the null terminator we inserted earlier. Some clients can also add a newline character ('\n') so we jump over that too if need be (it will be right after the ('\r').

char* newbuffer = new char[MUD_LINE_MAX];
memcpy( newbuffer, enterpos + ((enterpos[1] == '\n')?2:1),
    buffersize - ( enterpos + ((enterpos[1] == '\n')?1:0) - buffer ) );

If you haven't seen it before, the ?: ternary (three part) operator works like an if statement. If part one evaluates true, part two evaluates, otherwise part three evaluates. In this case parts two and three simply return a number.

Now we want the size of the new buffer:

buffersize -= ( enterpos + ((enterpos[1]=='\n')?2:1) - buffer );

Finally, we delete the old buffer and replace it with the new:

delete[] buffer;
buffer = newbuffer;

Now, before the next loop, you need to execute the buffer you have extracted. This will be in another member function of CUser.

To start with, we shall just make the server transmit that string to every connection. Before we do that, we need two new functions. In the class definition of CUser:

inline void SendText(AString& str) {socket.SendText(str.GetString());}

And then add the following function to CServer:

void CServer::Broadcast(AString& str)
{
  AString buf = str+A_STR("\r\n");
  for(CUser* curuser=rootuser; curuser != NULL; curuser=curuser->next)
  {
  curuser->SendText(buf);
  }
}

So now we add our execute function. This is just to test it works, it should look something like this:

void CUser::Execute(AString& str)
{
  if(str.GetLength() <= 1)
  {
  return;
  }
  GETSERVER->Broadcast(str);
}

Next, instead of simply sending the string out, that statement will be filled with our finite state machine.





Finite State Machines

Contents
  Introduction
  The Receiving Buffer
  Finite State Machines
  Putting it all Together

  Source code
  Printable version
  Discuss this article

The Series
  Part I
  Part II