您的位置:首页 > 编程语言 > C语言/C++

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 liyang
yu, 22 Jul 2004




   4.79 (82 votes)
 
Rate:vote
1vote
2vote
3vote
4vote
5
 
inShare0




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


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


 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 
myTcpSocket
 class 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 
myTcpSocket
 class, 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
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 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: 
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 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):


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


 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 
clientHandleThread
 is
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 
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: 
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 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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: