zoukankan      html  css  js  c++  java
  • epoll编程

    包含头文件:

    #include <sys/epoll.h>

    epoll的接口非常简单,一共就三个函数:
    1. int epoll_create(int size);
    创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。

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

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

    第一个参数是epoll_create()的返回值,

    第二个参数表示动作,用三个宏来表示:
    EPOLL_CTL_ADD;注册新的fd到epfd中;
    EPOLL_CTL_MOD;修改已经注册的fd的监听事件;
    EPOLL_CTL_DEL;从epfd中删除一个fd;
    第三个参数是需要监听的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 events */
        epoll_data_t data; /* User data variable */
    };

    events可以是以下几个宏的集合;
    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
    EPOLLOUT:表示对应的文件描述符可以写;
    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
    EPOLLERR:表示对应的文件描述符发生错误;
    EPOLLHUP:表示对应的文件描述符被挂断;
    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

    3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    等待事件的产生,类似于select()调用。

    第一个参数:epfd,epoll_create返回的句柄

    第二个参数:events用来从内核得到事件的集合

    第三个参数:maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,

    第四个参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。

    该函数返回需要处理的事件数目,如返回0表示已超时。

    4.close(epollfd)

    退出时记得释放创建的epoll句柄;

    5、关于ET、LT两种工作模式:
    可以得出这样的结论:
    ET模式仅当状态发生变化的时候才获得通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write直到出错为止,很多人反映为什么采用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;

    而LT模式是只要有数据没有处理就会一直通知下去的.

    二者的差异在于:

    level-trigger(LT)模式下只要某个socket处于readable/writable状态,无论什么时候进行epoll_wait都会返回该socket;

    edge-trigger(ET)模式下只有某个socket从unreadable变为readable或从unwritable变为writable时,epoll_wait才会返回该socket。

    所以,在epoll的ET模式下,正确的读写方式为:
    读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN
    写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN

    PS:

    epoll工作在ET模式的时候,必须使用非阻塞套接口

    //设置socket连接为非阻塞模式
    void setnonblocking(int sockfd) 
    {
        int opts;
      // 得到文件状态标志  
        opts = fcntl(sockfd, F_GETFL, 0);
        if(opts < 0)
        {
            perror("fcntl(F_GETFL)
    ");
            exit(1);
        }
      // 设置文件状态标志 opts
    = (opts | O_NONBLOCK); if(fcntl(sockfd, F_SETFL, opts) < 0) { perror("fcntl(F_SETFL) "); exit(1); } }

    6.

    参考网址:

    epoll详解

    http://blog.chinaunix.net/uid-24517549-id-4051156.html

    高并发的epoll+线程池,epoll在线程池内

    http://blog.chinaunix.net/uid-311680-id-2439723.html

    基于epoll实现socket编程完整实例

    http://blog.chinaunix.net/uid-20771605-id-4596400.html

    epoll使用详解(精髓)

    http://blog.csdn.net/ljx0305/article/details/4065058

    1)

    #define ERR_EXIT(m) do
    {
      perror(m);
      exit(EXIT_FAILURE);
    } while(0)

    //声明epoll_event结构体的变量,event用于注册事件,数组用于回传要处理的事件
    struct epoll_event event;
    struct epoll_event events[20];
    // 生成用于处理accept的epoll专用的文件描述符
    int epfd = epoll_create(256);

    // 注册监听的事件

    memset(&event,0,sizeof(event));

    event.data.fd = listenFd;
    event.events = EPOLLIN | EPOLLET | EPOLLOUT;

    // 注册epoll事件
    rlt = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &event);

    if(rlt == -1)
    {
      ERR_EXIT("注册事件失败! ");
    }


    struct timeval outtime;
    memset(&outtime, 0x00, sizeof(struct timeval));

    const int MAX_EVENTS = 1024; // 最大事件数
    
    void *epoll_loop(void* para)
    {
        int nfds; // 临时变量,存放返回值
        int epfd; // 监听用的epoll句柄,epoll_create返回值
        //struct epoll_event events[MAX_EVENTS]; // 监听事件数组
        //struct epoll_event event;
        // struct sockaddr_in client_addr;
        int i;
        int connfd; // 
        for(;;) 
        {
            // 等待epoll事件的发生
            nfds = epoll_wait(epfd,events,MAX_EVENTS,-1); // -1: timeout
            //printf("nfds = %d
    ", nfds);
    
            // 处理所发生的所有事件
            if(nfds > 0)
            {
                for(i=0; i<nfds; ++i)
                {
                    if(events[i].data.fd == listenfd) // 如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
                    {
                        //while(1)
                        //{
                            // socklen_t cliaddrlen;                         
                            connfd = accept(listenfd,(struct sockaddr *)&client_addr, &cliaddrlen);
                            
                            if(connfd > 0)
                            {
                                //cout << "AcceptThread, accept:" << connfd << ",errno:" << errno << ",connect:" << inet_ntoa(cliaddr.sin_addr) << ":" << ntohs(cliaddr.sin_port) << endl;
                                event.data.fd=connfd;
                                event.events = EPOLLIN | EPOLLET; // ET模式
                                epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&event); // //注册ev.将新的fd添加到epoll的监听队列中
    
                                //fd_Setnonblocking(event.data.fd);
                                //event.events=EPOLLIN|EPOLLET;
                                //epoll_ctl(epfd,EPOLL_CTL_ADD,event.data.fd,&event);
                            }
                            else
                            {
                                //cout << "AcceptThread, accept:" << connfd << ",errno:" << errno << endl;
                                if (errno == EAGAIN) // 没有连接需要接收了
                                {
                                    break;
                                }
                                else if(errno == EINTR) // 可能被中断信号打断,,经过验证对非阻塞socket并未收到此错误,应该可以省掉该步判断
                                {
                                    ;
                                }
                                else // 其它情况可以认为该描述字出现错误,应该关闭后重新监听
                                {
                                    //...
                                }
                            }
                        //}
                    }
                    else if(events[i].events & EPOLLIN ) // 接收到数据,读socket
                    {
                        n = read(sockfd, line, MAXLINE)) < 0; //
                        
                        ev.data.ptr = md;     //md为自定义类型,添加数据
                        ev.events=EPOLLOUT|EPOLLET;
                        epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓
    
                        // 或者如下处理:
                        char recvBuf[1024] = {0}; 
                        int ret = 999;
                        int rs = 1;
    
                        while(rs)
                        {
                            ret = recv(events[i].data.fd, recvBuf, 1024, 0);// 接受客户端消息
                            if(ret < 0)
                            {
                                // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读,在这里就当作是该次事件已处理过。
                                if(errno == EAGAIN)
                                {
                                    printf("EAGAIN
    ");
                                    break;
                                }
                                else
                                {
                                    printf("recv error!
    ");
                                    epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event); // 其中 struct epoll_event event;
                                                                                               // memset(&event,0,sizeof(event));
                                                                                               // event.data.fd=listenfd;
                                                                                               // event.events=EPOLLIN|EPOLLET;
                                    close(events[i].data.fd);
                                    break;
                                }
                            }
                            else if(ret == 0)
                            {
                                // 这里表示对端的socket已正常关闭. 
                                rs = 0;
                            }
                            if(ret == sizeof(recvBuf))
                            {
                                rs = 1; // 需要再次读取
                            }
                            else
                            {
                                rs = 0;
                            }
                        }
    
                    }
                    else if(events[i].events & EPOLLOUT) // 有数据待发送,写socket
                    {

                struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取数据
                sockfd = md->fd;
                send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //发送数据
                event.data.fd=sockfd;
                event.events=EPOLLIN|EPOLLET;
                epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据

    //sprintf(buf,"HTTP/1.0 200 OK
    Content-type: text/plain
    
    %s","Hello world!
    ");
                        send(events[i].data.fd,buf,strlen(buf),0);
    
                        close(events[i].data.fd);
                    }
                    else
                    {
                        close(events[i].data.fd);
                    }
                    //else if (events[i].events & EPOLLERR || events[i].events & EPOLLHUP) // 有异常发生
                    //{
                    //    //此时说明该描述字已经出错了,需要重新创建和监听
                    //    //close(listenfd);   
    
                    //    // epoll_ctl(epfd, EPOLL_CTL_DEL, listenfd, &event);
                    //    // 创建监听socket
                    //    //listenfd = socket(AF_INET, SOCK_STREAM, 0);
                    //    //...
                    //}
    
    
                    //epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event);
                    //close(events[i].data.fd);
    
                }
            }    
        }
    }
  • 相关阅读:
    awk统计命令(求和、求平均、求最大值、求最小值)(转)
    高性能跨平台网络IO(Reactor、epoll、iocp)总结
    进程通信和同步(转)
    C++11原子操作与无锁编程(转)
    在线代码编译运行工具
    linux ps 命令的查看
    转: linux sed 命令的使用
    转:利用Eclipse CDT 阅读C/C++代码
    转:Raft一致性选举算法的ppt与视频
    转:ffmpeg time_base详解
  • 原文地址:https://www.cnblogs.com/sylar-liang/p/4261388.html
Copyright © 2011-2022 走看看