poll和epoll函数介绍及使用
1.poll函数
-
函数原型:
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
-
参数:
fds:监听的文件描述符数组
struct pollfd{ int fd; //待监听的文件描述符 short events; //待监听的文件描述符对应的事件 //取值 POLLIN、POLLOUT、POLLERR short revents; //传入时给0,如果满足对应事件返回上述取值 }
nfds:监听数组的实际有效监听个数
timeout:超时时长,单位为毫秒,0表示不阻塞,-1表示阻塞等待
返回值:返回满足对应监听事件的文件描述符总个数
-
优点:
- 可以拓展监听上限,超出1024上限
- 自带数据结构,可以将监听事件集合和返回事件集合分离。
-
缺点:
- 不能跨平台,只能在linux下使用
- 无法直接定位满足监听事件的文件描述符,编码难度大
2.文件描述符上限修改
-
查看一个进程可以打开的socket描述符上限
cat /proc/sys/fs/file-max
-
修改进程可以打开的socke描述符上限
sudo vi /etc/security/limits.conf soft nofile 65536 hard nofile 100000
-
使用指令进行修改
ulimit -n 20000
3.epoll相关函数
-
epoll_create函数
- 功能:创建一个监听红黑树,并且返回红黑树的根节点
- 原型:
int epoll_create(int size);
-
参数
size:创建的红黑树的监听节点数量
-
返回值:
成功:指向新创建的红黑树的根节点fd
失败:-1,设置errno
-
epoll_ctl函数
- 原型:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
-
参数:
epfd:epoll_create函数的返回值
op:对该监听红黑树所做的操作
- EPOLL_CTL_ADD 添加fd到监听红黑树
- EPOLL_CTL_MOD 修改fd在监听红黑树上的监听事件
- EPOLL_CTL_DEL 将一个fd从监听红黑树上取下(取消监听)
fd:待监听的fd
event:本质是结构体,成员变量中有联合体
struct epoll_event{ uint32_t events; //取值为EPOLLIN EPOLLOUT EPOLLERR epoll_data_t data; } typedef union epoll_data{ void* ptr; int fd; //对应监听的fd uint32_t u32; uint64_t u64 }
-
epoll_wait函数
- 函数原型
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout)
-
参数:
epfd:epoll_create函数的返回值
events:传入传出参数,传出满足监听条件的fd结构体
maxevents:数组元素的总个数
timeout:超时时长,单位为毫秒,0表示不阻塞,-1表示阻塞等待
-
返回值:
大于0:满足监听的总个数,可以作循环上限
0:没有满足监听的fd
-1:失败,设置errno
-
epoll的应用流程
lfd = socket(); //创建套接字 bind(); //绑定地址结构 listen(); //设置监听上限 fd epfd, allset; epfd = epoll_create(1024); //返回监听红黑树根 //tep用于设置单个fd属性,ep是epoll_wait传出满足监听事件的数组 struct epoll_event* temp, ep[1024]; temp.events = EPOLLIN; temp.data.fd = lfd; epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &temp); //将lfd添加到监听红黑树上 int retl; //循环监听 while(1) { ret = epoll_wait(epfd, ep, 1024, -1); for(i = 0; i < ret; i++) { if(ep[i].data.fd == lfd) { //有新的客户端发起连接请求,lfd满足读事件 cfd = accept(); temp.events = EPOLLIN; temp.data.fd = cfd; //把建立连接的文件描述符放到监听红黑树上 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &temp); } else //有客户端写数据,要对数据进行处理 { n = Read(ep[i].data.fd, buf, sizeof(buf)); //读取数据 if(n == 0) { //关闭套接字 close(ep[i].data.fd); //关闭的fd从红黑树摘下 epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd, NULL); } else if(n > 0) { //对数据进行处理 //小写转大写并写回去 write(ep[i].data.fd, buf, n); } } } }
-
epoll事件模式
-
边沿触发:缓冲区剩余未读完的数据不会导致epoll_wait返回,新的事件满足才会触发
struct epoll_event event; event.events = EPOLLIN | EPOLLET; epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &event); int flag = fcntl(cfd, F_GETFL); fla |= O_NONBLOCK; fcntl(cfd, F_SETFL, flg);
-
水平触发:缓冲区剩余未读完的数据会导致epoll_wait返回
-
-
优缺点:优势在于高效并突破1024文件描述符,缺点在于不能跨平台只能在Linux上使用。
4.ET和LT
- 概念:
- **LT(level triggered)**是缺省的工作方式,同时支持阻塞和非阻塞模式的套接字。如果文件(比如socket套接字)中仍然有数据,内核还是会继续通知。这种模式编程错误可能性小,传统的select和poll都是这种模式的代表。
- **ET(edge-triggered)**是高速工作方式,只支持非阻塞模式的套接字。必须对文件有I/O操作,即文件描述符从就绪变成非就绪的时候,内核才会发起通知。