Linux 网络编程六(多进程服务器僵尸进程解决方案)

僵尸进程解决方案
1.忽略SIGCHLD信号,这样不会出现僵尸进程
2.安装信号,父进程接收到SIGCHLD信号后,wait()子进程
注意:这里的客户端有两个进程,一个接收信息,一个发送信息,当客户端退出时,会将sockfd套接字关闭两次
//头文件
int server_socket();

int client_socket();
//服务器端
#include "pub.h"

int main(int arg,char *args[])
{
    server_socket();
    return 0;
}
//客户端
#include "pub.h"

int main(int arg,char *args[])
{
    client_socket();
    return 0;
}
//辅助类实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <sys/wait.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "pub.h"

ssize_t readn(int fd, const void *buf, ssize_t count)
{
    if (buf == NULL)
    {
        printf("readn() params is not correct !\n");
        return -1;
    }
    //定义剩余字节数
    ssize_t lread = count;
    //定义辅助指针变量
    char *pbuf = (char *) buf;
    //定义每次读取的字节数
    ssize_t nread = 0;
    while (lread > 0)
    {
        nread = read(fd, pbuf, lread);
        if (nread == -1)
        {
            //read是可中断睡眠函数,需要屏蔽信号
            if (errno == EINTR)
                continue;
            perror("read() err");
            return -1;
        } else if (nread == 0)
        {
            printf("peer read socket is closed !\n");
            //返回已经读取的字节数
            return count - lread;
        }
        //重置剩余字节数
        lread -= nread;
        //辅助指针后移
        pbuf += nread;
    }
    return count;
}

ssize_t writen(int fd, const void *buf, ssize_t count)
{
    if (buf == NULL)
    {
        printf("writen() params is not correct !\n");
        return -1;
    }
    //定于剩余字节数
    ssize_t lwrite = count;
    //定义每次写入字节数
    ssize_t nwrite = 0;
    //定义辅助指针变量
    char *pbuf = (char *) buf;
    while (lwrite > 0)
    {
        nwrite = write(fd, pbuf, lwrite);
        if (nwrite == -1)
        {
            if (errno == EINTR)
                continue;
            perror("write() err");
            return -1;
        } else if (nwrite == 0)
        {
            printf("peer write socket is closed !\n");
            return count - lwrite;
        }
        //重置剩余字节数
        lwrite -= nwrite;
        //辅助指针变量后移
        pbuf += nwrite;
    }
    return count;
}

ssize_t recv_peek(int fd, const void *buf, ssize_t count)
{
    if (buf == NULL)
    {
        printf("recv_peek() params is not correct !\n");
        return -1;
    }
    ssize_t ret = 0;
    while (1)
    {
        //此处有多少读取多少,不一定ret==count
        ret = recv(fd, (void *)buf, count, MSG_PEEK);
        if (ret == -1 && errno == EINTR)
            continue;
        return ret;
    }
    return -1;
}

ssize_t mreadline(int fd, const void *buf, ssize_t count)
{
    //定义剩余字节数
    ssize_t lread = count;
    //定义每次读取的字节数
    ssize_t nread = 0;
    //定义辅助指针变量
    char *pbuf = (char *) buf;
    int i = 0, ret = 0;
    while (1)
    {
        nread = recv_peek(fd, pbuf, count);
        printf("recv_peek() 执行!\n");
        if (nread == -1)
        {
            perror("recv_peek() err");
            return -1;
        } else if (nread == 0)
        {
            //注意:这里一个客户端有两个进程,也就是套接字会关闭两次,会向服务器发送两次FIN信号
            printf("peer socket is closed !\n");
            return count - nread;
        }
        for (i = 0; i < nread; i++)
        {
            if (pbuf[i] == ‘\n‘)
            {
                //这是一段报文
                memset(pbuf, 0, count);
                //从socket缓存区读取i+1个字节
                ret = readn(fd, pbuf, i + 1);
                if (ret != i + 1)
                    return -1;
                return ret;
            }
        }
        //如果当前socket缓存区中没有\n,
        //那么先判断自定义buf是否还有空间,如果没有空间,直接退出
        //如果有空间,先将当前socket缓存区中的数据读出来,放入buf中,清空socket缓存
        //继续recv,判断下一段报文有没有\n
        if (lread >= count)
        {
            printf("自定义buf太小了!\n");
            return -1;
        }
        //读取当前socket缓存
        ret = readn(fd, pbuf, nread);
        if (ret != nread)
            return -1;
        lread -= nread;
        pbuf += nread;
    }
    return -1;
}

void handler(int sign)
{
    if (sign == SIGCHLD)
    {
        printf("子进程退出 !\n");
        wait(NULL);
    }
}

int server_socket()
{
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd == -1)
    {
        perror("socket() err");
        return -1;
    }
    //reuseaddr
    int optval = 1;
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
            == -1)
    {
        perror("setsockopt() err");
        return -1;
    }
    //bind
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(listenfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
    {
        perror("bind() err");
        return -1;
    }
    //listen
    if (listen(listenfd, SOMAXCONN) == -1)
    {
        perror("listen()err");
        return -1;
    }
    pid_t pid = 0;
    //忽略SIGCHLD信号
    //signal(SIGCHLD,SIG_IGN);
    //安装信号
    if (signal(SIGCHLD, handler) == SIG_ERR)
    {
        printf("signal() failed !\n");
        return -1;
    }
    while (1)
    {
        struct sockaddr_in peeraddr;
        socklen_t peerlen = sizeof(peeraddr);
        int conn = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen);
        printf("accept by %s\n", inet_ntoa(peeraddr.sin_addr));
        if (conn == -1)
        {
            perror("accept() err");
            return -1;
        }
        pid = fork();
        if (pid == -1)
        {
            perror("fork() err");
            return -1;
        }
        //子进程接收数据
        if (pid == 0)
        {
            //关闭监听套接字
            close(listenfd);
            char buf[1024] = { 0 };
            int ret = 0;
            while (1)
            {
                ret = mreadline(conn, buf, 1024);
                if (ret == -1)
                {
                    close(conn);
                    return -1;
                }
                //打印客户端数据
                fputs(buf, stdout);
                //把数据返回给客户端
                writen(conn, buf, ret);
                memset(buf, 0, sizeof(buf));
            }
        } else if (pid > 0)
        {
            close(conn);
        }
    }
    return 0;
}

int client_socket()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("socket() err");
        return -1;
    }
    //bind
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if(connect(sockfd,(struct sockaddr *)&addr,sizeof(addr))==-1)
    {
        perror("connect() err");
        return -1;
    }
    pid_t pid=0;
    pid=fork();
    if(pid==-1)
    {
        perror("fork() err");
        return -1;
    }
    char buf[1024]={0};
    int ret=0;
    //子进程发送数据
    if(pid==0)
    {
        while(fgets(buf,sizeof(buf),stdin)!=NULL)
        {
            ret=writen(sockfd,buf,strlen(buf));
            if(ret!=strlen(buf))
                return -1;
            memset(buf,0,sizeof(buf));
        }
    }else if(pid>0)
    {
        //父进程接收数据
        while(1)
        {
            ret=mreadline(sockfd,buf,sizeof(buf));
            if(ret==-1)
                return -1;
            //打印数据
            fputs(buf,stdout);
            memset(buf,0,sizeof(buf));
        }
    }
    return 0;
}
.SUFFIXES:.c .o
CC=gcc
SRCS=tec01.c    pub.c
OBJS=$(SRCS:.c=.o)
EXEC=runc

SRCS1=hello.c    pub.c
OBJS1=$(SRCS1:.c=.o)
EXEC1=hello

start:$(OBJS) $(OBJS1)
    $(CC) -o $(EXEC) $(OBJS)
    $(CC) -o $(EXEC1) $(OBJS1)
    @echo "-------OK----------"
.c.o:
    $(CC) -Wall -g -o [email protected] -c $<
clean:
    rm -f $(OBJS)
    rm -f $(EXEC)
    rm -f $(OBJS1)
    rm -f $(EXEC1)
时间: 2024-10-22 06:44:34

Linux 网络编程六(多进程服务器僵尸进程解决方案)的相关文章

Linux网络编程:客户端/服务器的简单实现

一. Socket的基本知识 1. socket功能 Socket层次 Socket实质上提供了进程通信的端点,进程通信之前,双方必须首先各自创建一个端点,否则是没有办法建立联系并相互通信的. 每一个Socket都一个半相关描述: {协议, 本地地址, 本地端口} 完整的Socket的描述: {协议, 本地地址, 本地端口, 远程地址, 远程端口} 2. Socket工作流程 面向连接(TCP)的Socket工作流程 UDP的socket工作流程 l 服务器端 首先,服务器应用程序用系统调用so

TCP/IP 网络编程 (抄书笔记 3) -- 僵尸进程和多任务并发服务器

TCP/IP 网络编程 (抄书笔记 3) – 僵尸进程和多任务并发服务器 TCP/IP 网络编程 (抄书笔记 3) – 僵尸进程和多任务并发服务器 Table of Contents 僵尸进程的产生 避免僵尸进程 信号 多任务的并发服务器 僵尸进程的产生 子进程先退出, 父进程没有退出 ==> 僵尸进程 父进程先退出, 子进程没有退出 ==> 子进程被 0 号进程回收, 不会产生僵尸进程 pid_t pid = fork(); if (pid == 0) { // child printf(&

Linux网络编程——tcp并发服务器(poll实现)

想详细彻底地了解poll或看懂下面的代码请参考<Linux网络编程--I/O复用之poll函数> 代码: #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/select.h> #include <sys/time.h> #include <sys/socket.h> #incl

Linux网络编程——tcp并发服务器(多进程)

一.tcp并发服务器概述 一个好的服务器,一般都是并发服务器(同一时刻可以响应多个客户端的请求).并发服务器设计技术一般有:多进程服务器.多线程服务器.I/O复用服务器等. 二.多进程并发服务器 在 Linux 环境下多进程的应用很多,其中最主要的就是网络/客户服务器.多进程服务器是当客户有请求时,服务器用一个子进程来处理客户请求.父进程继续等待其它客户的请求.这种方法的优点是当客户有请求时,服务器能及时处理客户,特别是在客户服务器交互系统中.对于一个 TCP 服务器,客户与服务器的连接可能并不

Linux网络编程——tcp并发服务器(多线程)

tcp多线程并发服务器 多线程服务器是对多进程服务器的改进,由于多进程服务器在创建进程时要消耗较大的系统资源,所以用线程来取代进程,这样服务处理程序可以较快的创建.据统计,创建线程与创建进程要快 10100 倍,所以又把线程称为"轻量级"进程.线程与进程不同的是:一个进程内的所有线程共享相同的全局内存.全局变量等信息,这种机制又带来了同步问题. tcp多线程并发服务器框架: 我们在使用多线程并发服务器时,直接使用以上框架,我们仅仅修改client_fun()里面的内容. 代码示例: #

Linux 网络编程系列教程

01.Linux网络编程1--网络协议入门 02.Linux网络编程2--无连接和面向连接协议的区别 03.Linux网络编程3--编程准备:字节序.地址转换 04.Linux网络编程4--UDP编程 05.Linux网络编程5--广播 06.Linux网络编程7--多播 08.Linux网络编程8--TCP编程 09.Linux网络编程9--并发服务器 10.Linux网络编程10--原始套接字能干什么? 11.Linux网络编程11--原始套接字编程 12.Linux网络编程12--原始套接

《Linux高性能服务器编程》学习总结(五)——Linux网络编程基础API

第五章      Linux网络编程基础API 对于网络编程,首先要了解的就是字节序的问题,字节序分为主机字节序和网络字节序,主机字节序又称小端字节序,是低字节存放在地地址,而网络字节序又称大端字节序,是低字节放在高地址.当数据在不同的机器上传播时,就需要统一字节顺序以保证不出现错误.在发送数据前,先将需要转变的数据转成网络字节序再发送,接收时先转成主机字节序再处理,要特别注意的是,即使是本机的两个进程通信,也要考虑字节序的问题,比如JAVA的虚拟机就使用大端字节序.使用如下代码可以查看本机的字

Linux网络编程(简单的时间获取服务器)

1.一个简单的服务器时间获取程序 服务器和客户端采用UDP通信的方式,来编写一个简单的时间获取应用. 把过程大致理顺一下,首先是服务器端的编写,使用的是迭代的方式,没有并发 先创建一个socket而后bind服务器,绑定之后就可以创建一个循环来接收和发送 信息了,以达到和客户端之间的通信. #include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include

Linux网络编程——进程池实现过程详解(1)

目录 进程池 父进程的实现流程 子进程的实现流程 进程池 父进程的实现流程 1.定义数据结构pChild,申请子进程数目的结构体空间 2.通过循环,socketpair创建全双工管道,创建子进程,将子进程pid,管道对端,是否忙碌等信息存储 3.socket,bind,listen,对应的端口处于监听状态 netstat 4.epoll_create创建epfd,监控socketFd和所有子进程的管道对端 5.while(1)循环 epoll_wait等待客户端的请求及子进程是否有通知 如果so