linux fork多进程并发服务器模型之C/C++代码实战
来源:互联网 发布:二维数组push方法 编辑:程序博客网 时间:2024/05/29 16:30
在很早的文章中, 我们一起聊过服务器如何与多个客户端进行通信, 那时, 我们要么用select, 要么用多线程, 却没有用多进程。 其实, 多进程也可以实现与多个客户端进行通信。
如果是在while中循环accept, 然后循环处理事情, 此时, 这种服务是迭代服务, 只能逐一处理客户端的请求, 后一个请求必须等前一个请求处理完毕, 无法并发处理, 真是急死人。 要实现并发, 我们可以考虑多线程, 也可以考虑多进程, 本文来说说后者。 在我们的多进程服务器模型中, 我们用父进程来处理连接(监听socket), 用fork子进程的方法来处理通信(通信socket), 各司其职, 美哉。
一旦涉及到fork, 就必须注意僵尸进程的处理, 所以, 我们要用waitpid进行收尸, 这一点, 我们已经说过了。 另外, 要注意, 父子进程共享socket句柄的文件表(如果不理解的话, 建议看看APUE), 所以close socket的时候, 只是使引用计数减1, 并不是真正地直接关闭socket(减为0才是真正的关闭), 这一点, 我们也已经说过了。
废话少说, 直接上菜。
服务端程序为:
#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <errno.h>#include <malloc.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/ioctl.h>#include <stdarg.h>#include <fcntl.h>#include <sys/types.h> #include <sys/wait.h> #include <netinet/in.h> #include <arpa/inet.h> #include <signal.h>void sigChildFun(int sigNO){ pid_t pid; int stat; while((pid = waitpid(-1, &stat, WNOHANG)) > 0) // 循环收尸(僵尸进程), 此时waitpid不会阻塞{NULL;} return;}int main(){ sockaddr_in servAddr; memset(&servAddr,0,sizeof(servAddr)); servAddr.sin_family = AF_INET; servAddr.sin_addr.s_addr = INADDR_ANY; servAddr.sin_port = htons(8765);int iListenSocket = socket(AF_INET, SOCK_STREAM, 0); bind(iListenSocket, (sockaddr *)&servAddr, sizeof(servAddr)); listen(iListenSocket,5); signal(SIGCHLD, sigChildFun); while(1) { sockaddr_in clientAddr; socklen_t iSize = sizeof(clientAddr); memset(&clientAddr, 0, sizeof(clientAddr));int iConnSocket = accept(iListenSocket,(sockaddr*)&clientAddr, &iSize); if(iConnSocket < 0) { if(errno == EINTR || errno == ECONNABORTED) { continue; } else { printf("accept error, server\n"); return -1; } }int tmpPid = fork(); if(tmpPid == 0) { close(iListenSocket); // 子进程让监听socket的计数减1, 并非直接关闭监听socket char szBuf[1024] = {0};snprintf(szBuf, sizeof(szBuf), "server pid[%u], client ip[%s]", getpid(), inet_ntoa(clientAddr.sin_addr)); write(iConnSocket, szBuf, strlen(szBuf) + 1);while(1){if(read(iConnSocket, szBuf, 1) <= 0){close(iConnSocket); // 子进程让通信的socket计数减1 return -2; // 子进程退出}} close(iConnSocket); // 子进程让通信的socket计数减1 return 0; // 子进程退出 } close(iConnSocket); // 父进程让通信的socket计数减1 }getchar();close(iListenSocket); // 父进程让监听socket计数减1, 此时会关掉监听socket(因为之前子进程已经有此操作) return 0;}
启动它。
客户端程序为:
#include <unistd.h>#include <unistd.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <ctype.h>#include <errno.h>#include <malloc.h>#include <netinet/in.h>#include <arpa/inet.h>#include <sys/ioctl.h>#include <stdarg.h>#include <fcntl.h>int main(){ int sockClient = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in addrSrv; addrSrv.sin_addr.s_addr = inet_addr("10.100.70.140"); addrSrv.sin_family = AF_INET; addrSrv.sin_port = htons(8765); connect(sockClient, ( const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));char szBuf[2048] = {0};int iRet = recv(sockClient, szBuf, sizeof(szBuf) - 1 , 0); printf("msg from server: %s\n", szBuf);getchar(); close(sockClient); return 0;}我们开启一个客户端, 此时如下:
客户端信息为:
xxxxxx$ ./clientmsg from server: server pid[16402], client ip[10.100.70.139]
服务端信息为:
xxxxxx$ ps -ef | grep -w serveruser_00 16096 32164 0 19:42 pts/18 00:00:00 ./serveruser_00 16402 16096 0 19:42 pts/18 00:00:00 ./server可以看到, 服务端16402子进程是与客户端通信的进程, 父进程16096是监听的父进程(主进程)。
另外再开启一个客户端(不要关闭旧的客户端), 此时如下:
新客户端信息为:
xxxxxx$ ./clientmsg from server: server pid[16497], client ip[10.100.70.139]
服务端信息为:
xxxxxx$ ps -ef | grep -w serveruser_00 16096 32164 0 19:42 pts/18 00:00:00 ./serveruser_00 16402 16096 0 19:42 pts/18 00:00:00 ./serveruser_00 16497 16096 0 19:42 pts/18 00:00:00 ./server可以看到, 父进程16096新开了一个子进程16497来与新的客户端进行通信。
我们关闭第一个客户端, 然后看到服务端为:
xxxxxx$ ps -ef | grep -w serveruser_00 16096 32164 0 19:42 pts/18 00:00:00 ./serveruser_00 16497 16096 0 19:42 pts/18 00:00:00 ./server
我们再关闭第二个客户端, 然后看到服务端为:
xxxxxx$ ps -ef | grep -w serveruser_00 16096 32164 0 19:42 pts/18 00:00:00 ./server显然, 客户端退出后, 发FIN包, 服务端子进程的recv函数就为0, 退出子进程的while循环了, 因此, 对应的子进程就over了, 而且不会留下僵尸进程(有waitpid)。 而且, 我们可以看到, 负责连接管理(accept)的父进程(主进程)依然安然无恙, 优哉游哉地等待下一个客户端连接。
在这里, 我们可以看到, 这个服务器是并发的, 而不是迭代的。 什么意思呢? 你看, 即使子进程处理业务需要很久很久, 那么上述服务依然能并发地响应n个几乎同时到达的客户端, 此时,父进程开启n个子进程, 并发地工作, 并发地与客户端进行通信, 而且还互不干扰, 大大提升了服务满意度。
客户自然满意多了, 因为服务方专门派人一对一地提供服务啊, 你要是再不满意, 这个客户就没良心了。
先说到这里, 吃个晚饭, 然后干点正事。
- linux fork多进程并发服务器模型之C/C++代码实战
- 聊聊Linux fork多进程并发服务器模型
- linux(一)------多进程并发服务器实现(fork)
- Linux c fork进程实践
- LINUX 并发服务器 fork
- Linux之并发进程服务器
- 【linux c开发】fork进程实例
- linux c 进程创建fork()
- 多进程并发C/S通信基本模型及实现
- 使用socket的Linux上的C语言helloworld多进程并发服务器
- 使用socket的Linux上的C语言helloworld多进程并发服务器
- 使用socket的Linux上的C语言helloworld多进程并发服务器
- C-socket编程-多进程版并发服务器
- Linux下的C编程实战之三进程控制
- 【C/C++】多进程:子进程的创建fork()
- 9 多进程并发服务器模型
- linux内核分析之fork.c
- linux c之创建进程fork和vfork函数之间的区别
- SPFA-hdu4076
- varnish
- ListView多条目加载以及优化
- (原创)C# MVC微信公众号支付之微信退款
- 大整数加法
- linux fork多进程并发服务器模型之C/C++代码实战
- CodeForces
- 二叉树的创建,遍历和销毁
- vue第三篇之组件
- Oozie学习笔记
- 判断两棵二叉树是不是相同
- JavaScript操作DOM的那些坑
- POJ-1466 Girls and Boys(最大独立集)
- redis系列--redis单机install