zoukankan      html  css  js  c++  java
  • linux epoll 学习

    一、epoll介绍

    epoll是linux内核为处理大批量句柄而作的改进的poll,是linux下IO多路复用select、poll的增强版,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

    epoll有两种工作方式:LT(水平触发)、ET(边缘触发)

    LT(level triggered,水平触发)是缺省的工作方式,并且同时支持block和non-block socket,在这种方式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性要小一点。传统的select/poll都是这种模型的代表。

    ET(edge-triggered,边缘触发)是高速工作方式,只支持non-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后会假设你知道文件描述符已就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。

    epoll相关的系统调用有3个:epoll_create,epoll_ctl,epoll_wait。

    #include <sys/epoll.h>
    int epoll_create(int size);

    参数size:用来告诉内核要监听的数目一共有多少个。

    返回值:成功时,返回一个非负整数的文件描述符,作为创建好的epoll句柄。调用失败时,返回-1,错误信息可以通过errno获得。

    说明:创建一个epoll句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建epoll句柄后,它就是会占用一个fd值,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

    #include <sys/epoll.h>
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

    参数epfd:epoll_create()函数返回的epoll句柄。

    参数fd:要进行操作的目标文件描述符。

    参数event:struct epoll_event结构指针,将fd和药进行的操作关联起来。

    返回值:成功时,返回0,作为创建好的epoll句柄。调用失败时,返回-1,错误信息可以通过errno获得。

    说明:epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

    参数op的可选值有以下3个:

    EPOLL_CTL_ADD:注册新的fd到epfd中。

    EPOLL_CTL_MOD:修改已经注册的fd的监听事件。

    EPOLL_CTL_DEL:从epfd中删除一个fd。

    struct epoll_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 event
        epoll_data_t data; // user data variables
    };

    events可以以下几个宏的集合:

    EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭)。

    EPOLLOUT:表示对应的文件描述符可以写。

    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里表示有带外数据到来)。

    EPOLLERR:表示对应的文件描述符发生错误。

    EPOLLHUP:表示对应的文件描述符被挂断。

    EPOLLET:将EPOLL设为边沿出触发模式,这是相对于水平触发模式来说的。

    EPOLLNESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入的EPOLL队列中。

    #include <sys/epoll.h>
    int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

    参数epfd:epoll_create()函数返回的epoll句柄。

    参数events:struct epoll_event结构体指针,用来从内核得到事件的集合。

    参数maxevents:告诉内核这个events有多大

    参数timeout:等待时的超时事件,以毫秒为单位。

    返回值:成功时,返回需要处理的事件数目。调用失败时,返回0,表示等待超时。

    说明:等待事件的产生。

    二、epoll的使用

    示例1:回显服务器

    /*******************************************************************************
    * File Name        : epoll.cpp
    * Author        : zjw
    * Email            : emp3XzA3MjJAMTYzLmNvbQo= (base64 encode)
    * Create Time    : 2015年07月15日 星期三 18时33分00秒
    *******************************************************************************/
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <sys/epoll.h>
    #include <sys/time.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <map>
    #include <errno.h>
    using namespace std;
    
    const int SERVER_PORT = 8080;
    const int RECV_SIZE = 1024;
    const int SEND_SIZE = 1040;
    const int MAX_EVENTS = 1024;
    typedef struct ClientInfo
    {
        sockaddr_in addr;
        string buf;
    }ClientInfo;
    
    int main(int argc, char **argv)
    {
        int server = socket(AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in addrServer;
        bzero(&addrServer, sizeof(addrServer));
        addrServer.sin_family = AF_INET;
        addrServer.sin_port = htons(SERVER_PORT);
        addrServer.sin_addr.s_addr = INADDR_ANY;
    
        if (bind(server, (struct sockaddr*)&addrServer, sizeof(addrServer)) < 0)
        {
            perror("bind failed!");
            return -1;
        }
    
        listen(server, 5);
        cout << "server listen on port:" << SERVER_PORT << " ..." << endl;
        
        int epollFd = epoll_create(MAX_EVENTS);
        if (epollFd <= 0)
        {
            perror("epoll_create failed,");
            return -1;
        }
    
        // set non block
        fcntl(server, F_SETFL, O_NONBLOCK);
        
        struct epoll_event ev;
        ev.data.fd = server;
        ev.events = EPOLLIN;
    
        // register event
        if (epoll_ctl(epollFd, EPOLL_CTL_ADD, server, &ev))
        {
            perror("epoll_ctl failed!");
            return -1;
        }
    
        struct epoll_event events[100];
        int ret = 0;
        char recvBuf[RECV_SIZE + 1];
        char sendBuf[SEND_SIZE + 1];
        int client;
        map<int, struct ClientInfo> mapClientInfo;
        while (1)
        {
            // wait epoll
            ret = epoll_wait(epollFd, events, 100, 500);
            // process active event
            for (int i = 0; i < ret; i++)
            {
                if (events[i].data.fd == server)
                { // a new client connect
                    sockaddr_in addrClient;
                    socklen_t len = sizeof(addrClient);
                    client = accept(server, (struct sockaddr*)&addrClient, &len);
                    if (client < 0)
                    {
                        perror("accept failed!");
                        return -1;
                    }
                    cout << "accept a new client:" << inet_ntoa(addrClient.sin_addr) << endl;
                    fcntl(client, F_SETFL, O_NONBLOCK);
                    ev.data.fd = client;
                    ev.events = EPOLLIN;
                    epoll_ctl(epollFd, EPOLL_CTL_ADD, client, &ev);
                    struct ClientInfo info;
                    info.addr = addrClient;
                    info.buf = "";
                    mapClientInfo.insert(make_pair<int, struct ClientInfo>(client, info));
                }
                else if (events[i].events & EPOLLIN)
                { // connectting user and sth can be read
                    if ((client = events[i].data.fd) < 0)
                    {
                        continue;
                    }
    
                    memset(recvBuf, 0, RECV_SIZE + 1);
                    memset(sendBuf, 0, SEND_SIZE + 1);
                    if ((ret = recv(client, recvBuf, RECV_SIZE, 0)) < 0)
                    { // client closed
                        if (errno == ECONNRESET)
                        {
                            close(client);
                            events[i].data.fd = -1;
                            cout << "client[" << inet_ntoa(mapClientInfo[client].addr.sin_addr) << "] exit!" << endl;
                            mapClientInfo.erase(client);
                        }
                        else
                        {
                            cout << "read from client[" << "ip" << "] failed!" << endl;
                        }
                    }
                    else if (ret = 0)
                    {
                        close(client);
                        events[i].data.fd = -1;
                        cout << "client[" << inet_ntoa(mapClientInfo[client].addr.sin_addr) << "] exit!" << endl;
                        mapClientInfo.erase(client);
                    }
    
                    cout << "client[" << inet_ntoa(mapClientInfo[client].addr.sin_addr) << "] said:" << recvBuf << endl;
                    sprintf(sendBuf, "Your said:%s", recvBuf);
                    mapClientInfo[client].buf = sendBuf;
    
                    // set write event
                    ev.data.fd = client;
                    ev.events = EPOLLOUT;
                    epoll_ctl(epollFd, EPOLL_CTL_MOD, client, &ev);
                }
                else if (events[i].events & EPOLLOUT)
                { // write event
                    client = events[i].data.fd;
                    string strTemp = mapClientInfo[client].buf;
                    if ((ret = send(client, strTemp.c_str(), strTemp.length(), 0) < 0))
                    {
                        perror("send failed!");
                        return -1;
                    }
    
                    ev.data.fd = client;
                    ev.events = EPOLLIN;
                    epoll_ctl(epollFd, EPOLL_CTL_MOD, client, &ev);
                }
            }
        }
    
        close(server);
        return 0;
    }

    Makefile:

    echo: epoll.cpp
        g++ -g -o $@ $<
    
    clean:
        rm -rf echo

    运行结果:

    server端

    client端192.168.0.161

    client端本地192.168.0.160

     三、参考

    http://blog.chinaunix.net/uid-23842323-id-2656592.html

    http://blog.chinaunix.net/uid-23842323-id-2656593.html

    http://blog.csdn.net/sparkliang/article/details/4770655#comments

  • 相关阅读:
    HDR & Bloom
    Parallax Mapping
    Normal Mapping & Tangent Space
    Gamma校正背后的原理
    内存对齐及其原理
    关于深度值的一些需要说明的点
    帧缓冲(Framebuffer)
    面剔除和深度测试
    工具·MashLab调整网格模型坐标系
    工具·notepad++正则表达式替换文本
  • 原文地址:https://www.cnblogs.com/lit10050528/p/4652213.html
Copyright © 2011-2022 走看看