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

news2024/12/23 8:54:12

写一个服务器端用多进程处理并发,使两个以上的客户端可以同时连接服务器端得到响应。每当接受一个新的连接就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);//相当于read
        if(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);//因为子进程不用sockfd
            do_run(c);//处理客户端,传入的参数是c这个描述符
            close(c);//子进程在退出之前关闭c
            printf("子进程退出,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);//相当于read
        if(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);//因为子进程不用sockfd
            do_run(c);//处理客户端,传入的参数是c这个描述符
            close(c);//子进程在退出之前关闭c
            printf("子进程退出,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);//相当于read
        if(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);//因为子进程不用sockfd
            do_run(c);//处理客户端,传入的参数是c这个描述符
            close(c);//子进程在退出之前关闭c
            printf("子进程退出,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.coloradmin.cn/o/964575.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Kubernetes(K8s 1.28.x)部署---创建方式Docker(超详细)

目录 一、基础环境配置&#xff08;所有主机均要配置&#xff09; 1、配置IP地址和主机名、hosts解析 2、关闭防火墙、禁用SELinux 3、安装常用软件 4、配置时间同步 5、禁用Swap分区 6、修改linux的内核参数 7、配置ipvs功能 二、容器环境操作 1、定制软件源 2、安…

RocketMQ消息队列-@RocketMQMessageListener实现原理

使用Spring-RocketMQ时&#xff0c;只需要引入rocketmq-spring-boot-starter包&#xff0c;并且定义以下消费者&#xff0c;就可以很简单的实现消息消费 Component RocketMQMessageListener(topic "first-topic", consumerGroup "my-producer-group", s…

过滤器的应用-Filter

过滤器 1.工作原理 2.创建Filter 2.1通过注解的方式实现 //创建一个类&#xff0c;实现Filter接口 WebFilter(urlPatterns "/myfilter") //urlPatterns表示需要拦截的路径 public class MyFilter implements Filter {Overridepublic void doFilter(ServletReques…

深度解读NeuS代码(1):输入数据格式

准备训练数据 在preprocess_cutsom_data中&#xff0c;笔者采取第二个经过colmap的方法。但是这个方法其实需要一个前置结果&#xff0c;即colmap与LLFF的处理。接下来分别讨论&#xff1a; Colmap 一个非常好的操作流程在这里&#xff1a; https://zhuanlan.zhihu.com/p/57…

Kubernetes(k8s)安装NFS动态供给存储类并安装KubeSphere

Kubernetes安装NFS动态供给存储类并安装KubeSphere KubeSphere介绍环境准备KubeSphereNFS动态供给 安装NFS动态供给搭建NFS下载动态供给驱动修改驱动文件安装动态供给 安装KubeSphere下载KubeSphere的yaml资源清单文件安装KubeSphere 使用KubeSphere部署应用创建项目部署MySQL …

Android 13 - Media框架(9)- NuPlayer::Decoder

这一节我们将了解 NuPlayer::Decoder&#xff0c;学习如何将 MediaCodec wrap 成一个强大的 Decoder。这一节会提前讲到 MediaCodec 相关的内容&#xff0c;如果看不大懂可以先跳过此篇。原先觉得 Decoder 部分简单&#xff0c;越读越发现自己的无知&#xff0c;Android 源码真…

nginx-缓存

disk cache&#xff1a;磁盘缓存数据&#xff0c;有时间延迟&#xff0c;但是非常小&#xff0c;相对于直接请求服务器返回 对于用户来说基本无感知。 memory cache&#xff1a;磁盘缓存数据&#xff0c;基本上没有时间延迟 协商缓存&#xff08;nginx自带功能&#xff0c; 不…

机器人中的数值优化(五)——信赖域方法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

18.kthread_worker:内核线程异步传输

目录 kthread_worker 驱动传输数据的方式 同步传输 异步传输 头文件 kthread_worker结构体 kthread_work结构体 kthread_flush_work结构体 init_kthread_worker()函数 为kthread_worker创建内核线程 init_kthread_work()函数 启动工作 刷新工作队列 停止内核线程…

3D点云测量:计算三个平面的交点

文章目录 0. 测试效果1. 基本内容文章目录:3D视觉测量目录微信:dhlddxB站: Non-Stop_0. 测试效果 1. 基本内容 计算三个平面的交点需要找到满足所有三个平面方程的点。三个平面通常由它们的法向量和通过它们的点(或参数形式的方程)来定义。以下是计算三个平面的交点的一般步…

在VScode中使用sftp传输本地文件到服务器端

安装SFTP 在VScode的扩展中安装sftp 注意这里需要在你没连接服务器的状态下安装&#xff0c;即本机需要有sftp 配置传输端口 安装成功后&#xff0c;使用快捷键"ctrlshiftp",输入sftp&#xff0c;选择Config 根据自己的实际情况修改配置文件&#xff0c;主要改h…

设计模式-6--装饰者模式(Decorator Pattern)

一、什么是装饰者模式&#xff08;Decorator Pattern&#xff09; 装饰者模式&#xff08;Decorator Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许你在不修改现有对象的情况下&#xff0c;动态地将新功能附加到对象上。这种模式通过创建一个包装类&#xff0c;…

排序之交换排序

文章目录 前言一、冒泡排序1、冒泡排序基本思想2、冒泡排序的效率 二、快速排序 -- hoare版本1、快速排序基本思想2、快速排序代码实现3、为什么最左边值做key时&#xff0c;右边先走 三、快速排序 -- 挖坑法1、快速排序 -- 挖坑法基本思想2、快速排序 -- 挖坑法代码实现3、为什…

stable diffusion实践操作-随机种子seed

系列文章目录 stable diffusion实践操作 文章目录 系列文章目录前言一、seed是什么&#xff1f;二、使用步骤1.多批次随机生成多张图片2.提取图片seed3. 根据seed 再次培养4 seed使用4.1 复原别人图4.1 轻微修改 三、差异随机种子1. webUI位置2. 什么是差异随机种子3.使用差异…

找redis大key工具rdb_bigkeys

github官网 https://github.com/weiyanwei412/rdb_bigkeys 在centos下安装go [roothadoop102 rdb_bigkeys-master]# wget https://dl.google.com/go/go1.13.5.linux-amd64.tar.gz [roothadoop102 rdb_bigkeys-master]# tar -zxf go1.13.5.linux-amd64.tar.gz -C /usr/local将g…

安装bpftrace和bcc的踩坑记录

最后在Ubuntu22.04使用Ubuntu提供的安装命令完成了安装。这里是记录尝试在Ubuntu18.04和Ubuntu22.04使用源码安装未果的过程。 文章目录 22版本安装bcc准备工具安装命令使用报错&#xff1a;iovisor封装的安装方式ubuntu的安装方式 For Bionic (18.04 LTS)官方提供的源码安装准…

SpringCloudGateway集成SpringDoc

SpringCloudGateway集成SpringDoc 最近在搞Spring版本升级&#xff0c;按客户要求升级Spring版本&#xff0c;原来用着SpringBoot 2.2.X版本&#xff0c;只需要升级SpringBoot 2.X最新版本也就可以满足客户Spring版本安全要求&#xff0c;可是好像最新的SpringBoot 2.X貌似也不…

Laravel chunk和chunkById的坑

在编写定时任务脚本的时候&#xff0c;经常会用到chunk和chunkById的API。 一、前言 数据库引擎为innodb。 表结构简述&#xff0c;只列出了本文用到的字段。 字段类型注释idint(11)IDtypeint(11)类型mark_timeint(10)标注时间&#xff08;时间戳&#xff09; 索引&#x…

手撕 视觉slam14讲 ch13 代码(1)工程框架与代码结构

在学习slam一年之后开始&#xff0c;开始自己理思路&#xff0c;全手敲完成ch13的整个代码 我们按照自己写系统的思路进行&#xff0c;首先确定好SLAM整体系统的流程&#xff0c;见下图&#xff0c;输入为双目图像&#xff0c;之后进入前端位姿估计和后端优化&#xff0c;中间…

滑动窗口实例3(最大连续1的个数Ⅲ)

题目&#xff1a; 给定一个二进制数组 nums 和一个整数 k&#xff0c;如果可以翻转最多 k 个 0 &#xff0c;则返回 数组中连续 1 的最大个数 。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1,0,0,0,1,1,1,1,0], K 2 输出&#xff1a;6 解释&#xff1a;[1,1,1,0,0,1,1…