Linux網(wǎng)絡(luò)編程——I/O復(fù)用之poll函數(shù)

一、回顧前面的select
select優(yōu)點(diǎn):

目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點(diǎn)

select缺點(diǎn):

1.每次調(diào)用 select(),都需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個開銷在 fd 很多時會很大,同時每次調(diào)用 select() 都需要在內(nèi)核遍歷傳遞進(jìn)來的所有 fd,這個開銷在 fd 很多時也很大。

2.單個進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在 Linux 上一般為 1024,可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但是這樣也會造成效率的降低

二、poll函數(shù)概述
select() 和 poll() 系統(tǒng)調(diào)用的本質(zhì)一樣,poll() 的機(jī)制與 select() 類似,與 select() 在本質(zhì)上沒有多大差別,管理多個描述符也是進(jìn)行輪詢,根據(jù)描述符的狀態(tài)進(jìn)行處理,但是 poll() 沒有最大文件描述符數(shù)量的限制(但是數(shù)量過大后性能也是會下降)。poll() 和 select() 同樣存在一個缺點(diǎn)就是,包含大量文件描述符的數(shù)組被整體復(fù)制于用戶態(tài)和內(nèi)核的地址空間之間,而不論這些文件描述符是否就緒,它的開銷隨著文件描述符數(shù)量的增加而線性增大。
poll()函數(shù)介紹
頭文件:

#include <poll.h>  

函數(shù)體:

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

功能:

監(jiān)視并等待多個文件描述符的屬性變化

參數(shù):

fds:指向一個結(jié)構(gòu)體數(shù)組的第0個元素的指針,每個數(shù)組元素都是一個struct pollfd結(jié)構(gòu),用于指定測試某個給定的fd的條件

struct pollfd{  
    int fd;         //文件描述符  
    short events;   //等待的事件  
    short revents;  //實(shí)際發(fā)生的事件  
};  

fd:每一個 pollfd 結(jié)構(gòu)體指定了一個被監(jiān)視的文件描述符,可以傳遞多個結(jié)構(gòu)體,指示 poll() 監(jiān)視多個文件描述符。

events:指定監(jiān)測fd的事件(輸入、輸出、錯誤),每一個事件有多個取值,如下:

revents:revents 域是文件描述符的操作結(jié)果事件,內(nèi)核在調(diào)用返回時設(shè)置這個域。events 域中請求的任何事件都可能在 revents 域中返回.

注意:每個結(jié)構(gòu)體的 events 域是由用戶來設(shè)置,告訴內(nèi)核我們關(guān)注的是什么,而 revents 域是返回時內(nèi)核設(shè)置的,以說明對該描述符發(fā)生了什么事件

nfds:用來指定第一個參數(shù)數(shù)組元素個數(shù)
timeout: 指定等待的毫秒數(shù),無論 I/O 是否準(zhǔn)備好,poll() 都會返回.

返回值:

成功時,poll() 返回結(jié)構(gòu)體中 revents 域不為 0 的文件描述符個數(shù);如果在超時前沒有任何事件發(fā)生,poll()返回 0;
失敗時,poll() 返回 -1,并設(shè)置 errno 為下列值之一:

EBADF:一個或多個結(jié)構(gòu)體中指定的文件描述符無效。

EFAULT:fds 指針指向的地址超出進(jìn)程的地址空間。

EINTR:請求的事件之前產(chǎn)生一個信號,調(diào)用可以重新發(fā)起。

EINVAL:nfds 參數(shù)超出 PLIMIT_NOFILE 值。

ENOMEM:可用內(nèi)存不足,無法完成請求。

三、poll示例舉例
用poll實(shí)現(xiàn)udp同時收發(fā)
代碼:

#include <string.h>  
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <sys/select.h>  
#include <sys/time.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <poll.h>  
  
int main(int argc,char *argv[])  
{  
    int udpfd = 0;  
    int ret = 0;  
    struct pollfd fds[2];//監(jiān)測文件描述結(jié)構(gòu)體數(shù)組:2個  
    struct sockaddr_in saddr;  
    struct sockaddr_in caddr;  
  
    bzero(&saddr,sizeof(saddr));  
    saddr.sin_family = AF_INET;  
    saddr.sin_port   = htons(8000);  
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);  
      
    bzero(&caddr,sizeof(caddr));  
    caddr.sin_family  = AF_INET;  
    caddr.sin_port    = htons(8000);  
      
    //創(chuàng)建套接字  
    if( (udpfd = socket(AF_INET,SOCK_DGRAM, 0)) < 0)  
    {  
        perror("socket error");  
        exit(-1);  
    }  
      
    //套接字端口綁字  
    if(bind(udpfd, (struct sockaddr*)&saddr, sizeof(saddr)) != 0)  
    {  
        perror("bind error");  
        close(udpfd);         
        exit(-1);  
    }  
  
    printf("input: \"sayto 192.168.220.X\" to sendmsg to somebody\033[32m\n");    
    fds[0].fd = 0;      //標(biāo)準(zhǔn)輸入描述符  
    fds[1].fd = udpfd;  //udp描述符  
      
    fds[0].events = POLLIN; // 普通或優(yōu)先級帶數(shù)據(jù)可讀    
    fds[1].events = POLLIN; // 普通或優(yōu)先級帶數(shù)據(jù)可讀  
      
    while(1)  
    {     
        // 監(jiān)視并等待多個文件(標(biāo)準(zhǔn)輸入,udp套接字)描述符的屬性變化(是否可讀)    
        // 沒有屬性變化,這個函數(shù)會阻塞,直到有變化才往下執(zhí)行,這里沒有設(shè)置超時    
        ret = poll(fds, 2, -1);   
          
        write(1,"UdpQQ:",6);  
          
        if(ret == -1){ // 出錯    
            perror("poll()");    
        }  
        else if(ret > 0){ // 準(zhǔn)備就緒的文件描述符    
            char buf[100] = {0};    
            if( ( fds[0].revents & POLLIN ) ==  POLLIN ){ // 標(biāo)準(zhǔn)輸入    
                  
                fgets(buf, sizeof(buf), stdin);  
                buf[strlen(buf) - 1] = '\0';  
                if(strncmp(buf, "sayto", 5) == 0)  
                {  
                    char ipbuf[16] = "";  
                    inet_pton(AF_INET, buf+6, &caddr.sin_addr);//給addr套接字地址再賦值.  
                    printf("\rsay to %s\n",inet_ntop(AF_INET,&caddr.sin_addr,ipbuf,sizeof(ipbuf)));  
                    continue;  
                }  
                else if(strcmp(buf, "exit")==0)  
                {  
                    close(udpfd);  
                    exit(0);  
                }  
                sendto(udpfd, buf, strlen(buf),0,(struct sockaddr*)&caddr, sizeof(caddr));    
                    
            }  
            else if( ( fds[1].revents & POLLIN ) ==  POLLIN ){ //udp套接字    
                struct sockaddr_in addr;  
                char ipbuf[INET_ADDRSTRLEN] = "";  
                socklen_t addrlen = sizeof(addr);  
                  
                bzero(&addr,sizeof(addr));  
                  
                recvfrom(udpfd, buf, 100, 0, (struct sockaddr*)&addr, &addrlen);  
                printf("\r\033[31m[%s]:\033[32m%s\n",inet_ntop(AF_INET,&addr.sin_addr,ipbuf,sizeof(ipbuf)),buf);    
            }    
                
        }  
        else if(0 == ret){ // 超時    
            printf("time out\n");    
        }    
    }  
      
    return 0;  
}  

運(yùn)行結(jié)果:

Paste_Image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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