目錄
- 1.IO多路轉接之select
- 1.初識select
- 2.select()
- 3.關于fd_set結構
- 4.關于timeval結構
- 5.理解select執行過程
- 6.select就緒條件
- 7.select特點
- 8.select優點(任何一個多路轉接方案,都具備)
- 9.select缺點
- 10.select的一般編寫代碼的模式
- 11.思考 && 問題
- 2.IO多路轉接之poll
- 1.poll()
- 2.pollfd結構
- 3.poll就緒條件
- 4.poll的優點
- 5.poll缺點
1.IO多路轉接之select
1.初識select
- select系統調用是用來讓程序監視多個文件描述符的狀態變化的
- 程序會停在select這里等待,直到被監視的文件描述符有一個或多個發生了狀態改變
- 總結:
- 幫用戶進行一次等待多個文件描述符
- 當哪些文件描述符就緒了,select就要通知用戶,對應就緒的sock有哪些,然后用戶再調用recv/read等接口進行數據讀取
2.select()
-
原型:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
-
參數:
- nfds:要監視的最大的文件描述符+1
- readfds,writefds,exceptfds:均為輸入輸出型參數,分別對應于需要檢測的可讀/可寫/異常文件描述符的集合
- 輸入時:用戶告訴內核,你要幫我關心哪些fd的哪一種事件
- bit位的位置表示文件描述符值,bit位的內容表示是否關心(1/0)
- 輸出時:內核告訴用戶,我所關心的fd中,哪些fd上的哪類事件已經就緒了
- bit位的位置表示文件描述符值,bit位的內容表示是否就緒(1/0)
- 用戶和內核都會修改同一個位圖結構,這些參數用一次之后,一定要進行重新設定
- 輸入時:用戶告訴內核,你要幫我關心哪些fd的哪一種事件
- timeout:用來設置select()的等待時間,其取值
- nullptr:阻塞等待
- 0:非阻塞等待
- 特定時間值:如果在指定的時間段里沒有事件發生,select()將超時返回
- 如果等待時間內,有fd就緒,會怎樣呢?
- 呈現輸出型,存放距離下一次timeout剩余多長時間
-
返回值:
- 執行成功則返回文件描述詞狀態已改變的個數
- 如果返回0代表在描述詞狀態改變前已超過timeout時間,沒有返回
- 當有錯誤發生時則返回-1,錯誤原因存于errno,此時參數readfds,writefds, exceptfds和timeout的值變成不可預測
-
錯誤碼可能值:
- EBADF:文件描述詞為無效的或該文件已關閉
- EINTR:此調用被信號所中斷
- EINVAL:參數n為負值
- ENOMEM:核心內存不足
3.關于fd_set結構
/* The fd_set member is required to be an array of longs. */
typedef long int __fd_mask;typedef struct{/* XPG4.2 requires this member name. Otherwise avoid the namefrom the global namespace. */
#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->fds_bits)
#else__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
# define __FDS_BITS(set) ((set)->__fds_bits)
#endif} fd_set;
- 其實這個結構就是一個整數數組,更嚴格的說,是一個**“位圖”,使用位圖中對應的bit位來表示要監視的文件描述符**
- 提供了一組操作fd_set的接口, 來比較方便的操作位圖
void FD_CLR(int fd, fd_set *set); // 用來清除描述詞組set中相關fd的bit位 int FD_ISSET(int fd, fd_set *set); // 用來測試描述詞組set中相關fd的but位是否為真 void FD_SET(int fd, fd_set *set); // 用來設置描述詞組set中相關fd的bit位 void FD_ZERO(fd_set *set); // 用來清除描述詞組set的全部bit位
4.關于timeval結構
- timeval結構用于描述一段時間長度,如果在這個時間內,需要監視的文件描述符沒有事件發生則函數返回,返回值為0
struct timeval {__time_t tv_sec; /* Seconds. */__suseconds_t tv_usec; /* Microseconds. */ };
5.理解select執行過程
- 理解select模型的關鍵在于理解fd_set,為說明方便,取fd_set長度為1字節
- fd_set中的每一bit可以對應一個文件描述符fd,則1字節長的fd_set最大可以對應8個fd.
- 執行fd_set set; FD_ZERO(&set);
- 則set用位表示是0000,0000
- 若fd=5,執行FD_SET(fd, &set);
- set變為0001,0000(第5個bit位置為1)
- 若再加入fd=2,fd=1
- 則set變為0001,0011
- 執行**select(6, &set, nullptr, nullptr, nullptr)**阻塞等待
- 若fd=1,fd=2上都發生可讀事件,則select返回
- 此時set變為0000,0011
- **注意:**沒有事件發生的fd=5被清空
- 執行fd_set set; FD_ZERO(&set);
6.select就緒條件
- 讀就緒
- socket內核中,接收緩沖區中的字節數,大于等于低水位標記SO_RCVLOWAT
- 此時可以無阻塞的讀該文件描述符,并且返回值大于0
- socket TCP通信中,對端關閉連接,此時對該socket讀,則返回0
- 監聽的socket上有新的連接請求
- socket上有未處理的錯誤
- socket內核中,接收緩沖區中的字節數,大于等于低水位標記SO_RCVLOWAT
- 寫就緒
- socket內核中,發送緩沖區中的可用字節數(發送緩沖區的空閑位置大小),大于等于低水位標記SO_SNDLOWAT
- 此時可以無阻塞的寫,并且返回值大于0
- socket的寫操作被關閉(close或者shutdown),對一個寫操作被關閉的socket進行寫操作,會觸發SIGPIPE信號
- socket使用非阻塞connect連接成功或失敗之后
- socket上有未讀取的錯誤
- socket內核中,發送緩沖區中的可用字節數(發送緩沖區的空閑位置大小),大于等于低水位標記SO_SNDLOWAT
7.select特點
- 可監控的文件描述符個數取決與sizeof(fd_set)的值
- 我虛擬機上sizeof(fd_set)=128,每bit表示一個文件描述符,則支持的最大文件描述符是128*8=1024
- 將fd加入select監控集的同時,還要再使用一個第三方數組用來保存所有的合法fd
- 用于在select返回后,array作為源數據和fd_set進行FD_ISSET判斷
- select返回后會把以前加入的但并無事件發生的fd清空,則每次開始select前都要重新從array取得fd并逐一加入(FD_ZERO最先),掃描array的同時取得fd最大值maxfd,用于select的第一個參數
8.select優點(任何一個多路轉接方案,都具備)
- 效率高
- 應用場景:有大量的鏈接,但是只有少量是活躍的,節省資源
9.select缺點
- 為了維護第三方數組,select服務器會充滿大量遍歷,OS底層幫用戶關心fd的時候,也要遍歷
- 每一次都要對select輸出參數進行重新設定
- 能夠同時管理的fd的個數是有上限的 <-- fd_set大小是被固定了的
- 因為幾乎每一個參數都是輸入輸出型的,select一定會頻繁地進行用戶到內核,內核到用戶的參數數據拷貝
- 編碼比較復雜
10.select的一般編寫代碼的模式
while(true)
{// 1.遍歷數組,更新出最大值// 2.遍歷數組,添加所有需要關心的fd到fd_set位圖中// 3.調用select進行事件檢測// 4.遍歷數組,找到就緒的事件,根據就緒的事件,完成對應的動作// a.Accepter b.recver
}
11.思考 && 問題
- 如何看待_listensock?
- 獲取新連接,依然把它看成IO,如果沒有連接到來,就阻塞
- 參數影響編碼模式
- nfds:隨著獲取的sock越來越多,添加到select的sock越來越多,注定了nfds每一次都可能要變化,需要對它動態計算
- readfds/writefds/exceptfds:都是輸入輸出型參數,輸入輸出不一定是一樣的,所以注定每次都要對其進行重新添加
- timeout:輸入輸出型參數,每一次都要進行重置,前提是需要的話
- 1,2 --> 注定必須自己將合法的fd單獨全部保存起來,以支持:a.更新最大fd b.更新位圖結構
2.IO多路轉接之poll
1.poll()
- 原型:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
- 參數:
- fds:是一個poll()監聽的結構的數組,每一個元素中,包含了三部分內容:文件描述符,監聽的事件集合,返回的事件集合
- nfds:表示fds數組的長度
- **timeout:
- 表示poll()的超時時間,單位是毫秒
- 設置為0,表示非阻塞等待
- 設置為-1,表示阻塞等待
- 返回值:
- 執行成功則返回文件描述詞狀態已改變的個數
- 如果返回0代表在描述詞狀態改變前已超過timeout時間,沒有返回
- 當有錯誤發生時則返回-1,錯誤原因存于errno
2.pollfd結構
struct pollfd
{int fd; /* File descriptor to poll. */short int events; /* Types of events poller cares about. */short int revents; /* Types of events that actually occurred. */
};
- events****和revents的取值
事件 | 描述 | 是否可作為輸入 | 是否可作為輸出 |
---|---|---|---|
POLLIN | 數據(包括普通數據和優先數據)可讀 | 是 | 是 |
POLLRDNORM | 普通數據可讀 | 是 | 是 |
POLLRDBAND | 優先級帶數據可讀(Linux不支持) | 是 | 是 |
POLLPRI | 高優先級數據可讀,比如TCP帶外數據 | 是 | 是 |
POLLOUT | 數據(包括普通數據和優先數據)可寫 | 是 | 是 |
POLLWRNORM | 普通數據可寫 | 是 | 是 |
POLLRDHUP | TCP****連接被對方關閉,或者對方關閉了寫操作,它由GNU引入 | 是 | 是 |
POLLERR | 錯誤 | 否 | 是 |
POLLHUP | 掛起,比如管道的寫端被關閉后,讀端描述符上將收到POLLHUP事件 | 否 | 是 |
POLLNVAL | 文件描述符沒有打開 | 否 | 是 |
3.poll就緒條件
- 同select
4.poll的優點
- 效率高
- 應用場景:有大量的鏈接,但是只有少量是活躍的,節省資源
- 輸入輸出參數是分離的,不需要進行大量的重置
- 沒有可以管理的fd最大數量限制
5.poll缺點
- poll依舊需要不少的遍歷,在用戶層檢測事件就緒、內核檢測fd就緒,都需要大量遍歷
- 且用戶還是需要自己維護第三方數組
- poll需要內核到用戶的拷貝 – 少不了的
- poll的代碼結構依然比較復雜 – 比select容易