I/O多路複用和Socket

原文:I/O多路複用和Sockethtml

因爲IO操做涉及到系統調用,涉及到用戶空間和內核空間的切換,因此理解系統的IO模型,對於須要進入到系統調用層面進行編程來講是很重要的。編程

阻塞IO和非阻塞IO

從程序編寫的角度來看,I/O就是調用一個或多個系統函數,完成對輸入輸出設備的操做。輸入輸出設置能夠是顯示器、字符終端命令行、網絡適配器、磁盤等。操做系統在這些設備與用戶程序之間完成一個銜接,稱爲驅動程序,驅動程序向下驅動硬件,向上提供抽象的函數調用入口。服務器

通常來講I/O操做是須要時間的,由於這涉及到系統、硬件等計算器模塊的互相配合,因此必然不像普通的函數調用那樣可以按照既定的方式當即返回。從用戶代碼的角度,I/O操做的系統調用分爲「阻塞」和「非阻塞」兩種。網絡

  • 「阻塞」的調用會在I/O調用完成前,掛起調用線程,即CPU會再也不執行後續代碼,而是等到I/O完成後再回來繼續執行,在用戶代碼看來,線程中止執行了,在調用處等待了。socket

  • 「非阻塞」的調用則不一樣,I/O調用基本上是當即返回,並且每每實際上I/O此時並無完成,因此須要用戶的程序輪詢結果。函數

那麼咱們以網絡IO爲例,看一下對於一個服務器,「阻塞」和「非阻塞」兩種模式,該如何設計。因爲服務器要同時服務多個客戶端,因此須要同時操做多個Socket。性能

能夠看到,若是使用阻塞的IO方式,由於每一個Socket都會阻塞,爲了同時服務多個客戶端,須要多個線程同時掛起;而若是採用非阻塞的調用方式,則須要在一個線程中不斷輪訓每一個客戶端是否有數據到來。spa

顯然純粹阻塞式的調用不可取,非阻塞式的調用看起來不錯,可是仍不夠好,由於輪詢實際也是經過某種系統調用完成的,至關於在用戶空間進行的,效率不高,若是可以在內核空間進行這種相似輪詢,而後讓內核通知用戶空間哪一個IO就緒了,就更好了。因而引出接下來的概念:IO多路複用操作系統

IO多路複用

IO多路複用是一種系統調用,內核可以同時對多個IO描述符進行就緒檢查。當全部被監聽的IO都沒有就緒時,調用將阻塞;當至少有一個IO描述符就緒時,調用將返回,用戶代碼可經過檢查到底是哪一個IO就緒來進一步處理業務。顯然,IO多路複用是解決系統裏面存在N個IO描述符的問題的,這裏必須明確IO複用和IO阻塞與否並非一個概念,IO複用只檢測IO是否就緒(讀就緒或者寫就緒等),具體的數據的輸入輸出仍是須要依靠具體的IO操做完成(阻塞操做或非阻塞操做)。最典型的IO多路複用技術有selectpollepoll等。select具備最大數量描述符限制,而epoll則沒有,而且在機制上,epoll也更爲高效。select的優點僅僅是跨平臺支持性,全部平臺和較低版本的內核都支持select模式,epoll則不是。命令行

在IO相關的編程中,IO複用起到的做用至關於一個閥門,讓後續IO操做更爲精準高效。

編程模型

綜上討論,咱們在進行實際的Socket編程的時候,不管是客戶端仍是服務端,大體有幾種模式能夠選擇:

  1. 阻塞式。純採用阻塞式,這種方式不多見,基本只會出如今demo中。多個描述符須要用多個進程或者線程來一一對應處理。

  2. 非阻塞式。純非阻塞式,對IO的就緒與否須要在用戶空間經過輪詢來實現。

  3. IO多路複用+阻塞式。僅使用一個線程就能夠實現對多個描述符的狀態管理,但因爲IO輸入輸出調用自己是阻塞的,可能出現某個IO輸入輸出過慢,影響其餘描述符的效率,從而體現出總體性能不高。此種方式編程難度比較低。

  4. IO多路複用+非阻塞式。在多路複用的基礎上,IO採用非阻塞式,能夠大大下降單個描述符的IO速度對其餘IO的影響,不過此種方式編程難度較高,主要表如今須要考慮一些慢速讀寫時的邊界狀況,好比讀黏包、寫緩衝不夠等。

下面以select爲例,整理 在select下,socket的阻塞和非阻塞的一些問題。這些細節在編寫基於Socket的網絡程序時,尤爲是底層數據收發時,是十分重要的。

socket讀就緒:

  • 【阻/非阻】接收緩衝區有數據,數據量大於SO_RCVLOWAT水位(默認是0)。此時調用recv將返回>0(即讀到的字節數)。

  • 【阻/非阻】對端關閉,即收到FIN。此時調用recv將返回=0。

  • 【阻/非阻】accept到一個新的鏈接,此時accept一般不會阻塞。

  • 【阻/非阻】socket發生某種錯誤。此時調用recv將返回-1,並應經過getsockopt獲得相應的待處理錯誤。

socket寫就緒:

  • 【阻/非阻】發送緩衝區有空餘的空間,空間大小大於SO_SNDLOWAT水位(默認是2048)。這種就緒是水平觸發的,只要有空間就會觸發寫就緒,即若是保持對這種套接字的就緒檢查將使得select每次都認爲有描述符寫就緒。因此應當對描述符進行寫狀態管理,一旦某個描述符可寫,應當即中止對該描述符的寫狀態檢查,直到寫緩衝區滿後,再次select寫狀態。

  • 【阻/非阻】鏈接的寫半部關閉,此時調用send將產生SIGPIPE信號。

  • 【非阻】connect完成。因爲非阻的connect將不會阻塞握手過程,因此,當握手在後續時刻完成後,在此保持寫狀態檢查,將觸發一次就緒,表示connect完成。

  • 【阻/非阻】socket發生某種錯誤。此時調用send將返回-1,並應經過getsockopt獲得相應的待處理錯誤。

補充:

非阻的調用recvsendaccept,分別地,若是收緩衝中無數據、發送緩衝不夠空間發、沒有外來鏈接,將當即返回,此時全局errno將獲得EWOULDBLOCKEAGIAN,表示「本應阻塞的調用,因爲採用了非阻塞模式,而返回」。非阻的調用connect將當即返回,此時全局errno將獲得EINPROGRESS,表示鏈接正在進行。

相關文章
相關標籤/搜索