Single Server With Multiple Clients : a Simple C++ Implementation
2014-01-02 00:24
501 查看
Single Server With Multiple Clients : a Simple C++ Implementation
By liyangyu, 22 Jul 2004
|
|
Is your email address OK? You are signed up for our newsletters
but your email address is either unconfirmed, or has not been reconfirmed in a long time. Please click here to
have a confirmation email sent so we can confirm your email address and start sending you newsletters again. Alternatively, you can update
your subscriptions.
Download source - 40 Kb
0. Introduction
This article presents the details of TCP/IPsocket 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
myThreadclass 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,
.NETis
all about
XMLand 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
MFCor
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
myTcpSocketcan be at least one of your candidates. Other applications may involve multi-threading,
in this case,
myThreadand
mySemaphoreclasses
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 later discussion), is about a single server and single client, and one reader asked me if we can make the server to 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 connectedwith 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/quitmessage
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,
myThreadand
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 .cppand
.hfiles
should be included in the
zipfile:
For the server:
Collapse | Copy
Code
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:
Collapse | Copy
Code
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:
Collapse | Copy
Code
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,
Nameshould show your domain name (or, in
some case, your PC's name, like in this example), and
Addressshould 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.txtwhich
contains only one line: the IP address of the server. So add
209.206.17.136into
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
Quitor
quitfrom
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, myTcpSocketclass 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:
Collapse | Copy
Code
// // server side // myTcpSocket myServer(PORTNUM); // create the socket using the given port numberx myServer.bindSocket(); // bind the socket to the port number myServer.listenToClient(); // listening to the port/socket while ( 1 ) { // waiting for the client call... string clientName; myTcpSocket* newClient = myServer.acceptClient(clientName); // if we reach here, the server got a call already... // declare string messageToClient,messageFromClient; // receive message from client newClient->receiveMessage(messageFromClient); // send message to client newClient->sendMessage(messageToClient); // other stuff here ... }
The following code shows the usage of
myTcpSocketclass on the client side:
Collapse | Copy
Code
// // client side // myTcpSocket myClient(PORTNUM); // create the client socket using the given port id myClient.connectToServer("209.206.17.136",ADDRESS); // connect to server at IP addr 209.206.17.136 while (1) { // declare string messageToServer, messageFromServer... // send message to server myClient.sendMessage(messageToServer); // get message from server int messageLength = myClient.recieveMessage(messageFromServer); // other stuff... }
Besides these major methods in
myTcpSocketclass, there are other methods you can use to manipulate the socket,
for example:
Collapse | Copy
Code
void setDebug(int); void setReuseAddr(int); void setKeepAlive(int); void setLingerOnOff(bool); void setLingerSeconds(int); void setSocketBlocking(int);
With all these 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
whileloop 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 prevent us from doing anything else!
To solve these problems, we need two more classes, namely,
myThreadand
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 be the best solution: 1. to handle multiple clients, we can create a thread for each incoming client call, and this thread will handle the communication between the serverand this particular client; 2. 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.
myThreadcan 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 later discussion).
Let us now take a look at how to use
myThreadclass together with
myTcpSocketclass
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 the details):
Collapse | Copy
Code
int main() { // initialization, and declaration of variables, etc. // Initialize the winsock library myTcpSocket::initialize(); // create the server: open socket on the local host(server) myTcpSocket myServer(PORTNUM); // create a thread to implement server process: // listening to socket,accepting client calls, // communicating with clients, etc. This will free the // main control (see below) to do other stuff. myThreadArgument* serverArgument = new myThreadArgument( &myServer,&coutSemaphore,serverName); myThread* serverThread = new myThread(serverHandleThread,(void*)serverArgument); serverThread->execute(); // main control: since the above serverThread is handling the server functions, // this main control is free to do other things. while ( 1 ) { // do whatever you need to do here, I am using // Sleep() to make a little delay, // pretending to be the other possible processings you might want to do... Sleep(50000); // report the server status here... // // code used to report server status // } 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
mainfunction
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 more clear if you continue to examine the definition of the server
thread,
serverHandleThread, that is created in the
main()function
(again, a simplified version):
Collapse | Copy
Code
DWORD WINAPI serverHandleThread(LPVOID threadInfo) { // other stuff... // get the server myTcpSocket* myServer = serverArgument->getClientConnect(); string serverName = serverArgument->getHostName(); myServer->bindSocket(); // bind the server to the socket myServer->listenToClient(); // server starts to wait for client calls // initialize the threads that will be generated to handle // each incoming client 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 ) { // wait to accept a client connection,processing // is suspended until the client connects myTcpSocket* client; // connection dedicated for client communication string clientName; // client name client = myServer->acceptClient(clientName); // other stuff... // for this client, generate a thread to handle it so we can // continue to accept and handle as more clients as we want 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
clientHandleThreadis
shown as follows (simplified version):
Collapse | Copy
Code
DWORD WINAPI clientHandleThread(LPVOID threadInfo) { // some related stuff ... // get the client connection: receiving messages from // client & sending messages to the // client will all be done by using this client connection myTcpSocket* clientConnection = clientArgument->getClientConnect(); string clientName = clientArgument->getHostName(); // the server is communicating with this client here while(1) { string messageFromClient = ""; // receive from the client int numBytes = clientConnection->recieveMessage(messageFromClient); // send to the client // ... construct a message to send, saved in messageToClient clientConnection->sendMessage(string(messageToClient)); // other possible stuff ... } 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
mySemaphoreclass. Aagain, once multiple threads are involved,
we right away have the synchronization issue.
mySemaphoreclass 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 openning 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
CSemaphoreclass
in MFC that provides wrappers to some of these APIs as well, however, believe it or not,
CSemaphorelacks 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 of mentioning 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
myHostInfoclass:
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.
myEventclass 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
myThreadArgumentclass
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.
myLogclass, 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
winLogin all
the
.cppfiles and comment them out. Also, in order to capture the possible errors, a simple
myExceptionclass
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 serverside, 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 talking to each other.
Collapse | Copy
Code
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 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
相关文章推荐
- Dropping multiple SQL Server objects with a single DROP statement
- STM32F2x Is it possible to request multiple DMA streams with single request
- 算法第四版3-2Binary Search Trees With C++ Implementation
- Simple server side cache for Express with Node.js——Express 实现简单的服务器端缓存【翻译】
- A implementation of a simple unix-like shell with pure c
- Reflection in C++: The simple implementation of Splinter Cell
- Java: Too Simple Hibernate Sample with Ms SQL Server
- SimpleHTTPServerWithUpload.py
- Working with Group Policy Objects Programmatically - simple C++ example illustrating how to modify a
- A Reusable Windows Socket Server Class With C++
- Simple HTTP Proxy Server Implementation, based on wcol - Projects - Michael Vorburger's Private Homepage
- Java: Too Simple Hibernate Sample with Ms SQL Server
- Tech Tip: Really Simple HTTP Server with Python
- Creating a simple static file server with Rewrite--reference
- pugixml Light-weight, simple and fast XML parser for C++ with XPath support
- Nicolas Lehuen’s pytst is a C++ ternary search tree implementation with a Python interface
- Tech Tip: Really Simple HTTP Server with Python
- Solving the SQL Server Multiple Cascade Path Issue with a Trigger (转载)
- Really Simple HTTP Server with Python
- Tech Tip: Really Simple HTTP Server with Python