基于Linux的多进程并发服务器设计与实现

发布于:2025-03-22 ⋅ 阅读:(18) ⋅ 点赞:(0)

基于Linux的多进程并发服务器设计与实现

简介

本项目实现了一个基于Linux的多进程并发服务器框架,采用进程池技术提高服务器并发处理能力,主要用于文件传输服务。该框架利用了Unix域套接字、管道通信、文件描述符传递和epoll机制等技术,实现了高效的任务分发和并发处理。

系统架构

该服务器采用主-从进程模型(Master-Worker模式):

  • 主进程(Master进程):负责监听客户端连接请求,并将连接分发给空闲的子进程
  • 子进程(Worker进程):负责处理具体的业务逻辑,如文件传输

系统架构图如下:

+------------------+
|   主进程(Master)  |
|    监听连接请求    |
|    分发任务给子进程 |
+--------+---------+
     |   |   |
     |   |   |
     v   v   v
+-----+ +-----+ +-----+
|子进程1| |子进程2| |子进程n|
|处理业务| |处理业务| |处理业务|
+-----+ +-----+ +-----+

关键技术

进程池设计

进程池是预先创建一组子进程,这些进程等待主进程分配任务。此设计可避免频繁创建和销毁进程的开销,提高系统响应速度。

// 进程状态定义
typedef enum worker_status
{
    FREE = 0,  // 空闲状态
    BUSY       // 忙碌状态
} worker_status_t;

// 进程数据结构
typedef struct
{
    pid_t pid;              // 子进程ID
    int pipefd;             // 与父进程通信的管道
    worker_status_t status; // 子进程当前状态
} process_data_t;

进程间通信

本项目使用Unix域套接字(socketpair)实现父子进程间的双向通信:

int makechild(process_data_t *p, int num)
{
    for (int i = 0; i < num; i++)
    {
        int pipefd[2];
        socketpair(AF_LOCAL, SOL_SOCKET, 0, pipefd); // 创建全双工通信管道
        pid_t pid = fork();
        if (pid == 0)
        {
            close(pipefd[1]); // 子进程关闭写端
            handleTask(pipefd[0]);
            exit(0);
        }
        else
        {
            close(pipefd[0]); // 父进程关闭读端
            // 保存子进程信息
            p[i].pid = pid;
            p[i].pipefd = pipefd[1];
            p[i].status = FREE;
        }
    }
    return 0;
}

文件描述符传递

使用 sendmsg/recvmsg 系统调用,通过 SCM_RIGHTS 控制消息传递文件描述符,实现父进程将客户端连接传递给子进程:

int sendfd(int pipefd, int fd)
{
    char buf[4] = {0};
    struct iovec iov;
    iov.iov_base = buf;
    iov.iov_len = sizeof(buf);

    size_t len = CMSG_LEN(sizeof(fd));
    struct cmsghdr *cmsg = (struct cmsghdr *)calloc(1, len);

    cmsg->cmsg_len = len;
    cmsg->cmsg_type = SCM_RIGHTS;  // 设置类型为传递文件描述符
    cmsg->cmsg_level = SOL_SOCKET;

    int *p = (int *)CMSG_DATA(cmsg);
    *p = fd;  // 填入要传递的文件描述符

    struct msghdr msg;
    memset(&msg, 0, sizeof(msg));
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = cmsg;
    msg.msg_controllen = len;

    sendmsg(pipefd, &msg, 0);
    free(cmsg);
    return 0;
}

I/O多路复用

使用epoll机制监听多个文件描述符,提高I/O效率:

int epolladdfd(int epfd, int fd)
{
    struct epoll_event ev;
    bzero(&ev, sizeof(ev));
    ev.events = EPOLLIN; // 监听读事件
    ev.data.fd = fd;
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    ERROR_CHECK(ret, -1, "epoll_ctl error");
    return 0;
}

工作流程

  1. 初始化阶段

    • 主进程创建指定数量的子进程
    • 每个子进程与主进程建立管道通信
    • 子进程进入事件循环,等待任务
  2. 连接处理阶段

    • 主进程监听客户端连接
    • 客户端请求到达时,主进程通过epoll监测到连接事件
    • 主进程接受连接,获取客户端socket描述符
  3. 任务分发阶段

    • 主进程寻找空闲子进程
    • 主进程通过sendfd将客户端socket描述符传递给子进程
    • 主进程将子进程状态设置为BUSY
  4. 任务处理阶段

    • 子进程通过recvfd接收客户端socket描述符
    • 子进程处理文件传输业务
    • 任务完成后,子进程关闭客户端socket描述符
    • 子进程向主进程发送完成信号
  5. 任务完成阶段

    • 主进程接收到子进程的完成信号
    • 主进程将子进程状态重新设置为FREE
    • 子进程可以继续处理新的任务

文件传输实现

文件传输采用分块传输方式,确保大文件也能高效传输:

int transferfile(int peerfd)
{
    // 打开文件
    int fd = open(FILENAME, O_RDWR, 0777);
    
    // 发送文件名
    int namelen = strlen(FILENAME);
    sendn(peerfd, &namelen, sizeof(namelen));
    sendn(peerfd, FILENAME, namelen);
    
    // 发送文件大小
    struct stat st;
    fstat(fd, &st);
    sendn(peerfd, &st.st_size, sizeof(st.st_size));
    
    // 分块发送文件内容
    char buf[1000] = {0};
    off_t sent = 0;
    while (sent < st.st_size)
    {
        int ret = read(fd, buf, sizeof(buf));
        sendn(peerfd, &ret, 4);   // 发送块大小
        sendn(peerfd, buf, ret);  // 发送块数据
        sent += ret;
        printf("\r发送进度: %.2f%%", (double)sent * 100 / st.st_size);
        fflush(stdout);
    }
    
    printf("\n发送完成\n");
    close(fd);
    return 0;
}

客户端实现

客户端连接服务器并接收文件:

int main()
{
    // 创建socket并连接服务器
    int clientfd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);
    addr.sin_addr.s_addr = inet_addr("192.168.34.xxx");
    connect(clientfd, (struct sockaddr *)&addr, sizeof(addr));
    
    // 接收文件名
    char filename[20] = {0};
    int len = 0;
    recv(clientfd, &len, 4, 0);
    recv(clientfd, filename, len, 0);
    
    // 接收文件大小
    off_t filesize = 0;
    recv(clientfd, &filesize, sizeof(filesize), 0);
    
    // 接收文件内容
    char buf[1000] = {0};
    int fd = open(filename, O_RDWR | O_CREAT, 0777);
    while (1)
    {
        int ret = recv(clientfd, &len, 4, 0);
        if (ret == 0) break;
        ret = recv(clientfd, buf, len, 0);
        write(fd, buf, len);
    }
    
    close(clientfd);
    close(fd);
    return 0;
}

编译与运行

编译

项目使用makefile进行编译管理:

# 编译所有目标
all: server client01

# 编译服务器
server: server.o transfer.o sendfd.o main.o child.o
	g++ $^ -o $@

# 编译客户端
client01: client01.cpp
	g++ $^ -o $@

# 清理编译产物
clean:
	rm -rf *.o server client01

运行

  1. 启动服务器:
./server 192.168.34.xxx 8080 4

参数分别为:IP地址、端口号、子进程数量

  1. 启动客户端:
./client01

总结

本项目通过进程池技术实现了一个高效的并发服务器框架,主要特点有:

  1. 高并发处理:采用预创建的进程池,避免频繁创建销毁进程的开销
  2. 高效通信:使用Unix域套接字实现父子进程间的双向通信
  3. 描述符传递:通过SCM_RIGHTS传递文件描述符,实现优雅的任务分发
  4. I/O多路复用:使用epoll机制监听多个文件描述符,提高I/O效率
  5. 状态管理:通过状态标识维护子进程的工作状态

该框架可作为网络服务器的基础架构,通过修改业务处理逻辑,可以适用于各种网络服务场景。