1 epoll 相关函数
epoll_create 函数
#include <sys/epoll.h>
int epoll_create(int size)
/*
* function: 创建一个epoll句柄(创建一颗红黑树)
*
* function arguments:
* argv1: 监听数目(创建的红黑树的监听节点数量) 【仅供内核参考】
*
* ruturn value:
* success: 非负文件描述符(指向新创建的红黑树的根节点);
* faild: -1,设置相应的errno
*/
epoll_ctl 函数
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
/*
* function: 控制某个epoll监控的文件描述符上的事件:注册、修改、删除。(操作监听红黑树)
*
* function arguments:
* argv1: epoll_creat的句柄epfd(epoll_creat函数返回值)
*
* argv2: 表示动作,用3个宏来表示:
* EPOLL_CTL_ADD (注册新的fd到epfd),
* EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
* EPOLL_CTL_DEL (从epfd删除一个fd);
*
* argv3: 待监听的fd(listenfd/connfd)
*
* argv4: 传入参数,告诉内核需要监听的事件【本质是一个struct epoo_event结构体(包括evetns和data)】
* events:
* EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
* EPOLLOUT: 表示对应的文件描述符可以写
* EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
* EPOLLERR: 表示对应的文件描述符发生错误
* EPOLLHUP: 表示对应的文件描述符被挂断;
* EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
* EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
* data:【共用体/联合体】
* fd----对应监听事件的fd(argv3)
* *ptr--可以将一个结构体的地址传入(此结构体中包括了int fd 和 一个函数指针)
* u32---基本不用
* u64---基本不用
*
* struct epoll_event {
* uint32_t events; // Epoll events
* epoll_data_t data; // User data variable
* };
*
* typedef union epoll_data {
* void *ptr;
* int fd;
* uint32_t u32;
* uint64_t u64;
* } epoll_data_t;
*
*
*
* ruturn value:
* success: 0
* faild: -1,设置相应的errno
*/
epoll_wait 函数
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
/*
* function: 等待所监控文件描述符上有事件的产生,类似于select()调用
*
* function arguments:
* argv1: epoll_creat的句柄epfd(epoll_creat函数返回值)
* argv2: 传出参数,用来存内核得到事件的集合,可以简单看作数组。
* argv3: 告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size.【如果struct epoll_event events[1024]; 填1024】
* argv4: 超时时间
* -1: 阻塞
* 0: 立即返回,非阻塞
* >0: 指定毫秒
*
* ruturn value:
* >0: 成功返回有多少个文件描述符就绪.(满足监听事件的总个数,可以用在循环上限)
* 0: 超时时间到,并且没有fd满足监听时间
* -1: 失败,并设置errno
*/
2 epoll实现多路IO转接(代码)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <sys/epoll.h>
#define OPEN_MAX 1024
int main()
{
int listenfd, connfd; // 监听套接字、连接套接字
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == listenfd)
{
perror("socket error");
exit(1);
}
// 端口复用
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in saddr, caddr;
memset(&saddr, 0, sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8000);
saddr.sin_addr.s_addr = inet_addr("192.168.71.132");
int ret = bind(listenfd, (struct sockaddr*)&saddr, sizeof(saddr));
if (-1 == ret)
{
perror("bind error");
close(listenfd);
exit(1);
}
ret = listen(listenfd, 5);
if (-1 == ret)
{
perror("listen error");
exit(1);
}
struct epoll_event tep{}; // tep: epoll_ctl参数
struct epoll_event ep[OPEN_MAX]{}; // ep[]: epoll_wait参数
int epfd = epoll_create(OPEN_MAX); // 创建epoll模型, epfd指向红黑线根节点
if (-1 == epfd)
{
perror("epoll_create error");
exit(1);
}
tep.events = EPOLLIN;
tep.data.fd = listenfd; // 指定listenfd的监听事件为"读"
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &tep); // 将listenfd以及对应的结构体设置到数上,epfd可找到该树
if (-1 == ret)
{
perror("epoll_ctl error");
exit(1);
}
int nReady = 0;
while (1)
{
// epoll为server阻塞监听事件, ep为struct epoll_event类型数组, OPEN_MAX为数组容量, -1代表永久阻塞
nReady = epoll_wait(epfd, ep, OPEN_MAX, -1);
if (-1 == nReady)
{
perror("epoll_wait error");
exit(1);
}
/*
else if (0 == nReady) // 阻塞等待,不会出现这种情况
{}
*/
for (int ii = 0; ii < nReady; ++ii)
{
if (!(ep[ii].events & EPOLLIN)) // 如果不是"读"事件, 循环继续
continue;
if (ep[ii].data.fd == listenfd) // 判断满足条件的fd是不是lisenfd
{
socklen_t cAddrLen = sizeof(caddr);
connfd = accept(listenfd, (struct sockaddr*)&caddr, &cAddrLen); // 接受连接
if (-1 == connfd)
{
perror("accept eorror");
exit(1);
}
printf("建立连接成功:%s:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
tep.events = EPOLLIN;
tep.data.fd = connfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &tep); // 加入红黑树
if (-1 == ret)
{
perror("epoll_ctl error");
exit(1);
}
}
else // 不是listenfd
{
int sockfd = ep[ii].data.fd;
char buf[1024] = {0};
int n = read(sockfd, buf, sizeof(buf));
if (-1 == n) // 出错
{
perror("read error");
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL);
close(sockfd);
exit(1);
}
else if (0 == n) // 客户端断开连接
{
printf("断开连接\n");
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, NULL); // 将该文件描述符从红黑树删除
if (-1 == ret)
{
perror("epoll_ctl error");
exit(1);
}
close(sockfd);
}
else
{
printf("recv:%s\n", buf);
}
}
}
}
close(listenfd);
return 0;
}
3 epoll 优缺点
-
优点
- 高效;可以突破1024文件描述符的限制;
-
缺点:
- 不能跨平台。(linux)
本文详细介绍了epoll的工作原理,包括epoll_create、epoll_ctl及epoll_wait等关键函数的使用方法,并通过示例代码展示了如何利用epoll进行多路I/O转接,最后讨论了epoll的优点和局限。
843

被折叠的 条评论
为什么被折叠?



