当前位置: 首页 > news >正文

Linux 多进程解决客户端与服务器端通信

写一个服务器端用多进程处理并发,使两个以上的客户端可以同时连接服务器端得到响应。每当接受一个新的连接就fork产生一个子进程,让子进程去处理这个连接,父进程只用来接受连接。

与多线程相比的不同点:多线程如果其中一个线程操作不当,产生了一个信号,会导致整个进程都终止。对于多进程来讲,是产生的子进程去处理客户端的连接,如果子进程终止了,不会影响到其他进程。创建线程的开销比产生新的子进程的开销要小,多线程可以共享整个进程的资源。

服务器端代码ser.c:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<signal.h>int socket_init();//封装一个套接字,套接字的声明
void do_run(int c)//子进程中处理客户端的方法的实现
{while(1){char buff[128]={0};int num=recv(c,buff,127,0);//相当于readif(num<=0){break;}printf("子进程接收到的信息:%s\n",buff);send(c,"ok",2,0);//相当于write}
}int main()
{int sockfd=socket_init();//调用封装好的套接字方法,创建套接字if(sockfd==-1){printf("创建失败\n");exit(1);}while(1){struct sockaddr_in caddr;//定义客户端的套接字地址int len = sizeof(caddr);//4.接收客户端连接int c=accept(sockfd,(struct sockaddr*)&caddr,&len);if(c<0)//失败{continue;}printf("c=%d\n",c);//5.产生一个子进程来处理客户端的连接,子进程会复制父进程打开的所有文件,上面接收的客户端的连接c也算是文件描述符,这个c也会被复制到子进程中pid_t pid=fork();if(pid==-1)  {printf("子进程产生失败\n"); close(c);//关闭c这个连接continue;//重新等待新的连接}else if(pid==0)//子进程产生成功{close(sockfd);//因为子进程不用sockfddo_run(c);//处理客户端,传入的参数是c这个描述符close(c);//子进程在退出之前关闭cprintf("子进程退出,pid=%d\n",getpid());exit(0);//处理完所连接的客户端,子进程必须要退出}else {close(c);//父进程关闭c连接,对子进程没有影响,因为在fork之后,描述符的引用计数加1,这时候父进程关闭,子进程也关闭,最终才能认为c被关闭//如果父进程不关闭c连接,c的值会一直增长}}exit(0);
}int socket_init()//套接字的具体实现
{int sockfd=socket(AF_INET,SOCK_STREAM,0);//1.创建套接字if(sockfd==-1){printf("创建失败\n");return -1;}struct sockaddr_in saddr;//定义服务器端的套接字地址memset(&saddr,0,sizeof(saddr));//清空saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//2.绑定,指定套接字的ip和端口int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("绑定失败\n");//ip写错或者端口被占用return -1;}//3.创建监听队列res=listen(sockfd,5);if(res==-1){return -1;}return sockfd;}

客户端代码cli.c:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>int main()
{//1.创建套接字int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){exit(1);}//要连接服务器端就要知道服务器端的ip和端口,把ip和端口存到下面定义的saddr中struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));//必须清空saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//2.连接服务器端int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//会随机填充客户端的ip和一个临时端口if(res==-1)//网络没联通,服务器端没启动{printf("连接服务器端失败\n");exit(1);}while(1){char buff[128]={0};printf("输入:\n");fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}//3.向服务器端发送消息send(sockfd,buff,strlen(buff),0);memset(buff,0,128);//4.接收服务器端回复的消息recv(sockfd,buff,127,0);printf("buff=%s\n",buff);}//5.关闭客户端printf("客户端关闭\n");close(sockfd);
}

运行结果:

在这里插入图片描述

此时,虽然两个客户端可以同时与服务器端通信,但是存在一个问题,就是僵死进程的问题,如下图:

在这里插入图片描述

之所以出现僵死进程是因为在子进程结束之后并没有处理子进程,在Linux系统中处理僵死进程的方法有两种,wait和忽略信号。

使用忽略信号的方式,服务器端的代码ser.c如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<signal.h>int socket_init();//封装一个套接字,套接字的声明
void do_run(int c)//子进程中处理客户端的方法的实现
{while(1){char buff[128]={0};int num=recv(c,buff,127,0);//相当于readif(num<=0){break;}printf("子进程接收到的信息:%s\n",buff);send(c,"ok",2,0);//相当于write}
}int main()
{signal(SIGCHLD,SIG_IGN);int sockfd=socket_init();//调用封装好的套接字方法,创建套接字if(sockfd==-1){printf("创建失败\n");exit(1);}while(1){struct sockaddr_in caddr;//定义客户端的套接字地址int len = sizeof(caddr);//4.接收客户端连接int c=accept(sockfd,(struct sockaddr*)&caddr,&len);if(c<0)//失败{continue;}printf("c=%d\n",c);//5.产生一个子进程来处理客户端的连接,子进程会复制父进程打开的所有文件,上面接收的客户端的连接c也算是文件描述符,这个c也会被复制到子进程中pid_t pid=fork();if(pid==-1)  {printf("子进程产生失败\n"); close(c);//关闭c这个连接continue;//重新等待新的连接}else if(pid==0)//子进程产生成功{close(sockfd);//因为子进程不用sockfddo_run(c);//处理客户端,传入的参数是c这个描述符close(c);//子进程在退出之前关闭cprintf("子进程退出,pid=%d\n",getpid());exit(0);//处理完所连接的客户端,子进程必须要退出}else {close(c);//父进程关闭c连接,对子进程没有影响,因为在fork之后,描述符的引用计数加1,这时候父进程关闭,子进程也关闭,最终才能认为c被关闭//如果父进程不关闭c连接,c的值会一直增长}}exit(0);
}int socket_init()//套接字的具体实现
{int sockfd=socket(AF_INET,SOCK_STREAM,0);//1.创建套接字if(sockfd==-1){printf("创建失败\n");return -1;}struct sockaddr_in saddr;//定义服务器端的套接字地址memset(&saddr,0,sizeof(saddr));//清空saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//2.绑定,指定套接字的ip和端口int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("绑定失败\n");//ip写错或者端口被占用return -1;}//3.创建监听队列res=listen(sockfd,5);if(res==-1){return -1;}return sockfd;}

或者使用wait方法,服务器端的代码ser.c如下:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<signal.h>int socket_init();//封装一个套接字,套接字的声明
void do_run(int c)//子进程中处理客户端的方法的实现
{while(1){char buff[128]={0};int num=recv(c,buff,127,0);//相当于readif(num<=0){break;}printf("子进程接收到的信息:%s\n",buff);send(c,"ok",2,0);//相当于write}
}
void fun(int sig)
{wait(NULL);
}
int main()
{signal(SIGCHLD,fun);int sockfd=socket_init();//调用封装好的套接字方法,创建套接字if(sockfd==-1){printf("创建失败\n");exit(1);}while(1){struct sockaddr_in caddr;//定义客户端的套接字地址int len = sizeof(caddr);//4.接收客户端连接int c=accept(sockfd,(struct sockaddr*)&caddr,&len);if(c<0)//失败{continue;}printf("c=%d\n",c);//5.产生一个子进程来处理客户端的连接,子进程会复制父进程打开的所有文件,上面接收的客户端的连接c也算是文件描述符,这个c也会被复制到子进程中pid_t pid=fork();if(pid==-1)  {printf("子进程产生失败\n"); close(c);//关闭c这个连接continue;//重新等待新的连接}else if(pid==0)//子进程产生成功{close(sockfd);//因为子进程不用sockfddo_run(c);//处理客户端,传入的参数是c这个描述符close(c);//子进程在退出之前关闭cprintf("子进程退出,pid=%d\n",getpid());exit(0);//处理完所连接的客户端,子进程必须要退出}else {close(c);//父进程关闭c连接,对子进程没有影响,因为在fork之后,描述符的引用计数加1,这时候父进程关闭,子进程也关闭,最终才能认为c被关闭//如果父进程不关闭c连接,c的值会一直增长}}exit(0);
}int socket_init()//套接字的具体实现
{int sockfd=socket(AF_INET,SOCK_STREAM,0);//1.创建套接字if(sockfd==-1){printf("创建失败\n");return -1;}struct sockaddr_in saddr;//定义服务器端的套接字地址memset(&saddr,0,sizeof(saddr));//清空saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//2.绑定,指定套接字的ip和端口int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("绑定失败\n");//ip写错或者端口被占用return -1;}//3.创建监听队列res=listen(sockfd,5);if(res==-1){return -1;}return sockfd;}

运行结果:

在这里插入图片描述
在这里插入图片描述
这次僵死进程就不存在了。

http://www.lryc.cn/news/151445.html

相关文章:

  • Scala的模式匹配
  • HttPClient简介及示例:学习如何与Web服务器进行通信
  • STS4 New 安装Spring Bean Configuration File
  • Java经典面试题(异或运算)
  • No primary or single unique constructor found for interface java.util.List
  • C#关于WebService中File.Exists()处理远程路径的异常记录
  • JavaWeb_LeadNews_Day10-Xxljob, Redis实现定时热文章
  • 【WebRTC---源码篇】(二:二)视频源VideoSourceBase
  • Linux_8_磁盘存储和文件系统
  • VS + QT 封装带UI界面的DLL
  • 逆向工程-架构真题(二十)
  • Zookeeper 入门
  • 记录--前端使用a链接下载内容增加loading效果
  • 如何获取用户的ip地址
  • 单片机-控制按键点亮LED灯
  • 微信小程序新版隐私协议弹窗实现最新版
  • GO语言圣经 第五章习题
  • 用kotlin 开发一个简单的多页面跳转
  • 记录我的tensorrt 部署yolov8
  • 什么是用户界面? 优漫动游
  • 基于 Docker 的 MySQL 主从复制搭建(Mac M1版本)
  • 【Locomotor运动模块】瞬移
  • 【负载均衡】常见的负载均衡策略有哪些?
  • ChatGPT如何应对紧急救援和医疗应急?
  • vue3 ref reactive响应式数据 赋值的问题
  • 【美团秋招】20230922小美的彩虹糖
  • 论文阅读_扩散模型_DM
  • 【每日运维】RockyLinux8.6升级OpenSSH9.4p1
  • libdrm全解析三十八 —— 源码全解析(35)
  • jar包和war包的区别