一、網絡編程的開發平臺
一般來說,目前開發者面對的主流開發平臺就是Windows和類Unix,或者干脆就是Windows和Linux平臺。至于如IBM或其它等公司的相關平臺,可能對絕大多數開發者來說,可能一生都遇不到。至于嵌入式平臺,除了專有的網絡通信外,一般情況下,不會有太復雜的網絡編程場景。基本都是借助網絡進行通信,而且大多基本也都是客戶端。
從上面的說明可以大概定義出,網絡編程的平臺,粗略的可以認為,針對絕大多數開發者,或者干脆就是Windows和Linux平臺。
而在網絡編程中,一般來講,客戶端編程是比較簡單的。大多數情況下基本都是一對一的通信,即使有異步等復雜情況,處理起來也相對簡單(不是絕對)。所以謂網絡編程一談起來復雜的原因,其實指的服務端。也就是說,在上述的兩個平臺上,如何進行高并發的服務端編程,這才是網絡編程的復雜度和難度的體現。
二、網絡服務端編程的特點
網絡服務端編程的難點和復雜度在哪兒?有如下幾點:
1、異步編程
即網絡數據的接收與發送的及時處理和非同步話的底層機制(即指排除簡單的網絡編程外)
2、內存管理
網絡IO與上層應用,上層應用與上層應用中大數據的交換時,內存的安全管理
3、多線程或者說線程池(協程)
高并發的網絡天然的需要多線程加快數據的吞吐,同時CPU核數的增加也要求更好的將多線程程與并發(并行)有機的結合起來
4、非阻塞IO
非阻塞IO等同于你做你的,我做我的。有事兒打招呼,沒事兒別找事兒。它的特點是不管有沒有IO可操作(讀寫),都會立即返回,只是返回值不同。即通過返回值可以判斷是否需要再次進行IO操作。所以,基本非阻塞IO都需要一個輪詢的過程。
5、異步IO
這里需要明白的是異步IO與非阻塞IO的不同,雖然二者可能在某些情況下被表述人混淆為一談,但其實是兩回事。異步IO強調的是對IO的異步操作,即更傾向于上層的應用。這個上層指的是IO的上層,可能是內核級也可能是用戶空間級。而非阻塞IO則指的IO操作本身,是否需要等待。異步IO其實是異步與IO操作的結合,當然開發者談起的異步IO,大多都是指異步和非阻塞IO的結合。
6、上述的結合
如果覺得上面的可能都不是多難,那么就一起吧。解決高并發的網絡服務時,把上述的技術融合到一起,如果還覺得得簡單,那么你就成為了一個真正的大牛。
三、網絡編程IO模型
所謂模型,可以理解為處理IO的標準流程或者說動作。而網絡IO模型就是處理網絡IO數據的流程。為了應對網絡IO的處理,無論哪個平臺,都可以抽象成幾個大類的模型:
1、阻塞I/O (blocking I/O)
這個非常容易理解,程序發起IO請求,如果內核沒有準備好,則阻塞,所謂阻塞就是等待,等待著內核準備好再把數據傳遞回來時,再進行動作。舉個例子,朝老婆要錢買東西,老婆去拿錢,你就得一直在原地等待,什么你不等待走了。好吧。錢也沒有了,當然東西也別買了。
2、非阻塞I/O (nonblocking I/O)
這個就有點意思,其實就是在用戶發起IO請求后,內核返回一個值(error,這個值大多數情況下不是一個真正的錯誤)告訴應用程序我開始準備數據了,你先去搞別的吧。回頭我把數據準備好你再來操作。所以上層應用會不斷的通過輪詢機制來查看數據是否準備成功。當然,也有可能已經準備好了,直接就把數據讀過來了,不阻塞。這就比如向老婆要錢買東西,老婆說,我去拿錢,你先忙別的,回頭我把錢放你書桌上。那你就可以去先洗碗,洗一個碗就去書桌看看有沒有錢,沒錢接著洗。直到發現有錢了,高高興興的出去買東西。
3、I/O多路復用(I/O multiplexing)
輪詢肯定不是一種好的機制,所以就出現這種多路復用技術,即內核提供一種機制,允許上層應用監控多個IO,一旦某個IO有動作,就通知上層來發起相關的IO請求。
4、信號機制驅動I/O (signal driven I/O (SIGIO))
這種機制就比較容易理解了,在上層應用進行IO請求發起時,只是向內核發送信號通知相關操作,然后應用就可以做其它任務了,當內核當相關的IO數據準備好后,向上層應用發送一個信號,上層應用得到信號后就會繼續進行IO操作,從而達到IO請求的一個整體流程。
5、異步I/O(asynchronous I/O (the POSIX aio_functions))
異步IO其實就上層應用在發起IO請求后,內核不會阻塞IO,會立刻返回一個error,通過其判斷內核的準備狀態,在內核未準備好的情況下,上層應用可以進行其它任務(準備好則繼續進行IO請求,這種情況比較簡單,不討論。當然也可能是真正的錯誤,直接返回即可)。當內核把數據準備完成后,通過通知機制(一般是信號),通知上層應用繼續操作IO。上層應用接到通知后,即再次發起IO請求即可得到相關數據。
這個有點類似于銀行的叫號機制,大家拿了一個號,然后坐在一排椅子上等著窗口叫號,叫到的前去辦理即可。
需要注意的是,對內核來說,IO操作是針對所有IO的,包括網口、串口等等。在網絡編程中,一般的描述中,一般不會明顯的去區分是網絡IO還是其它IO,畢竟大家在這個環境中,應該知道指的就是網絡IO。但是,在網絡應用上支持的一些IO模型未必在其它模型上可用,所以如果嚴格的討論一些具體的問題時,請注意表述的嚴謹性。
在此處還要談一個老生常談的問題,同步、異步與阻塞、非阻塞的關系。同步和異步一般是指多線(進)程間的協調一致或不一致;而阻塞一般是指IO的處理是否等待并交出時間片。二者間沒有必然的聯系。雖然在開發過程中二者聯系非常緊密,但其實它們之間確實是沒有必然的聯系,完全可以不搭界。比如一個IO可以直接連接另外一個IO,根本不過上層。同樣,線程和進程可以完全不訪問IO。
如果二者有關系呢?一般來說,分為以下幾種情況:
1、同步阻塞IO
這種一般用于非常簡單的場景,比如只是一個基本的數據交換或者命令傳遞。沒有并發量。這種情況下,整個線程會被阻塞,然后直到有IO響應。
2、同步非阻塞IO
這種一般用于處理多個IO,比如有三個網口,需要不斷的查詢一些命令數據之類的。所以不能在查詢某一個網絡IO時被阻塞住,誰也不知道另外兩個是不是已經有數據傳遞過來了。但它需要不斷的輪詢導致消耗較多的CPU時間片。
3、異步阻塞IO
這個應用景比較少,一般異步和IO結合后,都是指異步非阻塞IO。異步阻塞IO其實劃分到更細的程度后,其實和同步阻塞IO是一致的,只是邏輯上不同罷了。這種IO模型應用場景非常少。
4、異步非阻塞IO
這個就是常見的異步IO。一般來說,異步操作IO會拿到一個返回值,表示IO已經接收了處理動作。上層應用可以繼續處理其它任務,當此IO動作處理完成后,內核會以一定的機制(信號、回調或者事件等)通知應用進程處理相關數據,從而避免了輪詢的操作。
基本上目前高并發的各種網絡框架、庫及程序等都是基于這種情況進行開發的。
四、總結
網絡服務端編程是復雜的,要先從宏觀上把網絡通信的相關機制搞清楚。這樣才能更好的在開發過程中明白一些細節的掌控,而不至于迷失在細節當中。這個系列會從頂層一點點的抽絲剝繭的把相關背景和技術棧分析展開,并對它們的相對關系進行分析說明。某些具體的細節如果不必要就不展開了,請大家自行查閱相關書籍或者資料。