【linux 多进程并发】0203 网络资源的多进程处理,子进程完全继承网络套接字,避免“惊群”问题-三、套接字的继承


在父子进程的多任务架构中,父进程创建的网络套接字,fork出来子进程后,子进程是完全进行了复制。

3.1 验证代码

下面我们就以第二种网络模型为例进行验证。

代码如下:

/*
 * ex020302_netprocess.c
 */
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
#include <sys/epoll.h>  
#include <fcntl.h>  
#include <errno.h>  
  
#define MAX_EVENTS 10  
#define BUFFER_SIZE 1024  
#define PORT 5809

void initializeServerNet();
void closeServerFd();
void dispatchLoop();

void subprocess();


int listen_fd;

int main(int argc ,char *argv[])
{
	initializeServerNet();
    
    subprocess();
    subprocess();

    dispatchLoop();
    
    closeServerFd();
	return 0;
}

void subprocess()
{
    int pid = -1;
	pid = fork();
	if(pid < 0)
	{
		printf("fork error[%s]\n",strerror(errno));
		exit(-1);
	}
	else if(pid > 0)
	{
		// parent.
		return;
	}
	else 
	{
		// child 
        dispatchLoop();
		exit(0);
	}
}

void initializeServerNet()
{
    struct sockaddr_in server_addr;  
  
    // 创建监听socket  
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);  
    if (listen_fd == -1) 
    {  
        perror("socket");  
        exit(EXIT_FAILURE);  
    }  
   
    // 绑定地址和端口  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_addr.s_addr = INADDR_ANY;  
    server_addr.sin_port = htons(PORT);  
    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) 
    {  
        perror("bind");  
        close(listen_fd);  
        exit(EXIT_FAILURE);  
    }

    // 开始监听
    if (listen(listen_fd, SOMAXCONN) == -1)
    {
        perror("listen");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }
}

void closeServerFd()
{
    close(listen_fd);  
}

void dispatchLoop()
{
    int conn_fd;  

    // 主循环  
    while (1) 
    {  
        // 新的连接  
        conn_fd = accept(listen_fd, NULL, NULL);  
        if (conn_fd == -1) 
        {  
            printf("[%d] accept wakeup, but failure.\n", getpid());
            sleep(1);
            continue;  
        }

        printf("[%d] accept a new client [%d]. \n",getpid(), conn_fd);

        close(conn_fd);
    }  

    closeServerFd();
}

说明

  • 在主进程中创建网络套接字,绑定地址,并启动监听;
  • 创建多个工作子进程;
  • 工作子进程中继承了监听套接字;
  • 每个工作子进程可以独立与客户端建立连接,并处理消息;

这里会创建两个子进程,在父进程和子进程中都会对同一个socket监听连接请求。

3.2 网络客户端

为了方便验证,我们编写一个简单的客户端程序。

/*
 * ex020302_client.c
 */

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <arpa/inet.h>  
  
#define SERVER_IP "127.0.0.1"  
#define SERVER_PORT 4808  
#define BUFFER_SIZE 1024  


int main(int argc, char *argv[]) 
{  
    int sockfd;  
    struct sockaddr_in server_addr;  
    char buffer[BUFFER_SIZE] = {0};  
    const char *message = "Hello, Server!";  
    int port = SERVER_PORT;

    if(argc > 1)
    {
        port = atoi(argv[1]);
    }

    // 创建套接字  
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) 
    {  
        perror("socket creation failed");  
        exit(EXIT_FAILURE);  
    }  
  
    // 配置服务器地址信息  
    server_addr.sin_family = AF_INET;  
    server_addr.sin_port = htons(port);  
  
    // 将IP地址从字符串转换为二进制形式  
    if (inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr) <= 0) 
    {  
        perror("Invalid address/ Address not supported");  
        close(sockfd);  
        exit(EXIT_FAILURE);  
    }  
  
    // 连接到服务器  
    if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) 
    {  
        perror("Connection Failed");  
        close(sockfd);  
        exit(EXIT_FAILURE);  
    }  
  
    for(int i = 0; i < 20; i++)
    {
        // 发送消息到服务器  
        send(sockfd, message, strlen(message), 0);  
        printf("Message sent: %s\n", message);  
    
        // 接收服务器的响应  
        int bytes_received = recv(sockfd, buffer, BUFFER_SIZE - 1, 0);  
        if (bytes_received < 0) 
        {  
            perror("Error in receiving");  
        } 
        else if (bytes_received == 0) 
        {  
            printf("Server closed the connection\n");  
        } 
        else 
        {  
            buffer[bytes_received] = '\0'; // 确保字符串以空字符结尾  
            printf("Message received from server: %s\n", buffer);  
        }  

        sleep(1);
    }
  
    // 关闭套接字  
    close(sockfd);  
  
    return 0;  
}

说明

  • 客户端带一个参数,是服务端的端口号;
  • 创建socket,并且与服务端(IP:port)连接;
  • 不断发送和接收消息,重复20次;
  • 实际上服务端只是接收建立连接,不会接收和发送响应,这已经达到了测试的目的;

3.3 结果验证

  • 编译服务端,并且运行

这里没有使用deamon后台服务的方式运行,会停在这里。

[senllang@hatch ex02]$ gcc ex020105_forksocket.c -o test
[senllang@hatch ex02]$ ./test 
  • 编译客户端,并运行

在另外一个终端编译和运行网络客户端程序;

服务端默认的端口号是5809,作为参数输入。

[senllang@hatch ex02]$ gcc ex020302_client.c -o client_opt
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer
[senllang@hatch ex02]$ ./client_opt 5809
Message sent: Hello, Server!
Error in receiving: Connection reset by peer

为了模拟多客户端的场景,我们将客户端启动多次;

可以看到客户端启动后,打印了发送的消息,之后就退出了,

因为服务端与客户端建立连接成功后,随即就关闭了连接。

  • 运行结果

可以看到服务端打印的信息。

[1205954] accept a new client [4]. 
[1205955] accept a new client [4]. 
[1205956] accept a new client [4]. 
[1205954] accept a new client [4]. 
[1205955] accept a new client [4]. 
[1205956] accept a new client [4]. 

服务端启动了三个进程,可以看到三个进程的PID分别为1205954,1205955,1205956,它们都可以收到来自客户端的连接请求。

同时出现了一件很有意思的事情,三个进程轮流进行处理连接请求,这里主要避免了惊群的问题。

上一篇:Uniapp安装Pinia并持久化(Vue3)


下一篇:类和对象相关题