今天给大家实现一个半同步和半异步的进程池,实现一个客户端要查看某个文件然后服务器会把文件的数据发送给客户端 ,类似于网页服务器的效果,我也把服务器和客户端对应的逻辑粘贴好了,有需要的可以去修改,实现自己的服务器。源码复制粘贴就能跑,最好用qt编译,源码有注释,先跑通再看注释理解半同步半异步进程池的写法套路。
服务器代码与客户端代码
服务器
// 系统头文件包含(网络、进程、事件相关)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
/* 进程类(封装子进程信息)
* 采用类似Reactor模式中的Worker角色设计
*/
class process {
public:
process() : m_pid( -1 ) {} // 初始化PID为-1(无效进程)
public:
pid_t m_pid; // 子进程PID
int m_pipefd[2]; // 父子进程通信的管道(0读端,1写端)
};
/* 进程池模板类(单例模式)
* 采用主从Reactor设计,父进程作为主Reactor监听连接,子进程作为Sub-Reactor处理I/O
*/
template< typename T >
class processpool {
private:
// 私有构造函数(单例模式)
processpool( int listenfd, int process_number = 8 );
public:
// 单例创建方法(类似Reactor的初始化)
static processpool< T >* create( int listenfd, int process_number = 8 ) {
if( !m_instance ) {
m_instance = new processpool< T >( listenfd, process_number );
}
return m_instance;
}
~processpool() {
delete [] m_sub_process; // 释放子进程数组内存
}
void run(); // 启动事件循环
private:
void setup_sig_pipe(); // 初始化信号处理管道(类似libevent信号事件封装)
void run_parent(); // 主Reactor逻辑
void run_child(); // Sub-Reactor逻辑
private:
// 配置常量(参考libevent的默认配置)
static const int MAX_PROCESS_NUMBER = 16; // 最大子进程数
static const int USER_PER_PROCESS = 65536; // 单进程连接数(Linux FD上限)
static const int MAX_EVENT_NUMBER = 10000; // epoll最大事件数
// 成员变量
int m_process_number; // 子进程总数
int m_idx; // 子进程索引(-1表示父进程)
int m_epollfd; // epoll实例(事件多路分发器)
int m_listenfd; // 监听socket文件描述符
int m_stop; // 停止标志
process* m_sub_process; // 子进程数组指针
static processpool< T >* m_instance; // 单例指针
};
template< typename T >
processpool< T >* processpool< T >::m_instance = NULL; // 单例初始化
static int sig_pipefd[2]; // 全局信号管道(用于统一信号事件处理)
/* 设置非阻塞IO(libevent同类操作) */
static int setnonblocking( int fd ) {
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
/* 向epoll添加事件(边缘触发模式ET)*/
static void addfd( int epollfd, int fd ) {
epoll_event event;
event.data.fd = fd; // 设置监控的文件描述符
event.events = EPOLLIN | EPOLLET; // 监听可读事件+边缘触发模式
epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
setnonblocking( fd ); // 非阻塞避免事件循环阻塞
}
/* 从epoll移除事件 */
static void removefd( int epollfd, int fd ) {
epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 );
close( fd ); // 关闭文件描述符
}
/* 信号处理函数(将信号转为IO事件) */
static void sig_handler( int sig ) {
int save_errno = errno; // 保存原始errno
int msg = sig; // 信号值作为消息内容
send( sig_pipefd[1], ( char* )&msg, 1, 0 ); // 写入信号管道
errno = save_errno; // 恢复errno
}
/* 注册信号处理器(统一事件处理入口) */
static void addsig( int sig, void( handler )(int), bool restart = true ) {
struct sigaction sa;
memset( &sa, '\0', sizeof( sa ) );
sa.sa_handler = handler; // 设置信号处理函数
if( restart ) {
sa.sa_flags |= SA_RESTART; // 自动重启被中断的系统调用
}
sigfillset( &sa.sa_mask ); // 阻塞所有其他信号
assert( sigaction( sig, &sa, NULL ) != -1 ); // 注册信号处理器
}
/* 进程池构造函数(创建子进程和通信管道) */
template< typename T >
processpool< T >::processpool( int listenfd, int process_number )
: m_listenfd( listenfd ), m_process_number( process_number ),
m_idx( -1 ), m_stop( false )
{
// 校验进程数量合法性
assert( ( process_number > 0 ) && ( process_number <= MAX_PROCESS_NUMBER ) );
// 创建子进程数组
m_sub_process = new process[ process_number ];
assert( m_sub_process );
// 创建子进程组(类似Preforking模型)
for( int i = 0; i < process_number; ++i ) {
// 创建UNIX域套接字对用于进程间通信
int ret = socketpair( PF_UNIX, SOCK_STREAM, 0, m_sub_process[i].m_pipefd );
assert( ret == 0 ); // 确保socketpair成功
// 创建子进程
m_sub_process[i].m_pid = fork();
assert( m_sub_process[i].m_pid >= 0 );
// 父进程处理逻辑
if( m_sub_process[i].m_pid > 0 ) {
close( m_sub_process[i].m_pipefd[1] ); // 关闭写端
continue;
}
// 子进程处理逻辑
else {
close( m_sub_process[i].m_pipefd[0] ); // 关闭读端
m_idx = i; // 设置子进程在数组中的索引
break; // 子进程退出循环
}
}
}
/* 初始化信号处理管道(类似libevent的信号集成方式) */
template< typename T >
void processpool< T >::setup_sig_pipe() {
// 创建epoll实例
m_epollfd = epoll_create( 5 );
assert( m_epollfd != -1 );
// 创建信号通知管道
int ret = socketpair( PF_UNIX, SOCK_STREAM, 0, sig_pipefd );
assert( ret != -1 );
// 配置信号管道的写端为非阻塞模式
setnonblocking( sig_pipefd[1] );
// 将信号管道的读端加入epoll监听
addfd( m_epollfd, sig_pipefd[0] );
// 注册关键信号处理
addsig( SIGCHLD, sig_handler ); // 子进程终止信号
addsig( SIGTERM, sig_handler ); // 终止进程信号
addsig( SIGINT, sig_handler ); // 终端中断信号
addsig( SIGPIPE, SIG_IGN ); // 忽略管道破裂信号
}
/* 启动进程池运行(根据角色分发逻辑) */
template< typename T >
void processpool< T >::run() {
if( m_idx != -1 ) {
run_child(); // 子进程运行Sub-Reactor
return;
}
run_parent(); // 父进程运行主Reactor
}
/* 子进程事件循环(Sub-Reactor实现) */
template< typename T >
void processpool< T >::run_child() {
setup_sig_pipe(); // 初始化信号处理
int pipefd = m_sub_process[m_idx].m_pipefd[ 1 ]; // 获取管道写端
addfd( m_epollfd, pipefd ); // 监听父进程通知
// 创建用户连接处理器数组(模板类实例)
T* users = new T [ USER_PER_PROCESS ];
assert( users );
epoll_event events[ MAX_EVENT_NUMBER ];
int number = 0;
int ret = -1;
// 主事件循环
while( ! m_stop ) {
number = epoll_wait( m_epollfd, events, MAX_EVENT_NUMBER, -1 );
if ( ( number < 0 ) && ( errno != EINTR ) ) {
printf( "epoll failure\n" );
break;
}
for ( int i = 0; i < number; i++ ) {
int sockfd = events[i].data.fd;
// 处理来自父进程的通知(新连接到达)
if( ( sockfd == pipefd ) && ( events[i].events & EPOLLIN ) ) {
int client = 0;
ret = recv( sockfd, ( char* )&client, sizeof( client ), 0 );
if( ( ( ret < 0 ) && ( errno != EAGAIN ) ) || ret == 0 ) {
continue;
} else {
struct sockaddr_in client_address;
socklen_t client_addrlength = sizeof( client_address );
// 接受新连接
int connfd = accept( m_listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
if ( connfd < 0 ) {
printf( "errno is: %d\n", errno );
continue;
}
// 将新连接加入epoll监听
addfd( m_epollfd, connfd );
// 初始化连接处理器
users[connfd].init( m_epollfd, connfd, client_address );
}
}
// 处理信号事件
else if( ( sockfd == sig_pipefd[0] ) && ( events[i].events & EPOLLIN ) ) {
int sig;
char signals0[1024];
ret = recv( sig_pipefd[0], signals0, sizeof( signals0 ), 0 );
if( ret <= 0 ) {
continue;
} else {
for( int i = 0; i < ret; ++i ) {
switch( signals0[i] ) {
case SIGCHLD: // 子进程终止
{
pid_t pid;
int stat;
while ( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 ) {
continue;
}
break;
}
case SIGTERM: // 终止信号
case SIGINT: // 中断信号
{
m_stop = true;
break;
}
default:
break;
}
}
}
}
// 处理客户端数据到达
else if( events[i].events & EPOLLIN ) {
users[sockfd].process(); // 调用模板类的业务处理逻辑
}
else {
continue;
}
}
}
// 资源清理
delete [] users;
users = NULL;
close( pipefd );
close( m_epollfd );
}
/* 父进程事件循环(主Reactor实现) */
template< typename T >
void processpool< T >::run_parent() {
setup_sig_pipe(); // 初始化信号处理
addfd( m_epollfd, m_listenfd ); // 监听新连接
epoll_event events[ MAX_EVENT_NUMBER ];
int sub_process_counter = 0; // 子进程轮询计数器
int new_conn = 1; // 新连接标志
int number = 0;
int ret = -1;
// 主事件循环
while( ! m_stop ) {
number = epoll_wait( m_epollfd, events, MAX_EVENT_NUMBER, -1 );
if ( ( number < 0 ) && ( errno != EINTR ) ) {
printf( "epoll failure\n" );
break;
}
for ( int i = 0; i < number; i++ ) {
int sockfd = events[i].data.fd;
// 处理新连接到达
if( sockfd == m_listenfd ) {
// 使用Round-Robin算法选择子进程
int i = sub_process_counter;
do {
if( m_sub_process[i].m_pid != -1 ) {
break;
}
i = (i+1)%m_process_number;
} while( i != sub_process_counter );
// 没有可用子进程时停止
if( m_sub_process[i].m_pid == -1 ) {
m_stop = true;
break;
}
sub_process_counter = (i+1)%m_process_number; // 更新计数器
// 通知选中的子进程处理新连接
send( m_sub_process[i].m_pipefd[0], ( char* )&new_conn, sizeof( new_conn ), 0 );
printf( "send request to child %d\n", i );
}
// 处理信号事件
else if( ( sockfd == sig_pipefd[0] ) && ( events[i].events & EPOLLIN ) ) {
int sig;
char signals0[1024];
ret = recv( sig_pipefd[0], signals0, sizeof( signals0 ), 0 );
if( ret <= 0 ) {
continue;
} else {
for( int i = 0; i < ret; ++i ) {
switch( signals0[i] ) {
case SIGCHLD: // 子进程终止处理
{
pid_t pid;
int stat;
while ( ( pid = waitpid( -1, &stat, WNOHANG ) ) > 0 ) {
// 清理已终止的子进程
for( int i = 0; i < m_process_number; ++i ) {
if( m_sub_process[i].m_pid == pid ) {
printf( "child %d join\n", i );
close( m_sub_process[i].m_pipefd[0] );
m_sub_process[i].m_pid = -1;
}
}
}
// 检查是否所有子进程都已终止
m_stop = true;
for( int i = 0; i < m_process_number; ++i ) {
if( m_sub_process[i].m_pid != -1 ) {
m_stop = false;
}
}
break;
}
case SIGTERM: // 终止信号处理
case SIGINT: // 中断信号处理
{
printf( "kill all the clild now\n" );
// 向所有子进程发送终止信号
for( int i = 0; i < m_process_number; ++i ) {
int pid = m_sub_process[i].m_pid;
if( pid != -1 ) {
kill( pid, SIGTERM );
}
}
break;
}
default:
break;
}
}
}
}
else {
continue;
}
}
}
// 资源清理
close( m_epollfd );
}
/*
* CGI连接处理类
* 职责:管理客户端连接,处理HTTP请求,执行CGI程序
*/
class cgi_conn
{
public:
cgi_conn() {} // 默认构造函数
~cgi_conn() {} // 默认析构函数
// 初始化连接参数
void init(int epollfd, int sockfd, const sockaddr_in& client_addr)
{
m_epollfd = epollfd; // 所属epoll实例
m_sockfd = sockfd; // 客户端socket文件描述符
m_address = client_addr; // 客户端地址信息
memset(m_buf, '\0', BUFFER_SIZE); // 清空接收缓冲区
m_read_idx = 0; // 缓冲区当前写入位置
}
// 主处理函数
void process()
{
int idx = 0;
int ret = -1;
// 持续读取数据直到连接关闭或出错
while (true)
{
idx = m_read_idx;
// 非阻塞读取数据
ret = recv(m_sockfd, m_buf + idx, BUFFER_SIZE - 1 - idx, 0);
// 错误处理分支
if(ret < 0)
{
if(errno != EAGAIN) // 非临时错误则关闭连接
{
removefd(m_epollfd, m_sockfd);
}
break;
}
else if(ret == 0) // 客户端关闭连接
{
removefd(m_epollfd, m_sockfd);
break;
}
else // 正常收到数据
{
m_read_idx += ret; // 更新缓冲区索引
printf("Received data: %s\n", m_buf);
for(; idx < m_read_idx; ++idx)
{
if((idx >= 1) && (m_buf[idx-1] == '\r') && (m_buf[idx] == '\n'))
{
break;
}
}
m_buf[idx-1] = '\0';
if(idx == m_read_idx)
{
continue;
}
char* file_name = m_buf;
printf("file_name %s\n",file_name);
if(access(m_buf, F_OK) == -1)//检测文件是否存在
{
removefd(m_epollfd, m_sockfd);
break;
}
// 创建子进程执行CGI程序
ret = fork();
if(ret == -1) // fork失败
{
removefd(m_epollfd, m_sockfd);
break;
}
else if(ret > 0) // 父进程
{
removefd(m_epollfd, m_sockfd);
break;
}
else // 子进程
{
// 重定向标准输出到socket
close(STDOUT_FILENO);
dup(m_sockfd);
// 执行CGI程序
execl("/bin/cat", "cat", m_buf, NULL);//文件发送给客户端
exit(0); // exec失败时退出
}
}
}
}
private:
static const int BUFFER_SIZE = 1024; // 固定缓冲区大小(可能不足)
static int m_epollfd; // 共享的epoll实例
int m_sockfd; // 当前连接socket
sockaddr_in m_address; // 客户端地址信息
char m_buf[BUFFER_SIZE]; // 接收缓冲区
int m_read_idx; // 缓冲区写入位置
};
// 静态成员初始化
int cgi_conn::m_epollfd = -1;
int main(int argc, char* argv[])
{
// 参数检查(不完善)
if(argc <= 2)
{
printf("usage:%s ip_address port_number\n", basename(argv[0]));
return 0;
}
// 网络配置初始化
const char* ip = argv[1];
int port = atoi(argv[2]);
// 创建监听socket
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
// 地址结构体配置
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
// 绑定端口
int ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
// 开始监听(队列长度仅5)
ret = listen(listenfd, 5);
// 创建进程池
processpool<cgi_conn>* pool = processpool<cgi_conn>::create(listenfd);
if(pool)
{
pool->run(); // 启动事件循环
delete pool; // 清理资源
}
// 关闭监听socket
close(listenfd);
return 0;
}
客户端
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#define BUFFER_SIZE 4096
int main(int argc, char* argv[])
{
// 参数校验
if (argc != 3) {
printf("Usage: %s <Server IP> <Server Port>\n", argv[0]);
exit(EXIT_FAILURE);
}
const char* server_ip = argv[1];
int server_port = atoi(argv[2]);
// 创建客户端Socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
// 配置服务器地址
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(server_port);
if (inet_pton(AF_INET, server_ip, &server_addr.sin_addr) <= 0) {
perror("Invalid address format");
close(sockfd);
exit(EXIT_FAILURE);
}
// 连接服务器
if (connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("Connection failed");
close(sockfd);
exit(EXIT_FAILURE);
}
printf("Connected to %s:%d\n", server_ip, server_port);
// 测试用例集
char* test_cases= "a.txt\r\n";// 有效请求格式 文件名+\r\n
// 发送请求
ssize_t sent = send(sockfd, test_cases, strlen(test_cases), 0);
if (sent == -1) {
perror("Send failed");
//continue;
}
printf("Sent %zd bytes\n", sent);
// 接收响应
char buffer[BUFFER_SIZE];
ssize_t total_received = 0;
while (1) {
ssize_t received = recv(sockfd, buffer, BUFFER_SIZE - 1, 0);
if (received == -1) {
perror("Receive error");
break;
} else if (received == 0) {
printf("\nConnection closed by server\n");
break;
}
total_received += received;
buffer[received] = '\0';
printf("%s", buffer);
// 检测HTTP头结束标记
if (strstr(buffer, "\r\n\r\n") || strstr(buffer, "\n\n")) {
break;
}
}
printf("\nTotal received: %zd bytes\n", total_received);
// 短时等待后继续下一个测试
sleep(1);
close(sockfd);
return 0;
}
效果图
下面是客户端要的文件名
下面是服务器的逻辑位置
大家可以按自己的需求修改对应的逻辑部分实现自己的代码
半同步半异步的优势
1.子进程在启动时预先创建并常驻内存,避免频繁创建/销毁进程的开销(进程创建成本高)。
2.动态分配任务給子进程实现负载均衡,减少资源竞争。
3.子进程独立运行,单个进程崩溃不会影响整体服务。
4.异步层:仅关注事件监听和任务分发。
5.同步层:仅关注业务逻辑处理。