Creating Servers for handling Multiple clients
Fog Edition
By FireNet
(This document is dedicated to my friend Aaron Anderson)
(Fog Edition means I dont tell you everything stright.All the info will be there but you will have to do much thinking)
Servers and Clients,the backbone of the internet after the TCP/IP protocol.Right now I will go
yak yak on TCP/IP so if you know it skip this.Also I intend not to provide you with everything.
I will provide enought material and directions for you but you will have to do something on your
own.I just teach you how to setup a basic server which can accept multiple connection and
of course guidlines on how to make a robust server.We will also look into a very simple kind of
server 'Chat servers'.
TCP/IP
-------
TCP stands for Transmission Control Protocol.IP stands for Internet Protocol Address.TCP
implementations provides a defined way about how data transmissions can be done over
the wire between diffrent computers.There are a few sub protocols but we wont worry about
them since they are at a lower level.
OSI Model
----------
It is an open standard published by the ISO(International Standards Organisation) on how
systems can recive and transmit data with each other.TCP/IP is an implemetation of it.
It has 7 layers and each do a diffrent job.We dont want to go into details now.
This helps various computer with diffrent hardware and OS to communicate with each other
without any special work and diffrent OS can transfer data transperantly.This enable a
network to consit of diffrent machines and still transfer data without a problem
http://www.webopedia.com/quick_ref/OSI_Layers.asp
How TCP works
---------------
A basic overview of course.When a client wants to connect to a server it send an SYN
packet to the server.The server responds with a SYN/ACK packet to which the client responds
with a ACK packet.
Client ---> SYN ---> Server
Client <--- SYN/ACK <--- Server
Client ----> ACK ---> Server
This is called a 3 way TCP/IP hand shake.Then the connection is ready for transmitting data.
The data sent is broken up into multiple packets and sent between the client and server.There
are mechanisims to ensure that the packets are recived and assembled in the correct order
and the data has not been corrupted.If it has been corrupted the data has to be retransmitted.
Along with a lot more protections, error checks etc etc ....
We never see any of this as they are handled at lower levels so we dont need to worry about them.
Ports
-----
The ships dock here.Actually ports are a way to seperate diffrent connections.When a
client connects to a server there mabe more than one service running on that machine or if the data
is sent to the client how,does one say which app gets that data.Ports range from 0-6255.They serve
to identification which service a client wants to access or which client app should get the info.
Now you got a very basic intro on how computers communicate we can go about designing servers.
I will provide a basic client here with will be used for testing.We will not disscuss it much.
/*-------------------Echo_client.cpp-------------------*/
#include <windows.h>
#include <winsock.h>
#include <stdio.h>
#include <iostream.h>
#include <conio.h>
#include <signal.h>
#include <stdio.h>
//DECLARATIONS
//error trapping signals
#define SIGINT 2
#define SIGKILL 9
#define SIGQUIT 3
//SOCKETS
SOCKET sock,client;
void s_handle(int s)
{
if(sock)
closesocket(sock);
if(client)
closesocket(client);
WSACleanup();
cout<<"EXIT SIGNAL :"<<s;
exit(0);
}
void s_cl(char *a, int x)
{
cout<<a;
s_handle(x+1000);
}
void main()
{
//Declarations
int res,i=1,port=100;
char buf[100];
WSADATA data;
signal(SIGINT,s_handle);
signal(SIGKILL,s_handle);
signal(SIGQUIT,s_handle);
cout<<"\t\tEcho Client";
sockaddr_in ser;
sockaddr addr;
ser.sin_family=AF_INET;
ser.sin_port=htons(port); //Set the port
ser.sin_addr.s_addr=inet_addr("127.0.0.1"); //Set the address we want to connect to
memcpy(&addr,&ser,sizeof(SOCKADDR_IN));
res = WSAStartup(MAKEWORD(1,1),&data); //Start Winsock
cout<<"\n\nWSAStartup"
<<"\nVersion: "<<data.wVersion
<<"\nDescription: "<<data.szDescription
<<"\nStatus: "<<data.szSystemStatus<<endl;
if(res != 0)
s_cl("WSAStarup failed",WSAGetLastError());
sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //Create the socket
if(sock==INVALID_SOCKET )
s_cl("Invalid Socket ",WSAGetLastError());
else if(sock==SOCKET_ERROR)
s_cl("Socket Error)",WSAGetLastError());
else
cout<<"Socket Established"<<endl;
res=connect(sock,&addr,sizeof(addr)); //Connect to the server
if(res !=0 )
{
s_cl("SERVER UNAVAILABLE",res);
}
else
{
cout<<"\nConnected to Server: ";
memcpy(&ser,&addr,sizeof(SOCKADDR));
}
while(true)
{
cout<<"\n>";
gets(buf);
res = send(sock,buf,100,0); //Send Data
if(res==0)
{
//0==other side terminated conn
printf("\nSERVER terminated connection\n");
closesocket(client);
client =0;
break;
}
else if(res==SOCKET_ERROR)
{
//-1 == send error
printf("Socket error\n");
s_handle(res);
break;
}
res=recv(sock,buf,100,0); //Recive Data
if(res>0)
{
cout<<"\nRecieved string:"<<buf;
}
}
WSACleanup();
}
/*-------------------Echo_client.cpp-------------------*/
Server Mania
-------------
There is a curse which goes "May you live in interesting times" and making servers will only help it.
Planning is as improtant here as it is else where.I will give a few basics by which you should be able
to plan and build your own servers with multiple client support.
We will build a single thread server with support for multiple clients.All info about a client will be kept
in a structure and funtions will use that info to perform various jobs.
Client Sturcture:
struct _client
{
bool con; //Set true if a client is connected
sockaddr_in addr; //Client info like ip address
SOCKET cs; //Client socket
fd_set set; //used to check if there is data in the socket
int i; //any piece of additional info
};
The obvious funtions we need are:
accept(_client *); //accept client connections
recv(_client *); //recive data from them
send(_client *); //send data
Globals:
MAX_CONS //Total No of Client allowed
PR_CONS //No of currently connected clients
We will build a version of an echo server.Any data sent by any client is sent to all the clients connected.
Chat server work in a similar manner.We will be doing a bare bones server and wont put in too much
error checking.
Basic Server Skeleton
---------------------
This describes how a server is structured.
1.Initialise a few variables i.e. the port,ip etc of the server
2.Start Winsock.(A must for windows server and clients)
3.Create the Socket
4.Set the Socket options
5.Bind the server to a port
6.Set Listen Mode
7.Set non-blocking mode.(Important if your server supports multiple clients or is gui)
8.Start the server loop
9.Exit server loop and cleaup memory
10.Stop Winsock.
Note:Of course most parts have their own error checks.
Now the server is not very much diffrent from the client see below for steps 1 to 7.
/****************SERVER.cpp****************/
void main()
{
int res,i=1,port=100;
WSADATA data;
cout<<"\t\tEcho Server (Multiple Client Support)";
sockaddr_in ser;
sockaddr addr;
ser.sin_family = AF_INET;
ser.sin_port = htons(port);
ser.sin_addr.s_addr = INADDR_ANY;
memcpy(&addr,&ser,sizeof(SOCKADDR_IN));
res = WSAStartup(MAKEWORD(1,1),&data);
cout<<"\n\nWSAStartup"
<<"\n Version: "<<data.wVersion
<<"\n Description: "<<data.szDescription
<<"\n Status: "<<data.szSystemStatus<<endl;
if(res != 0)
s_cl("WSAStarup failed",WSAGetLastError());
sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sock==INVALID_SOCKET )
s_cl("Invalid Socket ",WSAGetLastError());
else if(sock==SOCKET_ERROR)
s_cl("Socket Error)",WSAGetLastError());
else
cout<<"SOCKET Established"<<endl;
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,(char *)&i,sizeof (i));
res = bind(sock,&addr,sizeof(addr));
cout<<"Binding socket:"<<res<<endl;
if(res != 0)
s_cl("BIND FAILED",WSAGetLastError());
else
cout<<"Socket Bound to port : "<<port<<endl;
res = listen(sock,5);
cout<<"Server Listen mode : "<<res<<" Size = "<<m*2<<endl;
unsigned long b=1;
ioctlsocket(sock,FIONBIO,&b);
/****************SERVER.cpp****************/
Notes
------
1.htons()
This funtions is used make port numbers correct according the hardware.This is
due to the fact the diffrent hardware store numbers diffrently.If this is not called
clients from other computers will not be able to establish a connection.
2.bind() - binds the server to a specified port
3.listen()
This is used to set the listen mode and set the max amount of packets that will be allowed
to be stored at a time.If the no of packets exceed the limit they will be conveniently dropped.
4.ioctlsocket();
Set the blocking mode.Set b to one to make the socket a non-blocking one.In non blocking
mode the funtions will return immediately even if they have not got any respone.This will prevent
them from stopping the program operation till they get a response.This will enable us to loop through
all the clients and respond to the ones who have sent commands.Eg.In blocking mode the accept()
will wait till it recives a connection before it returns and in non-blocking mode it returns immediately.
Server Loop
------------
This is where all the work of a server is done
while(true)
{
accept_clients(); //Recive connections
send_clients(); //Send data to clients
recv_clients(); //Recive data from clients
}
That's it basically.Of course the the 3 funtions will be very similar to a simple server but they will
also have to handle some more work.
Low Level funtions
------------------
In a simple server we used funtions accept(),send(),recv() to interact with the client.
We will now overload them to do our work.
Accept funtion
---------------
int accept(_client *x)
{
x->i = sizeof(sockaddr);
x->cs = accept(sock,(sockaddr *)&x->addr, &x->i);
if(cs != 0 && cs !=SOCKET_ERROR)
{
x->con = true;
FD_ZERO(&x->set);
FD_SET(x->cs,&x->set);
return (true);
}
return (false);
}
Notes
------
x->cs = accept(sock,(sockaddr *)&x->addr, &x->i)
is quite note worthy.Here we assign the return of the funtion to another SOCKET(x->cs) variable.
We will be using this variable to communicate with the client.'sock' will be used only for
accepting connections.
(x->addr) will have all client related info like the IP address.
The rest is simple.If the return is valid then set the connection status (i.e x->con) to true and
initialise the fd_set.This will be used to check if data is present in the socket.
Send Funtion
-------------
int send(_client *x,char *buffer,int sz)
{
x->i = send(x->cs,buffer,sz,0);
if(x->i == SOCKET_ERROR || x->i == 0)
{
disconnect(x);
return (false);
}
else return (true);
}
Notes
x->i is used just because it's avilable,use another one if you like.
If there is a socket error or the return is 0 disconnect.I recomment you
find some out.We will be making disconnect() later.Zero means the client
has disconnecter.-1 i.e socket error means the client is unreachable due
to any reason.
Recv Function
--------------
int recv(_client *x,char *buffer,int sz)
{
if(FD_ISSET(x->cs,&x->set))
{
x->i = recv(x->cs,buffer,sz,0);
if(x->i == 0)
{
disconnect(x);
return (false);
}
return (true)
}
return (false);
}
That's it you have all the basic structre and low level stuff you need to build a simple server.
Now we are free to tackle building a robust server.
Building Robust Servers
----------------------
Bang,Crash,Hi,Hello,Crack,Oh master,ha,Woof............... sounds weird.Well they repesent
how diffrent client connect and how they behave.Some leave by saying good bye others
leave the line open,other disappear etc etc.
So one of the most important things for any server would be to keep track of client connections.
Connections appear and disappear at random so it best to have some central command to keep
track of server status and various connection details.The major problem will be clients disappearing
if we are not implementing a lot of checks( like now). We will be relying on the send funtion mainly to
see if a client has left us or is unreachble.That means recv() will not show any error unless a client
has quit properly but send will pick up a missing client.This is not much of a problem since you will
almost always have to send data to client continously.If no activity( i.e sending) then a improperly
disconnected client will remain in the memory and will not noticed untill you send it some data.This
should not pose any probs anyways so no need to worry about this.
Its best to have a server as modular as possible to enable modifiactions (which will be extensive)
without breaking any code.That would make things like porting(ie getting it to work on other OSs)
upgrading etc easy.
Oh yes there are always memory, it's alway limited, even if you are in a sea of memory,llike the old
saying goes "water,water everywhere not drop to drink".Other problems include things like buffer
overflows and illegal memory access.Brr goes crazy very quickly if your server has even 1 leak.
Now these are the few common problems.There are more when you go to make your server more
funtional.Following a few guidelines will sure help reduce them.(Noodles but you cant have it anyother
way)
1.If random memory access is used always provide for errors and deallocate memory on crashing
2.As far as possible never hard code anything.
3.Use defines when values have to be hard coded
4.Modularity is a must.Keep funtional code seperate from network code
5.Memory should always be conserved.
6.If linked lists are used, use a manager class to use them through,never directly
7.Seperate your code into diffrent segments and keep them in diffrent headers
8.Always develop funtions to check the status and look for errors on your server including
but not limited to no. of clients, memory usage, client details etc.
9.Try for max performance even if you are on a supercomputer.
This should be enough for a start.I will only discuss a few of them.The others you have to
figure out.
Modularity
----------
Oh,well you should know it so I will just brush on it.Why you still need it?Security
Yes,that wonderful oxcymoron.If all your send and recive are channeled through
one place it will be eaiser to implemet restrictions and permmisions.So reduce the number
of places you call recv(sock... or send(sock... to one place and use higher level funtions
to call them.Try to limit the network code as much as possible.Also seperate the recive,
send, and accept funtions.This will enable you to go multi-thread without trearing anything.
Status and Error Monitering
---------------------------
Develop these early.They will be a great help for debuging your code.
Well below you will see what a status monitor looks like.You can build diffrent ones if you like
or even integrate it with the lower level funtions.It's better if all this goes to a central location
as you will always be informed.
Of course not just one funtion can do all the work.You will have to develop ones which will have to
look at each connection as report details like the IP address etc.Also debug funtions.
[Maybe more,but in a biginner tut it wont be of much use]
Back to coding ---- >
The server loop is given below.You will have to develop the funtions on your own.Hey do say
this is a tut and all has to be told.I did tell everything.I gave you funtions to handle send, recive
and accept through the client structure dint I.All you have to do now is make a glorified loop.
while(true)
{
accept_clients(); //Recive connections
send_clients(); //Send data to clients
recv_clients(); //Recive data from clients
}
I wont leave you hanging up on a tree though
_client client[MAX_CONS];
void accept_clients()
{
for(int i=0;i<MAX_CONS;i++)
{
if(!client[i].con) //i.e a client has not connected to this slot
{
if(accept(client[i]))
{
//update server status
Server_Status(CLIENT_CON);
}
}
}
}
Server_Status(int msg)
{
if(msg == CLIENT_CON)
{
PR_CONS++;
cout<<"\nWow, a client has connected";
}
else if(msg == CLIENT_DIS)
{
PR_CONS--;
cout<<"Wee, a client has disconnected";
}
else
{
//never leave out anything
cout<<"\n>>>>>>We got an unknown message :"<<msg;
}
}
void disconnect(_client *x) //this is called by the low level funtions
{
if(x->cs)closesocket(x->cs);
x->con = false;
x->i = -1;
Server_Status(CLIENT_DIS);
}
That's it,now you have enough stuff to setup a server that will accept multiple connections.
Chat Servers
-------------
Chat servers are the easy to build since they have little work to do and require no protocol.
The code I gave above is sufficent to build a simple one.You will just have to build recv_clients()
and a chat_message(char *s).
Now let look at what a basic chat server does.Well a client connect,nick names himself,and
the types messages which are transmitted to everyone.I wont show how to handle the nick
registration (you do that) but I will show you a way to respond to a command and a simple
chat message.
Let's say all our commads start with a / and they will be handled server side.Ok get hold of the
server code I already gave and put in the server loop.You will have to define the messages,MAX_CONS
and PR_CONS.(also remove the send_client() will will not need that here).
Thse chat_message funtion just has to send a text message to all connected clients.
void chat_message(char *s)
{
int len = strlen(s);
for(int i=0;i<MAX_CONS;i++)
{
if(client[i].con) //valid slot,i.e a client has parked here
{
send(client[i],s,len);
}
}
}
Now the recive funtion has to get a message from any client who sends a message
#define BF_SZ 100
void recv_client()
{
char buffer[BF_SZ]
for(int i=0;i<MAX_CONS;i++)
{
if(client[i].con) //valid slot,i.e a client has parked here
{
if(recv(client[i],buffer,BF_SZ))
{
if(buffer[0]=='/')
{
//respond to commands
if(strcmp(buffer,"/server_bang")==0)
{
chat_message("8*8* The Server Goes BANG *8*8");
}
}
else
{
chat_message(buffer);
}
}
}
}
}
Naturally,Now you have a chat server,but you cant say who said what.For that you will have to
add a char variable to the _client struct respond to a command to assign the nick.Then add the nick
to the beginig of every message and voila a better chat server.More polish would invlove alerting
all the clients when a fellow connects or disconnects.(Server_Stats() can do that).No need for
me to do that it's simple and you will get more satisfaction if you do it.Else it will be no more than
copy paste.You now have enough potential to build your own server for any purpose.(For God's
sake dont make IRC yet,just add more commands and polish and some status display funtions)
--------------------------------
Protocols
(Basic overview)
--------------------------------
Very boring.Diplomats use it a lot.Nah computer potocols are much more interesting.It's
a specified way in which a client and a server talk.
Eg of a protocol
struct chat_protocol
{
int id;
char nick[10];
char msg[100];
};
What !!! that's just a structure.Well my friend deplomats never say even a simple thing simply
you know.Some times life is very simple.Now how do you use this.Simple,any file i/o.
send(sock,(char*)&prt_buff,sizeof(chat_protocol),0);
recv(sock,(char*)&prt_buff,sizeof(chat_protocol),0);
It's as simple as that.This was a very simplistic view.I dont really care to go into the jargon
when dealing with newbies.
Actually a protocol define how large a transmission is,what it is made up of,and where to find what.
Eg.The first byte to the 2nd byte will contain an integer containing an id.The next 10 byte will contain
the nick name.The rest will contain the message.That how a protocol would be on paper so that others
can implement it in their programms which may not be in C/C++ and then they will still be able to
communicate with your server.C++ make this stuff quite easy as you can see :-).
Of course there can be sub-protocols as well.The string terminator NULL ('\0') is another protocol
you could say.It tells us where the string ends.Aowsome eh ?
--------------------------------
Security
(Concerns)
--------------------------------
Well you know diplomats they never say anything directly and a lot of things said are not
what they seem at the surface and maybe more shallow or deeper that you think.Never
trust them blindly.
Let me drive it home.Look at the chat_protocol above.Take the msg char variable.Now
if you use cout<< it should display some text.But suppose a client has filled the entire
100 bytes with charA without a \0.What happens then.Your server will happily start reading
the string, cross the limit and go into restricted parts till it hits a \0 or CRASHES !!.A simple
buffer overflow exploit.
Always be careful with read/write limits.This should be enough to get you worried so when
you make your servers keep such things in mind.Always.
[From personal experiance.When I used to work on a simple chat server I noticed that I could
over write a part of memory to get past restrictions by simply setting a huge nickname.It over
wrote an int which stored my current permission.The permission check funtion used to give normal
permission as default.ie
if(check)
{
perm = 9;
}
else if (check == 222)
{
perm = 8;
}
else perm = normal;
As far as possible dont do that and handle only conditions which can be predicted.Everything
else should generate an error message and any possible action like disconnecting the client]
That's a wrap folks.You know have a little above the beginners level of knowledge of building
servers and it various factors and concerns.
[You should be able to find the full source of the chat server on my site,compare it with yours]
Name: Aaron M.(a.k.a FireNet)
E-Mail: [email]xfirenet@hotmail.com[/email]
Website: [url]www.xlock.ssr.be[/url]
[url]http://xlock.hostcubix.com/[/url]
- Burn Out /*">"£"£)£")£"$"£("£&^£"
[Exit notes:Well you should have got a nice overview of building servers quickly and in a cut-trought fasion.I will post some compleated code and and few notes concerning a few things which I have not really dealt with,and links to more resources of course]