网络编程-IO多路复用
摘要:本文对比了select、poll和epoll三种I/O多路复用机制的特点:select使用位图存储文件描述符,最多支持1024个;poll使用链表存储,无数量限制;epoll使用红黑树存储,效率更高。epoll相比前两者优势明显:内核维护文件描述符集合,减少数据拷贝;直接返回就绪事件,无需遍历;支持水平触发(LT)和边沿触发(ET)两种模式。文中详细介绍了epoll的API使用步骤,包括ep
1.三者的特点
select特点
①使用位图(数组)实现对文件描述符集合的保存,最多允许同时监测1024个文件描述符
②需要应用层和内核层的反复数据(文件描述符集合表)拷贝
③返回的集合表需要遍历寻找到达的事件
④只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式)
poll特点
①使用链表实现对文件描述符集合的存储,没有监测的文件描述符上限限制
②需要应用层和内核层的反复数据(文件描述符集合表)拷贝
③返回的集合表需要遍历寻找到达的事件
④只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式)
epoll特点
①使用红黑树(二叉树)实现文件描述符集合的存储,没有文件描述符上限限制,提高查找效率
②将文件描述符集合创建在内核层,避免了应用层和内核层的反复数据拷贝
③返回到达事件,不需要遍历,只需要处理事件即可
④可工作在水平触发模式(低速模式),也可工作在边沿触发模式(高速模式)
| 特性 | 水平触发 (LT) | 边沿触发 (ET) |
|---|---|---|
| 通知频率 | 只要条件满足,持续通知 | 仅在状态变化时,通知一次 |
| 编程难度 | 简单,不易出错 | 复杂,容易因未处理完数据而导致 bug |
| 性能 | 相对较低(可能多次通知同一事件) | 更高(减少不必要的通知) |
| IO模式 | 阻塞和非阻塞IO均可 | 必须使用非阻塞IO |
| 行为 | 更“宽容”,允许延迟或分批处理 | 更“苛刻”,要求必须一次性处理完 |
| 默认模式 | epoll默认的模式 | 需要显式设置(EPOLLET flag) |
2.epoll
(1)步骤
①创建文件描述符结合
②添加关注的文件描述符
③epoll通知内核开始进行事件监测
④epoll返回时,获取到达事件的结果
⑤根据到达事件做任务处理
(2)函数接口
int epoll_create(int size);
功能:通知内核创建文件描述符集合
参数:
size:监测的文件描述符个数
返回值:
成功:文件描述符(代表内核创建的集合)
失败:-1
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:对epoll的文件描述符集合进行操作
参数:
epfd:创建的epoll集合
op:对文件描述符集合进行的操作
EPOLL_CTL_ADD : 添加文件描述符到集合
EPOLL_CTL_MOD : 修改集合中的文件描述符
EPOLL_CTL_DEL :删除集合中的文件描述符
fd:要操作的文件描述符
event:文件描述符对应的事件
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events:文件描述符的事件:
EPOLLIN: 读事件
EOPLLOUT:写事件
data.fd : 关注的文件描述符返回值:
成功:0
失败:-1
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
功能:通知内核开始监测文件描述符的事件
参数:
epfd:监测的文件描述符集合
events:保存返回的到达事件的结果(数组)eg:struct epoll_event evs[MAX_FD_CNT];
maxevents:最大的事件个数
timeout:监测的超时时间
-1 :不设置超时(一直阻塞)返回值:
成功:到达的事件的个数
失败:-1
eg:
#include "head.h"
#define MAX_SIZE 1024
#define MAX_FD_CNT 2
int epoll_fd_add(int epfds, int fd, uint32_t events)
{
struct epoll_event ev;
ev.data.fd = fd;
ev.events = events;
int ret = epoll_ctl(epfds, EPOLL_CTL_ADD, fd, &ev);
if(ret < 0)
{
perror("epoll ctl error");
return -1;
}
return 0;
}
int main(int argc, char const *argv[])
{
char buff[MAX_SIZE] = {0};
mkfifo("./myfifo", 0664);
int fifofd = open("./myfifo", O_RDONLY);
if(fifofd < 0)
{
perror("open error");
return -1;
}
int epfds = epoll_create(MAX_FD_CNT);
if(epfds < 0)
{
perror("epoll create error");
return -1;
}
epoll_fd_add(epfds, 0, EPOLLIN);
epoll_fd_add(epfds, fifofd, EPOLLIN);
struct epoll_event evs[MAX_FD_CNT];
while (1)
{
int cnt = epoll_wait(epfds, evs, MAX_FD_CNT, -1);
if(cnt < 0)
{
perror("epoll wait error");
return -1;
}
for (int i = 0; i < cnt; i++)
{
if(0 == evs[i].data.fd)
{
fgets(buff, sizeof(buff), stdin);
printf("stdin = %s\n", buff);
}
else if(fifofd == evs[i].data.fd)
{
memset(buff, 0, sizeof(buff));
ssize_t n = read(evs[i].data.fd, buff, sizeof(buff));
if (0 == n)
{
epoll_ctl(epfds, EPOLL_CTL_DEL, fifofd, NULL);
close(fifofd);
printf("fifo写端已关闭,停止监听\n");
}
else if (n > 0)
{
printf("fifo = %s\n", buff);
}
}
}
}
close(fifofd);
return 0;
}
(3)实现TCP的并发
#include "head.h"
#define SIN_PORT 50000
#define SIN_ADDR "192.168.0.176"
#define MAX_SIZE 1024
int init_tcp_ser()
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket error");
return -1;
}
//允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:
int optval = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
struct sockaddr_in seraddr;
seraddr.sin_family = AF_INET;
seraddr.sin_port = htons(SIN_PORT);
seraddr.sin_addr.s_addr = inet_addr(SIN_ADDR);
int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
if(ret < 0)
{
perror("bind error");
return -1;
}
ret = listen(sockfd, 100);
if(ret < 0)
{
perror("listen error");
return -1;
}
return sockfd;
}
int epoll_fd_addr(int epfds, int fd, uint32_t events)
{
struct epoll_event ev;
ev.data.fd = fd;
ev.events = events;
int ret = epoll_ctl(epfds, EPOLL_CTL_ADD, fd, &ev);
if(ret < 0)
{
perror("epoll ctl error");
return -1;
}
return 0;
}
int main(int argc, char const *argv[])
{
struct sockaddr_in cliaddrs[MAX_SIZE];
memset(cliaddrs, 0, sizeof(cliaddrs));
socklen_t clilen = sizeof(struct sockaddr_in);
int sockfd = init_tcp_ser();
if(sockfd < 0)
{
return -1;
}
int epfds = epoll_create(MAX_SIZE);
if(epfds < 0)
{
perror("epoll create error");
return -1;
}
epoll_fd_addr(epfds, sockfd, EPOLLIN);
struct epoll_event evs[MAX_SIZE];
while (1)
{
int cnt = epoll_wait(epfds, evs, MAX_SIZE, -1);
if(cnt < 0)
{
perror("epoll wait error");
return -1;
}
for (int i = 0; i < cnt; i++)
{
int fd = evs[i].data.fd;
if(sockfd == fd)
{
struct sockaddr_in temp_cliaddr;
socklen_t temp_clilen = sizeof(temp_cliaddr);
int connfd = accept(sockfd, (struct sockaddr *)&temp_cliaddr, &temp_clilen);
if(connfd < 0)
{
perror("accept error");
continue;
}
epoll_fd_addr(epfds, connfd, EPOLLIN);
// 保存客户端地址信息
cliaddrs[connfd] = temp_cliaddr;
printf("[%s:%d] online\n", inet_ntoa(cliaddrs[connfd].sin_addr), ntohs(cliaddrs[connfd].sin_port));
}
else
{
char buff[MAX_SIZE] = {0};
ssize_t recv_cnt = recv(fd, buff, sizeof(buff), 0);
if(recv_cnt < 0)
{
perror("recv error");
continue;
}
else if(0 == recv_cnt)
{
printf("[%s:%d] offline\n", inet_ntoa(cliaddrs[fd].sin_addr), ntohs(cliaddrs[fd].sin_port));
epoll_ctl(epfds, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}
else
{
printf("[%s:%d] : %s\n", inet_ntoa(cliaddrs[fd].sin_addr), ntohs(cliaddrs[fd].sin_port), buff);
strcat(buff, "---->ok");
send(fd, buff, strlen(buff), 0);
}
}
}
}
close(sockfd);
return 0;
}
更多推荐



所有评论(0)