epoll在网络编程中使用一

附带部分:

             1.tcp粘包处理方式
                (1)引入分割符

                 (2)在数据header中,加入数据长度

              2.listen函数第二个参数介绍:半连接队列的长度。主要用于保存三次握手完成的队列,可以accept的队列,详细请期待三次握手详解部分。 

              3.常识部分,在大型项目编程中,分配内存,请一定要memset,防止脏区。

一、设备epoll网络socketfd设置

      1.socketfd可以设置为阻塞和非阻塞,都是可以的

      2.非阻塞的读写效率更高,读写返回速度更快。

二、epoll管理fd介绍

      我们在创建一个epoll,在内核中,其实本质上是创建了一个红黑树,而我们的epoll_create创建的epfd为根节点,我们每次对epfd进行操作,其本质是对子节点进行操作。

      epoll的触发方式有以下两种:

        LT(level triggered)水平触发,是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表,使用场景为大数据块读取和发送。
       ET (edge-triggered)边沿触发, 是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认, 使用场景为小数据块读取和发送。

        以下两种使用相同:

        ET+循环读取到返回-1结束的读取方式

        LT+一次读取

三、epoll函数介绍

      1.int epoll_create(int size);

      功能:创建一个epoll对应的红黑树,返回对应的文件描述符

      参数介绍:

                      size:size的值只有两种,一种等于0,一种大于0, 我们在使用时,传入大于0的任意值都可以

     

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

       功能:对对应的epoll树进行操作

      参数介绍:

                    epfd:对应的epoll树文件描述符
                    op:操作方式, 具体有以下三种

                         EPOLL_CTL_ADD:增加一个fd

                         EPOLL_CTL_MOD: 修改fd

                         EPOLL_CTL_DEL:删除fd

                      fd:op操作的对象

                     events:events成员变量: 可以是以下几个宏的集合:

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

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

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

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

                                 EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。

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

返回值:成功返回0,不成功返回-1

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

        功能:阻塞等待IO事件的发生

        参数介绍:

                   epfd:对应的epfd树文件描述符

                   epoll_event:用于接收代处理事件的数组;

                   maxevents:每次能处理的事件数,为events数组的大小,注意:此大小最好是要监控fd总量的1/100,效率值最高

                   timeout:等待I/O事件发生的超时值(单位我也不太清楚);

                                 -1:相当于阻塞,一般使用-1

                                   0:相当于非阻塞

               返回值:发生事件数

四、编程实例

  1 、初始化部分,此部分直接上代码,不做描述

      int port = 8080;

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        return -1;
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));

    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
        return -2;
    }

    if (listen(sockfd, 5) < 0) {
        return -3;
    }

    // epoll opera

    int epfd = epoll_create(1);
    
    struct epoll_event ev, events[512] = {0};
    ev.events = EPOLLIN;
    ev.data.fd = sockfd; //int idx = 2000;
    

    struct sockitem *si = (struct sockitem*)malloc(sizeof(struct sockitem));
    si->sockfd = sockfd;
    si->callback = accept_cb;
    ev.data.ptr = si;
    
    epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev);

2、epoll阻塞等待IO发生

        while (1) {

                  int nready = epoll_wait(epfd, events, 512, -1);
                  if (nready < -1)

                   {
                        break;
                    }

                    int i = 0;
                    for (i = 0;i < nready;i ++)

                    {

                                  //返回的事件处理                   

                      }

}

3.返回事件处理

 (1)返回的fd分类,一种是要进行连接的sockfd,一种是可进行读写数据的clientfd,伪代码写法如下:

            if (events[i].data.fd == sockfd) { //

                struct sockaddr_in client_addr;
                memset(&client_addr, 0, sizeof(struct sockaddr_in));
                socklen_t client_len = sizeof(client_addr);
                
                int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
                if (clientfd <= 0) continue;

                char str[INET_ADDRSTRLEN] = {0};
                printf("recv from %s at port %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
                    ntohs(client_addr.sin_port));

                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = clientfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
                
            } else {

                int clientfd = events[i].data.fd;

                char buffer[1024] = {0};
                int ret = recv(clientfd, buffer, 5, 0);
                if (ret < 0) {

                    if (errno == EAGAIN || errno == EWOULDBLOCK) { //
                        continue;
                    } else {
                        
                    }

                    close(clientfd);

                    ev.events = EPOLLIN;
                    ev.data.fd = clientfd;
                    epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);

                } else if (ret == 0) { //

                    // 
                    printf("disconnect %d\n", clientfd);

                    close(clientfd);

                    ev.events = EPOLLIN;
                    ev.data.fd = clientfd;
                    epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
                    
                }

(2)按照返回的事件分类,分为可读和可写两部分,可读又分为连接和数据读取,伪代码如下

        if (events[i].events & EPOLLIN) {

                if (events[i].data.fd == sockfd) { //

                    struct sockaddr_in client_addr;
                    memset(&client_addr, 0, sizeof(struct sockaddr_in));
                    socklen_t client_len = sizeof(client_addr);
                    
                    int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len);
                    if (clientfd <= 0) continue;

                    char str[INET_ADDRSTRLEN] = {0};
                    printf("recv from %s at port %d\n", inet_ntop(AF_INET, &client_addr.sin_addr, str, sizeof(str)),
                        ntohs(client_addr.sin_port));

                    ev.events = EPOLLIN | EPOLLET;
                    ev.data.fd = clientfd;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &ev);
                    
                } else {

                    int clientfd = events[i].data.fd;

                    char buffer[1024] = {0};
                    int ret = recv(clientfd, buffer, 5, 0);
                    if (ret < 0) {

                        if (errno == EAGAIN || errno == EWOULDBLOCK) { //
                            continue;
                        } else {
                            
                        }

                        close(clientfd);

                        ev.events = EPOLLIN;
                        ev.data.fd = clientfd;
                        epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);

                    } else if (ret == 0) { //

                        // 
                        printf("disconnect %d\n", clientfd);

                        close(clientfd);

                        ev.events = EPOLLIN;
                        ev.data.fd = clientfd;
                        epoll_ctl(epfd, EPOLL_CTL_DEL, clientfd, &ev);
                        
                    } else {

                        printf("Recv: %s, %d Bytes\n", buffer, ret);

                    }

                }
            }

            if (events[i].events & EPOLLOUT) {

           

            }

(3)利用回调函数处理
         熟悉:epoll_data中的ptr使用,下次讲解

         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 */
         };
 

 

上一篇:Java 8 集合之流式(Streams)操作, Streams API 详解


下一篇:不知道怎么入门Selet、Poll、Epoll?看这篇文章就够了!