I/O複用使得程序能同時監聽多個文件描述符,對提高程序的性能至關重要。
I/O複用雖然能同時監聽多個文件描述符,但它本身是阻塞的。當多個文件描述符同時就緒時,如果不採取額外的措施,程序就只能按順序依次處理其中的每一個文件描述符,服務器程序看起來像是串行工作的。如果要實現併發,只能使用多進程或多線程等編程手段。
linux下實現I/O複用的系統調用主要有select、poll、epoll。
select系統調用
select系統調用的用途是:在一段指定時間內,監聽用戶感興趣的文件描述符上的可讀、可寫和異常等事件。
#include <sys/select.h>
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
1、nfds參數指定被監聽的文件描述符的總數。通常被設置爲select監聽的所有文件描述符中的最大值加1,因爲文件描述符是從0開始計數的。
2、readfds、writefds和exceptfds參數分別指向可讀、可寫和異常等事件對應的文件描述符集合。
fd_set結構體僅包含一個整形數組,該數組的每個元素的每一位(bit)標記一個文件描述符。fd_set能容納的文件描述符數量有FD_SETSIZE指定,這就限制了select能同時處理的文件描述符的數量。
由於位操作過於煩瑣,使用下面的一系列宏來訪問fd_set結構體中的位:
#include <sys/select.h>
FD_ZERO(fd_set *fdset); //清除fdset的所有位
FD_SET(int fd, fd_set *fdset); //設置fdset的位fd
FD_CLR(int fd, fd_set *fdset); //清除fdset的位fd
int FD_ISSET(int fd, fd_set *fdset); //測試fdset的位fd是否被設置
3、timeout參數用來設置select函數的超時時間。
select成功時返回就緒(可讀、可寫和異常)文件描述符的總數。如果在超時時間內沒有任何文件描述符就緒,select返回0。select失敗時返回-1並設置error。如果在select等待期間,程序接收到信號,則select立即返回-1,並設置error爲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上接收到帶外數據。