概述
EPOLLOUT事件触发的条件及典型用法
epoll是linux下实现IO multiplex的利器。一般编程实现方式如下:
- 使用epoll_create创建一个epoll fd
- 使用epoll_ctl往epoll fd里添加需要监听的fd,并注册需要监听的事件(由于EPOLLLET边缘触发方式更加高效,所以一般都使用边缘触发方式)
- 使用epoll_wait等待事件,然后依次处理各个事件,反复循环
其最常用的网络事件为EPOLLIN和EPOLLOUT,EPOLLIN对应为有socket缓冲区数据可读(当又收到了对端的一些数据,就会触发;或者作为服务端时有连接连过来),EPOLLOUT对应socket缓冲区可写。
这边EPOLLIN比较好理解,但是EPOLLOUT事件小白用户可能比较难理解了。比如经常看到小白用户的疑问:客户端需要发送数据给服务端,直接调用send(fd, ...)
发送即可,看着和EPOLLOUT事件没有啥关系(说明:这种一般情况下可以,但是当缓存区满就GG了)。所以本文下面以边缘触发下的场景为例,重点说下什么时候会触发EPOLLOUT。
下面总结了三种场景下的EPOLLOUT并分别说明:
- 客户端连接场景
触发条件:客户端connect上服务端后,得到fd,这时候把fd添加到epoll 事件池里面后,因为连接可写,会触发EPOLLOUT事件 - 客户端发包场景
触发条件:缓冲区从满到不满,会触发EPOLLOUT事件
典型应用场景(数据包发送问题):
数据包发送逻辑:将数据包发完内核缓冲区–>进而由内核再将缓冲区的内容发送出去;这边send只是做了第一部分的工作,如果缓存区满的话send将会得到已发送的数据大小(成功放到缓冲区的),而不是整个数据包大小。
这种情况我们可以借助EPOLLOUT事件加以解决:如果send部分成功,则表示缓存区满了,那么把剩下的部分交给epoll,当检测到EPOLLOUT事件后,再将剩余的包发送出去。 - 重新注册EPOLLOUT事件
触发条件:如果当连接可用后,且缓存区不满的情况下,调用epoll_ctl将fd重新注册到epoll事件池(使用EPOLL_CTL_MOD),这时也会触发EPOLLOUT时间。
典型应用场景:
send或write发包函数会涉及系统调用,存在一定开销,如果能将数据包聚合起来,然后调用writev将多个数据包一并发送,则可以减少系统调用次数,提高效率。这时EPOLLOUT事件就派上用场了:当发包时,可以将先数据包发到数据buffer(用户缓存区)中存放,然后通过重新注册EPOLLOUT事件,从而触发EPOLLOUT事件时,再将数据包一起通过writev发送出去。
Demo
贴下测试代码,有兴趣的同学可以基于此demo自己实践下,加深理解:
- 客户端代码
#include <stdio.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <sys/epoll.h>
#include <stdlib.h>
#define PORT 2222 // Your server port
#define SERVER_IP "127.0.0.1"
int main()
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
printf("create socket errorn");
return -1;
}
struct sockaddr_in addr = {0};
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
inet_aton(SERVER_IP, &addr.sin_addr);
int ret = connect(fd, (struct sockaddr*)&addr, sizeof(struct sockaddr));
if(ret)
{
printf("connect to server failed, error = %sn", strerror(errno));
close(fd);
return -1;
}
int efd = epoll_create1(0);
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLOUT | EPOLLET;
int s = epoll_ctl(efd, EPOLL_CTL_ADD, fd, &event);
if (s == -1) {
perror ("epoll_ctl error");
close(fd);
close(efd);
abort();
}
#define MAXEVENTS 10
struct epoll_event *events;
events = (struct epoll_event *) calloc (MAXEVENTS, sizeof event);
while (1) {
int n = epoll_wait(efd, events, MAXEVENTS, 1000);
usleep(100000);
for (int i = 0; i < n; i++) {
if (events[i].events & EPOLLOUT) {
printf("received epollout eventn");
printf("Start send hellon");
char input[] = "hello";
send(fd, input, sizeof(input), 0); // send packet
// mod epollout event to trigger event
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLOUT | EPOLLET;
epoll_ctl(efd, EPOLL_CTL_MOD, fd, &event);
}
}
}
close(fd);
close(efd);
return 0;
}
- 服务端代码
见弄懂Linux epoll 中的服务端代码实现
最后
以上就是火星上朋友为你收集整理的彻底弄懂EPOLLOUT事件EPOLLOUT事件触发的条件及典型用法Demo的全部内容,希望文章能够帮你解决彻底弄懂EPOLLOUT事件EPOLLOUT事件触发的条件及典型用法Demo所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复