少點代碼,多點頭髮linux
本文已經收錄至個人GitHub,歡迎你們踊躍star 和 issues。git
https://github.com/midou-tech/articlesgithub
三次握手創建連接,四次揮手斷開連接。這個問題算很是經典的問題,也是面試官很是喜歡問的問題。web
不誇張的說,龍叔在校招面試的時候每一家公司都問到過關於三次握手和四次揮手相關的問題,相信你們也都差很少被面試官各類懟。面試
這個問題的重要性,已經意識到。不說廢話了,接下來就是聽龍叔給你安排的明明白白。編程
先畫個圖,看下TCP的創建鏈接 和 斷開鏈接的總體過程。緩存
看完這個圖相信聰明的你在總體對三次握手和四次揮手有了一些基本把控。可是,裏面的細節確定是會有些生疏或者模糊的,接下來就一個一個問題的揭露本質。服務器
在解釋以前先看點基礎知識作作鋪墊。微信
狀態 | 描述 |
---|---|
CLOSED | 阻塞或關閉狀態,表示主機當前沒有正在傳輸或者創建的連接 |
LISTEN | 監聽狀態,表示服務器作好準備,等待創建傳輸連接 |
SYN RECV | 收到第一次的傳輸請求,還未進行確認 |
SYN SENT | 發送完第一個SYN報文,等待收到確認 |
ESTABLISHED | 連接正常創建以後進入數據傳輸階段 |
FIN WAIT1 | 主動發送第一個FIN報文以後進入該狀態 |
FIN WAIT2 | 已經收到第一個FIN的確認信號,等待對方發送關閉請求 |
TIMED WAIT | 完成雙向連接關閉,等待分組消失 |
CLOSING | 雙方同時關閉請求,等待對方確認時 |
CLOSE WAIT | 收到對方的關閉請求並進行確認進入該狀態 |
LAST ACK | 等待最後一次確認關閉的報文 |
首部有20字節的固定長度,含義以下:網絡
各佔2字節,就是存儲源端口號和目的端口的
佔4字節,表示的範圍就是整形的範圍[0~2^32]。序號使用在給數據部分每一個字節進行編號的,編號方式是mod 2^32 。
佔4字節,範圍也是無符號整數的範圍。使用在對端傳輸給個人數據最後一個字節序號,例如A傳輸給B 101—500,此時B返回的確認號必定是小於等於501的。當B段正確接收數據以後纔會返回確認號,換句話說確認號以前的數據已經所有接收。
佔4bit,數據偏移不少人很容易想到是否是表示數據的長度,那就錯了。偏移嘛,指的是TCP起始位置到數據部分的起始位置的偏移,也就是TCP首部的長度。
佔6bit,保留字段顧名思義,就是爲從此使用,默認置爲0。
佔用1bit,URG=1,表示緊急指針有效,此時tcp數據優先傳輸。至關於生活中的緊急通道,特殊狀況時使用。
在網絡中也會有特殊狀況,例如,發送一個很長的程序在遠程服務器上運行,此時發現程序有bug,須要中斷運行,所以咱們從鍵盤輸入Ctrl c,假如不使用緊急數據,須要在緩衝區裏排隊,都知道是bug了,還要排隊,這怕是要出鍋啊。
此時使用緊急數據傳輸,不須要排隊,直接中斷程序是否是更符合咱們的預期。
須要注意一點是,即便窗口爲0時,也能夠發送緊急數據。
如何使用緊急URG控制位,在socket編程中send函數flag參數
send(int socket, const void *buffer, size_t length, int flags);
flags參數傳MSG_OOB宏時,表示此時有緊急數據。MSG_OOB是個宏,
佔1bit,當ACK=1時生效。TCP有條硬性規定,當創建連接成功後全部傳輸的數據報文都必須把ACK置爲1。
佔1bit,發送方把PSH置爲1時 會當即發送該數據包,接收方收到PSH=1的報文會當即處理交付給應用層處理。是否是感受和URG很像,其實仍是有些區別的。
URG與PSH二者都使用於緊急處理的狀況,用來快速傳輸緊急數據。
URG置爲1時,對於發送發,「帶外數據」與正常狀況下應該發送的消息數據一塊兒,封裝成數據報發送,省去了在隊列中等待的時間。 在接收方,解析報文後,獲取數據以後仍是要放在緩存區中,等待滿了以後在向上往應用層交付。
PSH置爲1時,對於發送方,代表這些數據不須要等向下發送的緩存區滿,馬上封裝成報文,發送,省去了等待發送緩存區到達滿的狀態的時間。 在接收方,也不須要等接受緩存區滿,直接向上交付給應用層。
佔1bit,當RST=1時,TCP會主動釋放連接,兩種狀況會用上。
TCP出現嚴重差錯時,會主動釋放鏈接,重建連接,傳輸數據。
遇到非法報文或者拒絕鏈接時會把RST置爲1.
佔1bit,同步控制位,用來在傳輸鏈接創建時同步傳輸鏈接序號。
SYN=1時,表示這是一個鏈接請求或鏈接確認報文。
SYN=1,ACK=0,代表這是一個鏈接請求數據段,若是對方贊成創建鏈接,則對方會返回一個SYN=一、ACK=1的確認。
佔1bit,用於釋放一個傳輸鏈接。
FIN=1時,表示數據已所有傳輸完成,發送端沒有數據要傳輸了,要求釋放當前鏈接,可是接收端仍然能夠繼續接收尚未接收完的數據。
FIN=0,正常傳輸數據。
佔16bit,2byte,用於表示發送方能夠接受的最大數據大小。
該窗口是動態變化的,用做流量控制時使用。
佔16bit,2byte,用於對TCP頭部,僞頭部,數據三個部分進行校驗。
佔16bit,2byte,用於記錄緊急數據的末尾在數據段中的位置。
當URG=1時,該指針才生效。
可選項最長可達40byte,是可選的,能夠沒有。當可選項不存在時,TCP頭部長度爲20byte。
可選項能夠包括窗口縮放選項(Window ScaleOption,WSopt)、MSS(最大數據段大小)選項、SACK(選擇性確認)選項、時間戳(Timestamp)選項等。
TCP數據部分,由應用層應用程序提交的數據。
TCP頭部是基礎知識,必須瞭解才能更好的理解TCP數據如何封裝和傳輸,以及在創建連接和斷開連接時都在操做那些地方。
從圖中能夠清楚的看到,三次握手的過程,我在在把過程清楚的解釋一遍,順便說下每一個過程容易被問到的知識點。
採用C/S模式解釋,假設C端發起傳輸請求。
在發送創建連接請求以前,C端是保持CLOSED狀態,S端最開始也是處於CLOSED狀態,當執行listen函數套接字進入被動監聽狀態。
所謂被動監聽,是指當沒有客戶端請求時,套接字處於「睡眠」狀態,只有當接收到客戶端請求時,套接字纔會被「喚醒」來響應請求。
第一次:C端發送SYN=1的請求報文,此時C端進入SYN SENT狀態,等待服務器確認。
C端發送報文以後會啓動一個定時器,在超時以後未收到S端的確認,會再次發送SYN請求,每次嘗試的時間會是第一次的二倍,若是總的總嘗試時間爲75秒,這次創建連接失敗。
第二次:S端收到C端發送的SYN報文(創建連接請求)後,S端必須返回確認號而且同時發送一條SYN報文,此時進入SYN RCVD狀態。
TCP是全雙工通訊,協議規定當收到創建連接請求後必須返回序列號,同時創建本端到對端的通訊連接。這也叫作捎帶應答機制。
在發送完ACK+SYN報文後會啓動一個定時器,超時沒有收到ACK確認,會再次發送,會進行屢次重試。超時時間依舊每次翻倍,重試次數可設置。
修改
/proc/sys/net/ipv4/tcp_synack_retries
的值
第三次:C端收到S端發的ACK+SYN報文,須要返回一個應答ACK的報文,此時該鏈接會進入半鏈接狀態的隊列,當S端收到ACK後,一條完整的全雙工TCP連接創建完成,雙方進入ESTABLISHED狀態。
這裏有個經常使用攻擊手段,攻擊者僞造一個SYN請求發送給服務端,服務端響應以後,會收不到C端的ACK確認,服務端會不斷的重試,默認會重試五次。
此時服務端會維持這個連接的全部資源,若是有大量這樣的請求,服務端的資源會被耗完。
這就是DOS攻擊。
S端在發出ACK+SYN報文後會啓動一個定時器,在超時觸發還沒收到ACK就確認是丟失了,會重試一次發送。
這裏面的每一個狀態都必須搞明白,面試官也超級愛問上面的狀態轉移。
龍叔還遇到過一個面試官問我用過socket編程麼?問我用過哪些socket函數?
C端socket編程代碼
//C端
#define PORT 8080
#define BUFFER_SIZE 1024
int main(int argc, char **argv)
{
//定義IPV4的TCP鏈接的套接字描述符
int sock_cli = socket(AF_INET,SOCK_STREAM, 0);
//定義sockaddr_in
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr(argv[1]);
servaddr.sin_port = htons(PORT);
//鏈接服務器,成功返回0,錯誤返回-1
int ret = connect(sock_cli, (struct sockaddr *)&servaddr, sizeof(servaddr));
//客戶端將控制檯輸入的信息發送給服務器端,服務器原樣返回信息,阻塞
while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL)
{
ret=send(sock_cli, sendbuf, strlen(sendbuf),0); ///發送
recv(sock_cli, recvbuf, sizeof(recvbuf),0); ///接收
fputs(recvbuf, stdout);
}
close(sock_cli); // 關閉鏈接
return 0;
}
S端socket編程代碼
int main(int argc, char **argv)
{
//定義IPV4的TCP鏈接的套接字描述符
int server_sockfd = socket(AF_INET,SOCK_STREAM, 0);
//定義sockaddr_in
struct sockaddr_in server_sockaddr;
server_sockaddr.sin_family = AF_INET;
server_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
server_sockaddr.sin_port = htons(PORT);
//bind成功返回0,出錯返回-1
if(bind(server_sockfd,(struct sockaddr *)&server_sockaddr,sizeof(server_sockaddr))==-1)
//listen成功返回0,出錯返回-1,容許同時監聽的鏈接數爲QUEUE_SIZE
if(listen(server_sockfd,QUEUE_SIZE) == -1)
for(;;)
{
struct sockaddr_in client_addr;
socklen_t length = sizeof(client_addr);
//進程阻塞在accept上,成功返回非負描述字,出錯返回-1
int conn = accept(server_sockfd, (struct sockaddr*)&client_addr,&length);
//處理數據部分
...
}
close(server_sockfd);
return 0;
}
這問題問的,面試官是咋了?在這明知故問的,整些有的沒的。確定是不行啊,RFC 標準就是這樣寫的啊。
可不敢這樣回答啊,標準是說的三次握手創建連接,可沒說四次不行啊。要是這樣答,妥妥的會收到,同窗咱們今天的面試到此基本結束了,你回家等消息...
龍叔來講說這個問題,爲何不能兩次?
若是第二次不發送SYN+ACK,只是發送確認應答消息ACK,會形成只能創建單向通訊,並且不能應答。而TCP是全雙工通訊的,並且必須保證可靠性。
若是第二發送SYN+ACK,不用應答。此時會出現三種狀況
1、二次握手失敗,C端會重複發送SYN報文,等待對端發送確認報文,S端會保存tcp鏈接的全部資源,大量的這種狀況會致使S資源耗盡。
2、二次握手成功,S收不到ACK會重複發送SYN+ACK報文。
3、二次握手完之後,雙方覺得鏈接創建成功,便可開始通訊。假如此時鏈接並無真的創建成功,S端開始發送消息,會形成網絡擁堵發生。
爲何不能是四次?
四次其實原則上來講是能夠的,就是把第二次的ACK和SYN分兩次發送。在理論上是徹底能夠行得通的,可是TCP本着節約網絡網絡資源的前提。
還有一種是不拆開二次握手的捎帶應答,三次握手以後C端繼續發送SYN報文,其時這是徒勞的。第三次完成之後連接已經創建,後面不管多少次都是徒勞。
這就是雙方同時創建連接的狀況,狀況還不錯,反正能創建成功,這點是確定的。可是要注意兩點
第1、此時只會創建一條全雙工的TCP連接,不是兩條。
第2、雙方沒有CS之分,兩端都是同時承擔兩個角色,客戶端和服務器。
先整個圖看下四次揮手的整個過程和狀態轉移。狀態轉移會考看仔細點。
依舊採用C/S模式解釋此過程。
第一次:當C端的應用程序結束數據傳輸是,會向S端發送一個帶有FIN附加標記的報文段(FIN表示英文finish),此時C端進入FIN_WAIT1狀態,C端不能在發送數據到S端。
第二次:S端收到FIN報文會響應一個ACK報文,S端進入CLOSE_WAIT狀態。進入此狀態後S端把剩餘未發送的數據發送到C端,C端收到S端的ACK以後,進入FIN_WAIT2狀態。
同時繼續接受S端傳輸的其餘數據包。
第三次:S端處理完本身待發送的數據以後,也會發送FIN斷開連接的請求,S端進入LAST_ACK狀態。
第四次:C端收到S端的斷開連接請求後會啓動一個定時器,該定時器時長是2MSL(最大段報文生存時間),同時發送最後一次ACK報文。
TCP是全雙工的通訊機制,每一個方向必須單獨進行關閉。
TCP傳輸鏈接關閉的原則以下:
當一端完成它的數據發送任務後就能夠發送一個FIN字段置1的數據段來終止這個方向的數據發送;當另外一端收到這個FIN數據段後,必須通知它的應用層 對端已經終止了那個方向的數據傳送。
這點到是很迷惑人,可是掌握了TCP傳輸的一些細節就會發現並不難。
TCP是全雙工通訊的,S收到斷開連接請求後只是表示C端不會傳輸數據到S端了,可是並不表示S端不傳輸數據到C端。
若是採用捎帶應答,S端將沒法把剩餘的數據傳輸到C端。
網絡是不可靠的,TCP是可靠協議,必須保證最後一次報文送達以後才能斷開連接,不然會再次收到S端的FIN報文信息。
而等待2MSL時間就是爲了保證最後最後一次報文丟失時還能從新發送。
2MSL是報文一個往返的最長時間,假設小於這個時間會發生,ACK丟了,可是還沒接收到對方重傳的FIN我方就從新發送了ACK。
這個不難TCP本身作了保證,TCP默認有個定時器,每次收到客戶端的請求後會把定時器設置好,一般設置兩小時,超過兩小時還沒收到數據。
服務端會發送一個探測報文,之後每隔75秒鐘發送一次。若一連發送10個探測報文仍然沒反應,服務器就認爲客戶端出了故障,接着就關閉鏈接。
三次握手和四次揮手的知識基本告一段落了,就講到這裏了,若是有什麼不明白的地方能夠加我微信探討。
後面還會出一篇網絡編程經常使用的linux命令行工具,好比ping、tcpdump、netstat、nc等等,在出一篇計算機網絡的總結文章。計算機網絡這部分基本完結了,若是有不懂得能夠看看公號裏面前面的文章。