0. Introduction
This article presents the details of TCP/IP socket programming in C++. After reading this article, you will be able to build your own server that is able to handle multiple clients at the same time. Several key classes are developed in this article, and hopefully, you will have the chance to use these classes in your daily development work. The first one is myTcpSocket
, this class hides the details of socket programming by providing a simple and easy-to-use interface, it is used to build both the server and client. The second class is myThread
class whose main purpose is to make server to handle multiple clients simultaneously: for each incoming client call, the server will create a separate thread to communicate with this client, therefore it can handle as many incoming clients as there are. In a multi-thread environment like this, however, synchronization of these threads is always an important issue. To solve this problem, we need our last major class, namely, mySemaphore
.
The following are the reasons why you might be interested in this article:
- You might want to know the details of client/server programming. Sure, .NET is all about XML and web services, and it provides a whole new framework to make your life easier, but the foundation of its network part is still socket programming, and understanding the details about socket programming will make us not only feel easier, but also happier.
- You might want to develop application that has the client/server structure but for some reason, you cannot use the APIs/classes from
MFC
or C#
. For instance, if the application is to be developed on a Unix/Linux platform, then these APIs/classes are not even available. If you have a situation like this, hopefully you can remember the classes that we present here and give them a try: they are light-weighted and quite generic, they can be easily used with no need to provide any window handle or whatsoever, also they can be incorporated easily into applications that are developed on Unix/Linux platform by making simple changes to these classes. - Your application is not necessarily client/server oriented, but still you need a set of generic and low-level building blocks for sockets. One example is that your application might need inter-process communication (IPC) methods, then
myTcpSocket
can be at least one of your candidates. Other applications may involve multi-threading, in this case, myThread
and mySemaphore
classes could be helpful to your work.
One last reason, my earlier article, "A light-weighted client/server socket class in C++" (we will call this article #1 in a later discussion), is about a single server and single client, and one reader asked me if we can make the server handle multiple clients. Well, here is the answer for you, hope you have a chance to see this article too.
Hope this gives you the motivation to read this article. The next section will describe the client/server scenario that is implemented in this article, then we discuss how to build/compile the project if you want to try it out yourself. The next several sections present the details of implementing the client/server structure using the above-mentioned key classes - this also serves as an example of how to use these generic classes in real applications.
1. Client/Server Scenario
The client/server structure we are interested in is described as follows. The server will be started first and after it is started, we want the server to wait for the incoming client calls, and periodically report its status: how many clients have been connected with the server and how many clients have been disconnected with the server. Meanwhile, once an incoming call is detected and accepted, the server will create a separate thread to handle this client, it will therefore create as many separate sessions as there are incoming clients and it should be able to "talk" with any one of these clients. Once the server receives a Quit/quit
message from one of these clients, it will shutdown the connection with this particular client.
Again, to implement this client/server scenario, we will first build several key classes, namely, myTcpSocket
, myThread
and mySemaphore
. In the next section, we will discuss how to compile/build the project and we will then describe these classes and implementation details in the next several sections.
2. How To Build and Run the Project
You can download the source code and the following .cpp and .h files should be included in the zip file:
For the server:
winSeverForMultipleClient.cpp
mySocket.cpp
myThread.cpp
myThreadArgument.cpp
mySemaphore.cpp
myHostInfo.cpp
myEvent.cpp
myException.cpp
myLog.cpp
mySocket.h
myThread.h
myThreadArgument.h
mySemaphore.h
myHostInfo.h
myEvent.h
myException.h
myLog.h
For the client:
myClient.cpp
mySocket.cpp
myHostInfo.cpp
myException.cpp
myLog.cpp
mySocket.h
myHostInfo.h
myException.h
myLog.h
After downloading these files, you can then build two projects: one for the server and one for the client. After compiling, you should start the server first. Successfully starting the server will show the following console screen:
my localhost (server) information:
Name: liyang
Address: 209.206.17.136
Summary of socket settings:
Socket Id: 1936
port #: 1200
debug: false
reuse addr: false
keep alive: false
send buf size: 8192
recv bug size: 8192
blocking: true
linger on: false
linger seconds: 0
server finishes binding process...
server is waiting for client calls ...
This assumes that you are using your local PC as the server, therefore, Name
should show your domain name (or, in some cases, your PC's name, like in this example), and Address
should show your current IP address (depending on your ISP, your IP address can change from time to time. At the time I was running the server, this IP address was 209.206.17.136
).
Clearly, in order to communicate with your server, you need to let your client know the IP address of your server. To do so, you need to create a simple text file named serverConfig.txt which contains only one line: the IP address of the server. So add 209.206.17.136
into this file and save it in the directory where you client executable resides, and then start the client by double-click this executable. You can start as many clients as you want and you can send messages from server to any of these clients by typing on the keyboard, also, you can send messages from any client to the server again by typing a string
on the keyboard. To end a client, send the message Quit
or quit
from the client to server, this client will be terminated. Have fun playing with this simple client/server system!
In the next few sections, we will discuss the classes in details and show how to use these classes to implement this client/server system.
3. myTcpSocket class
In this section, myTcpSocket
class is described. In fact, article #1 presents a rather detailed description about this class, so here let us only show the key methods of this class by using the following examples. In a nutshell, this class encapsulates socket-related system calls into a single class to offer a simple and easy-to-use interface, a typical usage of this class (as a server) is shown as follows:
myTcpSocket myServer(PORTNUM);
myServer.bindSocket();
myServer.listenToClient();
while ( 1 )
{
string clientName;
myTcpSocket* newClient = myServer.acceptClient(clientName);
newClient->receiveMessage(messageFromClient);
newClient->sendMessage(messageToClient);
}
The following code shows the usage of myTcpSocket
class on the client side:
myTcpSocket myClient(PORTNUM);
myClient.connectToServer("209.206.17.136",ADDRESS);
while (1)
{
myClient.sendMessage(messageToServer);
int messageLength = myClient.recieveMessage(messageFromServer);
}
Besides these major methods in myTcpSocket
class, there are other methods you can use to manipulate the socket, for example:
void setDebug(int);
void setReuseAddr(int);
void setKeepAlive(int);
void setLingerOnOff(bool);
void setLingerSeconds(int);
void setSocketBlocking(int);
With all this being said, it is now easy to see how to construct a very basic client/server system using the above server side and client side code (see article #1). However, the following problems still exist in this basic model:
- The server will not be able to handle multiple clients.
- If we place the above server side code in
main()
function, the myServer.acceptClient()
call inside the while
loop will block everything, we cannot implement any other processing task in main()
, for example, what if we also want to collect the performance status of the server, such as how many clients have made the connection, etc., the blocking structure prevents us from doing anything else!
To solve these problems, we need two more classes, namely, myThread
and mySemaphore
. These classes are discussed in the next section.
4. Two More Key Classes: myThread, mySemaphore
The existing problems discussed in the previous section suggest that multithreading is the best solution:
- To handle multiple clients, we can create a thread for each incoming client call, and this thread will handle the communication between the server and this particular client.
- The problem of making blocking calls and continuing processing can be solved by creating a thread: you can call the blocking function in this thread and let the main thread continue its processing without waiting.
myThread
can be used for this situation, it is also a generic class that can be easily used in the development of other applications. Its main functionalities include the following: create a thread, start to execute a thread, suspend/resume a thread, wait for a thread to finish and get access of its exit code, access the settings of a thread (for instance, the priority of a thread), and report time statistics of a thread, etc. For details of this class and its usage example, you can read my previous article, "Producer/Consumer Implementation Using Thread, Semaphore and Event" (we call this article #2 in a later discussion).
Let us now take a look at how to use myThread
class together with myTcpSocket
class to create a client/server system and solve the previous two problems. Here is the improved (but also simplified) main()
function on the server side (don't worry about the details):
int main()
{
myTcpSocket::initialize();
myTcpSocket myServer(PORTNUM);
myThreadArgument* serverArgument = new myThreadArgument(
&myServer,&coutSemaphore,serverName);
myThread* serverThread = new myThread(serverHandleThread,(void*)serverArgument);
serverThread->execute();
while ( 1 )
{
Sleep(50000);
}
return 1;
}
Examining the above main()
function, one can tell that instead of calling the blocking function which will listen/accept the incoming clients, the main thread will only do the initialization work and create the server instance, the blocking call (acceptClient()
) is moved to a thread created by the main thread, this thread is serverHandleThread
. Therefore, the main
function is free to do any other processing you might want to do, for instance, reporting the status of the server (you can see the details in the source files you downloaded). This discussion can be clearer if you continue to examine the definition of the server thread, serverHandleThread
, that is created in the main()
function (again, a simplified version):
DWORD WINAPI serverHandleThread(LPVOID threadInfo)
{
myTcpSocket* myServer = serverArgument->getClientConnect();
string serverName = serverArgument->getHostName();
myServer->bindSocket();
myServer->listenToClient();
myThreadArgument* clientArgument[MAX_NUM_CLIENTS];
myThread* clientHandle[MAX_NUM_CLIENTS];
for ( int i = 0; i < MAX_NUM_CLIENTS; i++ )
{
clientArgument[i] = NULL;
clientHandle[i] = NULL;
}
int currNumOfClients = 0;
while ( 1 )
{
myTcpSocket* client;
string clientName;
client = myServer->acceptClient(clientName);
if ( currNumOfClients < MAX_NUM_CLIENTS-1 )
{
clientArgument[currNumOfClients] = new myThreadArgument(
client,coutSemaphore,clientName);
clientHandle[currNumOfClients] = new myThread(clientHandleThread,
(void*)clientArgument[currNumOfClients]);
serverArgument->addClientArgument(clientArgument[currNumOfClients]);
clientHandle[currNumOfClients]->execute();
currNumOfClients++;
}
}
return 1;
}
To see how we can handle multiple clients, notice that once acceptClient()
returns, i.e., an incoming client call is received and accepted, the above server thread will not start the communication with this client, instead, it will create a new thread, called clientHandleThread
, pass the client connection to this thread, and let the communication between the server and this new client become the full-time job for this newly created thread. It will then go back to wait for another incoming call, and create yet another new thread to handle the new incoming client. By doing so, the server can handle as many clients as there are. The main flow in the clientHandleThread
is shown as follows (simplified version):
DWORD WINAPI clientHandleThread(LPVOID threadInfo)
{
myTcpSocket* clientConnection = clientArgument->getClientConnect();
string clientName = clientArgument->getHostName();
while(1)
{
string messageFromClient = "";
int numBytes = clientConnection->recieveMessage(messageFromClient);
clientConnection->sendMessage(string(messageToClient));
}
return 1;
}
Now that we have presented the solutions to the two problems that the simple client/server structure has, we need to take a look at the client side. Fortunately, with the understanding of the above solution, it is much easier to understand the client side code, you should be able to read the code you downloaded with no problem.
The other class needed to discuss in this section is mySemaphore
class. Again, once multiple threads are involved, we right away have the synchronization issue. mySemaphore
class is developed for this purpose. For example, if some/all the client threads need to access some global variable(s), this class will be extremely helpful. In our case, since we are only developing a framework for the client/server structure, i.e., we don't really have a specific application to fit, we don't really have a serious synchronization issue here. However, just to show the usage of this class, we treat the console screen and the log file as the global resources, and we use this class to synchronize the access to this global resource.
The basic function set provided by this class is quite intuitive: you can create a semaphore instance by actually creating a new semaphore or opening an existing one. After having the semaphore instance, you can lock it (either wait on it until you can successfully lock it or simply try and return if you cannot lock it at the moment), unlock it, change its settings (initial count, maximum count, etc.), etc.
It is clear that this class encapsulates the WIN32 semaphore APIs, and, indeed, one can find a CSemaphore
class in MFC that provides wrappers to some of these APIs as well, however, believe it or not, CSemaphore
lacks a method to wait for the semaphore to be released, which, perhaps, is considered to be one of the key functions of this class! Since the application of this class is quite straightforward and intuitive, we are not going to discuss this class in more details, you can read article #2 to understand more about this class. It is also worth mentioning that this class is generic enough to be used in other application developments.
5. Several Helper Classes: myHostInfo, myEvent, myThreadArgument, myLog and myException
Up to this point, you should not have any big problems reading and understanding the code. When you are going through the code, you may also notice several other classes that we developed. In this section, we will briefly discuss these helper classes, again, all these classes are presented in much better detail in article #2, you can read it if you need to know more.
In a client/server environment, even before you can construct the client/server structure, you need to figure out several trivial yet important questions: if I am using my local PC as the server or client, what is the domain name and what is the IP address of my local PC? For the remote server, if I know its domain name is www.codeproject.com, how do I know its IP address, or, if I know the IP address of the server, how do I know its domain name? All these questions will be answered by using myHostInfo
class: if you pass a domain name to the constructor, this class will tell you the IP address and if you pass in an IP address to the constructor, it will tell you the domain name. If you are using your local PC, you can use the constructor which takes no parameter and you can query both the name and the IP address from this class. Again, read article #1 to get more information.
myEvent
class is another generic class that is used in the client/server structure. Its main purpose is to keep the main thread informed about which client thread has terminated its communication session so the main thread can report the server status. In article #2, its main usage is to terminate a thread safely. Therefore, in both applications, this class is of the function of a signal class. Article #2 presents a much more detailed description of this class. Also, article #2 presents myThreadArgument
class in detail, its main purpose is to pack a set of parameters and feed this set to the thread so the thread and its parent thread can share key information.
myLog
class, as its name suggested, is mainly for the purpose of understanding what is going on in the system since debugging a system with multithread could be difficult. If you don’t need the log, you can search for winLog
in all the .cpp files and comment them out. Also, in order to capture the possible errors, a simple myException
class is provided. We recommend that you keep this class, it is quite simple and easy to understand anyway.
6. An Example
Now that we finished all the necessary classes, we can present one example. In order to save the space on the CP server, I did not use any screen shots, but instead just pasted part of the log file into this article. Notice this log file was from the server side, you can see all the sessions between the server and each client, also you can see the periodical status report the server produced. Again, all the messages were typed from the keyboard, also, I used my own PC as both the server and client - it will be more fun if you could find two PCs and make them talk to each other.
DATE: 07/23/04 - 01:04:22 syslog.log
system started ...
initialize the winsock library ... successful
Retrieve the local host name and address:
==> Name: liyang
==> Address: 209.206.17.197
Summary of socket settings:
Socket Id: 1936
port #: 1200
debug: false
reuse addr: false
keep alive: false
send buf size: 8192
recv bug size: 8192
blocking: true
linger on: false
linger seconds: 0
server finishes binding process...
server is waiting for client calls ...
-----------------------------------------------------------------
server (name:liyang) status report:
the following clients have successfully connected with server:
the following clients have shutdown the connection:
-----------------------------------------------------------------
==> A client from [liyang-A] is connected!
==> A client from [liyang-B] is connected!
==> A client from [liyang-C] is connected!
[RECV fr liyang-A]: hi, this is client A.
[SEND to liyang-A]: hello, A
[RECV fr liyang-B]: this is client B
[SEND to liyang-B]: hello, B
[RECV fr liyang-C]: this is C1
[SEND to liyang-C]: no, you are C!
-----------------------------------------------------------------
server (name:liyang) status report:
the following clients have successfully connected with server:
liyang-A
liyang-B
liyang-C
the following clients have shutdown the connection:
-----------------------------------------------------------------
[RECV fr liyang-C]: yes, it was a typo!
[SEND to liyang-C]: okay!
[RECV fr liyang-A]: so, who has connected with you?
[SEND to liyang-A]: you, B and C.
[RECV fr liyang-B]: any news?
[SEND to liyang-B]: no, everything is cool.
-----------------------------------------------------------------
server (name:liyang) status report:
the following clients have successfully connected with server:
liyang-A
liyang-B
liyang-C
the following clients have shutdown the connection:
-----------------------------------------------------------------
[RECV fr liyang-C]: in that case, I am leaving.
[SEND to liyang-C]: bye!
[RECV fr liyang-C]: quit
[RECV fr liyang-A]: what about now?
[SEND to liyang-A]: you and B.
-----------------------------------------------------------------
server (name:liyang) status report:
the following clients have successfully connected with server:
liyang-A
liyang-B
liyang-C
the following clients have shutdown the connection:
liyang-C
-----------------------------------------------------------------
[RECV fr liyang-B]: I am also leaving.
[SEND to liyang-B]: okay, come back!
==> A client from [liyang-D] is connected!
[RECV fr liyang-D]: hello, are you there?
[SEND to liyang-D]: yes!
-----------------------------------------------------------------
server (name:liyang) status report:
the following clients have successfully connected with server:
liyang-A
liyang-B
liyang-C
liyang-D
the following clients have shutdown the connection:
liyang-B
liyang-C
-----------------------------------------------------------------
[RECV fr liyang-A]: anything new?
[SEND to liyang-A]: client D is also connected!
[RECV fr liyang-A]: good! ask D if anything is new.
[SEND to liyang-A]: okay.
[RECV fr liyang-D]: nothing is new!
[SEND to liyang-D]: okay.
-----------------------------------------------------------------
server (name:liyang) status report:
the following clients have successfully connected with server:
liyang-A
liyang-B
liyang-C
liyang-D
the following clients have shutdown the connection:
liyang-B
liyang-C
-----------------------------------------------------------------
7. Conclusion
This article presents the implementation of a client/server structure where the server can handle multiple clients at the same time. Several key classes are developed in this application and they are also generic enough to be used in other applications. Hope this will be of some help to your development work and I certainly welcome any suggestions and comments.
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.