多路復用 select

2021-08-21 03:56:43 字數 4044 閱讀 1303

在之前寫過一篇五種i/o模型,感興趣的可以去看一下,今天主要講其中的一種,那就是i/o多路復用。因為i/o多路復用可以使乙個程序同時處理多個連線。這對提高程式的效能至關重要。對於io復用的概念與理解在上文說的挺清楚了。本文主要說實現io復用的系統呼叫。

在linux下,實現io復用的系統呼叫主要有三個:selectpollepoll,下面我們將對其進行逐一講解。

##select

select是通過將需要監聽的檔案描述符加入相應的檔案描述符集合(readset、writeset,exceptset),由核心負責監視相應的檔案描述符是否就緒。

###select api

select函式原形如下:

#include #include int select(int nfds,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
#include typedef long int __fd_mask;

#define __nfdbits (8 * (int) sizeof (__fd_mask))

typedef struct

fd_set;

fd_set結構體的定義實際包含的是fds_bits位陣列,該陣列的每個元素的每一位標記乙個檔案描述符其大小固定,由fd_setsize指定(/usr/include/bits/typesizes.h中),在當前核心中數值為1024,可見每次select系統呼叫可監聽處理的檔案描述符最大數量為1024。

由於位操作過於繁瑣,所以我們採用下列一些的巨集來操作該結構體中的位:

fd_zero(fd_set *fdset);將指定的檔案描述符集清空。在對檔案描述符集合進行設定前,必須對其進行初始化,如果不清空,由於在系統分配記憶體空間後,通常並不作清空處理,所以結果是不可知的。

**fd_set(fd_set *fdset);**用於在檔案描述符集合中增加乙個新的檔案描述符。

**fd_clr(fd_set *fdset);**用於在檔案描述符集合中刪除乙個檔案描述符。

*fd_isset(int fd,fd_set fdset);用於測試指定的檔案描述符是否在該集合中。

最後乙個引數是超時時間

,是乙個struct timeval結構體指標,該結構體定義如下:

有兩個成員,乙個是秒,乙個是毫秒,

struct timeval
超時時間可以設定到微秒級別,有三種設定情況:

null:阻塞等待,直到某個檔案描述符上發生了事件。

0:僅檢測描述符集合的狀態,然後立即返回。

> 0: 指定超時時間,如果在該時間段裡沒有事件發生,select將超時返回。

####返回值

成功:返回就緒的檔案描述符(可讀、可寫、異常)的總數。

超時:返回0

失敗:返回-1,並設定errno

若在等待的過程中被訊號打斷,也返回-1,errno設定為eintr。

####檔案描述符就緒的條件

在網路程式設計中,下列情況下socket可讀:

1、socket核心接收快取區中的位元組數大於或等於其低水位標記so_rcvlowat。此時可以無阻塞地讀該socket,並且讀操作返回的位元組數大於0。

2、socket通訊對方關閉連線。此時對該socket讀操作將返回0。

3、監聽socket上有新的連線請求。

4、socket上有未處理的錯誤。此時我們可以使用getsockopt來讀取和清除該錯誤。

下列情況下socket可寫:

1、socket核心傳送緩衝區中的可用位元組數大於或等於其低水位標記so_sndlowat。此時我們可以無阻塞寫該socket,並且寫操作返回的位元組數大於0。

2、socket寫操作被關閉。對寫操作被關閉的socket執行寫操作將觸發乙個sigpipe訊號。

3、socket使用非阻塞connect連線成功或者失敗(超時)之後。

4、socket上有未處理的錯誤。此時我們可以使用getsockopt來讀取和清除該錯誤。

網路程式中,select能處理的異常情況只有一種:socket上接收到帶外資料。

不足:select監聽的最大檔案描述符受限於fd_setsize,unix系統通常會在標頭檔案 「sys/select.h」 中定義常量fd_setsize,一般為1024,要想更改需要重新編譯核心。而且因為select採取的是輪詢機制,當監聽的檔案描述符過多的話,效率會大大折扣。

在下文中將介紹一種新的實現io復用的函式,poll,與select相比,poll突破了最大檔案描述符是1024的限制。

##程式示例

以select實現的簡單回射伺服器作為本文的結束。

#include#include#include#include#include#include#include#include#include#include#include#define   myport  8888

#define backlog 10

#define maxdatasize 1024

int main()

//位址重複利用

int on = 1;

if(setsockopt(sock_fd,sol_socket,so_reuseaddr,&on,sizeof(on)) < 0)

//bind()函式

if(bind(sock_fd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))==-1)

//listen()函式

if(listen(sock_fd,backlog)==-1)

int i;

int client[fd_setsize];//儲存客戶端的套接字

//初始化

for(i = 0;i < fd_setsize;i++)

int nready = 0; //接受select的返回值

int maxi = -1; //儲存陣列下標

int maxfd = sock_fd;//初始化nfds

fd_set rset; //定義可讀事件集合

fd_set allset; //備份

fd_zero(&allset);

fd_set(sock_fd,&allset);

while(1)

else if(0 == nready)

//當客戶端請求連線

if(fd_isset(sock_fd,&rset))

printf("client ip: %s\t port : %d\n",inet_ntoa(their_addr.sin_addr),ntohs(their_addr.sin_port));

//將客戶端的套接字存入client

for(i = 0;i < fd_setsize;i++)

}//判斷是否達到連線上限

if(i == fd_setsize)

//將新加入的客戶端放入監聽隊伍

fd_set(new_fd,&allset);

//更新nfds

if(new_fd > maxfd)

//更新maxi

if(i > maxi)

//判斷是否已經處理完事件

if((--nready) == 0)

}//當客戶端傳送資料

for(i = 0;i <= maxi;i++)

if(fd_isset(connfd,&rset))

else if(numbytes == 0)

send(connfd,buf,numbytes,0);

}if((--nready) == 0)}}

close(sock_fd);

close(new_fd);

return 0;

}

select多路復用

在某些場景下我們需要同時從多個通道接收資料。通道在接收資料時,如果沒有資料可以接收發生阻塞。你也許會寫出 使用遍歷的方式來實現 for 這種方式雖然可以實現從多個channel接收值的需求,但是執行效能會差很多。為了應對這種場景,go內建了select關鍵字,可以同時響應多個通道的操作。select...

select 函式 多路復用

select 的機制中提供一fd set的資料結構,實際上是一long型別的陣列,每乙個陣列元素都能與一開啟的檔案控制代碼 不管是socket控制代碼,還是其他 檔案或命名管道或裝置控制代碼 建立聯絡,建立聯絡的工作由程式設計師完成,當呼叫select 時,由核心根據io狀態修改fd set的內容,...

IO多路復用 select

select系統呼叫的目的是 在一段指定時間內,監聽使用者感興趣的檔案描述符上的可讀 可寫和異常事件。poll和select應該被歸類為這樣的系統 呼叫,它們可以阻塞地同時探測一組支援非阻塞的io裝置,直至某乙個裝置觸發了事件或者超過了指定的等待時間 也就是說它們的職責不是做io,而是幫助 呼叫者尋...