I/O復用基本概念
I/O多路復用技術通過把多個I/O的阻塞復用到同一個select的阻塞上,從而使得系統在單線程的情況下可以同時處理多個客戶端請求。與傳統的多線程/多進程模型比,I/O多路復用的最大優勢是系統開銷小,系統不需要創建新的額外進程或者線程,也不需要維護這些進程和線程的運行,降底了系統的維護工作量,節省了系統資源。
select模型
使用select函數時可以將多個文件描述符集中到一起統一監視。監視項成為“事件”(event)。
-
select函數調用方法和順序:
步驟一:設置文件描述符、指定監視范圍、設置超時
步驟二:調用select函數
步驟三:查看調用結果
-
1、設置文件描述符
利用select函數可以同時監視多個文件描述符,首先需要將要監視的文件描述符集中到一起。集中時要按照監視項(接收、傳輸、異常)進行區分,即按照上述3中監視項分成3類。
使用fd_set數組變量執行此項操作。如果該位設置為1,則表示該文件描述符是監視對象。
在fd_set變量中注冊或更改值的操作都由下列宏完成:
FD_ZERO(fd_set *fdset):將fd_set變量的所有位初始化為0。
FD_SET(int fd,fd_set *fdset):在參數fdset指向的變量中注冊文件描述符fd的信息。
FD_CLR(int fd,fd_set *fdset):從參數fdset指向的變量中清楚文件描述符fd的信息。
FD_ISSET(int fd,fd_set * fdsest):若參數fdset指向的變量中包含文件描述符fd的信息,則返回“真”。
-
2、設置檢查(監視)范圍及超時
int select(
int maxfd,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval * timeout
);
成功時返回大于0的值,失敗時返回-1。發生錯誤時返回-1,超時返回時返回0。因發生關注的事件返回時,返回大于0的值,該值是發生事件的文件描述符。
參數解釋:
1、maxfd:監視對象文件描述符
2、readset:將所有關注“是否存在待讀取數據”的文件描述符注冊到fd_set型變量,并傳遞其地址值
3、writeset:將所有關注“是否可傳輸無阻塞數據”的文件描述符注冊到fd_set型變量,并傳遞其地址值
4、exceptset:將所有關注“是否發生異常” 的文件描述符注冊到fd_set型變量,并傳遞其地址值
5、timeout:調用select函數后,為防止陷入無限阻塞的狀態,傳入超時(time-out)信息
- select函數用來驗證3種監視項的變化情況。根據監視項聲明3個fd_set變量,分別向其注冊文件描述符信息,并把變量的地址值傳遞到上述函數的第二個到第四個參數。
- select函數只有在監視的文件描述符發生變化時才返回。如果未發生變化,就會進入阻塞狀態。將秒數填入tv_sec成員,將微秒數填入tv_user成員,然后將結構體的地址值傳遞到select函數的最后一個參數。
timeval結構體定義:
struct timeval
{
long tv_sec;//seconds
long tv_usec;//microseconds
}
-
3、調用select函數后查看結果
如果返回大于0的整數,說明相應數量的文件描述符發生變化。
文件描述符的變化是指監視的文件描述符中發生了相應的監視事件。
select函數調用完成后,向其傳遞的fd_set變量中將發生變化。原來為1的所有位均變為0,但發生變化的文件描述符對應位除外。因此,可以認為值仍未1的位置上的文件描述符發生了變化。
-
4、基于Windows的實現
int select(
int nfds,fd_set *readfds,fd_set *writefds,fd_set *excepfds,const struct timeval *timeout);
成功時返回0,失敗時返回-1
timeval結構體定義
typedef struct timeval
{
long tv_sec;//seconds
long tv_usec;//microseconds
}
【重點】Windows中的fd_set結構體
typedef struct fd_set
{
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
}fd_set;
Windows的fd_set由成員fd_count和fd_array構成,fd_count用于套接字句柄數,fd_array用于保存套接字句柄。
原因:Linux的文件描述符從0開始遞增,因此可以找出當前文件描述符數量和最后生成的文件描述符之間的關系。但Window的套接字句柄并非從0開始,而且句柄的整數值之間并無規律可循,
因此需要直接保存句柄的數組和記錄句柄數的變量。
PS:處理fd_set結構體的FD_XXX型的4個宏的名稱、功能及使用方法與Linux完全相同。
優于select的epoll【Linux】
1、與傳統select模型的對比
- 基于select的I/O復用缺點
1、調用select函數后常見的針對所有文件描述符的循環語句。
2、每次調用select函數時都需要向該函數傳遞監視對象信息。
應用程序向操作系統傳遞數據將對程序造成很大負擔。有些函數不需要操作系統的幫助就能完成功能,有些則必須借助于操作系統。select函數與文件描述符有關,是監視套接字變化的函數。而套接字是由操作系統管理的,所以select函數絕對需要借助于操作系統才能完成功能。 - 解決方法:
僅向操作系統傳遞1次監視對象,監視范圍或內容發生變化時只通知發生變化的事項。Linux的支持方式是epoll,Windows的支持方式是IOCP。 - select優點
大部分操作系統都支持select函數,因此基于select的I/Of復用具有兼容性。
服務器接入者少并且程序應具有兼容性時,應考慮使用select模型。
2、實現epoll時必要的函數和結構體
- epoll函數的優點
1、無需編寫以監視狀態變化為目的的針對所有文件描述符的循環語句。
2、調用對應于select函數的epoll_wait函數時無需每次傳遞監視對象信息。 - epoll服務器端實現中需要的三個函數
1 、epoll_create : 創建保存epoll文件描述符的空間。
2、epoll_clt:向空間注冊并注銷文件描述符。
3、epoll_wait:與select函數類似,等待文件描述符發生變化。
- epoll方式下由操作系統負責保存監視對象文件描述符,因此需要向操作系統請求創建保存文件描述符的空間,調用epoll_create函數。
- 在epoll方式中,通過epoll_clt函數請求操作系統完成添加和刪除監事對象文件描述符。
- epoll中調用epoll_wait函數等待文件描述符的變化。
- epoll方式中通過如下結構體epoll_event將發生變化的(發生事件的)文件描述符單獨集中到一起。
struct epoll_event
{
__unit32_t events;
epoll_data_t data;
}
typedef union epoll_data
{
void *ptr;
int fd;
__unit32_t u32;
__unit64_t u64;
}epoll_data_t;
聲明足夠大的epoll_event結構體數組后,傳遞給epoll_wait函數時,發生變化的文件描述符信息將被填入該數組。因此,無需像select函數那樣針對所有文件描述符進行循環。
epoll_create
int epoll_create(int size);
成功時返回epoll文件描述符,失敗時返回-1.
參數解釋:
size: epoll實例大小。
該函數返回的文件描述符主要用于區分epoll例程。需要終止時,與其他文件描述符相同,也要調用close函數。
epoll_ctl
- 生成epoll例程后,應在其內部注冊監視對象文件描述符,此時使用epoll_clt.
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
成功時返回0,失敗時返回-1。
參數解釋:
1、epfd:用于注冊監視對象的epoll例程的文件描述符。
2、op:用于指定監視對象的添加、刪除或更改等操作。
3、fd:需要注冊的監視對象文件描述符。
4、event:監視對象的事件類型。
- 向第二個參數傳遞的常量及含義:
1、EPOLL_CTL_ADD:將文件描述符注冊到epoll例程
2、EPOLL_CTL_DEL:從epoll例程中刪除文件描述符
3、EPOLL_CTL_MOD:更改注冊的文件描述符的關注事件發生情況
PS:向epoll_ctl的第二個參數傳遞EPOLL_CTL_DEL時,應同時向第四個參數傳遞NULL。
- epoll_event結構體用于保存發生事件的文件描述符集合。也可以在epoll例程中注冊文件描述符時,用于注冊關注的事件。
struct epoll_event event;
...
event.events = EPOLLIN;//發生需要讀取數據的情況(事件)時
event.data.fd = sockfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&event);
- epoll_event的成員events中可以保存的常量及所指的事件類型:
EPOLLIN:需要讀取數據的情況
EPOLLOUT:輸出緩沖為空,可以立即發送數據的情況
EPOLLPRI:收到OOB數據的情況
EPOLLRDHUP:斷開連接或半關閉的情況,這在邊緣觸發方式下非常有用
EPOLLERR:發生錯誤的情況
EPOLLET:以邊緣觸發的方式得到事件通知
EPOLLONESHOT:發生一次事件后,相應文件描述符不再收到事件通知。因此需要向epoll_ctl函數的第二個參數傳遞EPOLL_CTL_MOD,再次設置事件。
可以通過位或運算同時傳遞多個上述參數。
epoll_wait
- epoll_wait與select函數對應,epoll相關函數中默認最后調用該函數。
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
成功時返回發生事件的文件描述符,失敗時返回-1。
參數解釋:
1、epfd:表示事件發生監視范圍的epoll例程的文件描述符。
2、events:保存發生事件的文件描述符集合的結構體地址值。
3、maxevents:第二個參數中可以保存的最大事件數。
4、timeout:以1/1000秒為單位的等待時間,傳遞-1時,一直等待直到發生事件。
- 第二個參數所指緩沖需要動態分配。
int event_cnt;
struct epoll_event *ep_events;
...
ep_events = malloc(sizeof(struct epoll_event)*EPOLL_SIZE);//EPOLL_SIZE是宏常量
...
event_cnt = epoll_wait(epfd,ep_events,EPOLL_SIZE,-1);
- 函數調用后,返回發生事件的文件描述符數,同時在第二個參數指向的緩沖中保存發生事件的文件描述符集合。因此,無需像select那樣插入針對所有文件描述符的循環。
條件觸發和邊緣觸發
- 條件觸發方式中,只要輸入緩沖有數據就會一直通知該事件。
- 邊緣觸發中輸入緩沖收到數據時僅注冊1次該事件。即使輸入緩沖中還留有數據,也不會再進行注冊。
IOCP 完成端口【Windows】
異步通知I/O模型
- 異步通知I/O模型
異步I/O是指I/O函數的返回時刻與數據收發的完成時刻不一致,異步方式能夠比同步方式更有效地使用CPU。
異步通知I/O中指定I/O監視對象的函數和實際驗證狀態變化的函數是相互分離的。因此,指定監視對象后可以離開執行其他任務,最后再回來驗證狀態變化。
設置超時時間可以在未發生I/O狀態變化的狀態下防止函數阻塞,所以可以編寫類似異步方式的代碼。
- WSAEventSelect函數和通知I/O狀態的變化分為:
1、套接字的變化:套接字的I/O狀態變化
2、發生套接字相關事件:發生套接字I/O相關事件 - WSAEventSelect 用于指定某一套接字為事件監視對象
int WSAEventSelect(SOCKET s,WSAEVENT hEventObject,long lNetworkEvents);
成功時返回0,失敗時返回SOCKET_ERROR
參數解釋:
1、s:監視對象的套接字句柄
2、hEventObject:傳遞事件對象句柄以驗證事件發生與否
3、lNetworkEvents:希望監視的事件類型信息
- 傳入參數s的套接字內只要發生lNetworkEvents中指定的事件之一,WSAEventSelect函數就將hEventObject句柄所指內核對象改為signal狀態。因此,該函數又稱“連接事件對象和套接字的函數”。
無論事件發生與否,WSAEventSelect函數調用后都會直接返回,所以可以執行其他任務。 - 第三個參數的事件類型信息:(可以通過位或運算同時指定多個信息)。
FD_READ:是否存在需要接收的數據?
FD_WRITE:是否能以非阻塞方式傳輸數據?
FD_OOB:是否收到帶外數據?
FD_ACCEPT:是否有新的連接請求?
FD_CLOSE:是否有斷開連接的請求?
WSAEventSelect函數只能針對1個套接字對象使用。