IO多路復用之select總結(jié)

參考鏈接: IO多路復用之select總結(jié)

1懊缺、基本概念

IO多路復用是指內(nèi)核一旦發(fā)現(xiàn)進程指定的一個或者多個IO條件準備讀取,它就通知該進程培他。IO多路復用適用如下場合:

(1)當客戶處理多個描述字時(一般是交互式輸入和網(wǎng)絡(luò)套接口)鹃两,必須使用I/O復用。

(2)當一個客戶同時處理多個套接口時舀凛,而這種情況是可能的俊扳,但很少出現(xiàn)。

(3)如果一個TCP服務器既要處理監(jiān)聽套接口猛遍,又要處理已連接套接口馋记,一般也要用到I/O復用。

(4)如果一個服務器即要處理TCP懊烤,又要處理UDP梯醒,一般要使用I/O復用。

(5)如果一個服務器要處理多個服務或多個協(xié)議腌紧,一般要使用I/O復用茸习。

與多進程和多線程技術(shù)相比,I/O多路復用技術(shù)的最大優(yōu)勢是系統(tǒng)開銷小壁肋,系統(tǒng)不必創(chuàng)建進程/線程号胚,也不必維護這些進程/線程,從而大大減小了系統(tǒng)的開銷浸遗。

2猫胁、select函數(shù)

該函數(shù)準許進程指示內(nèi)核等待多個事件中的任何一個發(fā)送,并只在有一個或多個事件發(fā)生或經(jīng)歷一段指定的時間后才喚醒乙帮。函數(shù)原型如下:

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

int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值:就緒描述符的數(shù)目杜漠,超時返回0,出錯返回-1

函數(shù)參數(shù)介紹如下:

(1)第一個參數(shù)maxfdp1指定待測試的描述字個數(shù),它的值是待測試的最大描述字加1(因此把該參數(shù)命名為maxfdp1)驾茴,描述字0盼樟、1、2...maxfdp1-1均將被測試锈至。

因為文件描述符是從0開始的晨缴。

(2)中間的三個參數(shù)readset、writeset和exceptset指定我們要讓內(nèi)核測試讀峡捡、寫和異常條件的描述字击碗。如果對某一個的條件不感興趣,就可以把它設(shè)為空指針们拙。struct fd_set可以理解為一個集合稍途,這個集合中存放的是文件描述符,可通過以下四個宏進行設(shè)置:

void FD_ZERO(fd_set *fdset);           // 清空集合

void FD_SET(int fd, fd_set *fdset);   //將一個給定的文件描述符加入集合之中

void FD_CLR(int fd, fd_set *fdset);   //將一個給定的文件描述符從集合中刪除

int FD_ISSET(int fd, fd_set *fdset);   // 檢查集合中指定的文件描述符是否可以讀寫 

(3)timeout告知內(nèi)核等待所指定描述字中的任何一個就緒可花多少時間砚婆。其timeval結(jié)構(gòu)用于指定這段時間的秒數(shù)和微秒數(shù)械拍。

struct timeval{

    long tv_sec;   //seconds

    long tv_usec;  //microseconds

};

這個參數(shù)有三種可能:

(1)永遠等待下去:僅在有一個描述字準備好I/O時才返回。為此装盯,把該參數(shù)設(shè)置為空指針NULL坷虑。

(2)等待一段固定時間:在有一個描述字準備好I/O時返回,但是不超過由該參數(shù)所指向的timeval結(jié)構(gòu)中指定的秒數(shù)和微秒數(shù)埂奈。

(3)根本不等待:檢查描述字后立即返回迄损,這稱為輪詢。為此账磺,該參數(shù)必須指向一個timeval結(jié)構(gòu)芹敌,而且其中的定時器值必須為0。

原理圖:

3垮抗、測試程序

寫一個TCP回射程序党窜,程序的功能是:客戶端向服務器發(fā)送信息,服務器接收并原樣發(fā)送給客戶端借宵,客戶端顯示出接收到的信息。

服務端程序如下:


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

#define IPADDR      "127.0.0.1"
#define PORT        8787
#define MAXLINE     1024
#define LISTENQ     5
#define SIZE        10

typedef struct server_context_st
{
    int cli_cnt;        /*客戶端個數(shù)*/
    int clifds[SIZE];   /*客戶端的個數(shù)*/
    fd_set allfds;      /*句柄集合*/
    int maxfd;          /*句柄最大值*/
} server_context_st;

static server_context_st *s_srv_ctx = NULL;

/*===========================================================================
 * ==========================================================================*/
/* 創(chuàng)建服務,開始監(jiān)聽客戶端請求 */
static int create_server_proc(const char* ip,int port)
{
    int  fd;
    struct sockaddr_in servaddr;
    
    // 創(chuàng)建socket
    fd = socket(AF_INET, SOCK_STREAM,0);
    
    // 創(chuàng)建socket 失敗
    if (fd == -1) {
        fprintf(stderr, "create socket fail,erron:%d,reason:%s\n",
                errno, strerror(errno));
        return -1;
    }
    
    /*一個端口釋放后會等待兩分鐘之后才能再被使用矾削,SO_REUSEADDR是讓端口釋放后立即就可以被再次使用壤玫。*/
    int reuse = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {
        return -1;
    }
    
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    
    /* inet_pton:將“點分十進制” -> “二進制整數(shù)” */
    inet_pton(AF_INET,ip,&servaddr.sin_addr);
    servaddr.sin_port = htons(port);
    
    // socket 綁定端口
    if (bind(fd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1) {
        perror("bind error: ");
        return -1;
    }
    
    // server 開始監(jiān)聽
    listen(fd,LISTENQ);
    
    return fd;
}

/** 監(jiān)聽客戶端請求*/
static int accept_client_proc(int srvfd)
{
    // 客戶端ip、port
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen;
    cliaddrlen = sizeof(cliaddr);
    int clifd = -1;
    
    printf("accpet clint proc is called.\n");

    // 接受客戶端socket鏈接哼凯;
ACCEPT:
    clifd = accept(srvfd,(struct sockaddr*)&cliaddr,&cliaddrlen);
    
    if (clifd == -1) {
        if (errno == EINTR) {
            goto ACCEPT;
        } else {
            fprintf(stderr, "accept fail,error:%s\n", strerror(errno));
            return -1;
        }
    }
    
    fprintf(stdout, "accept a new client: %s:%d\n",
            inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
    
    // 將新的連接描述符socket添加到數(shù)組中
    int i = 0;
    for (i = 0; i < SIZE; i++) {
        if (s_srv_ctx->clifds[i] < 0) {     // s_srv_ctx 初始值為-1
            s_srv_ctx->clifds[i] = clifd;
            s_srv_ctx->cli_cnt++;
            break;
        }
    }
    
    if (i == SIZE) {
        fprintf(stderr,"too many clients.\n");
        return -1;
    }
    
    return -1;
 }

static int handle_client_msg(int fd, char *buf)
{
    assert(buf);
    printf("recv buf is :%s\n", buf);
    
    // 收到消息后欲间,回寫client的消息
    write(fd, buf, strlen(buf) +1);
    
    return 0;
}

 /* 接受處理客戶端消息 */
static void recv_client_msg(fd_set *readfds)
{
    int i = 0, n = 0;
    int clifd;
    char buf[MAXLINE] = {0};
    for (i = 0;i <= s_srv_ctx->cli_cnt;i++) {
        clifd = s_srv_ctx->clifds[i];
        if (clifd < 0) {
            continue;
        }
        
        /*判斷客戶端套接字是否有數(shù)據(jù)*/
        if (FD_ISSET(clifd, readfds)) {
            
            // 接收客戶端發(fā)送的信息
            n = read(clifd, buf, MAXLINE);
            
            if (n <= 0) {
                /* n==0表示讀取完成,客戶都關(guān)閉套接字 */
                FD_CLR(clifd, &s_srv_ctx->allfds);  // FD_CLR將一個給定的文件描述符從集合中刪除
                close(clifd);
                
                s_srv_ctx->clifds[i] = -1;
                continue;
            }
            
            handle_client_msg(clifd, buf);
        }
    }
}

static void handle_client_proc(int srvfd)
{
    int  clifd = -1;
    int  retval = 0;
    fd_set *readfds = &s_srv_ctx->allfds;
    struct timeval tv;
    int i = 0;
    
    while (1) {
        
        /* 每次調(diào)用select前都要重新設(shè)置文件描述符和時間断部,因為事件發(fā)生后猎贴,文件描述符和時間都被內(nèi)核修改啦*/
        FD_ZERO(readfds);
        
        /* 添加監(jiān)聽套接字 */
        FD_SET(srvfd, readfds);
        
        s_srv_ctx->maxfd = srvfd;
        
        tv.tv_sec = 30;
        tv.tv_usec = 0;
        
        /*添加客戶端套接字*/
        for (i = 0; i < s_srv_ctx->cli_cnt; i++) {
            clifd = s_srv_ctx->clifds[i];
            
            /*去除無效的客戶端句柄*/
            if (clifd != -1) {
                FD_SET(clifd, readfds);
            }
            
            // 記錄最大描述符
            s_srv_ctx->maxfd = (clifd > s_srv_ctx->maxfd ? clifd : s_srv_ctx->maxfd);
        }
        
        /* 開始輪詢接收處理服務端和客戶端套接字*/
        retval = select(s_srv_ctx->maxfd + 1, readfds, NULL, NULL, &tv); // 服務器監(jiān)聽讀的描述字
        
        if (retval == -1) {  // -1 出錯
            fprintf(stderr, "select error:%s.\n", strerror(errno));
            return;
        }
        if (retval == 0) {  // 0 超時
            fprintf(stdout, "select is timeout.\n");
            continue;
        }
        if (FD_ISSET(srvfd, readfds)) {  // FD_ISSET 檢查集合中指定的文件描述符是否可以讀寫
            /*監(jiān)聽客戶端請求*/
            accept_client_proc(srvfd);
        } else {
            /*接受處理客戶端消息*/
            recv_client_msg(readfds);
        }
    }
}

/* 銷毀 */
static void server_uninit()
{
    if (s_srv_ctx) {
        free(s_srv_ctx);
        s_srv_ctx = NULL;
    }
}

/*初始化服務端context*/
static int server_init()
{
    s_srv_ctx = (server_context_st *)malloc(sizeof(server_context_st));
    if (s_srv_ctx == NULL) {
        return -1;
    }
    
    memset(s_srv_ctx, 0, sizeof(server_context_st));
    
    int i = 0;
    for (;i < SIZE; i++) {
        s_srv_ctx->clifds[i] = -1;  // 初始值為-1
    }
    
    return 0;
}

int main(int argc,char *argv[])
{
    int  srvfd;
    /*初始化服務端context*/
    if (server_init() < 0) {
        return -1;
    }
    
    /* 創(chuàng)建服務,開始監(jiān)聽客戶端請求 */
    srvfd = create_server_proc(IPADDR, PORT);
    if (srvfd < 0) {
        fprintf(stderr, "socket create or bind fail.\n");
        goto err;
    }
    
    /*開始接收并處理客戶端請求*/
    handle_client_proc(srvfd);
    
    server_uninit();
    return 0;
    
err:
    server_uninit();
    return -1;
}

客戶端程序如下:


#include <stdio.h>

#include <netinet/in.h>
#include <sys/socket.h>

#include <string.h>
#include <stdlib.h>
#include <sys/select.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>

#define MAXLINE 1024
#define IPADDRESS "127.0.0.1"
#define SERV_PORT 8787

#define max(a,b) (a > b) ? a : b

static void handle_recv_msg(int sockfd, char *buf)
{
    printf("client recv msg is:%s\n", buf);
    sleep(5);
    write(sockfd, buf, strlen(buf) +1);     // client 將收到的消息再次回寫到服務器
}

static void handle_connection(int sockfd)
{
    char sendline[MAXLINE],recvline[MAXLINE];
    int maxfdp,stdineof;
    fd_set readfds;
    int n;
    struct timeval tv;
    int retval = 0;
    
    while (1) {
        
        FD_ZERO(&readfds);
        FD_SET(sockfd,&readfds);    // 將一個給定的文件描述符加入集合 fd_set 之中
        maxfdp = sockfd;
        
        tv.tv_sec = 5;
        tv.tv_usec = 0;
        
        /* 開始輪詢接收處理 客戶端套 接字; 始終只有一個socket */
        retval = select(maxfdp+1,&readfds,NULL,NULL,&tv);
        
        if (retval == -1) {   // -1 出錯
            return ;
        }
        
        if (retval == 0) {   // 0 超時
            printf("client timeout.\n");
            continue;
        }
        
        if (FD_ISSET(sockfd, &readfds)) {
            n = read(sockfd,recvline,MAXLINE);      // 讀取服務端 發(fā)回的信息
            if (n <= 0) {
                fprintf(stderr,"client: server is closed.\n");
                close(sockfd);
                FD_CLR(sockfd,&readfds);
                return;
            }
            
            handle_recv_msg(sockfd, recvline);
        }
    }
}

int main(int argc,char *argv[])
{
    int sockfd;
    struct sockaddr_in servaddr;
    
     /* 連接socket */
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);
    
    /* 鏈接服務器地址 */
    int retval = 0;
    retval = connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
    
    if (retval < 0) { // 連接失敗
        fprintf(stderr, "connect fail,error:%s\n", strerror(errno));
        return -1;
    }
    
    printf("client send to server .\n");
    write(sockfd, "hello server", 32);      // 客戶端向服務器發(fā)送信息
    
     /* 處理服務器端返回 */
    handle_connection(sockfd);
    
    return 0;
}

4、程序結(jié)果

啟動服務程序,執(zhí)行三個個客戶程序進行測試她渴,結(jié)果如下圖所示:

5达址、程序在mac xcode下結(jié)果


// Server
/* 開始輪詢接收處理服務端和客戶端套接字*/
retval = select(s_srv_ctx->maxfd + 1, readfds, NULL, NULL, &tv); // 服務器監(jiān)聽讀的描述字

// Client
/* 開始輪詢接收處理 客戶端套 接字; 始終只有一個socket */
retval = select(maxfdp+1,&readfds,NULL,NULL,&tv);
DingTalk-nn.png

參考:

http://konglingchun.is-programmer.com/posts/12146.html

http://blog.163.com/smileface100@126/blog/static/27720874200951024532966/

備注:

(1)2016-10-23修改服務端,添加注釋,非法客戶端的處理趁耗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末沉唠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子苛败,更是在濱河造成了極大的恐慌满葛,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,599評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罢屈,死亡現(xiàn)場離奇詭異嘀韧,居然都是意外死亡,警方通過查閱死者的電腦和手機缠捌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,629評論 3 385
  • 文/潘曉璐 我一進店門锄贷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鄙币,你說我怎么就攤上這事肃叶。” “怎么了十嘿?”我有些...
    開封第一講書人閱讀 158,084評論 0 348
  • 文/不壞的土叔 我叫張陵因惭,是天一觀的道長。 經(jīng)常有香客問我绩衷,道長蹦魔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,708評論 1 284
  • 正文 為了忘掉前任咳燕,我火速辦了婚禮蓝翰,結(jié)果婚禮上坷随,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好慧起,可當我...
    茶點故事閱讀 65,813評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著奖亚,像睡著了一般然眼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上顶籽,一...
    開封第一講書人閱讀 50,021評論 1 291
  • 那天玩般,我揣著相機與錄音,去河邊找鬼礼饱。 笑死坏为,一個胖子當著我的面吹牛究驴,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播匀伏,決...
    沈念sama閱讀 39,120評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼洒忧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了帘撰?” 一聲冷哼從身側(cè)響起跑慕,我...
    開封第一講書人閱讀 37,866評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摧找,沒想到半個月后核行,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,308評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡蹬耘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,633評論 2 327
  • 正文 我和宋清朗相戀三年芝雪,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片综苔。...
    茶點故事閱讀 38,768評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡惩系,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出如筛,到底是詐尸還是另有隱情堡牡,我是刑警寧澤,帶...
    沈念sama閱讀 34,461評論 4 333
  • 正文 年R本政府宣布杨刨,位于F島的核電站晤柄,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏妖胀。R本人自食惡果不足惜芥颈,卻給世界環(huán)境...
    茶點故事閱讀 40,094評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望赚抡。 院中可真熱鬧爬坑,春花似錦、人聲如沸涂臣。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,850評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽赁遗。三九已至闯估,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吼和,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,082評論 1 267
  • 我被黑心中介騙來泰國打工骑素, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留炫乓,地道東北人刚夺。 一個月前我還...
    沈念sama閱讀 46,571評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像末捣,于是被迫代替她去往敵國和親侠姑。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,666評論 2 350

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

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理箩做,服務發(fā)現(xiàn)莽红,斷路器,智...
    卡卡羅2017閱讀 134,637評論 18 139
  • 《UNIX 網(wǎng)絡(luò)編程卷一:套接字聯(lián)網(wǎng)API》筆記 套接字 套接字編程接口邦邦,是在 TCP/IP 協(xié)議族中安吁,應用層進入...
    超net閱讀 5,789評論 2 13
  • 本文摘抄自linux基礎(chǔ)編程 IO概念 Linux的內(nèi)核將所有外部設(shè)備都可以看做一個文件來操作。那么我們對與外部設(shè)...
    lintong閱讀 1,572評論 0 4
  • 文/梵高的火柴 我眺望燃辖,向著你來的方向鬼店,直到我變成了稻草人,不會說話黔龟,不會唱歌妇智,只有一群麻雀陪伴我,一邊吃掉我氏身,一...
    梵高的火柴閱讀 540評論 0 1
  • 如果以智商來區(qū)別人 你真是傻子 我說人與人沒什么差距 區(qū)別在于那點的勇氣與堅持
    浪痕閱讀 562評論 0 49