首先必须谈谈epollin和epollout事件触发的条件 没看这个 整个epoll就是懵的
1、EPOLLOUT事件触发的条件?
(1)、一次write操作,写满了发送缓冲区,返回错误码为EAGAIN(11)。
(2)、对端读取了一些数据,又重新可写了,此时会触发EPOLLOUT。
简单地说:内核缓冲区由不可写变为可写时会触发,才会触发一次,所以叫边缘触发。
暴力方法:直接调用epoll_ctl()重新设置一下event就可以了, event跟原来的设置一模一样都行(但必须包含EPOLLOUT),关键是重新设置,就会马上触发一次EPOLLOUT事件。
2、EPOLLIN事件触发的条件?
对端有数据写入时才会触发。
epoll还分为LT和ET 即水平触发和边缘触发
LT就是只要满足条件我就会一直触发事件 而ET是你满足一次条件我触发一次事件
LT模式会一直触发EPOLLOUT,当缓冲区有数据时会一直触发EPOLLIN
ET模式会在连接建立后触发一次EPOLLOUT,当收到数据时会触发一次EPOLLIN
LT模式触发EPOLLIN时可以按需读取数据,残留了数据还会再次通知读取
ET模式触发EPOLLIN时必须把数据读取完,否则即使来了新的数据也不会再次通知了
LT模式的EPOLLOUT会一直触发,所以发送完数据记得删除,否则会产生大量不必要的通知
ET模式的EPOLLOUT事件若数据未发送完需再次注册,否则不会再有发送的机会
通常发送网络数据时不会依赖EPOLLOUT事件,只有在缓冲区满发送失败时会注册这个事件,期待被通知后再次发送
下面就是epoll服务器的代码
#include#include #include #include #include #include #include #include #include struct sockitem { int sockfd; int (*callback)(int fd, int events, void *arg);` } //等会epoll传入的指针需要用到的结构体 用来回调 // ./epoll 8080 int recv_cb(int fd, int events, void *arg) { } //具体的回调函数 int accept_cb(int fd, int events, void *arg) { int clientfd = accept(); struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem)); si->sockfd = clientfd; si->callback = recv_cb; // epoll_ctl() } //具体的回调函数 int main(int argc, char *argv[]) { if (argc < 2) { return -1; } int port = atoi(argv[1]);// int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { return -1; } struct sockaddr_in addr; memset(&addr, 0, sizeof(struct sockaddr_in)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) { return -2; } if (listen(sockfd, 5) < 0) { return -3; } //上面都是最基本的socket流程 不想过多讲述 // 从这里开始epoll //首先创建一个epoll 参数无所谓01 int epfd = epoll_create(1); //接下来创建epoll需要的结构体 该结构体用来像epoll内注册io事件和获取一些信息 struct epoll_event ev, events[512] = {0}; //ev用来临时加入epoll events用来获取已经被写入或被读取数据的io ev.events = EPOLLIN;//帮我监听epollin事件 ev.data.fd = sockfd; //文件描述符是sockfd //epoll_event有个最大的好处就是他允许你带一个参数进去 那么这里我们就传入我们自定义的结构体 等下就能自动调用回调函数 struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem)); si->sockfd = sockfd; si->callback = accept_cb; //上面是填写结构体的值 没啥好说的 //注意第二个值是一个回调函数 准备回调的 ev.data.ptr = si; //将指针也写入epoll_event结构体 epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev); //将最开始用来listen的结构体传入epoll中开始监测 while (1) { //写一个循环不断的去epoll中拿被改写过的IO int nready = epoll_wait(epfd, events, 512, 50); //这里的意思就是我去epfd对应的epoll中去拿 最多拿512个 给我放到首地址为events的地方 最长等待时间是50ms //此时间将四舍五入为系统时钟的粒度,并且内核调度延迟意味着阻塞间隔可能会少量溢出。 if (nready < -1) { break; } //没收到的话 直接break掉 int i = 0; for (i = 0;i < nready;i ++) { if (events[i].events & EPOLLIN) { //如果是epollin事件 struct sockitem *si = (struct sockitem*)events[i].data.ptr; si->callback(events[i].data.fd, events[i].events, si); } if (events[i].events & EPOLLOUT) { //如果是epollout事件 //代码就不写了 跟上面流程是一样的 } } } }
但是以上代码明显有几个思考的点 比如多线程下如何同时使用epoll 文件描述符之间如何传递?



