一. 預(yù)備工作
測(cè)試server代碼
import socket
import sys
import os
addr = ('127.0.0.1', 9988)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(addr)
server.listen(10)
while True:
connection, address = server.accept()
print 'connection ip:', address
測(cè)試client代碼
from socket import *
import time
addr = ('127.0.0.1', 9988)
client = socket(AF_INET, SOCK_STREAM)
client.connect(addr)
抓包命令
tcpdump -i lo port 9988 -S
抓包數(shù)據(jù)
16:34:33.611019 IP localhost.41631 > localhost.nsesrvr: Flags [S], seq 2970024578, win 32792, options [mss 16396,sackOK,TS val 514254463 ecr 0,nop,wscale 7], length 0
16:34:33.611035 IP localhost.nsesrvr > localhost.41631: Flags [S.], seq 2032818397, ack 2970024579, win 32768, options [mss 16396,sackOK,TS val 514254463 ecr 514254463,nop,wscale 7], length 0
16:34:33.611045 IP localhost.41631 > localhost.nsesrvr: Flags [.], ack 2032818398, win 257, options [nop,nop,TS val 514254463 ecr 514254463], length 0
# == 三次握手結(jié)束==
# == 四次揮手開(kāi)始==
16:34:33.611150 IP localhost.nsesrvr > localhost.41474: Flags [F.], seq 507613731, ack 3023763844, win 256, options [nop,nop,TS val 514254463 ecr 514208493], length 0
16:34:33.611163 IP localhost.41474 > localhost.nsesrvr: Flags [.], ack 507613732, win 257, options [nop,nop,TS val 514254463 ecr 514254463], length 0
16:34:33.612059 IP localhost.41631 > localhost.nsesrvr: Flags [F.], seq 2970024579, ack 2032818398, win 257, options [nop,nop,TS val 514254464 ecr 514254463], length 0
16:34:33.612718 IP localhost.nsesrvr > localhost.41631: Flags [.], ack 2970024580, win 256, options [nop,nop,TS val 514254465 ecr 514254464], length 0
二. 三次握手
流程圖

中間的標(biāo)志位
S=SYN,發(fā)起連接標(biāo)志。
P=PUSH,傳送數(shù)據(jù)標(biāo)志。
F=FIN,關(guān)閉連接標(biāo)志。
ack,表示確認(rèn)包。
RST=RESET,異常關(guān)閉連接。
.,表示沒(méi)有任何標(biāo)志。
1.三次握手具體分析
- 第1行:16:34:33.611019,從localhost(client)的臨時(shí)端口41631向localhost.nsesrvr(server)的9988監(jiān)聽(tīng)端口發(fā)起連接,client初始包序號(hào)seq為2970024578,滑動(dòng)窗口大小為32792字節(jié)(滑動(dòng)窗口即tcp接收緩沖區(qū)的大小,用于tcp擁塞控制),mss大小為16396(即可接收的最大包長(zhǎng)度,通常為MTU減40字節(jié),IP頭和TCP頭各20字節(jié))?!緎eq=2970024578,ack=0,syn=1】
- 第2行:16:34:33.611035,server響應(yīng)連接,同時(shí)帶上第一個(gè)包的ack信息,為client端的初始包序號(hào)seq加1,即1944916151,即server端下次等待接受這個(gè)包序號(hào)的包,用于tcp字節(jié)流的順序控制。Server端的初始包序號(hào)seq為2032818397,mss也是16396?!緎eq=2032818397,ack=2970024579,syn=1】
- 第3行:16:34:33.611045,client再次發(fā)送確認(rèn)連接,tcp連接三次握手完成,等待傳輸數(shù)據(jù)包?!綼ck=2032818398,seq=2970024579】
2. 為什么是三次握手而不是兩次?
對(duì)于step3的作用,假設(shè)一種情況,客戶(hù)端A向服務(wù)器B發(fā)送一個(gè)連接請(qǐng)求數(shù)據(jù)報(bào),然后這個(gè)數(shù)據(jù)報(bào)在網(wǎng)絡(luò)中滯留導(dǎo)致其遲到了,雖然遲到了,但是服務(wù)器仍然會(huì)接收并發(fā)回一個(gè)確認(rèn)數(shù)據(jù)報(bào)。但是A卻因?yàn)榫镁檬詹坏紹的確認(rèn)而將發(fā)送的請(qǐng)求連接置為失效,等到一段時(shí)間后,接到B發(fā)送過(guò)來(lái)的確認(rèn),A認(rèn)為自己現(xiàn)在沒(méi)有發(fā)送連接,而B(niǎo)卻一直以為連接成功了,于是一直在等待A的動(dòng)作,而A將不會(huì)有任何的動(dòng)作了。這會(huì)導(dǎo)致服務(wù)器資源白白浪費(fèi)掉了,因此,兩次握手是不行的,因此需要再加上一次,對(duì)B發(fā)過(guò)來(lái)的確認(rèn)再進(jìn)行一次確認(rèn),即確認(rèn)這次連接是有效的,從而建立連接。
3. 對(duì)于雙方,發(fā)送序號(hào)的初始化為何值?
有的系統(tǒng)中是顯式的初始化序號(hào)是0,但是這種已知的初始化值是非常危險(xiǎn)的,因?yàn)檫@會(huì)使得一些黑客鉆漏洞,發(fā)送一些數(shù)據(jù)報(bào)來(lái)破壞連接。因此,初始化序號(hào)因?yàn)槿‰S機(jī)數(shù)會(huì)更好一些,并且是越隨機(jī)越安全。
二. 四次揮手
連接雙方在完成數(shù)據(jù)傳輸之后就需要斷開(kāi)連接。由于TCP連接是屬于全雙工的,即連接雙方可以在一條TCP連接上互相傳輸數(shù)據(jù),因此在斷開(kāi)時(shí)存在一個(gè)半關(guān)閉狀態(tài),即有有一方失去發(fā)送數(shù)據(jù)的能力,卻還能接收數(shù)據(jù)。因此,斷開(kāi)連接需要分為四次。主要過(guò)程如下:

step1. 主機(jī)A向主機(jī)B發(fā)起斷開(kāi)連接請(qǐng)求,之后主機(jī)A進(jìn)入FIN-WAIT-1狀態(tài);
step2. 主機(jī)B收到主機(jī)A的請(qǐng)求后,向主機(jī)A發(fā)回確認(rèn),然后進(jìn)入CLOSE-WAIT狀態(tài);
step3. 主機(jī)A收到B的確認(rèn)之后,進(jìn)入FIN-WAIT-2狀態(tài),此時(shí)便是半關(guān)閉狀態(tài),即主機(jī)A失去發(fā)送能力,但是主機(jī)B卻還能向A發(fā)送數(shù)據(jù),并且A可以接收數(shù)據(jù)。此時(shí)主機(jī)B占主導(dǎo)位置了,如果需要繼續(xù)關(guān)閉則需要主機(jī)B來(lái)操作了;
step4. 主機(jī)B向A發(fā)出斷開(kāi)連接請(qǐng)求,然后進(jìn)入LAST-ACK狀態(tài);
step5. 主機(jī)A接收到請(qǐng)求后發(fā)送確認(rèn),進(jìn)入TIME-WAIT狀態(tài),等待2MSL之后進(jìn)入CLOSED狀態(tài),而主機(jī)B則在接受到確認(rèn)后進(jìn)入CLOSED狀態(tài);
為何主機(jī)A在發(fā)送了最后的確認(rèn)后沒(méi)有進(jìn)入CLOSED狀態(tài),反而進(jìn)入了一個(gè)等待2MSL的TIME-WAIT?
- 第一,確保主機(jī)A最后發(fā)送的確認(rèn)能夠到達(dá)主機(jī)B。如果處于LAST-ACK狀態(tài)的主機(jī)B一直收不到來(lái)自主機(jī)A的確認(rèn),它會(huì)重傳斷開(kāi)連接請(qǐng)求,然后主機(jī)A就可以有足夠的時(shí)間去再次發(fā)送確認(rèn)。但是這也只能盡最大力量來(lái)確保能夠正常斷開(kāi),如果主機(jī)A的確認(rèn)總是在網(wǎng)絡(luò)中滯留失效,從而超過(guò)了2MSL,最后也無(wú)法正常斷開(kāi);
- 第二,如果主機(jī)A在發(fā)送了確認(rèn)之后立即進(jìn)入CLOSED狀態(tài)。假設(shè)之后主機(jī)A再次向主機(jī)B發(fā)送一條連接請(qǐng)求,而這條連接請(qǐng)求比之前的確認(rèn)報(bào)文更早地到達(dá)主機(jī)B,則會(huì)使得主機(jī)B以為這條連接請(qǐng)求是在舊的連接中A發(fā)出的報(bào)文,并不看成是一條新的連接請(qǐng)求了,即使得這個(gè)連接請(qǐng)求失效了,增加2MSL的時(shí)間可以使得這個(gè)失效的連接請(qǐng)求報(bào)文作廢,這樣才不影響下次新的連接請(qǐng)求中出現(xiàn)失效的連接請(qǐng)求。
為什么斷開(kāi)連接請(qǐng)求報(bào)文只有三個(gè),而不是四個(gè)?
因?yàn)樵赥CP連接過(guò)程中,確認(rèn)的發(fā)送有一個(gè)延時(shí)(即經(jīng)受延時(shí)的確認(rèn)),一端在發(fā)送確認(rèn)的時(shí)候?qū)⒌却欢螘r(shí)間,如果自己在這段事件內(nèi)也有數(shù)據(jù)要發(fā)送,就跟確認(rèn)一起發(fā)送,如果沒(méi)有,則確認(rèn)單獨(dú)發(fā)送。而我們的抓包實(shí)驗(yàn)中,由服務(wù)器端先斷開(kāi)連接,之后客戶(hù)端在確認(rèn)的延遲時(shí)間內(nèi),也有請(qǐng)求斷開(kāi)連接需要發(fā)送,于是就與上次確認(rèn)一起發(fā)送,因此就只有三個(gè)數(shù)據(jù)報(bào)了。
整體的圖

TCP狀態(tài)轉(zhuǎn)移要點(diǎn)
LISTENING狀態(tài)
FTP服務(wù)啟動(dòng)后首先處于偵聽(tīng)(LISTENING)狀態(tài)。
ESTABLISHED狀態(tài)
ESTABLISHED的意思是建立連接。表示兩臺(tái)機(jī)器正在通信。
CLOSE_WAIT
對(duì)方主動(dòng)關(guān)閉連接或者網(wǎng)絡(luò)異常導(dǎo)致連接中斷,這時(shí)我方的狀態(tài)會(huì)變成CLOSE_WAIT 此時(shí)我方要調(diào)用close()來(lái)使得連接正確關(guān)閉
TIME_WAIT
我方主動(dòng)調(diào)用close()斷開(kāi)連接,收到對(duì)方確認(rèn)后狀態(tài)變?yōu)門(mén)IME_WAIT。TCP協(xié)議規(guī)定TIME_WAIT狀態(tài)會(huì)一直持續(xù)2MSL(即兩倍的分段最大生存期),以此來(lái)確保舊的連接狀態(tài)不會(huì)對(duì)新連接產(chǎn)生影響。處于TIME_WAIT狀態(tài)的連接占用的資源不會(huì)被內(nèi)核釋放,所以作為服務(wù)器,在可能的情況下,盡量不要主動(dòng)斷開(kāi)連接,以減少TIME_WAIT狀態(tài)造成的資源浪費(fèi)。
參考文章:
https://my.oschina.net/xianggao/blog/678644
http://coolshell.cn/articles/11564.html
http://www.cnblogs.com/chobits/archive/2012/08/29/2662336.html