博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux fork多进程并发服务器模型之C/C++代码实战
阅读量:4142 次
发布时间:2019-05-25

本文共 4182 字,大约阅读时间需要 13 分钟。

        在很早的文章中, 我们一起聊过服务器如何与多个客户端进行通信, 那时, 我们要么用select, 要么用多线程, 却没有用多进程。 其实, 多进程也可以实现与多个客户端进行通信。 

        如果是在while中循环accept,  然后循环处理事情, 此时, 这种服务是迭代服务, 只能逐一处理客户端的请求, 后一个请求必须等前一个请求处理完毕, 无法并发处理, 真是急死人。 要实现并发, 我们可以考虑多线程, 也可以考虑多进程, 本文来说说后者。 在我们的多进程服务器模型中, 我们用父进程来处理连接(监听socket), 用fork子进程的方法来处理通信(通信socket), 各司其职, 美哉。

        一旦涉及到fork, 就必须注意僵尸进程的处理, 所以, 我们要用waitpid进行收尸, 这一点, 我们已经说过了。 另外, 要注意, 父子进程共享socket句柄的文件表(如果不理解的话, 建议看看APUE), 所以close socket的时候, 只是使引用计数减1, 并不是真正地直接关闭socket(减为0才是真正的关闭), 这一点, 我们也已经说过了。

 

        废话少说, 直接上菜。

        服务端程序为:

 

#include 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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 
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
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个子进程, 并发地工作, 并发地与客户端进行通信, 而且还互不干扰, 大大提升了服务满意度。 

       客户自然满意多了, 因为服务方专门派人一对一地提供服务啊, 你要是再不满意, 这个客户就没良心了。

 

       先说到这里, 吃个晚饭, 然后干点正事。

 

 

 

转载地址:http://qnwti.baihongyu.com/

你可能感兴趣的文章
[转]C语言printf
查看>>
C 语言 学习---获取文本框内容及字符串拼接
查看>>
C 语言学习 --设置文本框内容及进制转换
查看>>
C 语言 学习---判断文本框取得的数是否是整数
查看>>
C 语言 学习---ComboBox相关、简单计算器
查看>>
C 语言 学习---ComboBox相关、简易“假”管理系统
查看>>
C 语言 学习---回调、时间定时更新程序
查看>>
C 语言 学习---复选框及列表框的使用
查看>>
第十一章 - 直接内存
查看>>
JDBC核心技术 - 上篇
查看>>
一篇搞懂Java反射机制
查看>>
Single Number II --出现一次的数(重)
查看>>
Palindrome Partitioning --回文切割 深搜(重重)
查看>>
对话周鸿袆:从程序员创业谈起
查看>>
Mysql中下划线问题
查看>>
Xcode 11 报错,提示libstdc++.6 缺失,解决方案
查看>>
idea的安装以及简单使用
查看>>
Windows mysql 安装
查看>>
python循环语句与C语言的区别
查看>>
vue 项目中图片选择路径位置static 或 assets区别
查看>>