相比前面的fork方式的多进程服务器,用线程方式更轻量级。最少用fork时,需要将内存映像,描述符等都要从父进程复制到子进程中,
会占用大量的资源而多线程方式都在一个进程内,就无需占用这些资源,但同步也是个问题,而且一个线程挂了,可能会影响到进程中的其它线程。
多线程并发的原理就是当accept 成功连接一个客户端后,把与这个客户端的交互丢到新线程去处理它。
服务端代码:
/************************************************* Author: xiongchuanliang Description: 单进程多线程服务端代码 编译命令: Linux: g++ -o tcpserver_thread tcpserver_thread.cpp -m64 -I./common -lpthread **************************************************/ // 服务端代码 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <string.h> #include <sys/stat.h> #include <time.h> #include "initsock.h" #include "common.h" CInitSock initSock; //客户端Socket信息结构体 typedef struct _client_info{ int fd; //客户端socket描述符 struct sockaddr_in addr; //客户端地址信息结构体 time_t lastseconds; //可依这个计算空闲时间,空闲太长的连接可以关闭。 } client_info; void *TestSocket(void *cinfo); int main(int argc, char* argv[]) { struct _client_info *client_info = NULL; //创建套接字 SOCKET sListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(sListen == INVALID_SOCKET) { PrintError("socket() failed.\n"); exit(EXIT_FAILURE); } //绑定本地IP和端口到套接字 struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(SERVPORT); //大于1024且小于65535 server_addr.sin_addr.s_addr = INADDR_ANY; bzero(&(server_addr.sin_zero),8); //SO_REUSEADDR : 使bind函数能允许地址立即重用 int on = 1; setsockopt( sListen, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on) ); if(bind(sListen,(struct sockaddr *)&server_addr,sizeof(struct sockaddr)) == SOCKET_ERROR) { PrintError("bind() failed."); exit(EXIT_FAILURE); } //开始监听 if(listen(sListen, BACKLOG) == SOCKET_ERROR) { PrintError("sListen() failed."); exit(EXIT_FAILURE); } struct sockaddr_in remoteAddr = {0}; socklen_t nAddrlen = sizeof(struct sockaddr_in); pthread_t tpid; //循环接收数据 while(1){ SOCKET sClient; printf("等待客户端连接中...\n"); sClient = accept(sListen,(struct sockaddr *)&remoteAddr, &nAddrlen); if(sClient == INVALID_SOCKET) { PrintError("accept() failed."); continue; } printf("接收到一个客户端连接:%s \n",inet_ntoa(remoteAddr.sin_addr)); //复制连接 client_info = (struct _client_info *)malloc(sizeof(struct _client_info)); client_info->fd = sClient; memcpy((void *)&client_info->addr,&remoteAddr,sizeof(remoteAddr)); client_info->lastseconds = time(NULL); //在线程中处理 if( pthread_create(&tpid,NULL,&TestSocket,(void *)client_info) != 0 ) { PrintError("pthread_create() failed."); exit(EXIT_FAILURE); } }// end while exit(EXIT_SUCCESS); } void *TestSocket(void *cinfo) { pthread_t tid; char sendData[100] = {0}; struct _client_info *client_info = (struct _client_info *)cinfo; tid = pthread_self(); SOCKET sClient = client_info->fd; char recvData[MAXDATASIZE]={0}; //接收数据 int recvbytes = recv(sClient, recvData, MAXDATASIZE, 0); if( recvbytes == 0) { printf("recv() no data!\n"); }else if( recvbytes < 0) { PrintError("recv() failed"); }else if(recvbytes > 0) { recvData[recvbytes]=‘\0‘; printf("service thread id=%lu \n收到信息:%s\n",tid,recvData); //发送数据到客户端 //char * sendData = "客户端,你好啊!\n"; snprintf(sendData,100,"service thread id=%lu \n%s\n",tid, recvData); send(sClient, sendData, strlen(sendData), 0); } //关闭子进程连接的套接字 close(sClient); free(cinfo); pthread_exit(NULL); }
样做,新的客户端连接会替换掉之前的客户端连接。同时不要忘记空间的释放。
可以用<<网络编程(4)select函数实现I/O多路复用服务器>>中的客户端测试程序来测试下并发连接下的情况。
程序中用到的相关头文件在 <<网络编程(1)跨平台的Socket同步阻塞工作模式例子 >>中。
MAIL: xcl_168@aliyun.com
BLOG: http://blog.csdn.net/xcl168
原文:http://blog.csdn.net/xcl168/article/details/19247951