Linux網(wǎng)絡(luò)編程——I/O復(fù)用之select詳解

一、I/O復(fù)用概述
I/O復(fù)用概念:

解決進(jìn)程或線程阻塞到某個(gè) I/O 系統(tǒng)調(diào)用而出現(xiàn)的技術(shù)呈驶,使進(jìn)程不阻塞于某個(gè)特定的 I/O 系統(tǒng)調(diào)

I/O復(fù)用使用的場(chǎng)合:

1.當(dāng)客戶處理多個(gè)描述符(通常是交互式輸入努释、網(wǎng)絡(luò)套接字)時(shí)碘梢,必須使用I/O復(fù)用。
2.tcp服務(wù)器既要處理監(jiān)聽套接字伐蒂,又要處理已連接套接字煞躬,一般要使用I/O復(fù)用。
3.如果一個(gè)服務(wù)器既要處理tcp又要處理udp,一般要使用I/O復(fù)用恩沛。
4.如果一個(gè)服務(wù)器要處理多個(gè)服務(wù)或多個(gè)服務(wù)時(shí)在扰,一般要使用I/O復(fù)用。

I/O復(fù)用常用函數(shù):

select复唤、poll

二健田、select()函數(shù)
select函數(shù)介紹:
int select(int maxfd, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
功能:輪詢掃描多個(gè)描述符中的任一描述符是否發(fā)生響應(yīng),或經(jīng)過一段時(shí)間后喚醒

返回值:

0:超時(shí)
-1:出錯(cuò)

0:準(zhǔn)備好的文件描述符數(shù)量

頭文件:

#include <sys/select.h>  
#include <sys/time.h>  

超時(shí)時(shí)間:

//該結(jié)構(gòu)體表示等待超時(shí)的時(shí)間  
struct timeval{  
    long tv_sec;//秒  
    long tv_usec;//微秒  
};  
  
//比如等待10.2秒  
struct timeval timeout;  
timeoout.tv_sec = 10;  
timeoout.tv_usec = 200000;  
  
//將select函數(shù)的timeout參數(shù)設(shè)置為NULL則永遠(yuǎn)等待  

描述符集合的操作:
select()函數(shù)能對(duì)多個(gè)文件描述符進(jìn)行監(jiān)測(cè)佛纫,如果一個(gè)參數(shù)對(duì)應(yīng)一個(gè)描述符妓局,那么select函數(shù)的4個(gè)參數(shù)最多能監(jiān)測(cè)4個(gè)文件描述,那他如何實(shí)現(xiàn)對(duì)多個(gè)文件描述符的監(jiān)測(cè)的呢呈宇?
大家想一想文件描述符基本具有3種特性(讀好爬、寫、異常)甥啄,如果我們統(tǒng)一將監(jiān)測(cè)可讀的描述符放入可讀集合(readset)存炮,監(jiān)測(cè)可寫的描述符放入可寫集合(writeset),監(jiān)測(cè)異常的描述符放入異常集合(exceptset)蜈漓。然后將這3個(gè)集合傳給select函數(shù)穆桂,是不是就可監(jiān)測(cè)多個(gè)描述符呢.

如何將某個(gè)描述符加入到特定的集合中呢?這時(shí)就需要了解下面的集合操作函數(shù)

/初始化描述符集  
void FD_ZERO(fd_set *fdset);  
  
//將一個(gè)描述符添加到描述符集  
void FD_SET(int fd, fd_set *fdset);  
  
//將一個(gè)描述符從描述符集中刪除  
void FD_CLR(int fd, fd_set *fdset);  
  
//檢測(cè)指定的描述符是否有事件發(fā)生  
int FD_ISSET(int fd, fd_set *fdset);  

select()函數(shù)整體使用框架:

例子:檢測(cè) 0融虽、4享完、5 描述符是否準(zhǔn)備好讀

while(1)  
{  
    fd_set rset;//創(chuàng)建一個(gè)描述符集rset  
    FD_ZERO(&rset);//對(duì)描述符集rset清零  
    FD_SET(0, &rset);//將描述符0加入到描述符集rset中  
    FD_SET(4, &rset);//將描述符4加入到描述符集rset中  
    FD_SET(5, &rset);//將描述符5加入到描述符集rset中  
      
    if(select(5+1, &rset, NULL, NULL, NULL) > 0)  
    {  
        if(FD_ISSET(0, &rset))  
        {  
            //描述符0可讀及相應(yīng)的處理代碼  
        }  
          
        if(FD_ISSET(4, &rset))  
        {  
            //描述符4可讀及相應(yīng)的處理代碼  
        }  
        if(FD_ISSET(5, &rset))  
        {  
            //描述符5可讀及相應(yīng)的處理代碼  
        }  
    }  
}  

三、select函數(shù)的應(yīng)用對(duì)比
我們通過udp同時(shí)收發(fā)的例子來說明select的妙處有额。
對(duì)于udp同時(shí)收發(fā)立馬想到的是一個(gè)線程收般又、另一個(gè)線程發(fā),下面的代碼就是通過多線程來實(shí)現(xiàn)

#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 <pthread.h>  
  
//接收線程:負(fù)責(zé)接收消息并顯示  
void *recv_thread(void* arg)  
{  
    int udpfd = (int)arg;  
    struct sockaddr_in addr;  
    socklen_t addrlen = sizeof(addr);  
      
    bzero(&addr,sizeof(addr));  
    while(1)  
    {  
        char buf[200]  = "";  
        char ipbuf[16] = "";  
        recvfrom(udpfd, buf, sizeof(buf), 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);  
        write(1,"UdpQQ:",6);  
    }  
    return NULL;  
}  
  
int main(int argc,char *argv[])  
{  
    char buf[100] = "";  
    int  udpfd = 0;  
    pthread_t tid;  
    struct sockaddr_in addr;  
    struct sockaddr_in cliaddr;  
      
    //對(duì)套接字地址進(jìn)行初始化  
    bzero(&addr,sizeof(addr));  
    addr.sin_family = AF_INET;  
    addr.sin_port   = htons(8000);  
    addr.sin_addr.s_addr = htonl(INADDR_ANY);  
  
    bzero(&cliaddr,sizeof(cliaddr));      
    cliaddr.sin_family = AF_INET;  
    cliaddr.sin_port   = htons(8000);  
  
    //創(chuàng)建套接口  
    if( (udpfd = socket(AF_INET,SOCK_DGRAM, 0)) < 0)  
    {  
        perror("socket error");  
        exit(-1);  
    }  
      
    //設(shè)置端口  
    if(bind(udpfd, (struct sockaddr*)&addr, sizeof(addr)) < 0)  
    {  
        perror("bind error");  
        close(udpfd);         
        exit(-1);  
    }  
      
    printf("input: \"sayto 192.168.221.X\" to sendmsg to somebody\n");  
    //創(chuàng)建接收線程  
    pthread_create(&tid, NULL, recv_thread, (void*)udpfd); //創(chuàng)建線程  
    printf("\033[32m"); //設(shè)置字體顏色  
    fflush(stdout);  
      
    while(1)  
    {     
        //主線程負(fù)責(zé)發(fā)送消息  
        write(1,"UdpQQ:",6);//1 表示標(biāo)準(zhǔn)輸出  
        fgets(buf, sizeof(buf), stdin); //等待輸入  
        buf[strlen(buf) - 1] = '\0';    //確保輸入的最后一位是'\0'  
        if(strncmp(buf, "sayto", 5) == 0)  
        {  
            char ipbuf[INET_ADDRSTRLEN] = "";  
            inet_pton(AF_INET, buf+6, &cliaddr.sin_addr);//給addr套接字地址再賦值.  
            printf("\rconnect %s successful!\n",inet_ntop(AF_INET,&cliaddr.sin_addr,ipbuf,sizeof(ipbuf)));  
            continue;  
        }  
        else if(strncmp(buf, "exit",4) == 0)  
        {  
            close(udpfd);  
            exit(0);  
        }  
          
        sendto(udpfd, buf, strlen(buf),0,(struct sockaddr*)&cliaddr, sizeof(cliaddr));  
    }  
      
    return 0;  
}  

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


用select來完成上述同樣的功能:

#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>  
  
int main(int argc,char *argv[])  
{  
    int udpfd = 0;  
    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");    
    while(1)  
    {     
        char buf[100]="";     
        fd_set rset;    //創(chuàng)建文件描述符的聚合變量    
        FD_ZERO(&rset); //文件描述符聚合變量清0  
        FD_SET(0, &rset);//將標(biāo)準(zhǔn)輸入添加到文件描述符聚合變量中  
        FD_SET(udpfd, &rset);//將udpfd添加到文件描述符聚合變量中        
        write(1,"UdpQQ:",6);  
          
        if(select(udpfd + 1, &rset, NULL, NULL, NULL) > 0)  
        {  
            if(FD_ISSET(0, &rset))//測(cè)試0是否可讀寫  
            {                 
                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));  
            }  
            if(FD_ISSET(udpfd, &rset))//測(cè)試udpfd是否可讀寫  
            {  
                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);  
            }  
        }  
    }  
      
    return 0;  
}  

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


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末巍佑,一起剝皮案震驚了整個(gè)濱河市茴迁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌萤衰,老刑警劉巖堕义,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異脆栋,居然都是意外死亡胳螟,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門筹吐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來糖耸,“玉大人,你說我怎么就攤上這事丘薛〖尉梗” “怎么了?”我有些...
    開封第一講書人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)舍扰。 經(jīng)常有香客問我倦蚪,道長(zhǎng),這世上最難降的妖魔是什么边苹? 我笑而不...
    開封第一講書人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任陵且,我火速辦了婚禮,結(jié)果婚禮上个束,老公的妹妹穿的比我還像新娘慕购。我一直安慰自己,他們只是感情好茬底,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開白布沪悲。 她就那樣靜靜地躺著,像睡著了一般阱表。 火紅的嫁衣襯著肌膚如雪殿如。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評(píng)論 1 290
  • 那天最爬,我揣著相機(jī)與錄音涉馁,去河邊找鬼。 笑死爱致,一個(gè)胖子當(dāng)著我的面吹牛烤送,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蒜鸡,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼胯努,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼牢裳!你這毒婦竟也來了逢防?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蒲讯,失蹤者是張志新(化名)和其女友劉穎忘朝,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體判帮,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡局嘁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了晦墙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悦昵。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖晌畅,靈堂內(nèi)的尸體忽然破棺而出但指,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布棋凳,位于F島的核電站拦坠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏剩岳。R本人自食惡果不足惜贞滨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望拍棕。 院中可真熱鬧晓铆,春花似錦、人聲如沸莫湘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)幅垮。三九已至腰池,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間忙芒,已是汗流浹背示弓。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留呵萨,地道東北人奏属。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像潮峦,于是被迫代替她去往敵國(guó)和親囱皿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

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