并发服务器 unity 并发服务器的优点
并发服务器
优点:1)可以同时处理多个客户端请求,创建子进程或者分支线程来处理客户端的请求
2)父进程/主线程只负责连接,子进程/分支线程只负责与客户端进行交互;
1、多进程并发服务器
父进程只负责连接,子进程只负责与客户端进行交互;
模型
void handler(int sig) { //回收僵尸进程 //回收成功则再回收一次,直到回收失败或者没有回收到为止 while(waitpid(-1, NULL, WNOHANG) > 0); } //捕获17号信号 SIGCHLD sighandler_t s = signal(17, handler); sfd = socket(); bind(); listen(); while(1) { newfd = accept(); pid = fork(); if(pid > 0) { close(newfd); } else if(0 == pid) { close(sfd); while(1) { recv(); send(); } close(newfd); exit(0); } } close(sfd);捕捉子进程退出时发出的17号信号 SIGCHLD,并使用信号处理函数回收子进程的资源,防止僵尸进程的产生
//捕获17号信号 sighandler_t s = signal(17,handler); if(SIG_ERR == s) { ERR_MSG("signal"); return -1; } //信号处理函数 void handler(int sig) { //用非阻塞的形式回收僵尸进程, 如果成功回收,则再回收一次,直到回收子进程失败,或者没有收到为止 while(waitpid(-1,NULL,WNOHANG) > 0); }实现交互的函数
//负责与客户端的交互 int rcv_cli_mag(int newfd,struct sockaddr_in cin) { char buf[128] = ""; ssize_t res = 0; while(1) { bzero(buf, sizeof(buf)); //循环接收 res = recv(newfd, buf, sizeof(buf), 0); if(res < 0) { ERR_MSG("recv"); return -1; } else if(0 == res) { printf("[%s : %d] newfd = %d client off-line\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd); break; } printf("[%s : %d] newfd = %d : %s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd,buf); //发送数据 strcat(buf,"*_*"); if(send(newfd,buf,sizeof(buf),0) < 0) { ERR_MSG("send"); return -1; } printf("send message success\n"); } return 0; }宏函数与主函数
//打印错误信息的宏函数 #define ERR_MSG(msg) do{\ fprintf(stderr,"__%d__",__LINE__);\ perror(msg);\ }while(0) #define PORT 8888 //范围为1024~49151 #define IP "192.168.31.48" //本机IP,用ifconfig查看 typedef void (*sighandler_t)(int); int rcv_cli_mag(int newfd,struct sockaddr_in cin); int main(int argc, const char *argv[]) { //捕获17号信号 sighandler_t s = signal(17,handler); if(SIG_ERR == s) { ERR_MSG("signal"); return -1; } //创建流式套接字 int sfd = socket(AF_INET,SOCK_STREAM,0); if(sfd < 0) { ERR_MSG("socket"); return -1; } printf("create socket success\n"); //允许端口快速重用 int reuse = 1; if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0) { ERR_MSG("setsockopt"); return -1; } //填充地址信息结构体,真实的地址信息结构体与协议族相关 //AF_INET,所以详情请看man 7 ip struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(PORT); //网络字节序的端口号 sin.sin_addr.s_addr = inet_addr(IP); //网络字节序的IP地址 //将地址信息结构体绑定到套接字上 if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0) { ERR_MSG("bind"); return -1; } printf("bind success\n"); //将套接字转为监听状态,让内核监听是否有客户端连接 if(listen(sfd,8)<0) { ERR_MSG("listen"); return -1; } printf("listen success\n"); struct sockaddr_in cin; socklen_t addrlen = sizeof(cin); int newfd =0; pid_t pid =0; //从已完成连接的队列头中,取出一个客户端的信息,创建生成一个新的套接字文件描述符, //该文件描述符才是与客户端通信的文件描述符 while(1) { //父进程只负责连接 newfd = accept(sfd,(struct sockaddr*)&cin,&addrlen); if(newfd < 0) { perror("accept"); return -1; } //网络字节序的IP-->点分十进制 网络字节序的port-->本机字节序 printf("[%s : %d] newfd = %d\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd); //一旦连接成功,创建一个子进程用于交互 pid = fork(); if(pid >0) { close(newfd); } else if(0 == pid) { close(sfd); //子进程负责与客户端交互 rcv_cli_mag(newfd,cin); close(newfd); //交互结束后,不能让子进程继续执行后面的代码,需要直接退出 exit(0); } else { ERR_MSG("fork"); return -1; } } //关闭文件 close(sfd); return 0; }2、多线程并发服务器
主线程只负责连接,分支线程只负责与客户端进行交互
模型
sfd = socket(); bind(); listen(); struct msg cliInfo; while(1) { newfd = accept(); cliInfo.newfd = newfd; pthread_create(&tid, NULL, callback, &cliInfo); } void* callBack(void* arg) { pthread_detach(pthread_self()); while(1) { recv(); send(); } close(newfd); pthread_exit() }使用pthread_detach函数分离线程,线程退出后,自动回收线程资源
主线程传入分支线程的
分支线程实现交互
void* rcv_cli_msg(void* arg) { //分离分支线程 pthread_detach(pthread_self()); int newfd = (*(struct msg*)arg).newfd; struct sockaddr_in cin = (*(struct msg*)arg).cin; char buf[128] = ""; ssize_t res = 0; while(1) { bzero(buf, sizeof(buf)); //循环接收 res = recv(newfd, buf, sizeof(buf), 0); if(res < 0) { ERR_MSG("recv"); return NULL; } else if(0 == res) //表明客户端已下线 { //打印下线提醒 printf("[%s : %d] newfd = %d client off-line\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd); break; //退出该分支线程 } printf("[%s : %d] newfd = %d : %s\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd,buf); //发送数据 strcat(buf,"*_*"); if(send(newfd,buf,sizeof(buf),0) < 0) { ERR_MSG("send"); return NULL; } printf("send message success\n"); } close(newfd); pthread_exit(NULL); }宏函数、宏定义与主函数
//打印错误信息的宏函数 #define ERR_MSG(msg) do{\ fprintf(stderr,"__%d__",__LINE__);\ perror(msg);\ }while(0) #define PORT 8888 //范围为1024~49151 #define IP "192.168.31.48" //本机IP,用ifconfig查看 struct msg { int newfd; struct sockaddr_in cin; }; void* rcv_cli_msg(void* arg); int main(int argc, const char *argv[]) { //创建流式套接字 int sfd = socket(AF_INET,SOCK_STREAM,0); if(sfd < 0) { ERR_MSG("socket"); return -1; } printf("create socket success\n"); //允许端口快速重用 int reuse = 1; if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0) { ERR_MSG("setsockopt"); return -1; } //填充地址信息结构体,真实的地址信息结构体与协议族相关 //AF_INET,所以详情请看man 7 ip struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(PORT); //网络字节序的端口号 sin.sin_addr.s_addr = inet_addr(IP); //网络字节序的IP地址 //将地址信息结构体绑定到套接字上 if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0) { ERR_MSG("bind"); return -1; } printf("bind success\n"); //将套接字转为监听状态,让内核监听是否有客户端连接 if(listen(sfd,10)<0) { ERR_MSG("listen"); return -1; } printf("listen success\n"); int newfd = 0; pthread_t tid; struct sockaddr_in cin; socklen_t addrlen = sizeof(cin); struct msg cliinfo; //从已完成连接的队列头中,取出一个客户端的信息,创建生成一个新的套接字文件描述符, //该文件描述符才是与客户端通信的文件描述符 while(1) { //主线程负责连接 newfd = accept(sfd, (struct sockaddr*)&cin, &addrlen); if(newfd <0) { ERR_MSG("accept"); return -1; } //网络字节序的IP-->点分十进制 网络字节序的port-->本机字节序 printf("[%s : %d] newfd = %d\n",inet_ntoa(cin.sin_addr),ntohs(cin.sin_port),newfd); cliinfo.newfd = newfd; cliinfo.cin = cin; //一旦连接成功后,创建一个分支线程用于与客户端交互 if(pthread_create(&tid,NULL, rcv_cli_msg,(void*)&cliinfo) != 0) { ERR_MSG("pthread_create"); return -1; } } //关闭文件 close(sfd); return 0; }