epoll模型的优点:
1.
高效的事件通知机制:
epoll
使用事件驱动的方式,只在有事件发生时才触发通知,避免了轮询的开销,提高了效率。
2.
支持大规模并发:
epoll
支持同时监视大量的文件描述符,适用于高并发的网络编程场景。
3.
支持水平触发和边缘触发模式:
epoll
提供了水平触发(
LT
)和边缘触发(
ET
)两种模式,可以根据需求选择适合的模式。
4.
零拷贝技术:
epoll
支持零拷贝技术,可以将数据从内核空间直接拷贝到用户空间,减少了数据复制的开销。
5.
内核空间和用户空间的共享:
epoll
允许将文件描述符和事件信息存储在用户空间,减少了系统调用的次数,提高了性能。
epoll模型的缺点:
1.
Linux
特有:
epoll
是
Linux
特有的事件通知机制,不支持在其他操作系统上使用。
2.
API
复杂:
epoll
的
API
相对复杂,相比于传统的
select
和
poll
,使用起来有一定的学习曲线。
3.
对多线程支持较差:
epoll
在处理多线程编程时需要使用线程同步机制,比如互斥锁或信号量,编程复杂度较高。
使用epoll的步骤:
1.
创建
epoll
实例:
使用
epoll_create1
函数创建一个
epoll
实例。
2.
添加文件描述符:
使用
epoll_ctl
函数将需要监视的文件描述符添加到
epoll
实例中。
3.
等待事件:
使用
epoll_wait
函数等待事件的发生。
4.
处理事件:
根据事件的类型进行相应的处理,比如接受新连接、接收数据等。
5.
重复等待:
循环执行步骤
3
和步骤
4
,实现持续的事件监听和处理。
6.
清理资源:
在程序退出前,关闭
epoll
实例并释放相关资源。
使用 epoll 模型可以提高网络编程的性能和并发能力,特别适用于需要处理大量并发连接的服务器端程序。
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#define MAX_EVENTS 10
#define PORT 54000
#define MAX_CLIENTS 100
int main() {
int server_fd, client_fd, epoll_fd, event_count;
struct sockaddr_in server_addr, client_addr;
socklen_t client_len;
struct epoll_event event, events[MAX_EVENTS];
// 创建 TCP socket
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -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(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_fd, MAX_CLIENTS) == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
// 创建 epoll 实例
if ((epoll_fd = epoll_create1(0)) == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 添加监听的文件描述符到 epoll 实例中
event.events = EPOLLIN;
event.data.fd = server_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
std::cout << "Server listening on port " << PORT << std::endl;
// 开始监听事件
while (true) {
event_count = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
if (event_count == -1) {
perror("epoll_wait");
exit(EXIT_FAILURE);
}
for (int i = 0; i < event_count; ++i) {
if (events[i].data.fd == server_fd) {
// 有新的连接请求
client_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
std::cout << "New connection from " << inet_ntoa(client_addr.sin_addr) << std::endl;
// 将新的客户端文件描述符添加到 epoll 实例中
event.events = EPOLLIN | EPOLLET; // 设置为边缘触发模式
event.data.fd = client_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &event) == -1) {
perror("epoll_ctl");
exit(EXIT_FAILURE);
}
}
else {
// 有客户端发送数据
char buffer[1024];
int bytes_received = recv(events[i].data.fd, buffer, sizeof(buffer), 0);
if (bytes_received <= 0) {
// 客户端断开连接
std::cout << "Client disconnected" << std::endl;
close(events[i].data.fd);
}
else {
buffer[bytes_received] = '\0';
std::cout << "Received message from client: " << buffer << std::endl;
}
}
}
}
// 关闭服务器 socket 和 epoll 实例
close(server_fd);
close(epoll_fd);
return 0;
}