16.IO复用之epoll函数


前面介绍了select函数和poll函数, select我们知道能够支持的套接字个数太少了, 但是poll函数已经很好了也没有什么缺点啊为啥还要介绍epoll呢? 接下来我们就来谈谈pollselect函数的其他问题.


poll和select的问题

  1. poll函数一般使用都是将它设置为轮询的方式, 这样是很占用CPU的; 当然可以将其设置为阻塞也就能避免这个问题.

  2. select能够监听的描述符太少.

  3. pollselect的底层实现. poll函数其实和select底层实现类似.

    当有监听的事件到来时, select会将该事件的描述符和事件的状态从内核复制到用户空间中. poll会将到来事件的描述符状态复制到用户空间.

    最后用户都要重新遍历所有的描述符判断是哪一个描述符状态改变.


epoll的优点

poll和select最大的问题都是实现上需要占用CPU大量的时间, 那么epoll又是怎样避免这样的问题呢.

  1. epoll函数也要设置监听描述符并将其复制到内核中为每一个监听描述符注册一个回调函数.
  2. 当监听描述符的时候到来时直接返回该描述符的回调函数不需要在进行复制.

可以看出来epoll只有在设置监听时才复制一次描述符, 事件到来时不需要复制并且也不需要遍历判断. 大大的减少了占用CPU的时间.

如果对于底层实现感兴趣可以看一下以前总结的epoll源码分析select源码分析.


函数原型

epoll可是有三个函数哦. 我们一个个来介绍并探讨他们的底层实现的功能.

1. epoll_create
#include <sys/epoll.h>

int epoll_create(int size);

成功 : 返回一个文件描述符.

失败 : 返回-1.

功能 : 在cache中申请创建的红黑树大小.

参数

  • size : 该参数 原本是指定在cache中分配的内存大小, 在linux2.6以上已经没有用了.

2. epoll_ctl
int epoll_ctl(int epfd, int opt, int fd, struct epoll_event *event);

成功 : 返回0

失败 : 返回-1

功能 : 注册要监听的事件类型.

参数 :

  • epfd : epoll_create函数返回的文件描述符.

  • opt : 功能选项, 用三个宏定义表示不同的功能

    opt值描述
    EPOLL_CTL_ADD注册fd的回调函数到epfd中
    EPOLL_CTL_DEL删除fd注册的回调函数
    EPOLL_CTL_MOD修改已注册的fd的监听事件
  • fd : 需要监听的文件描述符

  • event : 内核需要监听的事件, 与poll的事件类似.

    event值描述
    EPOLLIN监听是描述符是否可读
    EPOLLOUT监听是描述符是否可写
    EPOLLERR发生错误, 对端异常断开
    EPOLLRDHUP对端挂断, 或其中一端关闭了
    EPOLLET [1]设置为边沿触发模式
    EPOLLONESHOT [2]设置关联文件描述符的一次性行为

3. epoll_wait
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

成功 : 返回0

失败 : 返回-1

功能 : 从就绪事件的链表中返回监听的回调函数.

参数

  • epfd : epoll_create函数返回的文件描述符.
  • events : 内核返回的监听事件
  • maxevents : events数组的大小
  • timeout : 超时时间
    1. timeout == INFTIM(负数) : select函数永远阻塞等待监视文件描述符集合中某个文件描述符发生变化为止.
    2. timeout == 0 : 函数为非阻塞函数, 不管有无等待的文件描述符发生变化都会返回
    3. timeout > 0 : 等待的超时时间, 即函数在timeout时间内阻塞, 超时时间之内有事件到来就返回了, 否则在超时后不管怎样一定返回.

函数调用

看了前面的三个函数估计第一次接触可能会产生抵触, 太麻烦了. 但是麻烦归麻烦, 好用高效率才是王道. 比如在百万连接时, epoll_wait都可以只需要常量时间就能精确返回触发事件的套接字.

说了那么多, 还是练习能最快掌握, 完整代码 epoll_service.c

void doService(int servicefd)
{
    int clientfd, epfd;
    char buf[1024];

  	// 设置监听套接字
    struct epoll_event event, evts[EPOLL_MAX];
    event.events = EPOLLIN;
    event.data.fd = servicefd;	// 这里需要设置监听的套接字
    epfd = epoll_create(1);
    epoll_ctl(epfd, EPOLL_CTL_ADD, servicefd, &event);	// 这里也需要设置监听的套接字

    int n, eventNum;
    while(1)
    {
        // 设置为永久阻塞
		eventNum = epoll_wait(epfd, evts, EPOLL_MAX, -1);
		if(eventNum == 0)
		    continue;
		else if(eventNum < 0)
		    EXIT("epoll_wait");
	        // 遍历状态改变的套接字
		for(int i = 0; i < eventNum; i++)
		{
		    if(evts[i].data.fd == servicefd && (evts[i].events &EPOLLIN))
		    {
				clientfd = Accept(servicefd, NULL, NULL);
				event.events = EPOLLIN;
				event.data.fd = clientfd;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &event);
		    }
		    else if(evts[i].events & EPOLLIN)
		    {
				n = recv(evts[i].data.fd, buf, sizeof(buf), 0);
				if(n <= 0)
				{
		            // 删除关闭的套接字
					    epoll_ctl(epfd, EPOLL_CTL_DEL, evts[i].data.fd, NULL);
				    close(evts[i].data.fd);
				    fprintf(stderr, "peer close\n");
				}
				send(evts[i].data.fd, buf, n, 0);
		    }
		}
    }
}

具体现象与select 是一样, 这里就不再验证了.

总结

本节介绍了epoll的三个函数, 希望能够掌握并且自己修改服务端的函数. 接下来我们还要继续介绍epoll的功能.

  • 掌握epoll
  • epoll 的优点
  • poll 和 select 的缺点
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值