【nginx】網絡通訊實戰一

C/S, TCP/IP協議妙趣橫生、惟妙惟肖談

一、客戶端與服務器

瀏覽器就是一個可執行程序(客戶端),淘寶網nginx服務器返回數據包,來回很多次才完全發完,最後發一個特殊的包結束。

【客戶端服務器角色規律總結】

a)數據通訊總在兩端進行,其中一端叫客戶端,另一端叫服務器端;

b)總有一方先泛起第一個數據包,這發起第一個數據包的這一端,就叫客戶端【瀏覽器】;被動收到第一個數據包這端,叫服務器端【淘寶服務器】;

c)連接建立起來,數據雙向流動(雙工)

d)既然服務器端是被動接收連接,那麼客戶端必須得能夠找到服務器在哪裏;

瀏覽器要訪問淘寶網,需要知道淘寶服務器的地址(IP地址),以及淘寶服務器的姓名(端口號,一個0-65535的無符號數字)

淘寶網服務器(nginx服務器)會調用listen()函數來監聽80端口。

在編寫網絡通訊程序時,只需要指定淘寶服務器的ip地址和淘寶服務器的端口號,就能夠跟淘寶服務器進行通訊。

e)epoll:單臺服務器高併發

二、網絡模型

1、OSI七層網絡模型

物【物理層】 鏈【數據鏈路層】 網【網絡層】 傳【傳輸層】 會【會話層】 表【表示層】 應【應用層】

把一個要發送出去的數據包從裏到外裹了7層發送到網絡上去。

2、TCP/IP協議四層模型

tcp/ip實際是一組協議的代名詞,而不僅僅是一個協議;每一層都對應着一些協議。

3、TCP/IP協議的解釋和比喻

上層數據先加TCP頭,再加IP頭,再加LLC頭,再加MAC頭。

三、最簡單的客戶端和服務器程序實現代碼

1、套接字socket概念

套接字(socket):就是個數字,通過調用socket()函數來生成;這個數字具有唯一性;一直用直到調用close()函數把這個數字關閉;一切皆文件,咱們就把socket也看成是文件描述符,我們可以用socket來收發數據;send(),recv()。

2、一個簡單的服務器端通訊程序範例

3、IP地址

寫服務器程序,不用考慮ipv4、ipv6的問題,遵照ipv4規則寫就行;

寫客戶端程序,只演示ipv4版本的客戶端範例。

4、一個簡單的客戶端通訊程序範例

c/s建立連接時雙方彼此都要有ip地址/端口號;連接一旦建立起來,那麼雙方的通訊【雙工收發】就只需要用雙方彼此對應的套接字即可。

5、客戶端服務器程序綜合演示和調用流程圖

服務器端程序要先運行

四、TCP和UDP的區別

TCP(Transfer Control Protocol):傳輸控制協議

UDP(User Datagram Protocol):用戶數據報協議

TCP協議:可靠的面向連接的協議,數據包丟失的話操作系統底層會感知並且幫助你重新發送數據包;

UDP協議:不可靠的,無連接的協議。

【優缺點】

a)tcp:可靠協議,耗費更多的系統資源確保數據傳輸的可靠;得到好處就是隻要不斷線,傳輸給對方的數據,一定正確的,不丟失,不重複,按順序到達對端;

b)udp:不可靠協議,發送速度特別快;但無法確保數據可靠性。

【各自的用途】

a)tcp:文件傳輸,收發郵件需要準確率高,但效率可以相對差;一般TCP比UDP用的範圍和場合更廣。

b)udp:qq聊天信息,DNS解析等,估計隨着網絡的發展,網絡性能更好,丟包率更低,那麼udp應用範圍更廣。

 

TCP三次握手詳析、telnet,wireshark示範

一、TCP連接的三次握手

只有TCP有三次握手(UDP沒有)

1、最大傳輸單元MTU

MTU:每個數據包包含的數據最多可以有多少個字節,1.5K左右;

要發送100K,操作系統內部會把這100K數據拆分成若干個數據包(分片),每個數據包大概1.5K之內(大概拆解成68個),對端重組;我們只需要知道有拆包,組包這68個包各自傳送的路徑可能不同,每一個包可能因爲路由器,交換機原因可能被再次分片;最終TCP/IP協議保證了我們收發數據的順序性和可靠性。

2、TCP包頭結構(往左90度)

a)源端口,目標端口

b)關注syn位和ack位(開/關)

c)一個tcp數據包,是可能沒有包體,此時,總會設置一些標誌位來達到傳輸控制信息的目的,起控制作用

3、TCP數據包收發之前的準備工作

TCP數據的收發是雙工的:每端既可以收數據,又可以發數據。

【TCP數據包的收發三大步驟】

a)建立TCP連接[connect:客戶端],三次握手

b)多次反覆的數據收發[read/wirte]

c)關閉TCP連接[close]

UDP不存在三次握手來建立連接的問題。UDP數據包是直接發送出去,不用建立所謂的連接。

4、TCP三次握手建立連接的過程([syn] [syn/ack] [ack]

1)客戶端給服務器發送 了一個SYN標誌位置位的無包體TCP數據包,SYN被置位,就表示發起TCP鏈接;

2)服務器收到了這個SYN標誌位置位的數據包,服務器給客戶端返回一個SYN和ACK標誌位都被置位的無包體TCP數據包;

3)客戶端收到服務器發送回來的數據包之後,再次發送ACK置位的數據包,服務器端收到這個數據包之後,客戶端和服務器端的TCP鏈接就正式建立。

5、爲什麼TCP握手是三次握手而不是二次

三次握手很大程度上是爲了防止惡意的人坑害別人而引入的一種TCP連接驗證機制。

爲了確保數據穩定可靠的收發 ,儘量減少僞造數據包對服務器的攻擊。

源ip,源端口 ---------------- 目的ip,目的端口

syn------------->

<--------syn/ack(驗證源ip和源端口真實存在)

ack-------------->

二、telnet工具使用介紹

telnet是一款命令行方式運行的客戶端TCP通訊工具,可以連接到服務器端,往服務器端發送數據,也可以接收從服務器端發送過來的信息;該工具能夠方便的測試服務器端的某個TCP端口是否通,是否能夠正常收發數據,所以是一個非常實用,重要,常用的工具。

telnet ip地址 端口號

telnet在windows下輸入一個字符就往server發,在linux下輸入字符後要回車才往server發。

三、wireshark監控數據包

wireshark是個軟件,分析網絡數據包,規則:host 192.168.1.126 and port 9000。

【TCP斷開的四次揮手】

a)FIN,ACK   服務器->客戶端

b)ACK            客戶端->服務器

c)FIN,ACK   客戶端->服務器

d)ACK            服務器->客戶端

 

TCP狀態轉換,TIME_WAIT詳解,SO_REUSEADDR

一、TCP狀態轉換

同一個IP(INADDR_ANY),同一個端口SERV_PORT,只能被成功的bind()一次,若再次bind()就會失敗,並且會顯示:Address already in use。

介紹命令netstat:顯示網絡相關信息

-a:顯示所有選項

-n:能顯示成數字的內容全部顯示成數字

-p:顯示段落這對應程序名

netstat -anp | grep -E 'State|9000'

【測試】用兩個客戶端連接到服務器,服務器給每個客戶端發送一串字符"I sent sth to client!\n",並關閉客戶端;我們用netstat觀察,原來那個監聽端口一直在監聽,但是當來了兩個連接之後(連接到服務器的9000端口),雖然這兩個連接被close掉了,但是產生了兩條TIME_WAIT狀態的信息。

客戶端連接到服務器,並且服務器把客戶端關閉,服務器端就會產生一條針對9000監聽端口的狀態爲 TIME_WAIT 的連接;只要用netstat看到 TIME_WAIT狀態的連接,那麼此時殺掉服務器程序再重新啓動,就會啓動失敗,bind()函數返回失敗。

TCP狀態轉換圖(11種狀態)是針對一個TCP連接來說的。

客戶端: CLOSED ->SYN_SENT->ESTABLISHED【連接建立,可以進行數據收發】

服務端: CLOSED ->LISTEN->【客戶端來握手】SYN_RCVD->ESTABLISHED【連接建立,可以進行數據收發】

誰主動close連接,誰就會給對方發送一個FIN標誌置位的一個數據包給對方,假設是服務器端先關閉:

服務器主動關閉連接:ESTABLISHED->FIN_WAIT1->FIN_WAIT2->TIME_WAIT(等一段時間回到close狀態)

客戶端被動關閉:ESTABLISHED->CLOSE_WAIT->LAST_ACK

二、TIME_WAIT狀態

【何時產生】四次揮手主動關閉的一方就會產生!

具有TIME_WAIT狀態的TCP連接,就好像一種殘留的信息一樣,服務器程序退出並重新執行會失敗;連接處於TIME_WAIT狀態是有時間限制的(1-4分鐘之間) = 2 MSL(最長數據包生命週期)。

【爲什麼要引入TIME_WAIT】:

(1)可靠的實現TCP全雙工的終止

如果服務器最後發送的ACK包因爲某種原因丟失了,那麼客戶端一定會重新發送FIN,這樣因爲服務器端有TIME_WAIT的存在,服務器會重新發送ACK包給客戶端,但是如果沒有TIME_WAIT這個狀態,服務器都已經關閉連接了,此時客戶端重新發送FIN,服務器給回的就不是ACK包,而是RST(連接復位)包,從而使客戶端沒有完成正常的4次揮手,可能出現錯誤

RST標誌:當我們close一個TCP連接時,如果發送緩衝區有數據,那麼操作系統會很優雅的把發送緩衝區裏的數據發送完畢,然後再發fin包表示連接關閉;出現這個RST標誌的包一般都表示異常關閉,如果發生了異常,一般都會導致丟失一些發送緩衝區的數據包,主動關閉一方也不會進入TIME_WAIT。

(2)允許老的重複的TCP數據包在網絡中消逝

如果最後一個ack還沒到客戶端,不用TIME_WAIT來阻止新server啓動,這時啓動一個server,再來一個老數據包,就不知道這個包是給老的還是給新的了,容易引起混亂。所以維持TIME_WAIT一段時間是爲了在這段時間內徹底丟棄老的數據包,等待最後一個ack到達客戶端,再全部結束。

三、SO_REUSEADDR選項

setsockopt(SO_REUSEADDR)用在服務器端,socket()創建之後,bind()之前。

所有TCP服務器都應該指定本套接字選項,以防止當套接字處於TIME_WAIT時bind()失敗的情形出現。

【設置後測試】兩個進程,綁定同一個IP和端口:bind()失敗;TIME_WAIT狀態時的bind綁定:bind()成功。

SO_REUSEADDR:主要解決TIME_WAIT狀態導致bind()失敗的問題。

 

listen()隊列剖析、阻塞非阻塞、同步異步

一、listen()隊列剖析

listen():監聽端口,用在TCP連接中的服務器端角色。

listen()函數調用格式:int listen(int sockfd, int backlog);

要理解好backlog這個參數,要了解 “監聽套接字隊列”!

1、監聽套接字的隊列

對於一個調用listen()進行監聽的套接字,操作系統會給這個套接字維護兩個隊列:

a)未完成連接隊列(保存連接用的)

當客戶端發送tcp連接三次握手的第一次(syn包)給服務器的時候,服務器就會在未完成隊列中創建一個跟這個syn包對應的一項,我們可以把這項看成是一個半連接(因爲連接還沒建立),這個半連接的狀態會從LISTEN變成SYN_RCVD狀態,同時給客戶端返回第二次握手包(syn,ack包),這個時候,其實服務器是在等待完成第三次握手。

b)已完成連接隊列(保存連接用的)

當第三次握手完成了,這個連接就變成了ESTABLISHED狀態,每個已經完成三次握手的客戶端都放在這個隊列中作爲一項。

backlog曾經的含義:已完成隊列和未完成隊列裏邊條目之和不能超過backlog。

【總結】

(1)客戶端這個connect()什麼時候返回?收到三次握手的第二次握手包(也就是收到服務器發回來的syn/ack)之後返回。

(2)RTT是未完成隊列中任意一項在未完成隊列中留存的時間,這個時間取決於客戶端和服務器。

對於客戶端,這個RTT時間是第一次和第二次握手加起來的時間;對於服務器,這個RTT時間實際上是第二次和第三次握手加起來的時間。如果這三次握手包傳遞速度特別快的話,大概187毫秒能夠建立起來這個連接,這個時間不快,所以建立TCP連接的成本很高。

(3)如果一個惡意客戶,遲遲不發送三次握手的第三個包。那麼這個連接就建立不起來,那麼這個處於SYN_RCVD的這一項,就會一致停留在服務器的未完成隊列中,這個停留時間大概是75秒,如果超過這個時間,這一項會被操作系統幹掉。

2、accept()函數

accept()函數:就是用來從已完成連接隊列中的隊首位置取出來一項(每一項都是一個已經完成三路握手的TCP連接)返回給進程。如果已完成連接隊列是空,那麼accept()一般會一直卡在這裏(休眠)等待,一直到已完成隊列中有一項時纔會被喚醒。因爲客戶端connet返回就可以收發數據了,所以服務器端要儘快地用accept()把已完成隊列中的數據(TCP連接)取走。

accept()返回的是個套接字,這個套接字就代表那個已經用三次握手建立起來的那個tcp連接,因爲accept()是從已完成隊列中取的數據,所以一定要區別連個服務器端的兩個套接字:

a)監聽9000端口這個套接字——“監聽套接字”(listenfd),只要服務器程序在運行,這個套接字就應該一直存在;

b)當客戶端連接進來,操作系統會爲每個成功建立三次握手的客端再創建一個套接字(connfd),accept()返回的就是這種套接字,也就是從已完成連接隊列中取得的一項。隨後,服務器是使用這個accept()返回的套接字和客戶端通信的。

【思考題】

(1)如果兩個隊列之和(已完成連接隊列和未完成連接隊列)達到了listen()所指定的第二參數,也就是說隊列滿了,此時,再有一個客戶發送syn請求,服務器怎麼反應?

實際上服務器會忽略這個syn,不給迴應;客戶端這邊,發現syn沒回應,過一會會重發syn包。

(2)從連接被扔到已經完成隊列中去,到accept()從已完成隊列中把這個連接取出之間是有個時間差的,如果還沒等accept()從已完成隊列中把這個連接取走的時候,客戶端如果發送來數據,這個數據就會被保存在已經連接的套接字的接收緩衝區裏,這個緩衝區有多大,最大就能接收多少數據量。

3、syn攻擊(syn flood):典型的利用TCP/IP協議涉及弱點進行坑爹的一種行爲。

拒絕服務攻擊(DOS/DDOS):不停發第一次握手的syn包,服務器未完成隊列中就越來越多,總會超過backlog;

所以backlog被進一步明確和規定了:已完成連接隊列中最大條目數。雖然這樣即使未完成連接隊列被填滿後還是無法處理新的連接,但是系統會自動處理未完成連接隊列的內容,75s後刪除。

所以在寫代碼時儘快用accept()把已完成隊列裏邊的連接取走,儘快留出空閒位置給後續的已完成三次握手的條目用,這個已完成隊列就一般不會滿,一般這個backlog值給300左右。

二、阻塞與非阻塞I/O

阻塞和非阻塞主要是指調用某個系統函數時,這個函數是否會導致我們的進程進入sleep()(卡住休眠)狀態而言的。

1、阻塞I/O

調用一個函數,這個函數卡在這裏等待一個事情發生,只有這個事情發生了,這個函數纔會往下走,如阻塞函數accept()。

這種阻塞並不好,效率很低,所以一般不會用阻塞方式來寫服務器程序。

2、非阻塞I/O:不會卡住,充分利用時間片,執行效率更高。

【非阻塞模式的兩個鮮明特點】

(1)不斷地調用accept()、recvfrom()函數來檢查有沒有數據到來,如果沒有,函數會返回一個特殊的錯誤標記來標識,這種標記可能是EWULDBLOCK,也可能是EAGAIN;如果數據沒到來,那麼這裏有機會執行其他函數,但是也得不停的再次調用accept(),recvfrom()來檢查數據是否到來,非常累;

(2)如果數據到來,那麼就得卡在這裏把數據從內核緩衝區複製到用戶緩衝區,所以複製這個階段是卡着完成的

三、同步與異步I/O:這兩個概念容易和阻塞/非阻塞混淆

1、異步I/O:

調用一個異步I/O函數時,要給這個函數指定一個接收緩衝區,我還要給定一個回調函數

調用完一個異步I/O函數後,該函數會立即返回,其餘判斷交給操作系統,操作系統會判斷數據是否到來,如果數據到來了,操作系統會把數據拷貝到你所提供的緩衝區裏,然後調用你所指定的這個回調函數來通知你。

【非阻塞和異步I/O區別】

a)非阻塞I/O要不停的調用I/O函數來檢查數據是否來,如果數據來了,就得卡在I/O函數這裏把數據從內核緩衝區複製到用戶緩衝區,然後這個函數才能返回;

b)異步I/O根本不需要不停的調用I/O函數來檢查數據是否到來,只需要調用一次,然後就可以幹別的事情去了;內核判斷數據到來,拷貝數據到你提供的緩衝區,調用你的回調函數來通知你,並沒有被卡在那裏的情況。

2、同步I/O:select/poll和epoll

步驟一:調用select()判斷有沒有數據,有數據,走下來,沒數據卡在那裏;

步驟二:select()返回之後,用recvfrom()去取數據,當然取數據的時候(內核到用戶)也會卡那麼一下。

同步I/O感覺更麻煩,要調用兩個函數才能把數據拿到手;但是同步I/O和阻塞式I/O比,有 I/O複用(2個函數的)能力。所謂I/O複用,就是多個socket(多個TCP連接)可以弄成一捆,我可以用select等數據,因爲select()的能力是等多條TCP連接上的任意一條有數據來都可以感知到,然後哪條TCP有數據來,我再用具體的比如recvfrom()去收。

所以,這種調用一個函數能夠判斷一堆TCP連接是否來數據的這種能力,叫I/O複用,英文I/O multiplexing。

很多資料把阻塞I/O,非阻塞I/O,同步I/O歸結爲一類 ,因爲他們多多少少的都有阻塞的行爲發生;也可以把阻塞I/O,非阻塞I/O 都歸結爲同步I/O模型,而把異步I/O單獨歸結爲一類,因爲異步I/O是真正的沒有阻塞行爲發生的。

【通俗理解】參考:https://www.cnblogs.com/wangzhaobo/articles/9596623.html

阻塞與非阻塞關注的是在等待數據來的時候是不是在幹別的事(這裏的等應該指第一階段),而同步與異步關注的是如何獲取到數據來了這件事,是自己看到的還是別人告訴的(其實可以把看這個動作理解爲第二階段),所以阻塞和非阻塞都算有一點同步。現實中非阻塞異步是最符合我們常理的,好比我們去買煎餅,我跟攤煎餅的阿姨說,你攤好了給我打電話(不用自己看,所以是異步),然後我就去聊天、玩手機(程序先返回幹別的,非阻塞),然後攤好了通知我(回調函數),我取走煎餅。最蠢的其實就是阻塞異步地做事,就好比你跟阿姨說煎餅好了你來通知我一下,然後你還在哪裏一直等着數據來而不幹別的事(阻塞),那你讓阿姨通知你幹嘛?同理,阻塞同步就是一直站在那裏等煎餅出來,然後看(同步)到煎餅就拿走,因爲是自己看而不是等通知所以是同步。非阻塞同步就是去聊天玩手機,然後每過一會兒就過來看一下煎餅攤好沒有,攤好了就拿走,因爲總歸是自己看的,所以是同步。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章