天天看點

無處不在的網絡程式設計,到底是如何工作的?今天我們一探究竟!

一、引言

不知道大家有沒有這樣的經曆,上網搜尋技術文章,總是會看到網絡程式設計這個字眼,而各個網際網路大廠,也對掌握了網絡程式設計的人才,求賢若渴。

其實網絡程式設計無處不在,我們平時用到的網際網路産品和網絡程式設計技術息息相關。

掌握網絡程式設計,才能在繁雜的網絡世界中,看透問題本質,遇到網絡相關技術問題,也才能解決得遊刃有餘。

二、目錄

三、網絡協定棧

那什麼是網絡程式設計呢?有人說http就是網絡程式設計,有人說開發RPC架構是網絡程式設計,有人說嵌入式硬體互相通信是網絡程式設計,其實這些都涉及網絡程式設計,都脫離不了網絡協定棧。

在大學課本《計算機網絡》中,我們學過網絡協定棧是OSI七層。現實中,大多數作業系統,實作的是TCP/IP四層協定棧,如圖所示:

而網絡程式設計就是在作業系統封裝的TCP/IP協定棧的基礎上,使用系統核心暴露出來的socket網絡程式設計Api,進行應用程式開發。

四、echo回顯伺服器代碼

=============

#編譯echo回顯伺服器

go build -o echoServer echoServer.go

# 啟動伺服器

./echoServer

使用nc,僞裝echoClient用戶端

# 和伺服器建立連接配接

nc 127.0.0.1 8888

# 用戶端

hello-echo

# 服務端

這時候,我們通過nc給伺服器發送字元串,伺服器會原樣把字元串回傳給我們。

nc和echoServer整個互動過程是怎樣的呢?下圖展示詳細過程!

五、網絡互動圖

六、過程詳解

衆所周知,TCP會有 3次握手 和 4次揮手,我們的echo伺服器就是基于TCP協定的。當然少不了3次握手和4次揮手。

伺服器 | 建立socket核心資料結構

✿ 首先,我們需要建立socket核心資料結構,通過socket()系統調用,我們可以告訴核心我們要建立基于ipv4的tcpsocket套接字,核心會維護一個sock資料結構并和一個檔案相綁定,同時給我們傳回一個socketfd,供後續函數使用。

✿ 之後,我們需要通過bind(),告知核心将哪個位址綁定到該socket核心資料結構。

✿ 然後,使用listen()系統調用,将socket轉換為已監聽套接字。此時,伺服器就可以進行被動連接配接了。

用戶端 | 發起主動連接配接;伺服器 | 被動連接配接

✿ 如果此時用戶端通過connect()系統調用發起主動連接配接,用戶端核心協定棧,向伺服器發送三次握手的第一步SYN。

✿ 當伺服器收到這個SYN,會把該套接字放入半連接配接隊列,并向用戶端發送ACK、SYN。

✿ 當用戶端接受到這個ACK、SYN,并向伺服器發送ACK。此時用戶端connect()系統調用傳回,用戶端認為三次握手完成。

✿ 當伺服器收到用戶端傳來的ACK,則将核心中的套接字放入全連接配接隊列,等待伺服器調用accept,并傳回給伺服器。

伺服器 | 等待已連接配接套接字

✿ 當伺服器調用accept(), 如果此時全連接配接隊列中沒有已完成三次握手的socket,則預設會阻塞,直到全連接配接隊列中擁有已經完成三次握手的socket。

✿ accept()會為之前的監聽套接字sock核心資料結構,建構一個新的檔案,并配置設定新的fd,這個檔案稱為已連接配接套接字。

✿ 此時,伺服器和用戶端就可以收發資料了。

伺服器 | 用戶端 | 收發資料

收發資料為何需要調用read和write呢?

✿ 在核心中,看到的acceptfd(已連接配接套接字),本質和檔案一樣,acceptfd就是檔案描述符,是以我們可以直接使用read、write這種操作檔案的系統調用。

✿ linux核心為每個已連接配接套接字,配置設定一個接受緩沖區和一個發送緩沖區。

✿ 對于read(),是本機讀取acceptfd(伺服器)或者socketfd(用戶端)相關的socket接收緩沖區,如果socket接收緩沖區中有資料,read()傳回,否則read()會阻塞,直到socket接收緩沖區中擁有了對端資料。這個接受資料的過程是由核心TCP/IP協定棧實作的。

✿ 對于write(),寫入的是socket發送緩沖區,如果此時發送緩沖區是滿的,write()則會被阻塞。寫入socket發送緩沖區的資料,由核心TCP/IP協定棧真實的發往對端。

=====================

這裡還有幾個點:

▶ read傳回大于0,表示讀取成功。

▶ read傳回等于0,表示對端關閉連接配接,此時應該調用Close關閉連接配接。

▶ read傳回小于0,表示讀操作産生錯誤。

▶ write傳回小于0,表示寫操作産生錯誤。

用戶端 | 伺服器 | 關閉連接配接

✿ 在nc所在的終端上鍵入Ctrl+c,結束掉nc程序。此時核心協定棧,會給伺服器發送FINTcp節,告知本端已經關閉。

✿ 服務端收到用戶端的FIN,核心協定棧會給用戶端回複ACK,同時read()調用會傳回0,這樣伺服器就知道用戶端關閉連接配接了。

✿ 這時候,伺服器應該調用close(),開啟四次揮手的第二個階段,向客服端發送一個FIN。

✿ 當用戶端收到該FIN後,核心協定棧回複ACK,自身并進入TIME_WAIT狀态,Linux下等待2MSL, 也就是60秒。

====================

至此,整個echoServer和nc的互動過程就講解完了。整個過程都是正常的網絡互動過程,很多異常的邊界沒有讨論。

七、網絡答疑

假如一方斷網了,不能進行4次揮手,伺服器會怎麼處理?

✿ 預設未斷開一端,會保持established狀态。

✿ 未斷開一端開啟了Keepalive,則會定期往對端發送TCP保活Segment,對端協定棧回複RST,未斷開端就會知道已經出現異常,關閉本端連接配接。

✿ 如果未斷開一端沒有開啟Kepalive,則一直不知道對端已經關閉,直到往對端寫資料會得到Connection reset by peer錯誤,進而知道對端已經關閉。這種情況下,如果未斷開端再次往斷開對端寫資料,則會産生EPIPE錯誤。

✿ 是以通常需要伺服器在應用層做保活心跳,對這種情況的連接配接做定時踢掉處理。

用戶端發送FIN之後,會發生什麼?

✿ TCP是全雙工通信,也就是雙通道通信4次握手中,主動關閉端(用戶端)發送FIN,是告訴被動關閉端(伺服器)我不會再給你發送資料了,你不要再在acceptFd上讀取資料了,此時服務端再讀取資料,會傳回0,也就是EOF。

✿ 如果服務端不需要發送資料給用戶端,通常需要調用close,服務端向用戶端發送FIN,也就是4次握手的第3步。

✿ 如果服務端有資料要發送給用戶端,此時依舊可以通過socketfd發送資料給用戶端,這也是通常說的TCP半關閉狀态。

八、後記

通過之前的講解,我們其實可以發現:TCP/IP協定和socketApi是緊密相關的。然而它們又有很多語義的差别。

很多時候,想當然的了解,并不是你所想的那樣。比如close()系統調用,會不會給對端發送FIN,要看該已連接配接套接字的引用計數是否達到0。

如果有多個程序共享這個檔案,其中一個程序close(),并不會給對端發送FINTcp節。諸如此類的細節還有很多很多,由此可見,學好網絡程式設計也是一門不簡單的學問。

不管你是轉行也好,初學也罷,進階也可,如果你想學程式設計,進階程式員~

【值得關注】我的 程式設計學習交流俱樂部 !【點選進入】

C語言入門資料:

C語言必讀書籍:

繼續閱讀