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