How to create a Non Blocking Server

Published September 20, 2001 by Richard Hallett, posted by Myopic Rhino
Do you see issues with this article? Let us know.
Advertisement

How to create a Non Blocking Server
with basic message handling using Winsock
by Richard Hallett

Well get ready, grab the food, prepare to lose your social life and prepare to enter the world of creating a non-blocking server in Winsock.

This tutorial could be called fairly advanced, but some of you may find it very easy; personally I found it all quite hard to learn. Bearing that in mind I will try and explain most things, but a basic understanding of Winsock might be helpful, and of course you should know C or C++ - if you don't you will find it hard to understand.

Ok, let's get started. What is a non-blocking server? I hear some of you say, "Well there are 3 normal types of servers: a threaded server, a non-blocking server, and a blocking server." Now there are other versions of servers and not everyone's servers will look the same, but in basic principle a non-blocking server is like what they use for games such as Ultima Online, Everquest, Asherons Call, most MUD's, etc. In other words a server that needs to handle more than one client/player.

A non-blocking server means it will not wait for messages; it will only get a message when there is one. If there's nothing it will continue with the application.

What is Winsock? Well Winsock is a Software Development Kit to handle networking functions. It is based on the Berkley Socket Descriptor kit, which was created on Unix. Now Winsock includes these inside its SDK as well as the Winsock versions, and so throughout this tutorial I will be using the BSD functions. Because I am using BSD with some alterations it should in theory work on Unix as well, but don't hold me to it.

NOTE: For a better understanding of anything I have said above, check out MSDN or ask about it on the forums or perhaps email me and I'll give it a shot. In this tutorial I just want to cover the code.

Before we get to the code, we need to set everything up. For this tutorial I will be using Visual C++ 6.0.

First, create a new project for an empty console application. You can use Win32 but you would need to do more setup and I won't be covering that in this tutorial.

Now go into your project settings and include the following library: wsock32.lib.

Now create a new CPP file. Call it whatever you wish. For this tutorial I am going to be putting everything in one cpp file, though normally you would most likely have your network code in a separate module.

Now onto the code.

Setting up


// INCLUDES //
#include  // Input and Output Stuff
#include   // Needed to use Winsock
PRE>
The code above includes the important header files that are needed for Winsock and this server to work properly.

// DEFINES // 
#define PORT 4000            // The Port Number of the Server
#define Max_Connections 10   // The Maximum Number of Connections allowed to connect 

#define YES 1  // Making 1 as YES, so its easier to understand our code.
#define NO  2  // Making 2 as NO, so its easier to understand our code.

struct CLIENT_TYP // This is the Structure that describes a Client.
{
  int InUse;                    // Is The Client Connected to Server
  SOCKET ClientSocket;          // A Socket for the client to sit on
  struct sockaddr_in clnt_addr; // Holds the address of the client
};

The above code sets up the macros and data types that we use for the server. They should be pretty easy to understand from the comments. But, having said that, I would like to explain that we are using a structure for our clients, as it is just a cleaner way of doing the code. You could do it without structures.


// GLOBALS //
SOCKET ListeningSocket;  // This Socket Listens for Connections from the clients

struct sockaddr_in Server_Address; // This Structure Holds the address of the Server.

CLIENT_TYP Client[Max_Connections]; // This Sets up Client Structures for However
                                    // many we set the MAX to.

int ClientsConnected = 0; // How many clients are currently connected to the server

timeval timeout; // This is used for the select function its seeing
                 // how long it should wait before timing out.

This code sets up all the global variables that are needed in the Server. All of them have been commented, so I think they are pretty much self-explanatory.


// FUNCTIONS // 
// Simple Error Message Code
bool Error(char *error)
{
  cout << error; "/n";
  return (true);
}

This is just a simple function for displaying a error. It's really only to save a little time; you could make it more advanced and have it save out the error to a log file.


// Sets Up Server
void Setup()
{
  // Local Variables
  WSADATA wsaData;

  // Set up Address Structure
  Server_Address.sin_family = AF_INET;
  Server_Address.sin_addr.s_addr = INADDR_ANY;
  Server_Address.sin_port = htons(PORT);

  // Start Up Winsock
  if (WSAStartup(MAKEWORD(1, 0), &wsaData))
  {
    Error("Error while attempting to initialize WinSock");
  }

  // Create a Socket
  ListeningSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  // Bind the Socket to Port
  bind(ListeningSocket, (struct sockaddr *)&Server_Address, sizeof(Server_Address));

  // Listen at the Binded Socket for Connections
  listen(ListeningSocket, SOMAXCONN);

  // Make all Clients Not In Use
  for (int i = 0; i < Max_Connections; i++)
  {
    Client.InUse = NO;
  }
} 

Okay this is not too hard. If you have used Winsock before, then no doubt you have done something like this already.

To start with I set up the structure that holds all the address information of the server (Server_Address).

I then load Winsock; this is pretty much the same in any Winsock application.

I then create the socket that listens for connections. I bind that Socket to the address it should use, as was set in the Server_Address Structure.

I then (this is getting repetitive) just listen on that Socket for connections. You might be wondering what SOMAXCONN is. It's what the ISP sets for the maximum length that the queue of pending connections may grow to. It is the backlog. (If that still didn't many any sense I suggest you go look it up :)

Finally I go through all of my clients and make them all flagged as not in use.

Shutting Down


// Function That Disconnects a Specified Client
int Disconnect(int s)
{
  closesocket(Client[s].ClientSocket);
  Client[s].InUse = NO;
  ClientsConnected--;
        
  return(1);
}

That function was pretty easy. It just disconnects whatever client socket "s" is, makes that client not in use and reduces the overall number of clients connected.


// Shutdown the Entire Server
void Shutdown_Server()
{
  // Close The Socket that Listens for Connections
  closesocket(ListeningSocket);
        
  // Loop that closes down all the Client Connections
  for (int index = 0; index < Max_Connections, index++;)
  {
    Disconnect(index);
  }
        
  // Remove Winsock
  WSACleanup();
}

This code closes down the listening socket so no more clients can join, then goes on and disconnects them all and finally closes down Winsock altogether. Now you might not want to shut down Winsock; instead, you might want to be able to restart the server. The choice is up to you.

Connections and select


// Check For Connections
int Check_Connections()
{
  int s; // s is where the data is stored from the select function
  int nfds; // This is used for Compatibility
  fd_set conn; // Setup the read variable for the Select function
  int Client_Length = sizeof(Client->clnt_addr); // Store the length of the client address

  timeout.tv_sec=0; // Set the timeout to 0 for Non Blocking
  timeout.tv_usec=0;
        
  if (ClientsConnected < Max_Connections)
  {
    FD_ZERO(&conn); // Set the data in conn to nothing
    FD_SET(ListeningSocket, &conn); // Tell it to get the data from the Listening Socket
    // Up the nfds value by one, shouldnt be the same for each
    // client that connects for compatability reasons
    nfds=ListeningSocket+1;
    s = select(nfds, &conn, NULL, NULL, &timeout); // Is there any data coming in?

    if (s > 0) // Someone is trying to Connect
    {
      for (int index = 0; index < Max_Connections; index++)
      {
        if (Client[index].InUse == NO)
        {
          Client[index].ClientSocket = accept(ListeningSocket,
                                              (struct sockaddr *)&Client[index].clnt_addr,
                                              &Client_Length);

          // Client Connected
          // Store the Ip of the Client that Just Connected.
          char *ClientIp = inet_ntoa(Client[ClientsConnected].clnt_addr.sin_addr);
          cout << "Accepted Connection From: " << ClientIp << endl;

          // Now that a client has connected up the number of clients thats connected
          ClientsConnected++;

          // This Client is now in use.
          Client[index].InUse = YES;

          // Exit the For loop that is looking for empty clients.
          break;
        }   
      }
    }
  }

  return (1);
}

Did you all just die a painful death? Yes I guess you have have. That was a big chunk of code. I have commented pretty much everything, but I'm going to elaborate on a few things.

The function itself isn't too complex. It just goes through sees if there's any data coming in. If there is then there must be a client wanting to connect, so you check to see what client area is free and connect them to that socket.

The select function is the most important bit of this function. It is the key to making the server non blocking.


select(int nfds, fdset readfds, fdset writefds, fdset exceptfds, timeval timeout)

That is the function with its parameters; here is a table with brief descriptions of the parameters.

Type Name Description int nfds This argument is ignored and included only for the sake of compatibility. fd_set FAR * readfds An optional pointer to a set of sockets to be checked for readability. fd_set FAR * writefds An optional pointer to a set of sockets to be checked for writability fd_set FAR * exceptfds An optional pointer to a set of sockets to be checked for errors. const struct timeval FAR * timeout The maximum time for select() to wait, or NULL for blocking operation.

Simple, no? ;)

Our fd_Set FAR* structures need to be setup as well, but because we are just checking for connections we only need to set it up for readability. So we basically do a fd_set conn. This sets up conn as that structure. Then before we can actually use the select function we need to clear out the fd_set structure by doing: FD_ZERO(&conn), then finally we do: FD_SET(ListeningSocket, &conn). This just makes it so we are checking for readability on the listening socket.

A quick note on the timeval structure: We are just setting it to 0 as we do not want it to wait at all, since it's a non-blocking server. However, you could set it to more than 0 and it would wait that long.

Okay so I hope you understood all that. I have not explained everything as I didn't think it was really necessary. Like I said before, you can look up all the Winsock functions on MSDN, and there is also a Winsock document you can download which has all the functions explained.

Processing Messages


// Get the message, and see what it is.
void Get_Message(int s)
{
  // Receive The Data and Put into a Buffer Until we know what it is.
  char buffer[1];
  recv(Client[s].ClientSocket,buffer,1,0);

  // Store the Ip of the Client that's sending message
  char *ClientIp = inet_ntoa(Client[s].clnt_addr.sin_addr);

  switch (buffer[0])
  {
  default:
    Disconnect(s);
    cout << "This Client Has Disconnected: " << ClientIp << endl;
    break;
  case 1:
    // DO SOMETHING IF YOU RECEIVE PACKET 1
    break; 
  case 2:
    // DO SOMETHING IF YOU RECEIVE PACKET 2
    break;
  }
} 

What have we just done? Well this function is designed to handle the messages we might receive from a client. It first receives the data that's coming in, then it uses a switch block to determine the appropriate response depending on the data.

I have not actually put any replies to the messages I have here, but, for example, if you sent 1 through the client you could make it quit, so the client is saying I am quitting, and a response would be reduce the number of clients on the server.

I don't think any of that is too hard; its only a simple messaging handling function, but I think it works quite well.


// See if there is a Message coming in
void Check_Message()
{
  fd_set input_set, exc_set; // Create Input and Error sets for the select function
  int s, // This is Used for the Select command to store the value it gets back
  nfds; // Used for Compatability

  timeout.tv_sec=0; // Used for Timeout
  timeout.tv_usec=0;

  //Set up the input,and exception sets for select().
  FD_ZERO(&input_set);
  FD_ZERO(&exc_set);
  nfds = 0;

  // Loop makes it so that the Input and Error sets will look at all the Sockets connected
  for (int i = 0; i < ClientsConnected;)
  {
    FD_SET(Client.ClientSocket,&input_set);
    FD_SET(Client.ClientSocket,&exc_set);
    nfds++;
    i++;
  }

  // The Actual Select Function sees if data coming in and stores it in s
  s = select(nfds,&input_set,NULL,&exc_set,&timeout);

  if (s > 0) // Is there data coming in?
  {
     // Look Through all the clients to see which one is giving error or data
    for (int i=0; i < ClientsConnected;)
    {
      if (FD_ISSET(Client.ClientSocket,&exc_set)) // There was an Error?
      {
        // For this Server we assume it was a disconnection or something so close the socket.
        // Store the IP of the Client that Just Disconnected
        char *ClientIp = inet_ntoa(Client.clnt_addr.sin_addr);
        cout << "This Client Has Disconnected: " << ClientIp << endl;
        Disconnect(i);
      }

      if ((FD_ISSET(Client.ClientSocket, &input_set))) // Actual Data coming through?
      {
        // Get whatever message the client sending, and carry out appropriate response
        Get_Message(i);
      }
     i++; // Next Client
    }
  }
}

That was quite a lot of code bunched together, I know. Try and stay with me here. I have commented practically everything that could be a little confusing.

Basically, this function also uses the select function, to determine if there is data coming in on a client socket, and if so, it will call the Get_Message() function which I explained earlier. This time though I have used the exceptfds parameter of select. I am using this to test if a client is sending an abnormal message, in which case I am assuming they have disconnected.

Everything else I think I have explained before, or its not too hard to understand.

Conclusion


int main()
{
  // Local Variables
  bool bye = false;

  // Initlizie Everything
  Setup();

  // This is a Continious loop aslong as bye == false, if not then it quits.
  while (bye == false)
  {
    // Check if any clients need connecting to server.
    Check_Connections();

    // Check to see if any messages are being sent from the clients.
    Check_Message();
  }     

  // Shutdown any Resources Used.
  Shutdown_Server();

  return (1); // Return Success
}

Well this last little bit is pretty damn simple. If you don't get it, well... I don't know how you understood the rest of code :).

One thing you might want to do in there is add a key like ESC to exit the while loop. I haven't put it in there as it's a console app and it would just been extra code that wasn't really needed.

Well that's it, we've finished, I hope you understood all that. I know I did not explain everything, some of this topic is a little bit hard, but if you just read things and experiment I don't see why you should have any trouble with it.

Recommended improvements:

  • When searching for free clients, I use a static array. I suggest (I know I will add this to my code) that you use a linked list.
  • I also know my messages like 1 and 2 are a bit stupid. Perhaps use binary or something else. Don't stick to my way, there's lots of possibilities.
  • Try sticking all of this network code in a class: fewer globals looks so much neater.

I hope you found this useful - it was my first tutorial after all. I hope I can do more tutorials for you all out there, not saying they will be on Winsock, as there's not too much you can go into it, but perhaps next time ill go onto more game networking theory algorithms, security, messages stuff like that - who knows?

If you have any feedback or questions (please if they are coding questions try your best to look on the net first for answers), then contact me at: Gachuk@gdnmail.net, as I would love to here any feedback, even if you thought my tutorial sucked. Its nice to know either way.

Note: Thanks to everyone in #GameDev on IRC who has helped me when learning Winsock. It was not a easy task, and I hope doing this tutorial gives something back to the community for all their time putting up with me saying "Oh I don't understand it"

Download the source code for this article here

Cancel Save
0 Likes 0 Comments

Comments

Nobody has left a comment. You can be the first!
You must log in to join the conversation.
Don't have a GameDev.net account? Sign up!
Advertisement