方法1
主要思路:
- 定义了一个
EventData
结构体,用于存储事件相关的数据,如文件描述符、epoll 文件描述符、协程 ID 等。 EchoDeal
函数用于处理请求消息,并生成响应消息。handlerClient
函数是协程的执行函数,用于处理客户端连接。它通过循环读取数据、解析请求、执行业务处理、发送响应等步骤,实现了对客户端请求的处理。handler
函数是主函数,用于创建监听套接字、初始化 epoll、设置非阻塞模式、添加读事件等操作。然后进入一个循环,通过 epoll_wait 等待事件发生,并根据事件类型进行相应的处理,如接受新连接、处理客户端请求等。- 在主函数中,通过 fork 创建多个子进程,每个子进程都执行
handler
函数,从而实现多进程并发处理。
示例代码:
#include <arpa/inet.h> // 包含网络地址转换相关的头文件
#include <assert.h> // 包含断言相关的头文件
#include <fcntl.h> // 包含文件控制相关的头文件
#include <netinet/in.h> // 包含网络协议相关的头文件
#include <stdio.h> // 包含标准输入输出相关的头文件
#include <stdlib.h> // 包含标准库相关的头文件
#include <sys/epoll.h> // 包含 epoll 相关的头文件
#include <sys/socket.h> // 包含套接字相关的头文件
#include <unistd.h> // 包含 Unix 标准相关的头文件
#include <iostream> // 包含 C++ 的输入输出流头文件
#include "../coroutine.h" // 包含自定义的协程相关头文件
#include "../epollctl.hpp" // 包含自定义的 epoll 控制相关头文件
// 定义 EventData 结构体,用于存储事件相关的数据
struct EventData {
EventData(int fd, int epoll_fd) : fd_(fd), epoll_fd_(epoll_fd){}; // 构造函数,初始化成员变量
int fd_{0}; // 文件描述符
int epoll_fd_{0}; // epoll 文件描述符
int cid_{MyCoroutine::INVALID_ROUTINE_ID}; // 协程 ID
MyCoroutine::Schedule *schedule_{nullptr}; // 协程调度器指针
}; // 结构体定义结束
// EchoDeal 函数,用于处理请求消息,并生成响应消息
void EchoDeal(const std::string reqMessage, std::string &respMessage) { respMessage = reqMessage; } // 函数定义结束
// handlerClient 函数,协程的执行函数,用于处理客户端连接
void handlerClient(void *arg) {
EventData *eventData = (EventData *)arg; // 将传入的 void* 指针转换为 EventData* 指针
auto releaseConn = [&eventData]() { // 定义一个匿名函数用于释放连接资源
EchoServer::ClearEvent(eventData->epoll_fd_, eventData->fd_); // 清除事件
delete eventData; // 释放内存
};
ssize_t ret = 0; // 用于存储读取或写入的返回值
EchoServer::Codec codec; // 编解码器对象
std::string reqMessage; // 请求消息字符串
std::string respMessage; // 响应消息字符串
while (true) { // 读操作循环
uint8_t data[100]; // 数据缓冲区
ret = read(eventData->fd_, data, 100); // 尝试从文件描述符读取数据
if (ret == 0) { // 对端关闭连接
perror("peer close connection"); // 打印错误信息
releaseConn(); // 释放连接资源
return; // 函数返回
}
if (ret < 0) { // 读取错误
if (EINTR == errno) continue; // 被中断,继续尝试读取
if (EAGAIN == errno or EWOULDBLOCK == errno) { // 无数据可读
MyCoroutine::CoroutineYield(*eventData->schedule_); // 让出 CPU
continue; // 继续下一次循环
}
perror("read failed"); // 打印读取失败的错误信息
releaseConn(); // 释放连接资源
return; // 函数返回
}
codec.DeCode(data, ret); // 解码读取的数据
if (codec.GetMessage(reqMessage)) { // 从解码数据中获取完整的请求消息
break; // 跳出读循环
}
}
// 执行到这里说明已经读取到一个完整的请求
EchoDeal(reqMessage, respMessage); // 调用处理函数生成响应
EchoServer::Packet pkt; // 数据包对象
codec.EnCode(respMessage, pkt); // 对响应进行编码
EchoServer::ModToWriteEvent(eventData->epoll_fd_, eventData->fd_, eventData); // 切换为监听可写事件
ssize_t sendLen = 0; // 已发送数据的长度
while (sendLen!= pkt.Len()) { // 写操作循环
ret = write(eventData->fd_, pkt.Data() + sendLen, pkt.Len() - sendLen); // 尝试写入数据
if (ret < 0) { // 写入错误
if (EINTR == errno) continue; // 被中断,继续尝试写入
if (EAGAIN == errno or EWOULDBLOCK == errno) { // 不可写
MyCoroutine::CoroutineYield(*eventData->schedule_); // 让出 CPU
continue; // 继续下一次循环
}
perror("write failed"); // 打印写入失败的错误信息
releaseConn(); // 释放连接资源
return; // 函数返回
}
sendLen += ret; // 更新已发送数据的长度
}
releaseConn(); // 释放连接资源
}
// handler 函数,主函数,用于创建监听套接字、初始化 epoll、设置非阻塞模式、添加读事件等操作
void handler(char *argv[]) {
int sockFd = EchoServer::CreateListenSocket(argv[1], atoi(argv[2]), true); // 创建监听套接字
if (sockFd < 0) { // 如果创建失败
return; // 函数返回
}
epoll_event events[2048]; // epoll 事件数组
int epollFd = epoll_create(1024); // 创建 epoll 实例
if (epollFd < 0) { // 如果创建失败
perror("epoll_create failed"); // 打印错误信息
return; // 函数返回
}
EventData eventData(sockFd, epollFd); // 创建事件数据对象
EchoServer::SetNotBlock(sockFd); // 设置套接字为非阻塞
EchoServer::AddReadEvent(epollFd, sockFd, &eventData); // 添加可读事件
MyCoroutine::Schedule schedule; // 协程调度器
MyCoroutine::ScheduleInit(schedule, 10000); // 初始化协程池
int msec = -1; // 超时时间
while (true) { // 无限循环
int num = epoll_wait(epollFd, events, 2048, msec); // 等待 epoll 事件
if (num < 0) { // 如果等待失败
perror("epoll_wait failed"); // 打印错误信息
continue; // 继续下一次循环
} else if (num == 0) { // 超时无事件
sleep(0); // 让出 CPU
msec = -1; // 下次超时时间设置为 -1
continue; // 继续下一次循环
}
msec = 0; // 下次大概率还有事件,故 msec 设置为 0
for (int i = 0; i < num; i++) { // 处理事件
EventData *eventData = (EventData *)events[i].data.ptr; // 获取事件数据指针
if (eventData->fd_ == sockFd) { // 是监听套接字的事件
EchoServer::LoopAccept(sockFd, 2048, [epollFd](int clientFd) {
EventData *eventData = new EventData(clientFd, epollFd); // 创建新的事件数据对象
EchoServer::SetNotBlock(clientFd); // 设置套接字为非阻塞
EchoServer::AddReadEvent(epollFd, clientFd, eventData); // 监听可读事件
});
continue; // 继续下一次循环
}
if (eventData->cid_ == MyCoroutine::INVALID_ROUTINE_ID) { // 第一次事件,则创建协程
if (MyCoroutine::CoroutineCanCreate(schedule)) { // 可以创建协程
eventData->schedule_ = &schedule; // 设置协程调度器
eventData->cid_ = MyCoroutine::CoroutineCreate(schedule, handlerClient, eventData, 0); // 创建协程
MyCoroutine::CoroutineResumeById(schedule, eventData->cid_); // 唤醒刚刚创建的协程处理客户端请求
} else {
std::cout << "MyCoroutine is full" << std::endl; // 输出协程已满的信息
}
} else {
MyCoroutine::CoroutineResumeById(schedule, eventData->cid_); // 唤醒之前主动让出 cpu 的协程
}
}
MyCoroutine::ScheduleTryReleaseMemory(schedule); // 尝试释放协程内存
}
}
// main 函数
int main(int argc, char *argv[]) {
if (argc!= 3) { // 如果命令行参数数量不正确
std::cout << "invalid input" << std::endl; // 输出错误信息
std::cout << "example:./EpollReactorProcessPoolCoroutine 0.0.0.0 1688" << std::endl; // 给出正确用法示例
return -1; // 程序返回 -1
}
for (int i = 0; i < EchoServer::GetNProcs(); i++) { // 循环创建子进程
pid_t pid = fork(); // 创建进程
if (pid < 0) { // 创建失败
perror("fork failed"); // 打印错误信息
continue; // 继续下一次循环
}
if (0 == pid) { // 子进程
handler(argv); // 处理客户端请求
exit(0); // 子进程退出
}
}
while (true) sleep(1); // 父进程进入死循环
return 0; // 程序正常结束返回 0
}
方法2:
思路:
main
函数首先检查命令行参数数量是否正确。- 然后通过循环创建子进程。
- 子进程调用
handler
函数。 - 在
handler
函数中,创建监听套接字和 epoll 实例,设置套接字为非阻塞并添加可读事件,初始化协程调度器。 - 进入一个无限循环,通过
epoll_wait
等待事件发生。 - 根据事件的类型进行处理:
- 如果是监听套接字的事件,处理新的连接。
- 对于客户端连接的事件,如果是第一次事件则创建协程并唤醒,否则唤醒已有的协程。
- 协程中的
handlerClient
函数处理客户端的读写操作。
#include <arpa/inet.h>
#include <assert.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>
#include <iostream>
#include "../coroutine.h"
#include "../epollctl.hpp"
// 定义事件数据结构体
struct EventData {
EventData(int fd, int epoll_fd) : fd_(fd), epoll_fd_(epoll_fd){}; // 构造函数
int fd_{0}; // 文件描述符
int epoll_fd_{0}; // epoll 文件描述符
int cid_{MyCoroutine::INVALID_ROUTINE_ID}; // 协程 ID
MyCoroutine::Schedule *schedule_{nullptr}; // 协程调度器指针
};
// 处理请求并生成响应的函数
void EchoDeal(const std::string reqMessage, std::string &respMessage) { respMessage = reqMessage; }
// 处理客户端的函数
void handlerClient(void *arg) {
EventData *eventData = (EventData *)arg; // 获取事件数据指针
auto releaseConn = [&eventData]() { // 定义释放连接的匿名函数
EchoServer::ClearEvent(eventData->epoll_fd_, eventData->fd_);
delete eventData; // 释放内存
};
ssize_t ret = 0; // 读取结果
EchoServer::Codec codec; // 编解码器对象
std::string reqMessage; // 请求消息
std::string respMessage; // 响应消息
while (true) { // 读操作循环
uint8_t data[100]; // 数据缓冲区
ret = read(eventData->fd_, data, 100); // 尝试读取数据
if (ret == 0) { // 对端关闭连接
perror("peer close connection");
releaseConn();
return;
}
if (ret < 0) { // 读取错误
if (EINTR == errno) continue; // 被中断,继续尝试
if (EAGAIN == errno or EWOULDBLOCK == errno) { // 无数据可读
MyCoroutine::CoroutineYield(*eventData->schedule_); // 让出 CPU
continue;
}
perror("read failed");
releaseConn();
return;
}
codec.DeCode(data, ret); // 解码数据
if (codec.GetMessage(reqMessage)) { // 获取完整请求
break;
}
}
// 执行到这里说明已经读取到一个完整的请求
EchoDeal(reqMessage, respMessage); // 处理请求生成响应
EchoServer::Packet pkt;
codec.EnCode(respMessage, pkt); // 编码响应
EchoServer::ModToWriteEvent(eventData->epoll_fd_, eventData->fd_, eventData); // 切换为监听可写事件
ssize_t sendLen = 0; // 已发送长度
while (sendLen!= pkt.Len()) { // 写操作循环
ret = write(eventData->fd_, pkt.Data() + sendLen, pkt.Len() - sendLen); // 尝试写入
if (ret < 0) { // 写入错误
if (EINTR == errno) continue; // 被中断,继续尝试
if (EAGAIN == errno or EWOULDBLOCK == errno) { // 不可写
MyCoroutine::CoroutineYield(*eventData->schedule_); // 让出 CPU
continue;
}
perror("write failed");
releaseConn();
return;
}
sendLen += ret; // 更新已发送长度
}
releaseConn(); // 释放连接资源
}
// 主处理函数
void handler(char *argv[]) {
int sockFd = EchoServer::CreateListenSocket(argv[1], atoi(argv[2]), true); // 创建监听套接字
if (sockFd < 0) {
return;
}
epoll_event events[2048]; // epoll 事件数组
int epollFd = epoll_create(1024); // 创建 epoll 实例
if (epollFd < 0) {
perror("epoll_create failed");
return;
}
EventData eventData(sockFd, epollFd); // 创建事件数据对象
EchoServer::SetNotBlock(sockFd); // 设置套接字为非阻塞
EchoServer::AddReadEvent(epollFd, sockFd, &eventData); // 添加可读事件
MyCoroutine::Schedule schedule; // 协程调度器
MyCoroutine::ScheduleInit(schedule, 10000); // 初始化协程池
int msec = -1; // 超时时间
while (true) {
int num = epoll_wait(epollFd, events, 2048, msec); // 等待 epoll 事件
if (num < 0) { // 等待失败
perror("epoll_wait failed");
continue;
} else if (num == 0) { // 超时无事件
sleep(0); // 让出 CPU
msec = -1; // 下次超时时间设置为 -1
continue;
}
msec = 0; // 有事件,下次超时时间设置为 0
for (int i = 0; i < num; i++) { // 处理事件
EventData *eventData = (EventData *)events[i].data.ptr;
if (eventData->fd_ == sockFd) { // 是监听套接字的事件
EchoServer::LoopAccept(sockFd, 2048, [epollFd](int clientFd) {
EventData *eventData = new EventData(clientFd, epollFd);
EchoServer::SetNotBlock(clientFd);
EchoServer::AddReadEvent(epollFd, clientFd, eventData); // 处理新连接
});
continue;
}
if (eventData->cid_ == MyCoroutine::INVALID_ROUTINE_ID) { // 第一次事件
if (MyCoroutine::CoroutineCanCreate(schedule)) { // 可以创建协程
eventData->schedule_ = &schedule;
eventData->cid_ = MyCoroutine::CoroutineCreate(schedule, handlerClient, eventData, 0); // 创建协程
MyCoroutine::CoroutineResumeById(schedule, eventData->cid_); // 唤醒协程
} else {
std::cout << "MyCoroutine is full" << std::endl;
}
} else {
MyCoroutine::CoroutineResumeById(schedule, eventData->cid_); // 唤醒已有协程
}
}
MyCoroutine::ScheduleTryReleaseMemory(schedule); // 尝试释放协程内存
}
}
int main(int argc, char *argv[]) {
if (argc!= 3) { // 检查命令行参数数量
std::cout << "invalid input" << std::endl;
std::cout << "example:./EpollReactorProcessPoolCoroutine 0.0.0.0 1688" << std::endl;
return -1;
}
for (int i = 0; i < EchoServer::GetNProcs(); i++) { // 循环创建子进程
pid_t pid = fork(); // 创建进程
if (pid < 0) { // 创建失败
perror("fork failed");
continue;
}
if (0 == pid) { // 子进程
handler(argv); // 处理客户端请求
exit(0);
}
}
while (true) sleep(1); // 父进程进入死循环
return 0;
}
总的来说,程序通过多进程和协程的结合,实现了对客户端连接的处理和高效的 I/O 操作。