Linux—TCP_server端编写多路转接之EPOLL

1、前言

之前我有写过 利用多路转接的select的TCP_server,但当时我们提到了很多关于select的缺点:

1、select可监听的文件描述符有上限制;
2、因为select参数是输入输出型的,所以每次重新设置select时,都需遍历式设置,对性能有一定的影响
3、用户增多时,多次重复遍历和频繁内核与进程数据拷贝(多次的返回)
4、需要自己维护一个数组/链表,对文件描述符的管理,实现也比较复杂
5、每次多需要重新设置select—将fd设置从用户拷贝到内核

基于这么多的缺和实现的复杂,所以我们基本不会使用select来实现,而今天的主题epoll对这些问题都进行解决

2、epoll

    epoll man手册上说linux2.6后性能最好的!!!

为什么性能好呢?
我们来看张图:
这里写图片描述
再来学习epoll的函数:
epoll有三个函数:
1、int epoll_create(int size); //创建epoll(创建红黑树)

  • 参数size :对内核的提醒(建议)空间大小,man手册解释size可以被忽略
  • 返回值: 返回一个epoll句柄,用于对红黑树的操作,在使用完epoll之后,因使用close()关闭;

2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) //设置红黑树

  • 参数:epfd , 红黑树的句柄
  • 参数:op,操作方式,有以下三种:
    EPOLL_CTL_ADD :添加事件(在红黑树上添加节点)
    EPOLL_CTL_MOD:更改事件(改变红黑树指定节点事件发生条件)
    EPOLL_CTL_DEL:删除事件(删除节点)
  • 返回值: 成功返回 0、失败返回-1;

3、int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);//等待事件就绪函数

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

           struct epoll_event {
               uint32_t     events;    /* 事件,用专门的宏来设置*/
               epoll_data_t data;      /* User data variable */
           };
  • 参数 epfd epoll句柄!!!
  • 参数 events,是用户创建的结构体数据组,是一个输出型参数,当内核的就绪时间队列中有事件时,将事件带回。
  • maxevents events最多可以带回多少事件,0 < maxevents < size(前面创建epoll是的大小)
  • 参数:timeout 超时时间,-1: 阻塞,0: 不阻塞
  • 返回值,就绪事件的个数
  • 函数功能:epoll_wait如果监测到事件就将所有就绪的事件传送到第二个参数struct epoll_event 结构体数组中,每次调用epoll_wait返回链表是从内核返回给用户空间的,每次从内核传送到用户空间的描述符并不是很多,epoll的时间复杂度是O(1).

讲完所有的接口是不是对EPOLL为什么效率高的原因,有了一定的了解;

3、总结epoll的高效性:

1、内核创建红黑树
2、不需要每次都对每个事件重新设置(比较于select)
3、 操作系统在检测文件描述符采用回调函数
4、用户查找就绪文件符的复杂度O(1),—利用队列遍历
5、用户与内核采用内存映射,看到同意内存,不需要拷贝

4、利用epoll编写TCP_server

这里写图片描述

下面的代码可以实现 server—client的交互式通信
    while(1)
    {
        int n = epoll_wait(epfd, rev, 64, -1);//等待就绪事件
        switch(n){ //
            case 0:{
                    printf("time out\n");
                    continue;
                }
            case -1:{
                    perror("epoll_wait");
                    return 5;
                }
            default:{//有事件就绪
                    int i = 0;
                    for(i=0; i<n; ++i)//遍历数组,时间复杂度O(n)=O(1)
                    {
                        if(rev[i].data.fd == listen_sock)
                        {
                            struct sockaddr_in client;
                            size_t len = sizeof(client);
                            int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
                            if(new_sock < 0)
                            {
                                perror("accept");
                                continue;
                            }
                            printf("get a new sock:%s, %d\n",\
                                    inet_ntoa(client.sin_addr), ntohs(client.sin_port));
                            ev.events = EPOLLIN;//添加新的套接字读事件
                            ev.data.fd = new_sock;
                            epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev);
                        }
                        else if(rev[i].events & EPOLLIN){//读事件就绪
                            char buf[1024];
                            ssize_t s = read(rev[i].data.fd, buf, sizeof(buf)-1);
                            if(s < 0){//当读取错误或者对端关闭连接时,删除该套接字
                                perror("read");
                                close(rev[i].data.fd);
                                ev.events = EPOLLIN;
                                ev.data.fd = rev[i].data.fd;
                                epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev);
                                continue;
                            }
                            else if(s == 0){
                                close(rev[i].data.fd);
                                ev.events = EPOLLIN;
                                ev.data.fd = rev[i].data.fd;
                                epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev);
                                printf("client quit\n");
                                continue;
                            }
                            buf[s] = '\0';
                            printf("clinet say#%s\n",buf);
                            //读取数据完成后,更改套接字读事件为写事件
                            ev.events = EPOLLOUT;
                            ev.data.fd = rev[i].data.fd;
                            epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev);
                        }
                        else{//写事件就绪
                            char *str = "wlcome to sock ";
                            write(rev[i].data.fd, str, strlen(str));
                            ev.events = EPOLLIN;//数据回馈完成后,更改套接字事件为读
                            ev.data.fd = rev[i].data.fd;
                            epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev);
                        }
                    }
                }
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章