linux系统编程-epoll

本文对epoll的注意点做一个小结。

接口

epoll常见的接口有三个:

1.创建epoll的实例,在内核中初始化相应的数据结构

// 创建epoll instance
// linux 2.6.8之后,size参数没有实际意义。但是,需要比0大
int epoll_create(int size);

2.向epoll instance注册,修改,删除相应的fd及其事件。这点有别于select,后者每次监听的时候,从用户空间拷贝监听的fd到内核空间。当fd较多的时候,这个开销是非常大的。但是,epoll在是在监听之前,直接想epoll instance的内核数据结构,注册相应的fd,修改或者删除也是直接操作内核的数据接口。所以,节省了大量时间。

// 向epoll instance注册/修改/删除,fd以及相应的事件
// EPOLL_CTL_ADD:register,向epoll注册fd,fd关联的事件
// EPOLL_CTL_MOD:change,修改已经注册到epoll的fd关联的事件
// EPOLL_CTL_DEL:remove,删除epoll当中的fd,关联的事件自动失效
int epoll_ctl(int epfd, 
              int op, 
              int fd, 
              struct epoll_event* event);

typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };

ev.events = EPOLLIN; // 可以设置ET LT
ev.data.fd = listen_sock; // 这里必须保存fd,否则,fd事件触发拷贝到用户空间后,不知道是哪一个fd
if (epoll_ctl(epollfd, 
              EPOLL_CTL_ADD, 
              listen_sock, 
              &ev) == -1) {
    perror("epoll_ctl: listen_sock");
    exit(EXIT_FAILURE);
}

3 . 监听多个fd,等待事件触发。

// events是传出参数,保存触发的事件数组。
// maxevents是事件数组的大小
// 返回值:nready>0表示返回的有效事件个数。nready=0,表示超时.
// 即timeout之间没有数据读入
// nready < 0出错
int epoll_wait(int epfd, 
               struct epoll_event* events,
               int maxevents,
               int timeout);

注意点

ET (边沿触发)

这种模式只支持non block socket, 只有当socket从未就绪变为就绪时,内核才会通过epoll告诉你。直到,socket重新变为未就绪状态。所以,读写的时候都需要小心处理。
为什么ET必须和non-blocking socket联合使用
这么考虑,缓冲区只有32byte,现在发过来36byte数据。
那么,第一次读取缓冲区大小的字节即32byte,发现返回值是32。证明,可能还有数据没有读取完毕。那么,继续读取,第二次读取缓冲区大小的字节,即32byte。发现读取到4byte,表明读取完毕。
考虑这种情形,缓冲区32byte,发送了32byte。那么,第一次读取32byte数据,没问题。第二次,继续读取发现缓冲区为空。!!!此时,如果要是阻塞模式,缓冲区为空进行读取,那么线程被挂起。没有办法再监听别的socket,这么做不对。所以,必须设置为非阻塞模式。

读取代码

while(rs)
{
    buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
    if(buflen < 0)
    {
    // 由于是非阻塞的模式,所以当 errno 为 EAGAIN 时,表示当前缓冲区已无数据可读
    // 在这里就当作是该次事件已处理处.
    if(errno == EAGAIN)
        break;
    else
        return;
}
else if(buflen == 0)
{
// 这里表示对端的 socket 已正常关闭.
}else {
    if(buflen == sizeof(buf)
        rs = 1; // 需要再次读取
    else
        rs = 0;
    }
}

写代码

ssize_t socket_send(int sockfd, const char* buffer, size_t buflen)
{
    ssize_t tmp;
    size_t total = buflen;
    const char *p = buffer;
    while(1)
    {
        tmp = send(sockfd, p, total, 0);
        if(tmp < 0)
        {
            // 当 send 收到信号时,可以继续写,但这里返回-1.
            if(errno == EINTR)
                return -1;

            // 当 socket 是非阻塞时,如返回此错误,表示写缓冲队列已满,
            // 在这里做延时后再重试.
            if(errno == EAGAIN)
            {
                usleep(1000);
                continue;
            }
            else return -1;
        }
        if((size_t)tmp == total)
            return buflen;
        else {
            total -= tmp;
            p += tmp;
        }
    }// while
    return tmp;
}

内核实现

  1. 红黑树:用来存储epoll_ctl注册的socket。用epoll_ctl向内核注册socket的同时,会向内核注册一个回调函数。告诉内核,当这个socket的事件触发了,就把它放入就绪链表当中。
  2. 就绪链表:内核维护一个就绪链表,当socket事件发生时,就会把它插入到就绪链表当中。返回到user space.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值