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:

Here We Go Again!

Welcome to part two of "MUD Pies". First and foremost, I apologize for this being so long in the making, and thank you to everyone who sent encouraging emails and enquiring when it would be finished. Here it is. If for whatever reason I am late with the series again, what you learn in this tutorial is enough for you to go off on your own MUD (this is the end of the substantial network code).

Once again, I strongly recommend downloading the code that comes with this article. It will make it easier for us both. I am working off the MSVC compatible version, but I do not guarantee that the code for this part is MSVC compatible.

As with any code, bugs will arise. A few were found within weeks of the first article going public, and have been changed in the downloadable code and the article but I am uncertain about the printable-version. In any case, none were major changes. These new bugs are more substantial and need to be dealt with. The worst of which is a big memory leak because I forgot to delete the server class. Add in a delete CServer::GetServer(); at the end of the main function in CApp. Next, the window is not being invalidated properly, so updates are not being displayed as expected. Add in InvalidateRgn(hWnd, 0, 0); in response to a SM_WINSOCK message in CApp::WndProc(). Finally, to make it easier later, CSock::GetText(char* dest, int length) now returns the number of characters read from the buffer (so we don't have to look at the string again.

Other aesthetic changes you may notice is that the macro GETSERVER, and the function CApp::PaintWindow() have been added (and the response to a WM_PAINT message has been moved into it).

That's the end of the minor changes. The major changes that are covered in this article are:

  • Addition of a string class to make our lives a whole lot easier
  • Grabbing strings from the socket
  • A finite state machine (FSM) that handles what the user is currently doing (entering a password, talking, etc)
  • Saving and loading files to and from disk (with a crash course in TinyXML)

A String Class

Note from the future: I no longer recommend using my class (unless you are just using the accompanying code). Use the Standard Template Library string class instead.

Rather than handle all the complexity of C strings again and again (and explain them over and over) I have taken a string class from another of my projects and used it for this tutorial. You should reuse code as often as possible, and always try and write code (especially utility-like classes – like string classes) that can be used in other projects. Searching for null characters, handling memory and such gets tiresome quite quickly.

Also note that this class is commented to be auto-documented by doc++, doxygen, etc.

I will now give a quick explanation of its internals. And seeing I won't get another chance, an overview of C strings.

C strings work by having a pointer into memory at the start of the string. Instead of holding the length of the string, the string ends in what is known as a null character. The actual value of this character is zero, and to create one in a string use '\0'. This really sucks, because whenever you want to do much manipulation of the string, you have to sequentially access it right til the end to know how long it is.

For instance, the string "Andrew", a pointer to the first letter exists, and then memory after that pointer is part of the string up until the null terminator. So when viewing the 8-bit character values in memory, you will see 'A', 'n', 'd', 'r', 'e', 'w', 0. The actual byte length of that string is seven.

If you have looked at your compiler's documentation of their runtime library, you may have seen different memory manipulation functions. There is a function strcpy that copies a string. It is inefficient and hard to use, because it must read the string's contents to find its null character. It also requires that there is a buffer waiting with enough memory to hold that string. This can get painful quickly. There is a similar function called memcpy that takes a length and can just grab memory from one place and throw it into another quickly (because it doesn't have to read it). The same is the case with other functions that start with str or mem. The mem function takes a length, whereas the str function searches for the null character.

So wouldn't it be easier if could keep track of the string's length. That is essentially what the string class I will use does. It has both has a null character (so the string can quickly be used by functions that need a string) and a length in bytes (that includes the null character). These are defined as follows:

class AString
{
private:
  char* m_strData;
  int m_iLength;
};

Now, we want to do operations on this string. For instance, we want to set strings to each other, add strings together, compare strings, etc. I will cover these basic operations briefly here, for their exact implementation, see the accompanying source code.

First of all, constructing the string class. We have two variables to set, a string and a length. Length is simply a matter of setting the length variable to the appropriate value. Setting the string involves copying memory.

For starters, we don't have any memory set aside for our string. We must use the new operator to create an array of characters of the appropriate length:

m_strData = new char[m_iLength];

And then copy the data into it using memcpy:

memcpy(m_strData, str, m_iLength);

The function memcpy takes three arguments. The first is a pointer to the start of the destination memory, the second is the pointer to the start of the source memory, and the third is the length to copy in bytes.

Deconstruction is a simple matter of releasing the string's memory using the delete[] operator. This frees an array of objects in memory (in this case, they are characters taking a single byte each).

An assignment operator can be implemented by combining the above two; first by freeing the old string, and then by copying in the new string. For those who do not know how to create an operator, here is how it's done. Note that different instances of the same class can access each other's privates. The variable rhs refers to the right hand side of the operator symbol.

AString& AString::operator= (const AString& rhs)
{
  delete[] m_strData;
  m_iLength = rhs.m_iLength;
  m_strData = new char[m_iLength];
  memcpy(m_strData, rhs.m_strData, m_iLength);
  return *this;
}

Appending one string onto another is not much harder. Note that when you have two strings, you also have two null terminators, whereas when you create the final one, you only need one. This is why we reduce the length by one here.

AString& AString::operator+= (const AString& rhs)
{
  char* oldstring = m_strData;
  m_strData = new char[m_iLength + rhs.m_iLength - 1];

  memcpy(m_strData, oldstring, m_iLength);
  memcpy(m_strData + m_iLength - 1, rhs.m_strData, rhs.m_iLength);

  m_iLength = m_iLength + rhs.m_iLength - 1;
  delete[] oldstring;
  return *this;
}

And with the += operator, we can cheat when we make our + operator:

AString& AString::operator+ (const AString& rhs)
{
  AString* newstring = new AString(*this);
  *newstring += rhs;
  return *newstring;
}

That covers the more complicated operations. Other operations that involve string comparisons and the like are easy to do, view the source if you are unsure of the functions to use.

Just finally, when you want to have a string compiled into your program, you must do it slightly differently than expected. For a C string, you can simply type it in between inverted commas, for an AString; use the following macro around a C string. This also avoids a warning message ("Temporary used for parameter").

#define A_STR(str) AString(str, sizeof(str))





The Receiving Buffer

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