本节实现一个多线程TCP通信程序,该程序与项目二的程序相比,最大的特点就是可以实现一对多通信,即一个服务器端可以与若干个客户端同时通信,一个服务器端与3个相同的客户端同时通信的运行效果如图10-1所示。
图10-1 多线程TCP通信程序的界面(左上为服务器端,其余均为客户端)
10.2.1服务器端程序的原理
在TCP通信中,要使服务器端能够同时接受多个客户端的连接,则可以把accept()函数放到一个while循环中,这样accept()函数就会执行很多次,每当有客户端发来连接请求时,accept()函数就会接受连接,从而实现了服务器能接受多个客户端的连接。
每接受一个客户端连接后,就创建一个子线程单独与这个客户端进行通信,只要让主线程在accept函数之后派出新线程,然后让主线程继续listen监听,处理新的连接请求,让新线程自行与客户端通信即可,程序的流程如图10-2所示。
socket() |
bind() |
accept() |
CreateThread() |
closesocket() |
recv() |
listen() |
服务器 |
send() |
派生线程 |
主线程 |
传递通信套接字 |
接收sockConn |
while 循环体 |
图10-2 TCP多线程通信的服务器端流程图
需要注意的是,子线程要与一个客户端通信,必须知道这个客户端对应的套接字,因此必须把accept()函数返回的套接字作为线程函数的参数传递给子线程。由于传递给线程函数的参数必须是一个指针类型,因此声明套接字时必须声明为Socket的指针类型,例如:
SOCKET *sockConn = new SOCKET; // sockConn是一个指针类型
// *sockConn是指针指向的套接字
*sockConn=accept(sockSer,(SOCKADDR*)&addrCli,&len)
10.2.2服务器端程序制作步骤
本例只制作服务器端程序,客户端程序采用的是2.3.2节制作的程序,服务器端程序的制作步骤如下:
新建工程,选择“Win32 Console Application”,输入工程名(如MThreadS),在下一步中选择“一个简单的程序”。在“工程名.cpp”文件中,输入如下代码,编译,运行。
#include<iostream.h>
#include<winsock2.h>
#pragma comment(lib,"ws2_32.lib")
DWORD WINAPI AnswerThread(LPVOID lparam); //线程函数声明
int main(){
WSADATA wsaData;
if(WSAStartup(MAKEWORD(2,2), &wsaData)) //初始化WinSock协议栈
{ cout<<"加载Winsock协议栈失败!";
WSACleanup();
return 0; }
SOCKET sockSer; //创建监听套接字
sockSer=socket(AF_INET,SOCK_STREAM,0); //初始化套接字
SOCKADDR_IN addrSer,addrCli; //服务器端要创建两个套接字地址
addrSer.sin_family=AF_INET;
addrSer.sin_port=htons(5566);
addrSer.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
bind(sockSer,(SOCKADDR*)&addrSer,sizeof(SOCKADDR)); //绑定套接字
listen(sockSer,5);
int len=sizeof(SOCKADDR);
cout<<"等待客户端连接…"<<endl;
while (true) { //循环接受客户端请求,这里是关键
SOCKET *sockConn = new SOCKET; /* 创建通信套接字 */
//将accept()函数放到while循环中,实现接受多个客户端连接
if ((*sockConn=accept(sockSer,(SOCKADDR*)&addrCli,&len)) == INVALID_SOCKET) {
cout<< "accept failed !\n";
closesocket(*sockConn); WSACleanup();
return -1; }
//创建新线程
DWORD ThreadID; //double word
CreateThread(NULL, 0, AnswerThread, (LPVOID)sockConn, 0, &ThreadID); }
closesocket(sockSer);
WSACleanup();
return 0; }
DWORD WINAPI AnswerThread(LPVOID lparam) { //线程函数AnswerThread
char sendbuf[256];
char recvbuf[256];
SOCKET *sockConn = (SOCKET *)lparam; //从参数中获得sockConn
while(1){
recv(*sockConn,recvbuf,256,0);
cout<<"客户端说:>"<<recvbuf<<endl;
cout<<"服务器说:>";
cin>>sendbuf;
if(strcmp(sendbuf,"bye")==0){
break;}
send(*sockConn,sendbuf,strlen(sendbuf)+1,0);
}
return 0; }