架构设计思路
- 事件驱动模型:采用基于非阻塞I/O的事件驱动模型,使用如
epoll
(Linux系统下)这样的多路复用技术。epoll
能高效处理大量并发连接,它将I/O事件通知给应用程序,应用程序只需处理发生事件的文件描述符。
- I/O事件处理
- 网络套接字:使用非阻塞套接字进行网络通信。当有新连接到达时,接受连接并将新的套接字加入到事件监听列表;当有数据可读或可写时,进行相应的数据收发操作。
- 文件读写:同样将文件描述符设置为非阻塞模式。在事件驱动下,当文件有数据可读或可写时,进行文件的读写操作。
- 信号处理:注册信号处理函数,并将信号处理纳入事件驱动机制。例如,使用
sigaction
函数来捕获信号,然后将信号处理逻辑集成到事件循环中。
- 资源管理
- 文件描述符管理:使用一个数据结构(如数组或链表)来管理所有注册到事件监听机制中的文件描述符。当某个文件描述符不再需要时,及时关闭并从管理结构中移除。
- 内存管理:对于动态分配的内存,如用于接收网络数据或文件数据的缓冲区,在使用完毕后及时释放,避免内存泄漏。可以使用内存池技术来提高内存分配和释放的效率。
- 错误处理
- I/O操作错误:在每次I/O操作(如
read
、write
、accept
等)后检查返回值,根据错误码进行相应处理。例如,如果read
操作返回-1
且errno
为EAGAIN
或EWOULDBLOCK
,表示当前没有数据可读,这是正常的非阻塞I/O情况,继续等待下一次事件通知;如果是其他错误码,如EBADF
表示文件描述符无效,需要进行适当的错误处理,如关闭文件描述符并从管理结构中移除。
- 事件监听错误:在
epoll
相关操作(如epoll_create
、epoll_ctl
、epoll_wait
)中,同样检查返回值。例如,epoll_create
返回-1
表示创建epoll
实例失败,需要进行错误处理,如记录错误日志并退出程序。
- 可扩展性
- 模块化设计:将不同功能模块(如网络模块、文件模块、信号处理模块)进行分离设计,每个模块有明确的接口。这样便于对单个模块进行扩展和维护,例如增加新的网络协议支持时,只需在网络模块中进行修改。
- 动态资源分配:采用动态资源分配策略,如根据系统负载动态调整内存池大小或增加事件监听队列的容量,以适应不同规模的并发请求。
关键代码实现要点
- 初始化
epoll
实例
#include <sys/epoll.h>
int epoll_fd = epoll_create1(0);
if (epoll_fd == -1) {
perror("epoll_create1");
// 错误处理,如记录日志并退出
exit(EXIT_FAILURE);
}
- 添加文件描述符到
epoll
监听列表
struct epoll_event event;
event.data.fd = sockfd; // 以网络套接字为例
event.events = EPOLLIN | EPOLLOUT | EPOLLET; // 监听读、写事件,采用边缘触发模式
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &event) == -1) {
perror("epoll_ctl: add");
// 错误处理,如关闭套接字并从管理结构中移除
}
- 事件循环
struct epoll_event events[max_events];
while (1) {
int num_events = epoll_wait(epoll_fd, events, max_events, -1);
if (num_events == -1) {
perror("epoll_wait");
// 错误处理,如记录日志并退出
break;
}
for (int i = 0; i < num_events; ++i) {
int fd = events[i].data.fd;
if (events[i].events & EPOLLIN) {
// 处理读事件,如网络套接字读数据或文件读数据
char buffer[BUFFER_SIZE];
ssize_t read_bytes = recv(fd, buffer, sizeof(buffer), 0);
if (read_bytes == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
} else {
// 其他错误处理
}
} else if (read_bytes == 0) {
// 连接关闭处理
} else {
// 处理接收到的数据
}
} else if (events[i].events & EPOLLOUT) {
// 处理写事件,如网络套接字写数据或文件写数据
}
}
}
- 非阻塞套接字设置
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
int flags = fcntl(sockfd, F_GETFL, 0);
flags |= O_NONBLOCK;
if (fcntl(sockfd, F_SETFL, flags) == -1) {
perror("fcntl: set non - blocking");
// 错误处理,如关闭套接字
}
- 信号处理集成
#include <signal.h>
void signal_handler(int signum) {
// 信号处理逻辑
}
struct sigaction sa;
sa.sa_handler = signal_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGINT, &sa, NULL) == -1) {
perror("sigaction");
// 错误处理
}