今天給大家介紹下一些網(wǎng)絡(luò)編程方面的需要掌握的基礎(chǔ)知識:
網(wǎng)絡(luò)分層模型
先來看一張圖:

從左到右向,分別是:
- OSI七層模型
- TCP/IP四層模型
- 應(yīng)用程序?qū)崿F(xiàn)部分和內(nèi)核實現(xiàn)部分
這里要認識到的是,我們最常用TCP的網(wǎng)絡(luò)處理部分,都是由內(nèi)核來完成的。
TCP服務(wù)端和客戶端編程模型
TCP連接創(chuàng)建和斷開
TCP創(chuàng)建連接需要三次握手,而斷開連接需要四次揮手,如圖:

這張圖清晰的說明了連接的建立、數(shù)據(jù)發(fā)送以及斷開連接時所對應(yīng)的編程函數(shù),另外還有相應(yīng)的TCP狀態(tài)轉(zhuǎn)換。
服務(wù)端客戶端編程函數(shù)

由此可見,服務(wù)端編程用到的主要函數(shù)為:
- socket:創(chuàng)建一個socket,返回的文件描述符fd之后用于bind和listen
- bind:綁定socket和ip+port
- listen:調(diào)用后,服務(wù)端狀態(tài)變?yōu)?code>LISTEN,可以接收網(wǎng)絡(luò)連接
- accept:函數(shù)在連接建立后返回一個connfd,對這個文件描述符的讀寫就是在做網(wǎng)絡(luò)接收和發(fā)送
- read:網(wǎng)絡(luò)對端發(fā)送來的數(shù)據(jù)會放到內(nèi)核的接收緩沖區(qū),read就是從這個緩沖區(qū)中讀取數(shù)據(jù)到應(yīng)用程序
- write:應(yīng)用程序要發(fā)送數(shù)據(jù)到網(wǎng)絡(luò)對端時,調(diào)用此函數(shù),會現(xiàn)將數(shù)據(jù)寫到內(nèi)核的發(fā)送緩沖區(qū)中,之后內(nèi)核會負責將數(shù)據(jù)發(fā)送給網(wǎng)絡(luò)對端
- close:關(guān)閉連接
關(guān)于服務(wù)端的用于連接的fd和用于讀寫的fd,請見下圖:


客戶端編程用到的函數(shù)為:
- socket:創(chuàng)建一個socket,之后用于連接服務(wù)器,做數(shù)據(jù)讀寫用
- connect:發(fā)起到服務(wù)端的鏈接,返回時TCP三次握手完成
- write:同服務(wù)端的write
- read:同服務(wù)端的read
- close:關(guān)閉連接
幾個概念
backlog
先附上一個圖:

內(nèi)核中會維護兩個隊列:
- 未完成連接的隊列: 服務(wù)端收到客戶端的連接請求(SYNC),在三次握手完成前,會放到這個隊列中
- 完成連接的隊列:完成三次握手后就創(chuàng)建了一個TCP連接,這個連接會放到這個隊列中
linux的man listen中說:
The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow
我理解,backlog定義了未完成連接的隊列的最大長度
RTT
這個概念常常聽到,請見圖:

RTT的定義是Round-Trip Time,即數(shù)據(jù)包的往返時延
TCP Stream
我們通常說TCP是流式的,這是什么樣的概念呢?

對應(yīng)一個TCP連接,內(nèi)核會給這個連接分配一個發(fā)送緩沖和接收緩沖,我們的應(yīng)用程序?qū)@來兩個緩沖區(qū)的讀寫就是在做網(wǎng)絡(luò)數(shù)據(jù)的接收和發(fā)送。
而數(shù)據(jù)在網(wǎng)絡(luò)上的傳輸是內(nèi)核自己在維護的,當發(fā)送緩沖區(qū)中有數(shù)據(jù)后,內(nèi)核就把這些數(shù)據(jù)發(fā)送給對端;同樣的,當對端有數(shù)據(jù)過來時,內(nèi)核會把它放到接收緩沖區(qū)中,等待應(yīng)用程序的讀寫。
這樣的發(fā)送和接收數(shù)據(jù)的過程,就像水流一樣,所以我們說TCP是流式的。
TCP狀態(tài)轉(zhuǎn)換
TCP定義了很多狀態(tài),這些狀態(tài)之間的轉(zhuǎn)換關(guān)系如下圖:

這些狀態(tài)都記住有難度,需要時查下就好了。
IO模型
網(wǎng)絡(luò)操作就是IO操作,而且網(wǎng)絡(luò)的IO是最慢的一種了,網(wǎng)絡(luò)編程的很大難點就是妥善的處理好這一塊的問題。
Unix定義了多種IO操作模型,分別是:
- 阻塞IO
- 非阻塞IO
- IO多路復用
- 信號驅(qū)動IO
- 異步IO
分別說明如下:
阻塞IO
這里有一點非常重要的概念要先說明下,那就是阻塞的是什么?

首先要記住:數(shù)據(jù)在網(wǎng)絡(luò)上的傳輸完全是內(nèi)核在控制的,應(yīng)用程序中的read和write只是在讀寫接收緩沖和發(fā)送緩沖。
讀阻塞:調(diào)用read時,如果接收緩沖區(qū)中沒有數(shù)據(jù),那么就會產(chǎn)生阻塞
寫阻塞:調(diào)用write時,如果發(fā)送緩沖區(qū)中的數(shù)據(jù)沒有發(fā)送出去,那么就會產(chǎn)生阻塞
那么如果沒有產(chǎn)生阻塞,那么兩者的執(zhí)行時間為:
read的執(zhí)行時間為:將內(nèi)核接收緩沖區(qū)的內(nèi)容拷貝到用戶空間中的應(yīng)用程序緩沖區(qū)
write的執(zhí)行時間為:將用戶空間中的應(yīng)用程序緩沖區(qū)的內(nèi)容拷貝到內(nèi)核的發(fā)送緩沖區(qū)
非阻塞IO
理解了導致阻塞的原因,那么非阻塞就非常好理解了:

當內(nèi)核緩沖區(qū)無法讀寫時,read和write就會返回EWOULDBLOCK,應(yīng)用程序就需要過會兒再來操作。
光是這樣,還不能實現(xiàn)高性能的網(wǎng)絡(luò)程序,這是因為我們無法判斷應(yīng)該什么時間再來做讀寫操作。
如果一直循環(huán)讀寫,那么CPU占用會很居高不下;如果sleep一段時間,那么多長時間合適呢?
所以,如果想開發(fā)高性能的網(wǎng)絡(luò)程序,我們還需要別的武器:
IO多路復用
這是操作系統(tǒng)提供的一種通知機制,告訴應(yīng)用程序何時可以做讀寫操作:

不同操作系統(tǒng)提供了不同的編程接口,一個非常有名的庫libevent就是對這些庫的一個統(tǒng)一接口封裝。
IO多路復用也是現(xiàn)在用的最多的一種高性能網(wǎng)絡(luò)服務(wù)器的IO處理模型,例如Nginx
信號驅(qū)動IO
不同于IO多路復用,操作系統(tǒng)用信號的方式告訴應(yīng)用程序何時可以做讀寫操作:

異步IO
最后這一種我沒有用過,從概念上理解,相當于操作系統(tǒng)將數(shù)據(jù)做完用戶空間和內(nèi)核空間的復制后,才會通知應(yīng)用程序:

結(jié)束語
上面這些,都是筆者編程這些年,覺得非常受用的基礎(chǔ)知識。正確的認識這些知識,很多問題你都可以自己想明白了。
筆者也是在不斷學習中,如果有錯誤的地方,還望指正,我們共同進步,謝謝!
參考
UNIX網(wǎng)絡(luò)編程(卷1):https://book.douban.com/subject/4859464/
TCP/IP詳解(卷1):https://book.douban.com/subject/26790659/
Linux/UNIX系統(tǒng)編程手冊:https://book.douban.com/subject/25809330/