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:
- Open a server socket in a port X
- Start a thread that will be listening in port X
- 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)
- 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.
<network.h>
#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
{ public: //functions cClient();//creator virtual ~cClient();//destructor void StartThreads();//Start listener thread static int listener( void *data );//Listener Thread void Send(const char* Msg);//Send data to client //variables 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; public: //functions cNetwork(); 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 //variables 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: //private variables IPaddress ip;//Server ip address TCPsocket ServerSock; //Server socket }; #endif // NETWORK_H
Then I have the client implementation:
<client.cpp>
#include "network.h" cClient::cClient() { //ctor ListenerThread = NULL; Connected = false; } cClient::~cClient() { //dtor //Stop the thread SDL_KillThread( ListenerThread ); //close the socket SDLNet_TCP_Close(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 if(result<=0) { //NO DATA // TCP Connection is broken. (because of error or closure) SDLNet_TCP_Close(((cClient*)data)->socket); cout << "Socket closed..." << endl; ((cClient*)data)->Connected = false; quit=true; } stringstream idmsg; idmsg << ((cClient*)data)->UID << "#" << in_msg;// I will add the UID from the client to the incoming message try{ 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 }else{ ((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 }//while 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 result=SDLNet_TCP_Send(socket,Msg,len); if(result<len) //If I can't send data probably I've been disconnected so.... { cout << "SDLNet_TCP_Send: " << endl << SDLNet_GetError(); Connected = false; } }else{ 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:
<network.cpp>
#include "network.h" cNetwork::cNetwork() { //ctor if(SDLNet_Init()==-1) {//Start SDL_NET cout << "SDLNet_TCP_INIT: \n " << SDLNet_GetError(); exit(2); } //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 } cNetwork::~cNetwork() { //dtor 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(); exit(1); } ServerSock=SDLNet_TCP_Open(&ip); if(!ServerSock) { cout << "SDLNet_TCP_open: " << endl << SDLNet_GetError(); exit(2); } 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 new_tcpsock=NULL; 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 }else{ cout << "New client incoming..." << endl; unsigned int x; for(x=0;x<((cNetwork*)data)->Clients.size();x++) { if (((cNetwork*)data)->Clients[x]->Connected==false) { ((cNetwork*)data)->CleanClient(x); ((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 break; } }//for new_tcpsock=NULL;//release temporary socket var }//if new data if (((cNetwork*)data)->ShutDown==true)doneMain =true; }//while cout << "Exiting Main thread..." << endl; return 0; } void cNetwork::ClearClients()//Clear disconnected clients { unsigned int x; bool done = false; //SDL_SemWait(ClientsLock); if (Clients.size()>0) { x=0; while (!done) { if (!Clients[x]->Connected) { Clients.erase(Clients.begin()+x); // cout << "Number of clients:" << Clients.size() << endl; if (x>0)x--; }//if !connected x++; if(x>=Clients.size())done=true; } }//clients size } void cNetwork::Finish() { ShutDown =true; SDL_WaitThread(MasterThread,&threadReturnMaster); unsigned int x; for(x=0;x<Clients.size();x++) { Clients[x]->Connected=false;//force disconnection }//for while (Clients.size()>0) { ClearClients(); } }
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 NET.InputQueue.pop(); 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
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:
Post a Comment