下面的代码都在ET下工作
群里小伙伴没搞懂EPOLLOUT 再写2个例子; 2个例子都是回声服务器代码;
关于EPOLLET的基础 , 以及EPOLLIN|EPOLLOUT|EPOLLET 一起注册的例子: EPOLLET简单例子
EPOLLOUT 的说明:
LT模式下:
与select 一致. 只要可写就一直触发
在ET模式下:
socket 一般情况下需要非阻塞的, 与EAGAIN 这个错误 配合使用.
如果是默认的阻塞socket, 不是不可以,而是你需要做额外的事, 每一次的read/write , 你都需要去getsockopt( sock,SO_ERROR,...)
直到获取 EAGAIN 算是正常读/写. 否则, 你完蛋了. 这个socket将不再收到 epoll 的触发条件了
对于EPOLLIN : 如果状态改变了[ 比如 从无到有],那么只要输入缓冲区可读就会触发
对于EPOLLOUT: 如果状态改变了[比如 从满到不满],只要输出缓冲区可写就会触发;
EPOLLOUT那么具体什么时间点触发呢
1. EPOLL_CTL_ADD或EPOLL_CTL_MOD 时 , 如果输出缓冲区状态改变了
2. 不论注册方式是 EPOLLIN | EPOLLOUT | EPOLLET 还是 EPOLLOUT | EPOLLET 只要含EPOLLOUT
只要状态改变就能触发.
状态改变到底是什么? 简单理解:
EPOLLOUT: 满->不满
EPOLLIN : 空->不空
有没有发现 , 都跟边缘有关
3.对于 send / write 需要依靠 EPOLLOUT 触发才调用吗 ? 什么时候需要 注册上 EPOLLOUT ?
不需要. 如果要 send / write 那么就直接调用, 如果返回值 > 0 , 证明数据已经复制到发送缓冲区中.一切正常.
如果 send / write 返回 < 0 且 errno == EAGAIN . 此时说明发送缓冲区满了. 那么需要把 剩余的字节保存起来,
然后注册上 EPOLLOUT , 直到epoll_wait 返回 , 说明发送缓冲区可写, 再把 之前保存起来的数据 发送,
如果此时 write 返回 > 0 那就把EPOLLOUT 取消掉.
简单来说 : 1. 直接发送 2. 看返回值, 没发送完才挂上EPOLLOUT 3. 发送完就把EPOLLOUT 取消掉
下面2个例子都是关于EPOLLOUT,
第一个例子利用EPOLL_CTL_MOD来触发EPOLLOUT,这种方式不太好,需要利用一次系统调用epoll_wait来触发;
第2个例子在读完时, 手动调用一次发送, 如果发送遇到 EAGAIN / EWOULDBLOCK,才把EPOLLOUT注册上去,等待
EPOLLOUT事件发生再去发送, 如果不需要发送则把EPOLLOUT关闭;
另外有关于下面例子中 util.h 头文件就包含了一些常用头文件,所以就不贴了;
再次注意, 我没有对监听socket设置EPOLLET , 如果你设置了那么需要 while(1) accept().. if(errno==EAGAIN)break;
否则会漏掉客户端连接的.
另外注意不论是write, send ..等这些个函数,都是把数据复制到socket输出缓冲区中 .并不是发送到对端的意思;
还有close(fd) 会附带着 epoll_ctl ( ... , EPOLL_CTL_DEL, ...); 自动从epoll中清除
第一个关于EPOLLOUT的例子:
这个例子使用 EPOLL_CTL_MOD 来主动触发EPOLLOUT事件 ;
当然这种方式需要让下一次epoll_wait返回一次来触发,不是特别好;
[ 当 EPOLL_CTL_ADD或EPOLL_CTL_MOD 时 , 如果socket输出缓冲区没满就能触发一次. ]
下面这个例子唯一需要注意的就 3行代码:
//把EPOLLOUT一起注册上去
ev.events = evts[i].events | EPOLLOUT;
ev.data.ptr = pData;
//利用EPOLL_CTL_MOD将触发EPOLLOUT的特性
epoll_ctl(epfd,EPOLL_CTL_MOD,pData->fd,&ev);
#include "util.h"
#define BUFF_SIZE 1024
#define MAX_EVENTS 1024
static int setnonblock(int fd , int nonblock){
int flag = fcntl(fd,F_GETFL,0);
if(nonblock)
return fcntl(fd,F_SETFL,flag|O_NONBLOCK);
else
return fcntl(fd,F_SETFL,flag&~O_NONBLOCK);
}
//每个套接字关联的信息
typedef struct _ev_data
{
int fd; //socket
char * buffer; //缓冲区
int nread; //读入字节长度
int start_write_index; //记录下一次写的位置
int epoll_fd; // 属于哪个epoll
} ev_data;
void free_event(ev_data * );
void free_event(ev_data * pEv){ //释放内存用的
if(!pEv)
return;
if(pEv->buffer){
free(pEv->buffer);
}
free(pEv);
}
//输出函数
void write_handler(ev_data *ptr)
{
static struct epoll_event ev ={0,{0}};
if(!ptr || !ptr->buffer){
printf("write_handler error , ptr is empty!\n");
return;
}
//需要写入socket输出缓冲区的长度
int left = ptr->nread - ptr->start_write_index;
//从哪里开始写
char *pbuffer = ptr->buffer + ptr->start_write_index;
//返回值
int nwriten = -1;
// 总共写入的字节数
int write_bytes = 0;
//sock输出缓冲区是否满了
int ful