Tuesday, August 7, 2012

A server socket class


I'm going to describe a new class, (last one before I start with the server)

We already have a class to store persistent data, now we need a way to communicate with the world.

I need a class to start a TCP socket server then accept incoming TCP connections , I need to be able to send data to any of the clients too, and finally enqueue the incoming data so I can process it when I need to do.

What the class is going to do is:
  1. Open a server socket in a port X
  2. Start a thread that will be listening in port X
  3. When we receive connections it will save the client info (ip/port) to a vector so we can communicate with the client, every client starts a new thread to get the incoming data from each client (it is a blocking-socket server)
  4. The data is stored into two different queues depending if the client has logged in  or not, I made that because I will apply different priority for the queues , the main (client updates) must be processed with maximum priority other way clients will be lagging.. in the other hand client login request can wait a pair of seconds without big problems.
To achieve that I use two classes, first one is cNetwork, it is the main server, second one os cClient, used to store information from every connected client, the header from both classes are stored in the same file:

#ifndef NETWORK_H
#define NETWORK_H

#define MAXLEN 1024
#include "SDL/SDL.h"
#include "SDL/SDL_net.h"
#include "SDL/SDL_thread.h"

#include <vector>
#include <queue>
#include <iostream>
#include <sstream>

using namespace std;

class cNetwork;

class cClient //the connected clients
virtual ~cClient();//destructor
void StartThreads();//Start listener thread
static int listener( void *data );//Listener Thread
void Send(const char* Msg);//Send data to client

cNetwork *_server;//To comunicate with main server instance
TCPsocket socket;//client socket
SDL_Thread *ListenerThread;//The network client thread
int         threadReturnValueclient;
Uint16 UID;//Unique identifier
bool Connected;//socket connected?
bool Login; //loged in?


class cNetwork//main network server class
      friend class cClient;

        virtual ~cNetwork();
        void Start(Uint16 MAXUsers, Uint16 Port);
        void CleanClient(unsigned int Client);//clean a client so we can use it
        static int Master( void *data );//Master listener function/thread
        void ClearClients();//Clear disconnected clients
        void Finish();//close all client connections and finish server

        queue<std::string> InputQueue;//Here we store received data
        queue<std::string> LoginQueue;//Here we store login requests
        SDL_sem *ClientsLock; //Semaphore to prevent server from accesing a variable from different threads
        cNetwork *myserver;//server pointer
        vector<cClient*> Clients;//Here I store clients
        SDL_Thread *MasterThread;//The Master thread >> accepts new clients
        int         threadReturnMaster;//return value for master thread
        bool ShutDown;//closing the server?

        //private variables
        IPaddress ip;//Server ip address
        TCPsocket ServerSock; //Server socket

#endif // NETWORK_H

Then I have the client implementation:

#include "network.h"

    ListenerThread = NULL;
    Connected = false;

    //Stop the thread
    SDL_KillThread( ListenerThread );
    //close the socket

void cClient::StartThreads()
    //Create and run the thread +
    ListenerThread = SDL_CreateThread( &listener, this );

int cClient::listener( void *data )
{ //While the program is not over
    cout << "Starting client listener Thread..." << endl;
    int result;
    char in_msg[MAXLEN];
    bool quit=false;

    while( quit == false )
        memset(in_msg, 0, MAXLEN); // Clear in buffer
        if ( ((cClient*)data)->Connected)//If I'm connected...
                result=SDLNet_TCP_Recv(((cClient*)data)->socket,in_msg,MAXLEN);//Check for incoming data
                            //NO DATA
                            // TCP Connection is broken. (because of error or closure)
                            cout << "Socket closed..." << endl;
                            ((cClient*)data)->Connected = false;

                stringstream idmsg;
                idmsg << ((cClient*)data)->UID << "#" << in_msg;// I will add the UID from the client to the incoming message
                    SDL_SemWait(((cClient*)data)->_server->ClientsLock);//lock smaphore to prevent client to freeze
                    cout << idmsg.str();//only for debuging

                    if  (((cClient*)data)-> Login==true)//Am I loged in?
                            ((cClient*)data)->_server->InputQueue.push(idmsg.str());  //I'm logged in
                            ((cClient*)data)->_server->LoginQueue.push(idmsg.str());  //New player requests
                    SDL_SemPost(((cClient*)data)->_server->ClientsLock);//unlock smaphore
                catch(exception& e)
                        cout << "QUEUE IN ERROR: " << e.what() << endl;
            }//if connected
    cout << "Closing client listener thread..." <<endl;
    return 0;

void cClient::Send(const char* Msg)
    if (Connected==true)
            int len;
            int result;
            len=strlen(Msg+1); // add one for the terminating NULL
            if(result<len) //If I can't send data probably I've been disconnected so....
                    cout << "SDLNet_TCP_Send: " << endl << SDLNet_GetError();
                    Connected = false;
            cout << "Not connected!!" << endl;

Notice the client stores the data in two different queues depending if the client has loged in or not, finally we have the main socket server class:

#include "network.h"

if(SDLNet_Init()==-1) {//Start SDL_NET
cout << "SDLNet_TCP_INIT: \n " << SDLNet_GetError();

//Clean thread variables
MasterThread = NULL;
ClientsLock = NULL;
ClientsLock = SDL_CreateSemaphore( 1 );//semafor protector, previene que mas de un thread vuelque informaciĆ³n a la vez a la cola

SDLNet_TCP_Close(ServerSock);//Close socket
SDLNet_Quit();//Close SDL_NET

void cNetwork::CleanClient(unsigned int Client)//clean a client so we can use it
//cout << "cleaning client: " << Client << endl;
Clients[Client]->UID =Client;
Clients[Client]->socket = NULL;
Clients[Client]->_server = myserver;
Clients[Client]->Connected = false;
Clients[Client]->Login = false;

void cNetwork::Start(Uint16 MAXUsers, Uint16 Port)

//1-initialize MAXUsers clients
//2-Start sock server

unsigned int x;
for (x=1;x<=MAXUsers;x++)
Clients.push_back(new cClient());//insert a new client into the clients vector
CleanClient(Clients.size() -1);

cout << "OPENING SERVER SOCKET... " << endl;
if(SDLNet_ResolveHost(&ip,NULL,Port)==-1) {
cout << "SDLNet_TCP_ResolveHost:" << endl  << SDLNet_GetError();

if(!ServerSock) {
cout << "SDLNet_TCP_open: " << endl << SDLNet_GetError();

ShutDown = false;
//Create and run the threads
MasterThread = SDL_CreateThread( &Master, this );


int cNetwork::Master( void *data )//Master listener function/thread
TCPsocket new_tcpsock; //Temporary socket to store incoming connections

cout << "Waiting for incoming connections... " << endl;

bool doneMain=false;
while(!doneMain)//MAIN LOOP
    if(!new_tcpsock)//We have a new client incoming
        new_tcpsock=SDLNet_TCP_Accept(((cNetwork*)data)->ServerSock); // accept a connection coming in on server_tcpsock
        SDL_Delay(5);//No new clients, wait a little
        cout << "New client incoming..." << endl;
        unsigned int x;
        if (((cNetwork*)data)->Clients[x]->Connected==false)
            ((cNetwork*)data)->Clients[x]->socket=new_tcpsock;//asign the socket
            ((cNetwork*)data)->Clients[x]->Connected = true;
            ((cNetwork*)data)->Clients[x]->Login = false;
            ((cNetwork*)data)->Clients[x]->StartThreads();//start client listener thread
        new_tcpsock=NULL;//release temporary socket var

        }//if new data

if (((cNetwork*)data)->ShutDown==true)doneMain =true;

cout << "Exiting Main thread..." << endl;
return 0;

void cNetwork::ClearClients()//Clear disconnected clients
unsigned int x;
bool done = false;
if (Clients.size()>0)
while (!done)
 if (!Clients[x]->Connected)
           // cout << "Number of clients:" << Clients.size() << endl;
            if (x>0)x--;
            }//if !connected
}//clients size


void cNetwork::Finish()
    ShutDown =true;
    unsigned int x;
            Clients[x]->Connected=false;//force disconnection

    while (Clients.size()>0)

There are many things here, but I will resume it to the maximum, to do that, below there is a simple example to use the class:

#include "network.h"//socket server class 
cNetwork NET;
NET.myserver = &NET;
NET.Start(100,55555); //start networking, up to 100 clients on port 55555
std::sting text;
SDL_SemWait( NET.ClientsLock );
text = NET.InputQueue.front();//take a message out from the queue
SDL_SemPost( NET.ClientsLock ); 
NET.Clients[X]->Send("data");//will send "data" to client X
NET.Finish();//finish socket server 

  • We include the class
  • Then instantiate it
  • Start the listener thread (at this moment it is fully functional)
  • Then I extract a message from the input queue (let's suppose we have a connected client that send it..)
  • I send a message to the server ("data")
  • Finally, I close the server
Want to test the class? basically start an empty project, add the three files I have described above and then add the example, after you compile the project you will be able to connect just using telnet (telnet localhost 55555)

That's all for now, in the next post I will mix everything to make the client login a reality, I will make a text based client too and hope everything is clear that way.

No comments: