在之前寫過一篇五種i/o模型,感興趣的可以去看一下,今天主要講其中的一種,那就是i/o多路復用。因為i/o多路復用可以使乙個程序同時處理多個連線。這對提高程式的效能至關重要。對於io復用的概念與理解在上文說的挺清楚了。本文主要說實現io復用的系統呼叫。
在linux下,實現io復用的系統呼叫主要有三個:select、poll和epoll,下面我們將對其進行逐一講解。
##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,而是幫助 呼叫者尋...