【2】多进程TCP并发服务器

fork 函数

fork函数用于创建一个子线程,函数原型如下:

#include <unistd.h>

pit_t fork(void); 

fork函数调用一次,返回2次,在父进程中返回一次,在子进程中也返回一次。在子进程中返回0,在父进程中返回子进程的pid,因为父进程无法通过额外的手段获取子进程的id,所以必须记录fork之后的子进程id,而子进程在任何时候都可以通过getppid来获取其父进程的id。


僵尸进程

僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。


SIGCHLD 信号

当任何一个进程终止时都会向其父进程发送SIGCHLD信号,告知父进程回收自己,但该信号的默认处理动作为忽略,忽略该信号可能会导致出现僵尸进程。


signal 函数

signal函数用来设置一个信号处理函数来处理信号,函数原型如下:

#include <signal.h>

void (*signal(int sig, void (*func)(int)))(int);

中间部分:signal函数有2个参数,第一个是int,第二个是无返回值,带一个int参数的函数指针。
外围:signal函数返回的是一个函数指针,无返回值,有一个int参数。
通过typedef的方式进行简化,简化后如下:

typedef void Sigfunc(int)

// Sigfunc就是一个返回值是一个无返回值,有一个int参数的函数
Sigfunc *signal(int, Sigfunc*);

waitpid 函数

waitpid函数用来清理已经停止的子进程,可以防止出现僵尸进程,函数原型如下:

#include <sys/wait.h>

/*
 * pid: 子进程的ID,-1表示等待第一个终止的子进程
 * statloc: 返回子进程的终止状态
 * options: 附加选项,常用的为WNOHANG,意为告知内核在没有已终止子进程时候不要阻塞
*/
pid_t waitpid(pid_t pid, int* statloc, int options);

并发服务端程序

父进程来监听所有的客户端连接,当连接上时候,fork产生一个子进程来处理信息交互。同时父进程监听SIGCHLD信号,通过函数sig_func来处理该信号,在sig_func函数中通过waitpid来防止产生僵尸进程。

服务端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>

static void do_msg_process(int accsocfd) {
    printf("do_msg_process enter!\n");
    int size = 0;
    char read_buffer[512] = {0};
    char write_buffer[1024] = {0};

    while (true) {
        memset(read_buffer, 0x00, sizeof(read_buffer));
        memset(write_buffer, 0x00, sizeof(write_buffer));

        size = read(accsocfd, read_buffer, 1024);
        if (size > 0) {
            printf("pid: %d, client say: %s\n", getpid(), read_buffer);

            snprintf(write_buffer, 1024, "%s%s", "[SERVER RECV]: ", read_buffer);
            size = write(accsocfd, write_buffer, strlen(write_buffer));
        } else {
            break;
        }
    }
    printf("do_msg_process exit!\n");
}

static void sig_func(int arg) {
    pid_t pid;
    int stat;

    while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) {
        printf("child %d terminated!\n", pid);
    }
}

int main(int argc, char const *argv[]) {
    // 调用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
    int socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketfd < 0) {
        perror("socket create failed!\n");
        return 0;
    }

    // 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,IP地址使用INADDR_ANY表示任意IP
    struct sockaddr_in s_addr;
    memset(&s_addr, 0x00, sizeof(struct sockaddr_in));
    s_addr.sin_family = AF_INET;
    s_addr.sin_port = htons(6666);
    s_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    // 调用bind函数绑定所创建的套接字
    bind(socketfd, (struct sockaddr*)&s_addr, sizeof(struct sockaddr));

    // 调用listen将套接字转换为监听套接字,这样外来连接可以通过该套接字被内核接受
    listen(socketfd, 10);

    // 监听SIGCHLD信号,通过函数sig_func来处理该信号
    signal(SIGCHLD, sig_func);

    while (true) {
        // 调用accept,服务器进入睡眠状态,等待客户的连接,完成3次握手,然后返回一个握手成功的描述符,新描述符用于与客户端通讯
        int accsocfd = accept(socketfd, NULL, NULL);

        pid_t forkpid = fork();
        if (forkpid == 0) {
            // 子进程不需要监听
            close(socketfd);
            // 处理消息
            do_msg_process(accsocfd);
            // 子进程退出
            exit(0);
        }

        // 调由子进程处理了,父进程用close来关闭已连接的套接字
        close(accsocfd);
    }

    close(socketfd);
    return 0;
}


客户端程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

static void do_msg_process(int socketfd) {
    printf("do_msg_process enter!\n");
    int size = 0;
    char read_buffer[512] = {0};
    char write_buffer[1024] = {0};

    while(true) {
        memset(read_buffer, 0x00, sizeof(read_buffer));
        memset(write_buffer, 0x00, sizeof(write_buffer));

        printf("input:> ");
        fgets(write_buffer, 1024, stdin);

        size = write(socketfd, write_buffer, strlen(write_buffer));

        size = read(socketfd, read_buffer, 1024);
        printf("server say: %s\n", read_buffer);
    }
    printf("do_msg_process exit!\n");
}

int main(int argc, char const *argv[]) {
    // 利用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
    int socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (socketfd < 0) {
        perror("socket create failed!\n");
        return 0;
    }

    // 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,使用htons转换端口,使用inet_pton转换IP地址
    struct sockaddr_in c_addr;
    memset(&c_addr, 0x00, sizeof(struct sockaddr_in));
    c_addr.sin_family = AF_INET;
    c_addr.sin_port = htons(6666);
    inet_pton(AF_INET, "127.0.0.1", &c_addr.sin_addr);

    // connet将和指定的服务器建立一个TCP连接
    int result = connect(socketfd, (struct sockaddr*)&c_addr, sizeof(struct sockaddr_in));
    if (result < 0) {
        perror("connect failed!\n");
        return 0;
    }

    // 处理消息
    do_msg_process(socketfd);

    close(socketfd);
    return 0;
}


运行结果

在这里插入图片描述
两个客户端分别是由2个子进程处理,pid = 54212和pid = 54214,客户端退出后,相应的处理消息的子进程也退出,父进程捕获到了信号利用waitpid函数进行了资源回收。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值