select、poll墓猎、epoll詳解

IO讀寫(xiě)基本原理

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

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

write()函數(shù)的調(diào)用也并不是直接把數(shù)據(jù)寫(xiě)入網(wǎng)卡中毙沾,而是把用戶緩沖區(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)備好,用戶進(jìn)程發(fā)起read操作左胞,阻塞則會(huì)一直等待內(nèi)存緩沖區(qū)數(shù)據(jù)完整后再解除阻塞寇仓,而非阻塞則會(huì)立即返回不會(huì)等待

而內(nèi)核緩沖區(qū)與用戶緩沖區(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ù)流程
  • 用戶進(jìn)程調(diào)用read()系統(tǒng)函數(shù)乳愉,用戶進(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ù),用戶進(jìn)程同步阻塞等待
  • 內(nèi)核緩沖區(qū)中有完整的數(shù)據(jù)后慨丐,內(nèi)核會(huì)將內(nèi)核緩沖區(qū)中的數(shù)據(jù)復(fù)制到用戶緩沖區(qū)
  • 直到用戶緩沖區(qū)中有數(shù)據(jù)坡脐,用戶進(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);
// 接受客戶端連接
int socketFd = accept(listenfd, (struct sockaddr*) &clientaddr, &clientaddrlen) 

// 接收客戶端數(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è)客戶端請(qǐng)求,新的連接創(chuàng)建一個(gè)線程去處理即可
  • 阻塞的時(shí)候捅暴,線程掛起恬砂,不消耗CPU資源

缺點(diǎn):

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

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

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

同步非阻塞IO

image-20210320122907177
讀取數(shù)據(jù)流程
  • 用戶進(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)求立即返回,用戶進(jìn)程不斷的重試查詢內(nèi)核緩沖區(qū)數(shù)據(jù)有沒(méi)有準(zhǔn)備好
  • 當(dāng)內(nèi)核緩沖區(qū)數(shù)據(jù)準(zhǔn)備好了之后晕粪,用戶進(jìn)程阻塞挤悉,內(nèi)核開(kāi)始將內(nèi)核緩沖區(qū)數(shù)據(jù)復(fù)制到用戶緩沖區(qū)
  • 復(fù)制完成后,用戶進(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);
// 接受客戶端連接
int socketFd = accept(listenfd, (struct sockaddr*) &clientaddr, &clientaddrlen);
// 設(shè)置為非阻塞
ioctl(socketFd, FIONBIO, 1);
while (1) {
    int fd;
    // 循環(huán)遍歷
    for (fd : fds) {
        // 接收客戶端數(shù)據(jù)
        recv(fd, buf, 256, 0); 
    }
}
同步非阻塞IO的優(yōu)缺點(diǎn)

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

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

缺點(diǎn):

  • 假如現(xiàn)在有1萬(wàn)個(gè)客戶端連接诀诊,但只有1個(gè)客戶端發(fā)送數(shù)據(jù)過(guò)來(lái),為了獲取這個(gè)1個(gè)客戶端發(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è)客戶端socket連接傳入,在內(nèi)核中去遍歷魂迄,如果沒(méi)有數(shù)據(jù)這個(gè)方法就一直阻塞粗截,一但有數(shù)據(jù)這個(gè)方法解除阻塞并把所有有數(shù)據(jù)的socket返回,把這個(gè)遍歷的過(guò)程交給內(nèi)核去處理捣炬,是不是就可以避免空跑熊昌,避免1萬(wàn)次用戶態(tài)到內(nèi)核態(tài)的切換呢?

IO多路復(fù)用模型

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

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

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

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

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

select函數(shù)

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

select函數(shù)執(zhí)行流程
  • 從用戶空間拷貝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)到新的客戶端連接
                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 集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個(gè)開(kāi)銷在 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ū)別,它將用戶傳入的數(shù)組拷貝到內(nèi)核空間惨撇,然后查詢每個(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)到新的客戶端連接
                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("添加客戶端成功...\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的輪詢機(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ù)制到用戶態(tài),同時(shí)將事件數(shù)量返回給用戶芦圾。

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返回給用戶的滿足條件的事件*/
    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,    // 操作類型 新增、刪除等
              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)到新的客戶端連接
            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采用輪詢的方式來(lái)檢查文件描述符是否處于就緒態(tài)妓湘,而epoll采用回調(diào)機(jī)制。造成的結(jié)果就是乌询,隨著fd的增加榜贴,select和poll的效率會(huì)線性降低,而epoll不會(huì)受到太大影響妹田,除非活躍的socket很多
select唬党、poll、epoll比較
image-20210322173910834
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鬼佣,一起剝皮案震驚了整個(gè)濱河市驶拱,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌晶衷,老刑警劉巖蓝纲,帶你破解...
    沈念sama閱讀 206,968評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件阴孟,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡税迷,警方通過(guò)查閱死者的電腦和手機(jī)永丝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)翁狐,“玉大人类溢,你說(shuō)我怎么就攤上這事÷独粒” “怎么了闯冷?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,220評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)懈词。 經(jīng)常有香客問(wèn)我蛇耀,道長(zhǎng),這世上最難降的妖魔是什么坎弯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,416評(píng)論 1 279
  • 正文 為了忘掉前任纺涤,我火速辦了婚禮,結(jié)果婚禮上抠忘,老公的妹妹穿的比我還像新娘撩炊。我一直安慰自己,他們只是感情好拧咳,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪锥涕。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,144評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音座每,去河邊找鬼。 笑死捂寿,一個(gè)胖子當(dāng)著我的面吹牛治笨,可吹牛的內(nèi)容都是我干的顺又。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼绳瘟,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起悦施,我...
    開(kāi)封第一講書(shū)人閱讀 37,088評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肴熏,沒(méi)想到半個(gè)月后鞋吉,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體馁龟,經(jīng)...
    沈念sama閱讀 43,586評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡改抡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蟋软,到底是詐尸還是另有隱情侦铜,我是刑警寧澤专甩,帶...
    沈念sama閱讀 33,783評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站钉稍,受9級(jí)特大地震影響涤躲,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贡未,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評(píng)論 3 307
  • 文/蒙蒙 一种樱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧俊卤,春花似錦嫩挤、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,333評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至狠怨,卻和暖如春约啊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背佣赖。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,559評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工恰矩, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人憎蛤。 一個(gè)月前我還...
    沈念sama閱讀 45,595評(píng)論 2 355
  • 正文 我出身青樓外傅,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親俩檬。 傳聞我的和親對(duì)象是個(gè)殘疾皇子萎胰,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容