Single Server With Multiple Clients : a Simple C++ Implementation

来源:互联网 发布:科学计算器软件 编辑:程序博客网 时间:2024/06/04 17:51

Single Server With Multiple Clients : a Simple C++ Implementation

By liyang yu22 Jul 2004
 
Rate:
vote 1vote 2vote 3vote 4vote 5
 
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/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 ismyTcpSocket, 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:

  1. 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.
  2. 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.
  3. 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 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 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.cppmySocket.cppmyThread.cppmyThreadArgument.cppmySemaphore.cppmyHostInfo.cppmyEvent.cppmyException.cppmyLog.cppmySocket.hmyThread.hmyThreadArgument.hmySemaphore.hmyHostInfo.hmyEvent.hmyException.hmyLog.h

For the client:

myClient.cppmySocket.cppmyHostInfo.cppmyException.cppmyLog.cppmySocket.hmyHostInfo.hmyException.hmyLog.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.136Summary 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: 0server 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 case, 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 was209.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:

//// server side//myTcpSocket myServer(PORTNUM);  // create the socket using the given port numberxmyServer.bindSocket();          // bind the socket to the port numbermyServer.listenToClient();      // listening to the port/socketwhile ( 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 myTcpSocket class on the client side:

// // client side//myTcpSocket myClient(PORTNUM);  // create the client socket using the given port idmyClient.connectToServer("209.206.17.136",ADDRESS);          // connect to server at IP addr 209.206.17.136while (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 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 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:

  1. The server will not be able to handle multiple clients;
  2. If we place the above server side code in main() function, the myServer.acceptClient() call inside thewhile 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 prevent 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: myThreadmySemaphore

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 server and 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.

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 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 the details):

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 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 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):

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 clientHandleThread is shown as follows (simplified version):

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 mySemaphore class. Aagain, 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 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 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 of mentioning this class is generic enough to be used in other application developments.

5. Several Helper Classes: myHostInfomyEventmyThreadArgumentmyLogand 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 talking to each other.

DATE: 07/23/04 - 01:04:22                    syslog.logsystem started ...initialize the winsock library ... successfulRetrieve the local host name and address:        ==> Name: liyang        ==> Address: 209.206.17.197Summary 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: 0server 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

0 0