首页 科普 资讯 养生 问答 找医院 相关问答
首页> 问答

C++网络编程踩坑记之多进程服务器,详解代码细节,多问为什么

发布网友 发布时间:2024-09-27 14:32

我来回答

1个回答

热心网友 时间:2024-10-05 17:27

  首先介绍多进程并发服务器是什么,然后按步骤描述怎么用,在最后给出完整server.c的代码,wrap.c错误处理代码,和client.c的代码。希望大佬多交流,敬请斧正。

是什么:

提示:这节是多进程并发服务器,其实现原理是,服务端通过lfd负责监听客户端的连接。当接受到一个客户端的连接时,建立一个子进程通过cfd与客户端进行数据通信,那么这个服务端就可以响应多个客户端。涉及到的是信号,多进程的相关知识。

服务器按处理方式可以分为迭代服务器和并发服务器两类。

迭代服务器:服务器每次只能处理一个客户的请求,它实现简单但效率很低。

并发服务器:同时处理多个客户请求,效率很高却实现复杂。

linux有3种实现并发服务器的方式:多进程并发服务器,多线程并发服务器,多路IO复用。

怎么用:

多进程并发服务器的实现框架。

图片出处

编写多进程并发服务器的基本思路:

1. 服务端与客户端建立连接:lfd=socket() //创建 socketbind(lfd) //绑定服务器地址结构listen(lfd) //设置监听上限cfd=accept() //阻塞监听客户端连接2. 服务器调用fork()产生新的子进程,用于处理数据通信pid=fork();3. 父进程关闭连接套接字,子进程关闭监听套接字cfd = Accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);if (errno == EINTR)continue;pid=fork();if(pid==-1){sys_err("fork error");}else if(pid ==0){close(lfd); //子进程关闭lfdbreak;}else{struct sigaction act, oldact;act.sa_handler= catch_child;sigemptyset(&act.sa_mask);act.sa_flags=0;ret = sigaction(SIGCHLD,&act,&oldact);if(ret==-1){sys_err("sigaction error");}close(cfd);//父进程关闭cfdcontinue;}

3.1为什么要关fd? 1、节省系统资源。2、防止上面提到的父、子进程同时对共享描述符进程操作。3、确保close函数能够正确关闭套接字描述符。

3.2为什么可以确保正确关闭?   因为引用计数的存在。在每个文件描述符或者套接字都有一个引用计数机制,只有当它的引用计数变为0的时候才会真正清理和释放该套接字的资源。

3.3为什么父进程close(cfd),子进程close(lfd),没有导致连接的断开?   因为父进程fork(),产生子进程。是copy自己的地址空间给子进程,此时子进程拥有与父进程相同的打开的文件描述符!即,父子进程都有一个监听套接字、一个连接套接字。不严谨的说,就是在某个时刻lfd和cfd的引用计数都是2。连接建立后,父进程关闭连接套接字,子进程关闭监听套接字。两者的引用计数变成1。

3.4如果父进程从来都不关闭连接套接字会发生什么?  子进程如果不对lfd做误操作,没什么太大影响,因为它手里就lfd和cfd这两个fd。但是,父进程不是啊,一直在accept,一直在产生cfd,由于任何进程在任何时刻可拥有的打开着的描述符数量通常是有限制的。如果父进程不关闭连接套接字会导致套接字资源的耗尽,而且,没有一个客户连接会被终止。因为这些连接套接字的引用计数值永远是1,不可能为0.

3.5为什么会导致套接字资源的耗尽?  在进行高并发TCP连接处理时,最高的并发数量都要受到系统对用户单一进程同时可打开文件数量的限制(这是因为系统为每个TCP连接都要创建一个socket句柄,每个socket句柄同时也是一个文件句柄)。可使用ulimit -n命令查看系统允许当前用户进程打开的文件数限制。  这里要说的是,不仅仅是资源有限,而是socket建立的机制,也可以说是文件描述符建立的机制。每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。进程刚被创建时,标准输入、标准输出、标准错误输出设备文件被打开,对应的文件描述符0、1、2 记录在表中。在进程中打开其他文件时,系统会返回文件描述符表中最小可用的文件描述符,并将此文件描述符记录在表中。这样相当于在一个个的消耗fd资源。  注意:Linux中一个进程最多只能打开NR_OPEN_DEFAULT(即1024)个文件,故当文件不再使用时应及时调用close函数关闭文件。

3.6 accept()反复赋值给cfd,为什么cfd没有被覆盖,没有导致文件描述符表上的资源被覆盖?  用户使用文件描述符(file descriptor)来访问文件,fd创建了它就在那里,只要通过close减少引用计数的方式关闭。通过赋值,服务端失去了cfd的旧值,得到了cfd的新值,只是仅仅代表着服务端失去了通过cfd对旧文件的控制,但不代表旧cfd消失在文件描述符表中。

4. 子进程处理与客户端信息通信,父进程等待其他客户端的连接。if(pid==0){ //子进程while(1){ret = read(cfd, buf, sizeof(buf));if (ret == 0) {close(cfd);exit(1);//谨记,多进程可以这么直接退出,但是多线程不可以!!!!上一篇文章有讲。} else if (ret == -1) {sys_err("read error");} else {write(STDOUT_FILENO, buf, ret);for (int i = 0; i < ret; i++) {buf[i] = toupper(buf[i]);}write(cfd, buf, ret);}}}while(1){cfd = Accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);//父进程if (errno == EINTR)continue;pid=fork();if(pid==-1){sys_err("fork error");}else if(pid ==0){close(lfd);break;}else{//父进程struct sigaction act, oldact;act.sa_handler= catch_child;sigemptyset(&act.sa_mask);act.sa_flags=0;ret = sigaction(SIGCHLD,&act,&oldact);if(ret==-1){sys_err("sigaction error");}close(cfd);continue;}}5. 父进程注册信号捕捉函数: SIGCHLD,在回调函数中, 完成子进程回收while(waitpid())。struct sigaction act, oldact;act.sa_handler= catch_child;sigemptyset(&act.sa_mask);act.sa_flags=0;ret = sigaction(SIGCHLD,&act,&oldact); //注册信号捕捉函数if(ret==-1){sys_err("sigaction error");}void catch_child(int signum){//捕捉到信号之后的动作while((waitpid(0,NULL,WNOHANG))>0){}}

5.1为什么要进行子进程回收? &emsp;&emsp;为了处理僵尸进程,避免占据内核资源。&emsp;&emsp;进程终止存在两种可能:&emsp;&emsp;父进程先于子进程终止会产生孤儿进程。所有子进程的父进程被改为 init 进程,就是由 init 进程领养。在一个进程终止是,系统会逐个检查所有活动进程,判断 这些进程是否是正要终止 的进程的子进程。如果是,则该进程的父进程 ID 就更改为 1(init 的 ID)。这就保证了每个 进程都有一个父进程。&emsp;&emsp;子进程先于父进程终止会产生僵尸进程。系统内核会为每个终止子进程保存一些信息,这样父进 程就可以通过调用 wait()或 waitpid()函数,获得子进程的终止信息。终止子进程保存的信息 包括进程 ID、该进程的终止状态,以及该进程使用的 CPU 时间总量。当父进程调用 wait() 或 waitpid()函数时,系统内核可以释放终止进程所使用的所有存储空间,关闭其所有打开文 件。一个已经终止,但是其父进程尚未对其进行善后处理的进程称为僵尸进程。

5.2怎么处理父进程没来得及去回收这个子进程,产生的僵尸进程? &emsp;&emsp;可以通过在父进程捕获信号,SIGCHLD信号:当一个进程正常或异常终止时,它将给其父进程发送一个SIGCHLD信号,默认情况下, 父进程忽略该信号,父进程可以注册信号捕捉函数,捕获SIGCHLD信号,并在信号处理函数中调用waitpid函数以“彻底结束”一个子进程。

5.3为什么是while((waitpid(0,NULL,WNOHANG))>0),关键为什么是while? &emsp;&emsp;options参数的WNOHANG:若由pid指定的子进程没有退出则立即返回,则waitpid不阻塞,此时返回值为0。&emsp;&emsp;当多个信号同时发送时, catch_child 只执行了一次,但由于while的存在,执行完一个waitpid后又重新回到 waitpid , 那么waitpid 就会检查是否有进程状态改变。如果有,那么继续执行,就可以回收其他子进程的资源,如果没有则返回0,退出循环。所以要用循环去处理.把僵尸进程回收完。

5.4为什么是waitpid(),而不是wait()? &emsp;&emsp;一个并发服务器, 每一个客户端连接服务器就fork一个子进程.当同时有n多个客户端断开连接时,服务器端同时有n多个子进程终止,这时候父进程的n个子进程同时向父进程发送SIGCHILD时.既然sigchld是不可靠的信号,进程就不可能对sigchld进行排队, 直接丢弃了sigchld信号(当进程注册信号的时候,发现已有sigchld注册进未决信号, 因为内核同时发送多个sigchld).&emsp;&emsp;那如何保证回收僵尸进程呢?父进程只会调用一次信号处理程序,并丢弃其余四个信号。虽然只接受了一个SIGCHLD信号,但是while((waitpid(0,NULL,WNOHANG))>0)这条语句会处理所有的僵尸子进程。这就意味着,即使丢失了一部分SIGCHLD信号,父进程也能够回收所有的子进程。&emsp;&emsp;事实上,waitpid 和 SIGCHLD 没关系,waitpid函数不是由SIGCHILD信号驱动的,即使是某个子进程对应的 SIGCHLD 丢失了,只要父进程在任何一个时刻调用了 waitpid,那么这个进程还是可以被回收的。waitpid的函数构造是,回收第一个僵尸子进程。而加上while之后就能够回收所有在while运行期间内结束的子进程。而此时如果用wait却只能处理一个进程。&emsp;&emsp;我们不能在循环内调用wait,因为没有办法阻止wait在正运行的子进程中还有未终止的进程时阻塞。当有多个进程时,有一个发送了 SIGCHLD, 那么就会进入 catch_child 处理完,wait 将会阻塞,等待其他的进程状态改变,那么这里将回不到被中断的代码中去了。而使用 waitpid 时, 因为有了 WNOHANG (return imediately if no child has exited)这个 option , 那么 waitpid 将不会阻塞在这里。&emsp;&emsp;所以waitpid可以循环调用,等待所有任意进程结束,而wait只有一次机会。完整代码:

提示:写到这里,准备wrap.c和wrap.h错误处理准备单独开一节,就没有贴这两个代码,不过server.c中的函数首字母大写改为小写就是本来的函数原型。

//server.c// Created by 11406 on 2022/5/19.//#include <string.h>#include <strings.h>#include<netinet/in.h>#include <ctype.h>#include <pthread.h>#include <signal.h>#include <sys/wait.h>#include "wrap.h"#define IP "127.44.44.44"#define PORT 6266void catch_child(int signum){while((waitpid(0,NULL,WNOHANG))>0){}}int main(int argc,char *argv[]){int lfd,cfd;int ret;char buf[BUFSIZ];pid_t pid;struct sockaddr_in srv_addr,clt_addr;socklen_t clt_addr_len = sizeof(clt_addr);//memset(&saddr,0,sizeof(saddr));bzero(&srv_addr,sizeof(srv_addr));srv_addr.sin_family=AF_INET;srv_addr.sin_port= htons(PORT);srv_addr.sin_addr.s_addr= htonl(INADDR_ANY);lfd= Socket(AF_INET,SOCK_STREAM,0);Bind(lfd,(struct sockaddr *)&srv_addr, sizeof(srv_addr));Listen(lfd,128);while(1){cfd = Accept(lfd,(struct sockaddr*)&clt_addr,&clt_addr_len);if (errno == EINTR)continue;pid=fork();if(pid==-1){sys_err("fork error");}else if(pid ==0){close(lfd);break;}else{struct sigaction act, oldact;act.sa_handler= catch_child;sigemptyset(&act.sa_mask);act.sa_flags=0;ret = sigaction(SIGCHLD,&act,&oldact);if(ret==-1){sys_err("sigaction error");}close(cfd);continue;}}if(pid==0){while(1){ret = read(cfd, buf, sizeof(buf));if (ret == 0) {close(cfd);exit(1);} else if (ret == -1) {sys_err("read error");} else {write(STDOUT_FILENO, buf, ret);for (int i = 0; i < ret; i++) {buf[i] = toupper(buf[i]);}write(cfd, buf, ret);}}}return 0;}//client.c// Created by 11406 on 2022/5/16.//#include <stdio.h>#include <signal.h>#include <stdlib.h>#include <string.h>#include <unistd.h>#include <errno.h>#include <pthread.h>#include <fcntl.h>#include<netinet/in.h>#include <sys/socket.h>#include <ctype.h>#include <arpa/inet.h>#define IP "127.44.44.44"#define PORT 6266int main(int argc,char *argv[]){struct sockaddr_in addr;int sockfd;int n=0;char buf[256];bzero(&addr,sizeof(addr));//初始化结构体addr.sin_family=AF_INET;addr.sin_port= htons(PORT );addr.sin_addr.s_addr = inet_addr(IP);sockfd= socket(AF_INET,SOCK_STREAM,0);connect(sockfd,(struct sockaddr*)&addr,(socklen_t)sizeof(addr));while(1){n=read(STDIN_FILENO,buf,sizeof(buf));write(sockfd,buf,n);n=read(sockfd,buf,sizeof(buf));write(STDOUT_FILENO,buf,n);}return 0;}参考:

Linux并发服务器编程之多进程并发服务器

为什么 while((pid = waitpid(-1, &stat, WNOHANG)) > 0)能处理所有子进程

对while((pid = waitpid(-1, &stat, WNOHANG)) > 0)不懂的地方,现在懂了

文件描述符概述

僵尸进程以及如何处理

并发服务器编程模型

原文:https://juejin.cn/post/7101549535960236069
如何分别真金和仿金首饰 怎样区分真金和仿金首饰呢 小学生新年晚会主持人的串词!!(不要太多)急 大大后天就需要了!!!_百度... 周年晚会策划公司 奥格瑞玛传送门大厅在哪 奥格瑞玛传送门大厅怎么走 锻炼颈椎的几个动作 水多久能结冰 冰能在多长时间内形成 请问水低于0度会结冰吗? 如何防止脱发严重 嘴唇上有黑印用蜜蜡和棉线去除了胡须 软柿子的热量 孕妇可以吃软柿子吗不是西红柿 脆柿子和软柿子的区别 脆柿子好还是软柿子好 软柿子可以多吃吗 “鱼悬洁白振清风”的出处是哪里 用大自然的声音评课好吗? 妇产科博士找超声科工作容易吗 怎能把微信6.2.0版本换回6.1.2版 微信群6.2.4怎么增加人数上限 微信6.2.2如何备份手机通讯录 电脑桌面图标不能放大? 有什么好用的识图软件 识图认人哪个软件最好 手机识图软件什么软件能识别图片位置 小米手机自动锁屏时间怎么修改 小米手机屏幕锁定时间设置教程 能举起100斤算大力吗 中医美容专业是什么 中医美容证有什么用 单声道音频什么意思(开启单声道音频有什么好处) 单声道音频是什么,有什么用处? 户口还未迁移到婆家 娘家户口怎么就没了呢 我结婚没有迁户口,现在娘家也没有怎么办 没领证生的孩子一般会判给谁 没领证生的孩子会判给谁 信用卡卡种有哪些 找一首古风歌曲 男声 低配电脑装w10还是w7流畅 电脑配置低装win7还是win10好 低配电脑适合装WIN7系统还是WIN10系统? ...500s-15isk这个联想笔记本的内存条尺寸是什么型号的有没有知道的... 越快越好.怎样减肥.而且胸部不缩水 请问徐闻县海安长途汽车客运站客服是多少? 过了平台期还会瘦吗 悦耳的意思悦耳的解释 重庆师范大学应用心理学专业的权威性如何? 打印机laserjetm1136mfp怎样设置无线打印 经典电影赏析之1:《精武英雄》 爆米花用的什么玉米 糯玉米哪个好 有机糯玉米的营养价值如何? 四大直辖市换帅原因 四大直辖市换帅为啥 我爱上了自己的老师,他年纪比我大很多。怎么办啊!爱得很深 请高人算命。男,1966年农历9月9日,子时。算婚姻,事业,钱财等命运 ...1965年9月10日晚7点半左右出生,主要看工作和婚姻 梦瑶是什么意思 梦瑶名字的含义,带梦瑶的名字 十代思域近光灯高度不一样吗? 盼盼木门质量如何 翻毛皮鞋油污怎么清洗 翻毛皮鞋油污如何清洗 “枝上柳绵吹又少,天涯何处无芳草”是什么意思_出处及原文翻译_学习力... ★100分!原题发错版了,如下链接:比较这几款数码相机?帮忙推荐一个 佳能G9佳能 G9 存储性能 请问想开始学摄影买佳能A720、A650还是尼康的P50好?有没必要买长焦和... 佳能PowerShot G9 用来录象清楚吗? 佳能G9佳能 G9 拍摄性能 佳能G9 相片无法用相机读取 求助~~~佳能G9拍人像时,人一动,就会出现鬼影. 佳能G9在调的过程中突然变黑要调到什么位才对 关于佳能G9录制视频的问题!!! 佳能g9照相问题 css设置艺术字体的代码? 我的VIST系统字体全都变成的微软雅黑!!好难看。求各位大大帮忙_百度... ...器是微软雅黑的字体 但是用着用着变成宋体的了 怎么换回微软雅黑... ...的字体(将宋体改为微软雅黑),已经安装了微软雅黑的字体了 我妈肺上长个东西现在在合肥济民肿瘤医院这个医院好不好?急急急啊... 佳能g9市场价到底多少? CS高手和菜鸟在准心设置上有什么区别? CS哪里打没人作弊啊??? CS菜鸟应该如何练习成为...高手不说了最起码中等水平吧. php怎么转换成word(php怎么转换成pdf) php格式怎么转换? 皮手套怎么保养? QQ聊天字体怎么变会初始状态,我改了聊天的字体变不回去了,找不到以前... 我想改变QQ字体成楷体。可QQ里只有宋体和微软雅黑。对此我一点点不懂... 皮手套放久了变硬怎么办? 请问这是什么字体,感觉看着眼睛比较舒服 ...哪些人才市场啊,特别是朝阳区都有哪些人才市场啊。地址在什么地方... word设置默认字体 1660s和1070对比 dota游戏攻略大全(dota游戏攻略视频) 魔兽争霸群高手肯教新手的
声明声明:本网页内容为用户发布,旨在传播知识,不代表本网认同其观点,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。E-MAIL:11247931@qq.com