淺談Linux-I/O複用【select、poll、epoll】+詳細使用示例

目錄

1 爲什麼要進行I/O複用?

2 select

3 poll 

4 epoll(linux特有)

4.1 epoll介紹與示例

4.2 LT模式和ET模式

5 select、poll和epoll的區別


本文中的代碼示例建議實踐一遍。

1 爲什麼要進行I/O複用?

在這之前,大家應該先對5中I/O模型有一個簡單的瞭解。

首先我們應該都知道,I/O複用可以同時對多個文件描述符進行監聽,能很大程度上去提高程序的性能。就相當於一個同時能監聽多個電話的電話接線員。

I/O複用在網絡編程中最爲常用,在傳統的網絡編程中,如果有多個TCP連接,往往都是去開闢多個線程去進行處理,在線程數量較多的情況下,線程的開闢、喚醒等操作大大的浪費CPU時間。因此如果能有一個類似於觀察者的角色去監聽多個連接,那麼程序的性能將能得到大幅度的提升。這個時候I/O複用就閃亮登場了。

在網絡編程的過程中,以下幾種情況(不限於)需要使用I/O複用模型:

  • 客戶端要處理多個socket。
  • 客戶端程序要同時處理用戶輸入和網絡連接。
  • TCP服務器要同時處理監聽socket和連接socket。這種情況是I/O複用使用最多的一種情況。
  • 服務器要同時處理TCP請求和UDP請求。
  • 服務器要同時監聽多個窗口,或者處理多種任務。

I/O複用和其他I/O模型的比較如下圖所示:

Linux通過select、poll和epoll三種系統調用實現了I/O複用。epoll是linux特有的。下面進行一一介紹。

2 select

select系統調用可以在某一時間段內監聽文件描述符上的可讀、可寫或者異常事件。

爲了便於理解select,我形象的比喻一下這句話:

假如你是公司老闆,我們將select看作是你的專屬接線員,你有幾個客戶的電話號碼(相當於文件描述符),然後你將這幾個電話好號碼告訴了你的接線員,並且告訴他,如果這些客戶要是買產品的話再告訴你(將可讀操作比喻爲買產品),其他情況不用告訴我,並且一個小時彙報一次,不管有沒有客戶買產品。那麼對於你而言,只有當固定的幾個客戶打電話並且要買產品,你才能從接線員那裏收到消息。當然除了買產品以外,你還可以規定其他兩件事情,但是不能超過三件。(這些事情相當於:可讀、寫和異常)

select其實是在指定事件內輪詢一定數量的文件描述符,檢測是否有就緒者。

下面是select常用的操作:

#include<sys/select.h>

int select(int nfds,fd_set *readfds,fd_set* writefds,fd exceptfds,struct timeval * timeout);

此函數用於監聽文件描述符,文件描述符存於readfds,writefds,exceptfds.

nfds是需要監聽文件描述符的個數,假設readfds包含1個文件描述符,writefds包含兩個文件描述符,exceptfds包含3個文件描述符,那麼,nfds = 1+2+3=6。通常設置爲文件描述符的最大值+1,因爲文件描述符是從0開始計數的。

readfds、writefds和exceptfds是三個fd_set指針,fd_set是一個結構體,裏面有一個整型數組,用於存儲文件描述符。三個指針爲空,那麼select只監聽超時。如果不爲空,那麼select就去監聽文件描述符的響相應事件。readfds監聽讀事件,writefds監聽寫事件,exceptfds監聽異常事件。某一個爲NULL,那就不監聽相應的事件。

timeout爲一個結構體,用於設置超時時間。

struct timeval

{

long tv_ser;//秒

long tv_usec;//毫秒

};

 

select失敗返回-1,超時返回0,事件就緒返回大於0的數,表示滿足就緒事件文件描述符的個數。

例如有3個文件描述符滿足可讀操作,那麼select就返回3。

FD_ZERO(fd_set* fdset);//對fdset進行清零操作

FD_SET(int fd,fd_set *fdset);//即將fd添加到fdset的存儲描述符的數組中

FD_CLR(int fd,fd_set* fdset);//將fd從fdset的文件描述符數組中刪除

int FD_ISSET(int fd,fd_set *fdset);//即檢查fdset文件描述符數組中的fd是否發生變化,即相應的事件是否滿足,如果事件滿足返回真,否則返回假。這個函數是在使用select中最常使用的一個函數,用於檢測相應文件描述符是否有事件發生。

 

下面是一個示例,用監聽socket和連接socket,包含兩個文件select.c和cli.c。

在介紹poll和epoll的時候客戶端的代碼和cli.c一樣,這裏給出,後面不再展示。

select.c

#include <stdio.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/select.h>
#include<sys/time.h>
#include<signal.h>
//同時監聽套接字的最大個數
# define MAXD 10
//收集文件描述符
void FdsAdd(int fds[],int fd)
{
    int i = 0;
    for(;i<MAXD;++i)
    {
        //找到一個位置放入
        if(fds[i]==-1)
        {
            fds[i]=fd;
            break;
        }
    }
}
//刪除文件描述符
void FdsDel(int fds[],int fd)
{
    int i = 0;
    for(;i<MAXD;++i)
    {
        if(fds[i]==fd)
        {
            fds[i]=-1;
            break;
        }
    }
}
int main()
{
    //創建套接字和地址
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    assert(sockfd!=-1);
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family=AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    assert(res!=-1);
    listen(sockfd,5);
    fd_set fdSet;//一個描述符集合,用於監聽固定事件
    int fds[MAXD];//收集要監聽的描述符
    //對fds進行初始化操作
    int i = 0;
    for(;i<MAXD;++i)
        fds[i]=-1;
    //將監聽套接字sockfd添加到集合fds中
    FdsAdd(fds,sockfd);
    while(1)
    {
        //對fdSet進行清零操作
        FD_ZERO(&fdSet);
        //用於存儲要最大的文件描述符
        int maxFd = 0;
        for(i=0;i<MAXD;++i)
        {
            if(fds[i]==-1)
                continue;
            //將文件描述符添加到fdSet中
            FD_SET(fds[i],&fdSet);
            if(fds[i]>maxFd)
                maxFd=fds[i];
        }
        //設置超時時間
        struct timeval tv = {5,0};
        int n = select(maxFd+1,&fdSet,NULL,NULL,&tv);//只對讀事件感興趣
        printf("n = %d\n",n);
        //從select的返回值來判斷
        if(n==-1)
            perror("error\n");
        else if(n==0)
            printf("time out\n");
        else
        {
            printf("進入事件處理\n");
            //判斷是哪個文件描述符有可讀的操作
            for(i = 0;i<MAXD;++i)
            {
                if(fds[i]==-1)
                    continue;
                //可讀
                if(FD_ISSET(fds[i],&fdSet))
                {
                    //監聽套接字sockfd可讀,表明有新連接
                    if(fds[i]==sockfd)
                    {
                        //創建新連接
                        struct sockaddr_in caddr;
                        int len = sizeof(caddr);
                        int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
                        if(c<0)
                            continue;
                        printf("accept c =%d\n",c);
                        //將連接套接字放入集合中
                        FdsAdd(fds,c);   
                    }
                    //監聽套接字可讀
                    else
                    {
                        char buff[128] = {0};
                        int res = recv(fds[i],buff,127,0);
                        if(res<=0)//斷開連接
                        {
                            close(fds[i]);
                            FdsDel(fds,fds[i]);
                            printf("one client is over\n");
                        }
                        else
                        {
                            printf("recv %d :%s\n",fds[i],buff);
                            send(fds[i],"ok",2,0);
                        }
             
                    }
                                                  
                }
        
           }
        }
    }
    return 0;
}

cli.c

#include <stdio.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<assert.h>
#include<string.h>
#include<stdlib.h>
int main()
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in caddr;
    memset(&caddr,0,sizeof(caddr));
    caddr.sin_family=AF_INET;
    caddr.sin_port=htons(6000);
    caddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    int res = connect(sockfd,(struct sockaddr*)&caddr,sizeof(caddr));
    assert(res!=-1);
    while(1)
    {
        printf("input:\n");
        char buff[128]={0};
        fgets(buff,128,stdin);
        if(strncmp(buff,"end",3)==0)
        {
            break;
        }
        send(sockfd,buff,strlen(buff),0);
        memset(buff,0,128);
        recv(sockfd,buff,127,0);
        printf("recv buff = %s\n",buff);
    }
    close(sockfd);
    return 0;
}

執行結果如下圖所示:

可以發現select可以完美的處理監聽套接字和連接套接字。

我們可以發現,select有個缺點,假設我們有一個文件描述符,如果我們要監聽它的可讀和可寫,那麼這個文件描述符就需要傳入到兩個fd_set中,再將兩個fd_set傳入到select中,略顯繁瑣,另一個系統調用poll將這個問題完美的解決掉了。另外,select監測的事件只有3種,而poll將監測事件細化了。

3 poll 

poll和select非常相似,也是在指定事件內輪詢一定數量的文件描述符,檢測是否有就緒者。

至於poll和select有什麼區別,看完poll的相關操作就明瞭了。

#include<poll.h>

int poll(struct pollfd* fds,nfds_t nfds,int timeout);

用於監聽文件描述符。nfds文件描述符個數,timeout爲超時時間,單位毫秒,如果爲-1,poll永遠阻塞,爲0立即返回。

返回值的含義也和select相同,所以我們着重看一下pollfd這個結構體。

struct pollfd

{

     int fd;//文件描述符

    short events;//註冊的事件,即感興趣的事件。可以同時註冊多個事件,用“|”隔開即可

    short revents;//實際發生的事件,當檢測有某個事件發生後,內核會將事件填充到這個變量裏。如果沒有事件發生,內核會將這個變量清零。

}

poll中傳入的是一個pollfd數組

poll可以監測的事件和相應的描述如下所示:(圖片來源於網絡)

其中POLLIN最常用。接下來我們來看poll的示例,兩個文件poll.c和cli.c,cli.c上一節中已經給出。

poll.c

#include <stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<poll.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

//監聽文件符最大個數
#define MAXFD 10
//創建監聽套接字
int CreateSocket()
{
    int sockfd= socket(AF_INET,SOCK_STREAM,0);
    assert(sockfd!=-1);
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family=AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    assert(res!=-1);
    listen(sockfd,5);
    return sockfd;
}
//創建連接套接字
int  CreateConSocket(int sockfd)
{
    struct sockaddr_in caddr;
    int len = sizeof(caddr);
    memset(&caddr,0,sizeof(caddr));
    int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
    assert(c>=0);
    printf("accept c = %d\n",c);
    return c;
}
//添加文件描述符
void FdsAdd(struct pollfd fds[],int fd)
{
    int i =0;
    for(;i<MAXFD;++i)
    {
        //插入到合適的位置
        if(fds[i].fd==-1)
        {
            fds[i].fd= fd;
            fds[i].events=POLLIN;//註冊可讀事件
            fds[i].revents=0;
            break;
        }
    }
}
//刪除文件描述符
void FdsDel(struct pollfd fds[],int fd)
{
    int i = 0;
    for(;i<MAXFD;++i)
    {
        if(fds[i].fd==-1)
            continue;
        if(fds[i].fd==fd)
        {
            fds[i].fd=-1;
            fds[i].events=0;
            fds[i].revents=0;
            break;
        }
    }
}
//對pollfd數組進行初始化
void FdsInit(struct pollfd fds[])
{
    int i = 0;
    for(;i<MAXFD;++i)
    {
        fds[i].fd=-1;
        fds[i].events=0;
        fds[i].revents=0;
    }
}


int main()
{
    int sockfd = CreateSocket();
    struct pollfd fds[MAXFD];
    FdsInit(fds);
    FdsAdd(fds,sockfd);
    while(1)
    {
        int n = poll(fds,MAXFD,5000);//5秒超時
        if(n==-1)
            perror("poll error\n");
        if(n ==0)
            printf("time out\n");
        else
        {
            int i = 0;
            for(;i<MAXFD;++i)
            {
                if(fds[i].fd==-1)
                    continue;
                if(fds[i].revents&POLLIN)
                {
		            //監聽套接字
                    if(fds[i].fd==sockfd)
                    {
                        int c =CreateConSocket(sockfd);
                        FdsAdd(fds,c);
                     }
                    //連接套接字
                    else
                    {
                        char buff[128]={0};
                        int n = recv(fds[i].fd,buff,127,0);
                        if(n<=0)
                        {
                            close(fds[i].fd);
                             FdsDel(fds,fds[i].fd);
                            printf("one client is over\n");
                        }
                        else
                        {
                            printf("recv %d:%s\n",fds[i].fd,buff);
                            send(fds[i].fd,"ok",2,0);
                        }
                }
               }
                
            }
        }

    }
    return 0;
}

程序運行效果如下圖所示:

select和poll採用的都是輪詢檢測的機制,即每次調用都要重複的將文件描述符傳入到內核當中,這一點很大程度上降低了程序的運行效率,因此linux提出了自己特有的epoll。

4 epoll(linux特有)

epoll是linux特有的I/O複用函數,它在實現、使用上與select和poll有很大的差異。首先,epoll是用一組函數來完成監聽任務,而不是一個函數。其次,epoll把用戶關心的文件描述符上的事件放在內核中的一個事件表上,無需像select和poll那樣每次都將文件描述符拷入到內核當中。

4.1 epoll介紹與示例

接下來我們介紹一下epoll的幾個操作函數。

#include<sys/epoll.h>

int epoll_create(int size);

此函數用於在內核中創建一個事件表,size並不起作用,只是給一個提示,告訴內核事件表多大。

函數返回內核事件表的文件描述符。

因爲epoll需要在內核中去創建一個內核事件表,因此需要使用一個額外的文件描述符去標識內核中的這個事件表。

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

此函數用於對事件表進行操作,epfd爲epoll_create的返回值。

op爲操作方式,有如下幾種:

EPOLL_CTL_ADD:向事件表中添加事件。

EPOLL_CTL_MOD:修改事件表上的事件。

EPOLL_CTL_DEL:刪除事件表上的事件。

fd爲要操作的文件描述符,event參數指定事件,即用來表明用戶感興趣的文件描述事件。

epoll_event的定義如下:

struct epoll_event

{

    _uint32_t events;//epoll事件,即用戶感興趣的事件

    epoll_data_t data; //用戶數據

};

typedef union epoll_data

{

   ……

   int fd;//用來存儲文件描述符

   ……

};

當要刪除文件描述符的時候,可以將event設爲NULL.

epoll的核心函數爲epoll_wait,用於監聽文件描述符

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

epfd、maxevents和timeout很好理解。

events是指向一個epoll_event的數組,是一個用來收集文件描述符的集合。內核會將事件發生的文件描述符和相關信息存入events。

返回值的含義與select和poll一樣。

什麼都沒有代碼來的直觀,我們能來看一下epoll的示例。epoll.c和cli.c,cli.c和前面一樣。

#include <stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<sys/epoll.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>

//監聽文件符最大個數
#define MAXFD 10
//創建監聽套接字
int CreateSocket()
{
    int sockfd= socket(AF_INET,SOCK_STREAM,0);
    assert(sockfd!=-1);
    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family=AF_INET;
    saddr.sin_port = htons(6000);
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    assert(res!=-1);
    listen(sockfd,5);
    return sockfd;
}
//創建連接套接字
int  CreateConSocket(int sockfd)
{
    struct sockaddr_in caddr;
    int len = sizeof(caddr);
    memset(&caddr,0,sizeof(caddr));
    int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
    if(c<0)
        perror("accept error\n");
    printf("accept c = %d\n",c);
    return c;
}
//向事件表中註冊事件
void EpollAdd(int epfd,int fd)
{
    //創建epoll_event結構體
    struct epoll_event ev;
    ev.data.fd=fd;
    ev.events=EPOLLIN;//這個事件和poll的很相似,前面加E
    //將事件添加到事件表
    if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
        perror("epoll add error\n");
}
//從事件表中刪除事件
void EpollDel(int epfd,int fd)
{
    if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
        perror("epoll del error\n");
}
int main()
{
    int sockfd = CreateSocket();
    //創建內核事件表
    int epfd = epoll_create(MAXFD);
    if(epfd==-1)
        perror("epoll create errror\n");
    //將監聽套接字加入
    EpollAdd(epfd,sockfd);
    //用來存儲事件發生變化的文件描述符和相關的信息
    struct epoll_event  events[MAXFD];
    while(1)
    {
        int n = epoll_wait(epfd,events,MAXFD,5000);
        if(n==-1)
            perror("epoll wait error\n");
        if(n==0)
            printf("time out\n");
        else
        {
            int i = 0;
            for(;i<n;++i)
            {
                int fd = events[i].data.fd;
                if(events[i].events&EPOLLIN)
                {
                    //監聽套接字
                    if(fd==sockfd)
                    {
                        int c =CreateConSocket(sockfd);
                        EpollAdd(epfd,c);
                    }
                    else
                    {
                        char buff[128]={0};
                        int res = recv(fd,buff,127,0);
                        if(res<=0)
                        {
                            EpollDel(epfd,fd);
                        
                            close(fd);
                            printf("one client is over\n");
                        }
                        else
                        {
                            printf("recv %d : %s\n",fd,buff);
                            send(fd,"ok",2,0);
                        }
                    }
                }
            }
        }
    }
    return 0;

}

代碼運行結果如下圖所示:

你以爲epoll這就完了?想的太簡單,接下來我介紹一些epoll的兩種工作模式。

4.2 LT模式和ET模式

linux epoll的工作方式有兩種,一種是LT(電平觸發)模式,另一種是ET(邊沿觸發)模式。

LT模式:即當文件描述符上有數據的時候,如果一次沒有讀完,io複用函數會一直提醒我們知道數據讀完。LT模式下有阻塞和非阻塞兩種模式,epoll默認的工作方式是阻塞的LT模式。

ET模式:當數據描述符上有數據時,io複用函數只會提醒一次。因此在ET模式下,當文件描述符事件發生的時候,要一次將數據處理完,如果一次沒有將數據處理完那麼不會有第二次提醒。因此ET工作方式只有非阻塞模式,因爲如果是阻塞模式的話,那麼程序一定會阻塞在最後一次的write或者read函數。

如上述,ET模式很大程度上降低了同一個epoll事件被重複觸發的次數,因此ET模式的效率要比LT模式高。

應用下面代碼可以將文件描述符設置位非阻塞的:

void setnonblock(int fd)
{
	int oldfl = fcntl(fd,F_GETFL);//取得文件標誌位
	int newfl = oldfl|O_NONBLOCK;//設置非阻塞模式
	if(fcntl(fd,F_SETFL,newfl)==-1)
	{
		perror("fcntl error\n");
	}

}

下面的代碼實現了一個非阻塞的ET模式,大家可以參考下面代碼來搞清楚上述函數setnoblock是怎麼使用的。



# include<stdio.h>
# include<unistd.h>
# include<stdlib.h>
# include<string.h>
# include<assert.h>
# include<poll.h>
# include<netinet/in.h>
# include<sys/socket.h>
# include<arpa/inet.h>
# include<sys/epoll.h>
# include<fcntl.h>
# include<signal.h>
# define MAXFD 10
# include <errno.h>
int pipefd[2];



int create_sockfd()
{
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd ==-1)
	{
		return -1;
	}
	struct sockaddr_in saddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
	int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
	if(res ==-1)
	{
		return -1;
	}
	listen(sockfd,5);
	return sockfd;

}
void setnonblock(int fd)
{
	int oldfl = fcntl(fd,F_GETFL);//取得文件標誌位
	int newfl = oldfl|O_NONBLOCK;//設置非阻塞模式
	if(fcntl(fd,F_SETFL,newfl)==-1)
	{
		perror("fcntl error\n");
	}

}
void epoll_add(int epfd,int fd)
{

	struct epoll_event ev;
	ev.data.fd = fd;
	ev.events = EPOLLIN|EPOLLRDHUP;//開啓et EPOLLET
	if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
	{
		perror("epoll add error \n");
	}
	setnonblock(fd);
}
void epoll_del(int epfd,int fd)
{
	if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
	{
		perror("epoll del error");
	}
}
void fun(int sig)
{
	write(pipefd[1],&sig,sizeof(sig));
}
int main()
{
	int sockfd = create_sockfd();
	assert(sockfd !=-1);
	int epfd = epoll_create(MAXFD);//創建了一個內核事件表
	assert(epfd!=-1);
	epoll_add(epfd,sockfd);
	pipe(pipefd);
	epoll_add(epfd,pipefd[0]);
	signal(SIGINT,fun);
	struct epoll_event events[MAXFD];
	int run =1;	
	while(run)
	{
		int n = epoll_wait(epfd,events,MAXFD,5000);
		if(n==-1)
		{
			if(errno!=EINTR)
			{
				perror("epoll wait error\n");
			}
		}
		if(n==0)
		{
			printf("time out\n");
		}
		else
		{
			int i = 0;
			for(;i<n;++i)
			{
				int fd = events[i].data.fd;
				if(events[i].events&EPOLLIN)
				{
					if(fd == sockfd)
					{
						struct sockaddr caddr;
						int len = sizeof(caddr);
						int c =accept(sockfd,(struct sockaddr*)&caddr,&len);
						if(c<0)
						{
							continue;
						}
						printf("accept c = %d\n",c);
						epoll_add(epfd,c);
					}
					else if(fd == pipefd[0])
					{
						int sig = 0;
						read(pipefd[0],&sig,sizeof(sig));
						printf("recv sig = %d",sig);
						run = 0;				
					}
					else
					{
							char buf[128]={0};
							int res = recv(fd,buf,1,0);
							if(res==0)
							{
								epoll_del(epfd,fd);
								close(fd);
								//epoll_del(epfd,fd);
								printf("client %d is out\n",fd);
							}
							else if(res==-1)
							{
								send(fd,"ok",2,0);
								break;
							}
							else
							{
								printf("recv(%d)=%s\n",fd,buf);
							}
						
					}
				}
			}
		
		}
	
	}
	close(sockfd);
	close(epfd);
	printf("service over\n");
	exit(0);
}

如上所示,ET模式除了在添加事件的時候要添加EPOLLRDHUP事件,開啓ET模式外,還要設置非阻塞的文件描述符。

5 select、poll和epoll的區別

經過上面的幾個示例,對三種模式有了很深的瞭解,三者的區別上述也斷斷續續有提到,下圖展示了三種模式的詳細區別。

I/O複用是網絡編程的一個重點內容,面試也會經常問道,希望我的博文能夠幫助大家進行理解。同時歡迎各位批評指正。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章