Python常见面试题:TCP 协议中的三次握手与四次挥手相关概念详解
今天來聊聊Python常見面試題中面試頻率特別高的一個(gè)題目:TCP 協(xié)議中的三次握手與四次揮手。
涉及到的知識(shí)點(diǎn)有:
1、TCP、UDP 協(xié)議的區(qū)別
2、TCP 頭部結(jié)構(gòu)
3、三次握手與四次揮手過程詳解
4、什么是 TIME_WATI 狀態(tài)
?
內(nèi)容稍微有點(diǎn)多,大家耐心些看完!
一、TCP、UDP 協(xié)議的區(qū)別
在介紹這兩者的區(qū)別之前,我們要需要了解一個(gè)概念:TCP/IP 協(xié)議族。定義如下:
目前 Internet(因特網(wǎng))使用的主流協(xié)議族是 TCP/IP 協(xié)議族,它是一個(gè)分層、多協(xié)議的通信體系 《Linux高性能編程》
提取關(guān)鍵詞:分層、多協(xié)議和通信。也就是說,它有多個(gè)層次,每個(gè)層次有不同的協(xié)議,這些層次之間通過協(xié)議相互協(xié)作,最終達(dá)到網(wǎng)絡(luò)通信的目的。
說到分層,應(yīng)該不會(huì)很陌生,TCP/IP 協(xié)議族是一個(gè)四層協(xié)議系統(tǒng),自底而上分別是:數(shù)據(jù)鏈接層、網(wǎng)絡(luò)層、傳輸層、應(yīng)用層。我們這里要說到的 TCP 和 UDP 協(xié)議屬于傳輸層。(各層的作用及相關(guān)協(xié)議這里暫時(shí)先不做介紹)
下面我們回到標(biāo)題:TCP、UDP 協(xié)議的區(qū)別,總結(jié)起來這個(gè)問題的答案要點(diǎn)如下:
1、首頁它們倆都是傳輸層的協(xié)議,而所謂“傳輸層”,它是為兩臺(tái)主機(jī)提供端到端的通信,即從 A <-> B 。
2、TCP 協(xié)議可靠,UDP 協(xié)議不可靠。可靠即指數(shù)據(jù)由 A 發(fā)送到 B,是否能確保數(shù)據(jù)真的有送達(dá)到 B。TCP 協(xié)議使用 超時(shí)重傳、數(shù)據(jù)確認(rèn)等方式來確保數(shù)據(jù)包被正確地發(fā)送至目的端,而 UDP 協(xié)議無法保證數(shù)據(jù)從發(fā)送端正確傳送到目的端,如果數(shù)據(jù)在傳輸過程中丟失、或者目的端通過數(shù)據(jù)檢驗(yàn)發(fā)現(xiàn)數(shù)據(jù)錯(cuò)誤,則 UDP 協(xié)議只是簡(jiǎn)單地通知應(yīng)用程序發(fā)送失敗,對(duì)于 TCP 協(xié)議擁有的超時(shí)重傳、數(shù)據(jù)確認(rèn)等需要應(yīng)用程序自己來處理這些邏輯。
3、TCP 是面向連接的,UDP 是無連接的。這也比較好理解,因?yàn)?TCP 連接才需要“三次握手,四次揮手”。
4、TCP 服務(wù)是基于流的,而 UDP 是基于數(shù)據(jù)報(bào)的,基于流的數(shù)據(jù)沒有邊界(長(zhǎng)度)限制,而基于數(shù)據(jù)報(bào)的服務(wù),每個(gè) UDP 數(shù)據(jù)報(bào)都有一個(gè)長(zhǎng)度,接收端必須以該長(zhǎng)度為最小單位將其所有內(nèi)容一次性讀出。
5、當(dāng)發(fā)送方多次執(zhí)行寫操作時(shí),TCP 模塊會(huì)先將這些數(shù)據(jù)放入 TCP 發(fā)送緩沖區(qū)中,當(dāng) TCP 模塊真正開始發(fā)送數(shù)據(jù)時(shí),發(fā)送緩沖區(qū)中這些等待發(fā)送的數(shù)據(jù)可能被封裝成一個(gè)或多個(gè) TCP 報(bào)文段發(fā)出,因此,TCP 模塊發(fā)出的 TCP 報(bào)文段的個(gè)數(shù)與應(yīng)用程序執(zhí)行的寫操作次數(shù)是沒有固定數(shù)量關(guān)系的。同樣,當(dāng)接收端收到一個(gè)或多個(gè) TCP 報(bào)文段后,TCP 模塊將這些數(shù)據(jù)按照序號(hào)(序號(hào)說明見下面 的 TCP 頭部結(jié)構(gòu))依次放入 TCP 接收緩沖區(qū)中,并通知應(yīng)用程序讀取數(shù)據(jù)。接收端可選擇一次或者分多次將數(shù)據(jù)從緩沖區(qū)中讀出(這取決于用戶指定的應(yīng)用程序讀緩沖區(qū)的大小)。因此,接收端讀取數(shù)據(jù)的次數(shù)與發(fā)送端發(fā)出多少個(gè)報(bào)文段個(gè)數(shù)也沒有固定的數(shù)量關(guān)系。總結(jié)來說,即**對(duì)于 TCP 連接,發(fā)送端執(zhí)行的寫操作次數(shù)與接收端執(zhí)行的讀操作次數(shù)之間沒有任何數(shù)據(jù)關(guān)系,這也是基于流服務(wù)的特點(diǎn)。**而對(duì)于 UDP 服務(wù),發(fā)送端每執(zhí)行一次寫操作,就會(huì)將其封裝成一個(gè) UDP 數(shù)據(jù)報(bào)并發(fā)送之,同時(shí)接收端必須根據(jù)發(fā)送的進(jìn)行讀,否則就會(huì)丟包。因此,對(duì)于 UDP 連接,發(fā)送端寫的次數(shù)據(jù)與讀的次數(shù)是一致的,這也是基于數(shù)據(jù)報(bào)的服務(wù)的特點(diǎn)。
6、TCP 的連接是一對(duì)一的,所以如果是基于廣播或者多播的的應(yīng)用程序不能使用 TCP,而 UDP 則非常適合廣播和多播。
總結(jié)一句定義:
TCP 協(xié)議(Transmission Control Protocal,傳輸控制協(xié)議)為應(yīng)用層提供可靠的、面向連接的、基于流的服務(wù)。而 UDP 協(xié)議(User Datagram Protocal,用戶數(shù)據(jù)報(bào)協(xié)議)則與 TCP 協(xié)議完全相反,它為應(yīng)用層提供不可靠、無連接和基于數(shù)據(jù)報(bào)的服務(wù)。
二、TCP 頭部結(jié)構(gòu)
TCP 報(bào)文結(jié)構(gòu)分為頭部部分和數(shù)據(jù)部分,為什么需要了解 TCP 頭部結(jié)構(gòu),因?yàn)樵诤竺妗叭挝帐峙c四次揮手”里會(huì)用到頭部結(jié)構(gòu)里的標(biāo)志位。簡(jiǎn)單了解下對(duì)理解后面的過程有一定的好處。
下面一一說明每個(gè)的作用:
16位源端口號(hào)與目的端口號(hào),這個(gè)比較好理解,就不過多解釋。
32位序號(hào):在建立連接(或者關(guān)閉)的過程,這個(gè)序號(hào)是用來做占位,當(dāng) A 發(fā)送連接請(qǐng)求到 B,這個(gè)時(shí)候會(huì)帶上一個(gè)序號(hào)(隨機(jī)值,稱為 ISN),而 B 確認(rèn)連接后,會(huì)把這個(gè)序號(hào) +1 返回,同時(shí)帶上自己的充號(hào)。當(dāng)建立連接后,該序號(hào)為生成的隨機(jī)值 ISN 加上該段報(bào)文段所攜帶的數(shù)據(jù)的第一個(gè)字節(jié)在整個(gè)字節(jié)流中的偏移量。比如,某個(gè) TCP 報(bào)文段發(fā)送的數(shù)據(jù)是字節(jié)流中的第 100 ~ 200 字節(jié),那該序號(hào)為 ISN + 100。所以總結(jié)起來說明建立連接(或者關(guān)閉)時(shí),序號(hào)的作用是為了占位,而連接后,是為了標(biāo)記當(dāng)前數(shù)據(jù)流的第一個(gè)字節(jié)。
4位頭部長(zhǎng)度:標(biāo)識(shí) TCP 頭部有多少個(gè) 32 bit 字,因?yàn)槭?4位,即 TCP 頭部最大能表示 15,即最長(zhǎng)是 60 字節(jié)。即它是用來記錄頭部的最大長(zhǎng)度。
6位標(biāo)志位,包括:
URG 標(biāo)志:表示緊急指針是否有效。
ACK 標(biāo)志:確認(rèn)標(biāo)志。通常稱攜帶 ACK 標(biāo)志的 TCP 報(bào)文段為確認(rèn)報(bào)文段。
PSH 標(biāo)志:提示接收端應(yīng)該程序應(yīng)該立即從 TCP 接收緩沖區(qū)中讀走數(shù)據(jù),為接收后續(xù)數(shù)據(jù)騰出空間(如果不讀走,數(shù)據(jù)就會(huì)一直在緩沖區(qū)內(nèi))。
RST 標(biāo)志:表示要求對(duì)方重新建立連接。通常稱攜帶 RST 標(biāo)志的 TCP 報(bào)文段為復(fù)位報(bào)文段。
SYN 標(biāo)志:表示請(qǐng)求建立一個(gè)連接。通常稱攜帶 SYN 標(biāo)志的 TCP 報(bào)文段稱為同步報(bào)文段。
FIN 標(biāo)志:關(guān)閉標(biāo)志,通常稱攜帶 FIN 標(biāo)志的 TCP 報(bào)文段為結(jié)束報(bào)文段。
這些標(biāo)志位說明了當(dāng)前請(qǐng)求的目的,即要干什么。
16 位窗口大小:表示當(dāng)前 TCP 接收緩沖區(qū)還能容納多少字節(jié)的數(shù)據(jù),這樣發(fā)送方就可以控制發(fā)送數(shù)據(jù)的速度,它是 TCP 流量控制的一個(gè)手段。
16 位校驗(yàn)和:驗(yàn)證數(shù)據(jù)是否損壞,通過 CRC 算法檢驗(yàn)。這個(gè)校驗(yàn)不僅包括 TCP 頭部,也包括數(shù)據(jù)部分。
16 位緊急指針:正的偏移量,它和序號(hào)字段的值相加表示最后一個(gè)緊急數(shù)據(jù)的下一字節(jié)的序號(hào)。TCP 的緊急指針是發(fā)送端向接收端發(fā)送緊急數(shù)據(jù)的方法。
TCP 頭部選項(xiàng):可變長(zhǎng)的可選信息,這部分最多包含 40 字節(jié),因?yàn)?TCP 頭部最長(zhǎng)是 60 字節(jié),所以固定部分占 20 字節(jié)。這里不做詳細(xì)介紹,可以參考《Linux高性能編程》3.2.2
三、三次握手與四次揮手過程詳解
畫圖
先來解釋三次握手過程:
1、發(fā)送端發(fā)送連接請(qǐng)求,6位標(biāo)志為 SYN,同時(shí)帶上自己的序號(hào)(此時(shí)由于不傳輸數(shù)據(jù),所以不表示字節(jié)的偏移量,只是占位),比如是 223。
2、接收端接到請(qǐng)求,表示同意連接,發(fā)送同意響應(yīng),帶上 SYN + ACK 標(biāo)志位,同時(shí)將確認(rèn)序號(hào)為 224(發(fā)送端序號(hào)加1),并帶上自己的序號(hào)(此時(shí)同樣由于不傳輸數(shù)據(jù),所以不表示字節(jié)的偏移量,只是占位),比如是 521。
3、發(fā)送端接收到確認(rèn)信息,再發(fā)回給接收端,表示我已接受到你的確認(rèn)信息,此時(shí)標(biāo)志仍為 ACK,確認(rèn)序號(hào)為 522。
涉及到的問題:為什么是三次握手,而不是四次或者兩次?
首先解釋為什么不是四次。四次的過程是這樣的:
發(fā)送方:我要連你了。
接收方:好的。
接收方:我準(zhǔn)備好了,你連吧。
發(fā)送方:好的。
顯然接收方準(zhǔn)備好連接并同意連接是可以合并的,這樣可以提高連接的效率。
再來,我們解釋為什么不是兩次。其實(shí)也比較好理解,我們知道 TCP 是全雙工通信的,同時(shí)也是可靠的,連接和關(guān)閉都是兩邊都要執(zhí)行才算真正的完成,同時(shí)還需要確保兩端都已經(jīng)執(zhí)行了連接或者關(guān)閉。如果只有兩次,過程是這樣的:
發(fā)送方:我要連你了。
接收方:好的。
很明顯,接收方并不知道也不能保證發(fā)送方一定接收到 “好的” 這條信息,一旦接收方真的沒有收到這條信息,就會(huì)出現(xiàn)接收收“單方面連接”的情況,這個(gè)時(shí)候發(fā)送方就會(huì)一直重試發(fā)送連接請(qǐng)求,直到真正收到 “好的” 這條信息之后才算連接完成。而對(duì)于三次,如果發(fā)送方?jīng)]有等待到你回復(fù)確認(rèn),它是不會(huì)真正處于連接狀態(tài)的,它會(huì)重試確認(rèn)請(qǐng)求。
接著我們來看看四次揮手過程:
1、發(fā)送方發(fā)送關(guān)閉請(qǐng)求,標(biāo)志位為:FIN,同時(shí)也會(huì)帶上自己的序號(hào)(此時(shí)同樣由于不傳輸數(shù)據(jù),所以不表示字節(jié)的偏移量,只是占位)。
2、接收方接到請(qǐng)求后,回復(fù)確認(rèn):ACK,同時(shí)確認(rèn)序號(hào)為請(qǐng)求序號(hào)加1。
3、接收方也決定關(guān)閉連接,發(fā)送關(guān)閉通知,標(biāo)志位為 FIN,同時(shí)還會(huì)帶上第2步中的確認(rèn)信息,即 ACK,以及確認(rèn)序號(hào)和自己的序號(hào)。
4、發(fā)送方回復(fù)確認(rèn)信息:ACK,接收方序號(hào)加1。
涉及到的問題:為什么需要四次握手,不是三次?
三次的過程是這樣的:
發(fā)送方:我不再給你發(fā)送數(shù)據(jù)了。
接收方:好的,我也不給你發(fā)了。
發(fā)送方:好的,拜拜。
這是因?yàn)楫?dāng)接收方收到關(guān)閉請(qǐng)求后,它能立馬響應(yīng)的就是確認(rèn)關(guān)閉,它這里確認(rèn)的是接收方的關(guān)閉,即發(fā)送方不再發(fā)數(shù)據(jù)給接收方了,但他還是可以接收接收方發(fā)給他的數(shù)據(jù)。而接收方是否需要關(guān)閉“發(fā)送數(shù)據(jù)給發(fā)送方”這條通道,取決于操作系統(tǒng)。操作系統(tǒng)也有可能 sleep 個(gè)幾秒再關(guān)閉,如果合并成三次,就可能造成接收方不能及時(shí)收到確認(rèn)請(qǐng)求,可能造成超時(shí)重試等情況。因此需要四次。
四、什么是 TIME_WAIT 狀態(tài)
首先,我們來看一段代碼(說了這么多理論,終于要看點(diǎn)代碼了)。這里舉一個(gè) python 簡(jiǎn)單的使用 socket 進(jìn)行 tcp 通信的示例:
服務(wù)端:
socket_server_test.py# -*- coding: utf-8 -*-
"""
@Time : 2019/6/26 下午4:58
@Author : Demon
@File : socket_server_test.py
@Desc :
"""
import socket
HOST = '127.0.0.1' # 標(biāo)準(zhǔn)的回環(huán)地址 (localhost)
PORT = 9999 # 監(jiān)聽的端口 (非系統(tǒng)級(jí)的端口: 大于 1023)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 第三個(gè)參數(shù),如果為0,也不可復(fù)用
# 第三個(gè)如果為1,可以復(fù)用
# s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen()
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)
print("data:", data)
if data:
print("close")
s.close()
break
conn.sendall(data)
客戶端:
# -*- coding: utf-8 -*-"""
@Time : 2019/6/26 下午4:55
@Author : yrr
@File : socket_client_test.py
@Desc : 測(cè)試 self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
"""
import socket
HOST = '127.0.0.1' # 服務(wù)器的主機(jī)名或者 IP 地址
PORT = 9999 # 服務(wù)器使用的端口
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
s.sendall(b'Hello, world')
data = s.recv(1024)
print('Received', repr(data))
執(zhí)行效果:
我們會(huì)發(fā)現(xiàn),當(dāng)我們服務(wù)端主動(dòng)關(guān)閉時(shí),如果我們?cè)俅芜\(yùn)行這個(gè)程序,會(huì)報(bào)錯(cuò)誤說端口仍然被占用。這就很奇怪了,明明已經(jīng)關(guān)閉了連接,為什么還會(huì)占用著端口呢?我們使用 netstat -an|grep 9999 命令查看,發(fā)現(xiàn)當(dāng)前這個(gè)連接處于 TIME_WAIT 狀態(tài)。
我們來說下 TIME_WAIT 狀態(tài)。即當(dāng)一方斷開連接后,它并沒有直接進(jìn)入 CLOSED 狀態(tài),而是轉(zhuǎn)移到 TIME_WAIT 狀態(tài),在這個(gè)狀態(tài),需要等待 2MSL(Maximum Segment Life,報(bào)文段最大生存時(shí)間)的時(shí)間,才能完全關(guān)閉。
涉及問題:
1、為什么需要有 TIME_WAIT 狀態(tài)存在?
簡(jiǎn)單來說有兩點(diǎn)原因如下:
a. 當(dāng)最后發(fā)送方發(fā)出確認(rèn)信息后,仍然不能保證接收方能收到信息,萬一沒收到,那接收方就會(huì)重試,而此時(shí)發(fā)送方已經(jīng)真正關(guān)閉了,就接受不到請(qǐng)求了。
b. 如果發(fā)送方在發(fā)出確認(rèn)信息后就關(guān)閉了,在接收方接到確認(rèn)信息的過程中,發(fā)送方是有可能再次發(fā)出連接請(qǐng)求的,那這個(gè)時(shí)候就亂套了。剛連接完,又收到確認(rèn)關(guān)閉的信息。
2、為什么時(shí)長(zhǎng)是 2MSL 呢?
這個(gè)其實(shí)也比較好理解,所以我發(fā)送確認(rèn)信息,到達(dá)最長(zhǎng)時(shí)間是 MSL,而你如果沒接受到,再重試,時(shí)間最長(zhǎng)也是 MSL,那我等 2MSL,如果還沒收到請(qǐng)求,證明你真的已經(jīng)正常收到了。
正因?yàn)槲覀冇羞@個(gè) TIME_WAIT 狀態(tài),所以通常我們說是客戶端先關(guān)閉,一般不會(huì)讓服務(wù)器端先關(guān)閉。那如何避免出現(xiàn)關(guān)閉后端口被占用的情況(即上面代碼示例問題怎么解決)呢?很簡(jiǎn)單,加一行代碼即可實(shí)現(xiàn):
# socket.SO_REUSEADDR 表示 close 后端口可復(fù)用s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
Python常見面試題中這個(gè)三次握手與四次揮手的出題率還是比較高的,包含的知識(shí)點(diǎn)也比較多,大家慢慢消化!有哪里不清楚的地方,
轉(zhuǎn)載于:https://www.cnblogs.com/cherry-tang/p/11139746.html
總結(jié)
以上是生活随笔為你收集整理的Python常见面试题:TCP 协议中的三次握手与四次挥手相关概念详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python2与Python3区别
- 下一篇: 多线程交替打印示例