應(yīng)用編程基礎(chǔ)課第二講:網(wǎng)絡(luò)編程基礎(chǔ)

今天給大家介紹下一些網(wǎng)絡(luò)編程方面的需要掌握的基礎(chǔ)知識:

網(wǎng)絡(luò)分層模型

先來看一張圖:

osi-tcp-ip.png

從左到右向,分別是:

  1. OSI七層模型
  2. TCP/IP四層模型
  3. 應(yīng)用程序?qū)崿F(xiàn)部分和內(nèi)核實現(xiàn)部分

這里要認識到的是,我們最常用TCP的網(wǎng)絡(luò)處理部分,都是由內(nèi)核來完成的。

TCP服務(wù)端和客戶端編程模型

TCP連接創(chuàng)建和斷開

TCP創(chuàng)建連接需要三次握手,而斷開連接需要四次揮手,如圖:

tcp-3-4.png

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

服務(wù)端客戶端編程函數(shù)

client-server.png

由此可見,服務(wù)端編程用到的主要函數(shù)為:

  1. socket:創(chuàng)建一個socket,返回的文件描述符fd之后用于bind和listen
  2. bind:綁定socket和ip+port
  3. listen:調(diào)用后,服務(wù)端狀態(tài)變?yōu)?code>LISTEN,可以接收網(wǎng)絡(luò)連接
  4. accept:函數(shù)在連接建立后返回一個connfd,對這個文件描述符的讀寫就是在做網(wǎng)絡(luò)接收和發(fā)送
  5. read:網(wǎng)絡(luò)對端發(fā)送來的數(shù)據(jù)會放到內(nèi)核的接收緩沖區(qū),read就是從這個緩沖區(qū)中讀取數(shù)據(jù)到應(yīng)用程序
  6. 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ò)對端
  7. close:關(guān)閉連接

關(guān)于服務(wù)端的用于連接的fd和用于讀寫的fd,請見下圖:

listenfd.png
connfd.png

客戶端編程用到的函數(shù)為:

  1. socket:創(chuàng)建一個socket,之后用于連接服務(wù)器,做數(shù)據(jù)讀寫用
  2. connect:發(fā)起到服務(wù)端的鏈接,返回時TCP三次握手完成
  3. write:同服務(wù)端的write
  4. read:同服務(wù)端的read
  5. close:關(guān)閉連接

幾個概念

backlog

先附上一個圖:

backlog.png

內(nèi)核中會維護兩個隊列:

  1. 未完成連接的隊列: 服務(wù)端收到客戶端的連接請求(SYNC),在三次握手完成前,會放到這個隊列中
  2. 完成連接的隊列:完成三次握手后就創(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.png

RTT的定義是Round-Trip Time,即數(shù)據(jù)包的往返時延

TCP Stream

我們通常說TCP是流式的,這是什么樣的概念呢?

socket-stream.png

對應(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)系如下圖:

tcp-status.png

這些狀態(tài)都記住有難度,需要時查下就好了。

IO模型

網(wǎng)絡(luò)操作就是IO操作,而且網(wǎng)絡(luò)的IO是最慢的一種了,網(wǎng)絡(luò)編程的很大難點就是妥善的處理好這一塊的問題。

Unix定義了多種IO操作模型,分別是:

  1. 阻塞IO
  2. 非阻塞IO
  3. IO多路復用
  4. 信號驅(qū)動IO
  5. 異步IO

分別說明如下:

阻塞IO

這里有一點非常重要的概念要先說明下,那就是阻塞的是什么?

io-block.png

首先要記住:數(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

理解了導致阻塞的原因,那么非阻塞就非常好理解了:

io-nonblock.png

當內(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)用程序何時可以做讀寫操作:

io-multiple.png

不同操作系統(tǒng)提供了不同的編程接口,一個非常有名的庫libevent就是對這些庫的一個統(tǒng)一接口封裝。

IO多路復用也是現(xiàn)在用的最多的一種高性能網(wǎng)絡(luò)服務(wù)器的IO處理模型,例如Nginx

信號驅(qū)動IO

不同于IO多路復用,操作系統(tǒng)用信號的方式告訴應(yīng)用程序何時可以做讀寫操作:

io-signal.png

異步IO

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

io-async.png

結(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/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容