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);
                        }
                    }
                }
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章