select、poll、epoll詳解

IO讀寫(xiě)基本原理

用戶(hù)程序進(jìn)行IO操作實(shí)際依賴(lài)于linux系統(tǒng)內(nèi)核read()、write()函數(shù)

read()函數(shù)的調(diào)用并不是直接從網(wǎng)卡把數(shù)據(jù)讀取到用戶(hù)內(nèi)存中,而是把內(nèi)核緩沖區(qū)中的數(shù)據(jù)復(fù)制到用戶(hù)緩沖區(qū)中

write()函數(shù)的調(diào)用也并不是直接把數(shù)據(jù)寫(xiě)入網(wǎng)卡中,而是把用戶(hù)緩沖區(qū)的數(shù)據(jù)寫(xiě)入到內(nèi)核緩沖區(qū)中

網(wǎng)卡與內(nèi)核緩沖區(qū)數(shù)據(jù)的讀寫(xiě)則是由操作系統(tǒng)內(nèi)核完成

image-20210322173910834
阻塞IO和非阻塞IO

網(wǎng)卡同步數(shù)據(jù)到內(nèi)核緩沖區(qū),如果內(nèi)核緩沖區(qū)中的數(shù)據(jù)未準(zhǔn)備好,用戶(hù)進(jìn)程發(fā)起read操作,阻塞則會(huì)一直等待內(nèi)存緩沖區(qū)數(shù)據(jù)完整后再解除阻塞,而非阻塞則會(huì)立即返回不會(huì)等待

而內(nèi)核緩沖區(qū)與用戶(hù)緩沖區(qū)之間的讀寫(xiě)操作肯定是阻塞的

同步和異步

同步:調(diào)用者主動(dòng)發(fā)起請(qǐng)求,調(diào)用者主動(dòng)等待這個(gè)結(jié)果返回,一但調(diào)用就必須有返回值

異步:調(diào)用發(fā)出后直接返回,所以沒(méi)有返回結(jié)果。被調(diào)用者處理完成后通知回調(diào)、通知等機(jī)制來(lái)通知調(diào)用者

同步阻塞IO

image-20210320122907177
讀取數(shù)據(jù)流程
  • 用戶(hù)進(jìn)程調(diào)用read()系統(tǒng)函數(shù),用戶(hù)進(jìn)程進(jìn)入阻塞狀態(tài)
  • 系統(tǒng)內(nèi)核收到read()系統(tǒng)調(diào)用,網(wǎng)卡開(kāi)始準(zhǔn)備接收數(shù)據(jù),在一開(kāi)始內(nèi)核緩沖區(qū)數(shù)據(jù)為空,內(nèi)核在等待接收數(shù)據(jù),用戶(hù)進(jìn)程同步阻塞等待
  • 內(nèi)核緩沖區(qū)中有完整的數(shù)據(jù)后,內(nèi)核會(huì)將內(nèi)核緩沖區(qū)中的數(shù)據(jù)復(fù)制到用戶(hù)緩沖區(qū)
  • 直到用戶(hù)緩沖區(qū)中有數(shù)據(jù),用戶(hù)進(jìn)程才能解除阻塞狀態(tài)繼續(xù)執(zhí)行
同步阻塞IO底層實(shí)現(xiàn)
// 創(chuàng)建socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 綁定
bind(listenfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
// 監(jiān)聽(tīng)
listen(listenfd, 5);
// 接受客戶(hù)端連接
int socketFd = accept(listenfd, (struct sockaddr*) &clientaddr, &clientaddrlen) 

// 接收客戶(hù)端數(shù)據(jù)
recv(socketFd, buf, 256, 0); 
同步阻塞IO的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  • 開(kāi)發(fā)簡(jiǎn)單,由于accept()、recv()都是阻塞的,為了服務(wù)于多個(gè)客戶(hù)端請(qǐng)求,新的連接創(chuàng)建一個(gè)線(xiàn)程去處理即可
  • 阻塞的時(shí)候,線(xiàn)程掛起,不消耗CPU資源

缺點(diǎn):

  • 每新來(lái)一個(gè)IO請(qǐng)求,都需要新建一個(gè)線(xiàn)程對(duì)應(yīng),高并發(fā)下系統(tǒng)開(kāi)銷(xiāo)大,多線(xiàn)程上下文切換頻繁
  • 創(chuàng)建線(xiàn)程太多,內(nèi)存消耗大
同步阻塞IO缺點(diǎn)帶來(lái)的思考

因?yàn)閍ccept()、recv()函數(shù)都是阻塞的,如果系統(tǒng)想要支持多個(gè)IO請(qǐng)求,就創(chuàng)建更多的線(xiàn)程,如果去解決這個(gè)問(wèn)題呢?

如果可以把a(bǔ)ccept、recv函數(shù)變成非阻塞的方式,是不是就可以避免創(chuàng)建多個(gè)線(xiàn)程了?這就引入了我們的同步非阻塞IO

同步非阻塞IO

image-20210320122907177
讀取數(shù)據(jù)流程
  • 用戶(hù)進(jìn)程發(fā)起請(qǐng)求調(diào)用read()函數(shù),系統(tǒng)內(nèi)核收到read()系統(tǒng)調(diào)用,網(wǎng)卡開(kāi)始準(zhǔn)備接收數(shù)據(jù)
  • 內(nèi)核緩沖區(qū)數(shù)據(jù)沒(méi)有準(zhǔn)備好,請(qǐng)求立即返回,用戶(hù)進(jìn)程不斷的重試查詢(xún)內(nèi)核緩沖區(qū)數(shù)據(jù)有沒(méi)有準(zhǔn)備好
  • 當(dāng)內(nèi)核緩沖區(qū)數(shù)據(jù)準(zhǔn)備好了之后,用戶(hù)進(jìn)程阻塞,內(nèi)核開(kāi)始將內(nèi)核緩沖區(qū)數(shù)據(jù)復(fù)制到用戶(hù)緩沖區(qū)
  • 復(fù)制完成后,用戶(hù)進(jìn)程解除阻塞,讀取數(shù)據(jù)繼續(xù)執(zhí)行
同步非阻塞IO底層實(shí)現(xiàn)
// 創(chuàng)建socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 綁定
bind(listenfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
// 監(jiān)聽(tīng)
listen(listenfd, 5);
// 設(shè)置為非阻塞
ioctl(listenfd, FIONBIO, 1);
// 接受客戶(hù)端連接
int socketFd = accept(listenfd, (struct sockaddr*) &clientaddr, &clientaddrlen);
// 設(shè)置為非阻塞
ioctl(socketFd, FIONBIO, 1);
while (1) {
    int fd;
    // 循環(huán)遍歷
    for (fd : fds) {
        // 接收客戶(hù)端數(shù)據(jù)
        recv(fd, buf, 256, 0); 
    }
}
同步非阻塞IO的優(yōu)缺點(diǎn)

優(yōu)點(diǎn):

  • 非阻塞, accept()、recv()均不阻塞,用戶(hù)線(xiàn)程立即返回
  • 規(guī)避了同步阻塞模式的多線(xiàn)程問(wèn)題

缺點(diǎn):

  • 假如現(xiàn)在有1萬(wàn)個(gè)客戶(hù)端連接,但只有1個(gè)客戶(hù)端發(fā)送數(shù)據(jù)過(guò)來(lái),為了獲取這個(gè)1個(gè)客戶(hù)端發(fā)送的消息,我需要循環(huán)向內(nèi)核發(fā)送1萬(wàn)遍recv()系統(tǒng)調(diào)用,而這其中有9999次是無(wú)效的請(qǐng)求,浪費(fèi)CPU資源
同步非阻塞IO缺點(diǎn)帶來(lái)的思考

針對(duì)同步非阻塞IO的缺點(diǎn),設(shè)想如果內(nèi)核提供一個(gè)方法,可以一次性把1萬(wàn)個(gè)客戶(hù)端socket連接傳入,在內(nèi)核中去遍歷,如果沒(méi)有數(shù)據(jù)這個(gè)方法就一直阻塞,一但有數(shù)據(jù)這個(gè)方法解除阻塞并把所有有數(shù)據(jù)的socket返回,把這個(gè)遍歷的過(guò)程交給內(nèi)核去處理,是不是就可以避免空跑,避免1萬(wàn)次用戶(hù)態(tài)到內(nèi)核態(tài)的切換呢?

IO多路復(fù)用模型

image-20210320122907177
什么是IO多路復(fù)用?

一個(gè)線(xiàn)程監(jiān)測(cè)多個(gè)IO操作

IO多路復(fù)用實(shí)現(xiàn)原理

IO多路復(fù)用模型是建立在內(nèi)核提供的多路分離函數(shù)select基礎(chǔ)之上的,使用select函數(shù)可以避免同步非阻塞IO模型中輪詢(xún)等待的問(wèn)題,即一次性將N個(gè)客戶(hù)端socket連接傳入內(nèi)核然后阻塞,交由內(nèi)核去輪詢(xún),當(dāng)某一個(gè)或多個(gè)socket連接有事件發(fā)生時(shí),解除阻塞并返回事件列表,用戶(hù)進(jìn)程在循環(huán)遍歷處理有事件的socket連接。這樣就避免了多次調(diào)用recv()系統(tǒng)調(diào)用,避免了用戶(hù)態(tài)到內(nèi)核態(tài)的切換。

IO多路復(fù)用的三種實(shí)現(xiàn)

select函數(shù)

select函數(shù)僅僅知道有幾個(gè)I/O事件發(fā)生了,但并不知道具體是哪幾個(gè)socket連接有I/O事件,還需要輪詢(xún)?nèi)ゲ檎?,時(shí)間復(fù)雜度為O(n),處理的請(qǐng)求數(shù)越多,所消耗的時(shí)間越長(zhǎng)。

select函數(shù)執(zhí)行流程
  • 從用戶(hù)空間拷貝fd_set(注冊(cè)的事件集合)到內(nèi)核空間
  • 遍歷所有fd文件,并將當(dāng)前進(jìn)程掛到每個(gè)fd的等待隊(duì)列中,當(dāng)某個(gè)fd文件設(shè)備收到消息后,會(huì)喚醒設(shè)備等待隊(duì)列上睡眠的進(jìn)程,那么當(dāng)前進(jìn)程就會(huì)被喚醒
  • 如果遍歷完所有的fd沒(méi)有I/O事件,則當(dāng)前進(jìn)程進(jìn)入睡眠,當(dāng)有某個(gè)fd文件有I/O事件或當(dāng)前進(jìn)程睡眠超時(shí)后,當(dāng)前進(jìn)程重新喚醒再次遍歷所有fd文件
select函數(shù)接口定義
#include <sys/select.h>
#include <sys/time.h>

// 最大支持1024個(gè)連接
#define FD_SETSIZE 1024
#define NFDBITS (8 * sizeof(unsigned long))
#define __FDSET_LONGS (FD_SETSIZE/NFDBITS)

/**
* 數(shù)據(jù)結(jié)構(gòu) (bitmap)
* fd_set保存了相關(guān)的socket事件
*/
typedef struct {
    unsigned long fds_bits[__FDSET_LONGS];
} fd_set;

/**
* select是一個(gè)阻塞函數(shù)
*/
// 返回值就緒描述符的數(shù)目
int select(
    int max_fd,  // 最大的文件描述符值,遍歷時(shí)取0-max_fd
    fd_set *readset,  // 讀事件列表
    fd_set *writeset,  // 寫(xiě)事件列表
    fd_set *exceptset,  // 異常列表
    struct timeval *timeout  // 阻塞超時(shí)時(shí)間
)

FD_ZERO(int fd, fd_set* fds)   // 清空集合
FD_SET(int fd, fd_set* fds)    // 將給定的描述符加入集合
FD_ISSET(int fd, fd_set* fds)  // 判斷指定描述符是否在集合中 
FD_CLR(int fd, fd_set* fds)    // 將給定的描述符從文件中刪除  
select使用示例
#include<stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>                     
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>  

void server() {
    
    // 創(chuàng)建socket連接
    int lfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in my_addr; 
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET; // ipv4
    my_addr.sin_port   = htons(9090);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
    // 綁定端口
    bind(lfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    // 監(jiān)聽(tīng)連接請(qǐng)求
    listen(lfd, 128);
    printf("listen client @port=%d...\n", 9090);
    int lastfd = lfd;
    // 定義文件描述符集
    fd_set read_fd_set, all_fd_set;
    // 服務(wù)socket描述符加入set集合中
    FD_ZERO(&all_fd_set);
    FD_SET(lfd, &all_fd_set);
    printf("準(zhǔn)備進(jìn)入while循環(huán)\n");
    while (1) {
        read_fd_set = all_fd_set;
        printf("阻塞中... lastfd=%d\n", lastfd);
        int nready = select(lastfd+1, &read_fd_set, NULL, NULL, NULL);
        switch (nready) {
            case 0 :
                printf("select time out ......\n");
                break;
            case -1 :
                perror("select error \n");
                break;
            default:
                // 監(jiān)聽(tīng)到新的客戶(hù)端連接
                if (FD_ISSET(lfd, &read_fd_set)) {
                    struct sockaddr_in client_addr; 
                    socklen_t cliaddr_len = sizeof(client_addr);
                    char cli_ip[INET_ADDRSTRLEN] = "";  
                    // 肯定有連接不會(huì)阻塞
                    int clientfd = accept(lfd, (struct sockaddr*)&client_addr, &cliaddr_len);
                    inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
                    printf("----------------------------------------------\n");
                    printf("client ip=%s,port=%d\n", cli_ip, ntohs(client_addr.sin_port));
                    // 將clientfd加入讀集合
                    FD_SET(clientfd, &all_fd_set);  
                    lastfd = clientfd;
                    if(0 == --nready) {
                        continue;
                    }
                }
                int i;
                for (i = lfd + 1;i <= lastfd; i++) {
                    // 處理讀事件
                    if (FD_ISSET(i, &read_fd_set)) {
                        char recv_buf[512] = "";
                        int rs = read(i, recv_buf, sizeof(recv_buf));
                        if (rs == 0 ) {
                            close(i);
                            FD_CLR(i, &all_fd_set);
                        } else {
                            printf("%s\n",recv_buf);
                            // 給每一個(gè)服務(wù)端寫(xiě)數(shù)據(jù)
                            int j;
                            for (j = lfd + 1;j <= lastfd; j++) {
                                if (j != i) {
                                    write(j, recv_buf, strlen(recv_buf));
                                }
                            }
                        }
                    }
                }
        }
        
    }
}

int main(){
    server();
    return 0;
}
select函數(shù)的缺點(diǎn)
  • 單個(gè)進(jìn)程所打開(kāi)的FD是有限制的,通過(guò) FD_SETSIZE 設(shè)置,默認(rèn)1024
  • 每次調(diào)用 select,都需要把 fd 集合從用戶(hù)態(tài)拷貝到內(nèi)核態(tài),這個(gè)開(kāi)銷(xiāo)在 fd 很多時(shí)會(huì)很大
  • 每次調(diào)用select都需要將進(jìn)程加入到所有監(jiān)視socket的等待隊(duì)列,每次喚醒都需要從每個(gè)隊(duì)列中移除
  • select函數(shù)在每次調(diào)用之前都要對(duì)參數(shù)進(jìn)行重新設(shè)定,這樣做比較麻煩,而且會(huì)降低性能
  • 進(jìn)程被喚醒后,程序并不知道哪些socket收到數(shù)據(jù),還需要遍歷一次

poll

poll本質(zhì)上和select沒(méi)有區(qū)別,它將用戶(hù)傳入的數(shù)組拷貝到內(nèi)核空間,然后查詢(xún)每個(gè)fd對(duì)應(yīng)的設(shè)備狀態(tài), 但是它沒(méi)有最大連接數(shù)的限制,原因是它是基于鏈表來(lái)存儲(chǔ)的

poll函數(shù)接口
#include <poll.h>
// 數(shù)據(jù)結(jié)構(gòu)
struct pollfd {
    int fd;                         // 需要監(jiān)視的文件描述符
    short events;                   // 需要內(nèi)核監(jiān)視的事件
    short revents;                  // 實(shí)際發(fā)生的事件,1:表示有事件發(fā)生,0:沒(méi)有事件發(fā)生
};

// 阻塞方法
int poll(struct pollfd fds[],   // 需要監(jiān)聽(tīng)的文件描述符列表
         nfds_t nfds,           // 文件描述符個(gè)數(shù)
         int timeout            // 超時(shí)時(shí)間
        );
poll示例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <unistd.h>
#include <sys/time.h>


#define MAX_POLLFD_LEN 4096  
#define PORT 9108


void server() {
    
    // 創(chuàng)建socket連接
    int lfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in my_addr; 
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET; // ipv4
    my_addr.sin_port   = htons(PORT);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
    // 綁定端口
    bind(lfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    // 監(jiān)聽(tīng)連接請(qǐng)求
    listen(lfd, 128);
    printf("listen client @port=%d...\n",PORT);
    
    // 定義pollfd對(duì)象
    struct pollfd fds[MAX_POLLFD_LEN];
    memset(fds, 0, sizeof(fds));
    // 添加socket服務(wù)監(jiān)聽(tīng)
    fds[0].fd = lfd;
    fds[0].events = POLLIN;
    int nfds = 1;
    int i;
    for(i = 1; i < MAX_POLLFD_LEN; i++) {
        fds[i].fd = -1;
    }
    int maxFds = 0;
    printf("準(zhǔn)備進(jìn)入while循環(huán)\n");
    while (1) {
        printf("阻塞中, [maxFds=%d]...\n", maxFds);
        int nready = poll(fds, maxFds + 1, -1);
        switch (nready) {
            case 0 :
                printf("select time out ......\n");
                break;
            case -1 :
                perror("select error \n");
                break;
            default:
                // 監(jiān)聽(tīng)到新的客戶(hù)端連接
                if (fds[0].revents & POLLIN) {
                    struct sockaddr_in client_addr; 
                    socklen_t cliaddr_len = sizeof(client_addr);
                    char cli_ip[INET_ADDRSTRLEN] = "";  
                    // 肯定有連接不會(huì)阻塞
                    int clientfd = accept(lfd, (struct sockaddr*)&client_addr, &cliaddr_len);
                    inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
                    printf("----------------------------------------------\n");
                    printf("client ip=%s,port=%d\n", cli_ip, ntohs(client_addr.sin_port));
                    // 將clientfd加入讀集合
                    int j;
                    for (j = 1; j < MAX_POLLFD_LEN; ++j) {
                        if (fds[j].fd < 0) {
                            fds[j].fd = clientfd;
                            fds[j].events = POLLIN;
                            printf("添加客戶(hù)端成功...\n");
                            maxFds++;   
                            break;
                        }
                        if(j == MAX_POLLFD_LEN){
                            printf("too many clients"); 
                            exit(1);
                        }
                        
                    }
                    
                    if(--nready <= 0) {
                        continue;
                    }
                }
                int i;
                printf("maxFds=%d\n", maxFds);
                for (i = 1; i <= maxFds; i++) {
                    printf("i=%d\n", i);
                    // 處理讀事件
                    if (fds[i].revents & POLLIN) {
                        int sockfd = fds[i].fd;
                        char recv_buf[512] = "";
                        int rs = read(sockfd, recv_buf, sizeof(recv_buf));

                        if (rs == 0) {
                            close(sockfd);
                            fds[i].fd = -1;
                        } else {
                            printf("%s\n",recv_buf);
                            // 給每一個(gè)服務(wù)端寫(xiě)數(shù)據(jù)
                            int j;
                            for (j = 1;j <= maxFds; j++) {
                                if (j != i) {
                                    write(fds[j].fd, recv_buf, strlen(recv_buf));
                                }
                            }
                        }
                    }
                }
        }
        
    }
}

int main(){
    server();
    return 0;
}

epoll

epoll可以理解為event pool,不同與select、poll的輪詢(xún)機(jī)制,epoll采用的是事件驅(qū)動(dòng)機(jī)制,每個(gè)fd上有注冊(cè)有回調(diào)函數(shù),當(dāng)網(wǎng)卡接收到數(shù)據(jù)時(shí)會(huì)回調(diào)該函數(shù),同時(shí)將該fd的引用放入rdlist就緒列表中。

當(dāng)調(diào)用epoll_wait檢查是否有事件發(fā)生時(shí),只需要檢查eventpoll對(duì)象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發(fā)生的事件復(fù)制到用戶(hù)態(tài),同時(shí)將事件數(shù)量返回給用戶(hù)。

epoll函數(shù)的接口定義
#include <sys/epoll.h>

// 數(shù)據(jù)結(jié)構(gòu)
// 每一個(gè)epoll對(duì)象都有一個(gè)獨(dú)立的eventpoll結(jié)構(gòu)體
// 用于存放通過(guò)epoll_ctl方法向epoll對(duì)象中添加進(jìn)來(lái)的事件
// epoll_wait檢查是否有事件發(fā)生時(shí),只需要檢查eventpoll對(duì)象中的rdlist雙鏈表中是否有epitem元素即可
struct eventpoll {
    /*紅黑樹(shù)的根節(jié)點(diǎn),這顆樹(shù)中存儲(chǔ)著所有添加到epoll中的需要監(jiān)控的事件*/
    struct rb_root  rbr;
    /*雙鏈表中則存放著將要通過(guò)epoll_wait返回給用戶(hù)的滿(mǎn)足條件的事件*/
    struct list_head rdlist;
};

// API
// 內(nèi)核中間加一個(gè) ep 對(duì)象,把所有需要監(jiān)聽(tīng)的socket都放到ep對(duì)象中
int epoll_create(int size); 
// epoll_ctl 負(fù)責(zé)把 socket 增加、刪除到內(nèi)核紅黑樹(shù)
int epoll_ctl(int epfd,  // 創(chuàng)建的ep對(duì)象
              int op,    // 操作類(lèi)型 新增、刪除等
              int fd,    // 要操作的對(duì)象
              struct epoll_event *event  // 事件
             ); 
// epoll_wait 負(fù)責(zé)檢測(cè)可讀隊(duì)列,沒(méi)有可讀 socket 則阻塞進(jìn)程
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

epoll執(zhí)行流程
  • 調(diào)用epoll_create()創(chuàng)建一個(gè)ep對(duì)象,即紅黑樹(shù)的根節(jié)點(diǎn),返回一個(gè)文件句柄
  • 調(diào)用epoll_ctl()向這個(gè)ep對(duì)象(紅黑樹(shù))中添加、刪除、修改感興趣的事件
  • 調(diào)用epoll_wait()等待,當(dāng)有事件發(fā)生時(shí)網(wǎng)卡驅(qū)動(dòng)會(huì)調(diào)用fd上注冊(cè)的函數(shù)并將該fd添加到rdlist中,解除阻塞
image-20210322173504762
示例代碼
#include<stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>                     
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>  
#include <sys/epoll.h>

void server() {
    
    // 創(chuàng)建socket連接
    int lfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in my_addr; 
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET; // ipv4
    my_addr.sin_port   = htons(8088);
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); 
    // 綁定端口
    bind(lfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
    // 監(jiān)聽(tīng)連接請(qǐng)求
    listen(lfd, 128);
    printf("listen client @port=%d...\n", 8088);
    int epct, i;
    struct epoll_event event;
    struct epoll_event events[100];
    memset(events, 0, 100 * sizeof(struct epoll_event));
    int epfd = epoll_create(1);
    event.data.fd = lfd;
    event.events = EPOLLIN;
    epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &event);
    while (1) {
        printf("阻塞中....\n");
        int nready = epoll_wait(epfd, events, 20, -1);
        int i;
        for (i = 0; i < nready; ++i) {
            // 監(jiān)聽(tīng)到新的客戶(hù)端連接
            if (events[i].data.fd == lfd) {
                struct sockaddr_in client_addr; 
                socklen_t cliaddr_len = sizeof(client_addr);
                char cli_ip[INET_ADDRSTRLEN] = "";  
                // 肯定有連接不會(huì)阻塞
                int clientfd = accept(lfd, (struct sockaddr*)&client_addr, &cliaddr_len);
                inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
                
                event.data.fd = clientfd;
                event.events = EPOLLIN | EPOLLET;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &event);
                
                printf("----------------------------------------------\n");
                printf("client ip=%s,port=%d\n", cli_ip, ntohs(client_addr.sin_port));
            } else {
                char recv_buf[512] = "";
                int rs = read(events[i].data.fd, recv_buf, sizeof(recv_buf));
                if (rs < 0) {
                    close(events[i].data.fd);
                    epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, &event);
                    continue;
                }
                printf("%s\n",recv_buf);
            }
        }
    
        
    }
}

int main(){
    server();
    return 0;
}

epoll總結(jié)
  • EPOLL支持的最大文件描述符上限是整個(gè)系統(tǒng)最大可打開(kāi)的文件數(shù)目, 1G內(nèi)存理論上最大創(chuàng)建10萬(wàn)個(gè)文件描述符
  • 每個(gè)文件描述符上都有一個(gè)callback函數(shù),當(dāng)socket有事件發(fā)生時(shí)會(huì)回調(diào)這個(gè)函數(shù)將該fd的引用添加到就緒列表中,select和poll并不會(huì)明確指出是哪些文件描述符就緒,而epoll會(huì)。造成的區(qū)別就是,系統(tǒng)調(diào)用返回后,調(diào)用select和poll的程序需要遍歷監(jiān)聽(tīng)的整個(gè)文件描述符找到是誰(shuí)處于就緒,而epoll則直接處理即可
  • select、poll采用輪詢(xún)的方式來(lái)檢查文件描述符是否處于就緒態(tài),而epoll采用回調(diào)機(jī)制。造成的結(jié)果就是,隨著fd的增加,select和poll的效率會(huì)線(xiàn)性降低,而epoll不會(huì)受到太大影響,除非活躍的socket很多
select、poll、epoll比較
image-20210322173910834
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容