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);//相当于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;

}

运行结果:

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值