TCP協議
本文內容如下:
1)TCP協議概念
2)TCP頭部結構和字段介紹
3)TCP流量控制
滑動窗口
4)TCP擁塞控制
慢啟動、擁塞避免、快重傳、快恢復
有關TCP的三次握手四次揮手單獨寫了一篇博客:【TCP協議】---TCP三次握手和四次揮手
有關TCP粘包和黏包,也單獨寫一篇博客,下一篇博客就寫有關粘包黏包問題。
一、TCP概念
TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接(連接導向)的、可靠的、 基於IP的傳輸層協議。
首先來看看OSI的七層模型
我們需要知道TCP工作在網絡OSI的七層模型中的第四層——傳輸層,IP在第三層——網絡層,ARP 在第二層——數據鏈路層;同時,我們需要簡單的知道,數據從
應用層發下來,會在每一層都會加上頭部信息,進行 封裝,然后再發送到數據接收端。這個基本的流程你需要知道,就是每個數據都會經過數據的封裝和解封 裝的過程。
在OSI七層模型中,每一層的作用和對應的協議如下:
二、TCP頭部結構和字段介紹
從上面圖片可以看出,TCP協議是封裝在IP數據包中。
下圖是TCP報文數據格式。TCP首部如果不計選項和填充字段,它通常是20個字節。
下面分別對其中的字段進行介紹:
源端口和目的端口
各占2個字節,這兩個值加上IP首部中的源端IP地址和目的端IP地址唯一確定一個TCP連接。有時一個IP地址和一個端口號也稱為socket(插口)。
序號(seq)
占4個字節,是本報文段所發送的數據項目組第一個字節的序號。在TCP傳送的數據流中,每一個字節都有一個序號。例如,一報文段的序號為300,而且數據共100字節,
則下一個報文段的序號就是400;序號是32bit的無符號數,序號到達2^32-1后從0開始。
確認序號(ack)
占4字節,是期望收到對方下次發送的數據的第一個字節的序號,也就是期望收到的下一個報文段的首部中的序號;確認序號應該是上次已成功收到數據字節序號+1。
只有ACK標志為1時,確認序號才有效。
數據偏移
占4比特,表示數據開始的地方離TCP段的起始處有多遠。實際上就是TCP段首部的長度。由於首部長度不固定,因此數據偏移字段是必要的。數據偏移以32位為長度單位,
也就是4個字節,因此TCP首部的最大長度是60個字節。即偏移最大為15個長度單位=1532位=154字節。
保留
6比特,供以后應用,現在置為0。
6個標志位比特
① URG:當URG=1時,注解此報文應盡快傳送,而不要按本來的列隊次序來傳送。與“緊急指針”字段共同應用,緊急指針指出在本報文段中的緊急數據的最后一個字節的序號,
使接管方可以知道緊急數據共有多長。
② ACK:只有當ACK=1時,確認序號字段才有效;
③ PSH:當PSH=1時,接收方應該盡快將本報文段立即傳送給其應用層。
④ RST:當RST=1時,表示出現連接錯誤,必須釋放連接,然后再重建傳輸連接。復位比特還用來拒絕一個不法的報文段或拒絕打開一個連接;
⑤ SYN:SYN=1,ACK=0時表示請求建立一個連接,攜帶SYN標志的TCP報文段為同步報文段;
⑥ FIN:發端完成發送任務。
窗口
TCP通過滑動窗口的概念來進行流量控制。設想在發送端發送數據的速度很快而接收端接收速度卻很慢的情況下,為了保證數據不丟失,顯然需要進行流量控制, 協調好
通信雙方的工作節奏。所謂滑動窗口,可以理解成接收端所能提供的緩沖區大小。TCP利用一個滑動的窗口來告訴發送端對它所發送的數據能提供多大的緩 沖區。窗口大小為
字節數起始於確認序號字段指明的值(這個值是接收端正期望接收的字節)。窗口大小是一個16bit字段,因而窗口大小最大為65535字節。
檢驗和
檢驗和覆蓋了整個TCP報文段:TCP首部和數據。這是一個強制性的字段,一定是由發端計算和存儲,並由收端進行驗證。
緊急指針
只有當URG標志置1時緊急指針才有效。緊急指針是一個正的偏移量,和序號字段中的值相加表示緊急數據最后一個字節的序號。
三、TCP流量控制(滑動窗口協議)
在看這里,為了更好的理解,建議先了解TCP三次握手,四次揮手。博客:【TCP協議】---TCP三次握手和四次揮手
TCP流量控制主要是針對接收端的處理速度不如發送端發送速度快的問題,消除發送方使接收方緩存溢出的可能性。
TCP流量控制主要使用滑動窗口協議,滑動窗口是接受數據端使用的窗口大小,用來告訴發送端接收端的緩存大小,以此可以控制發送端發送數據的大小,從而達到流量
控制的目的。這個窗口大小就是我們一次傳輸幾個數據。對所有數據幀按順序賦予編號,發送方在發送過程中始終保持着一個發送窗口,只有落在發送窗口內的幀才允許被發送;
同時接收方也維持着一個接收窗口,只有落在接收窗口內的幀才允許接收。這樣通過調整發送方窗口和接收方窗口的大小可以實現流量控制。
我們可以通過下圖來分析:
1、發送方接收到了對方發來的報文 ack = 33, win = 10,知道對方收到了 33 號前的數據,現在期望接收 [33, 43) 號數據。那我們開始發送[33, 43) 號的數據。
2、[33, 43) 號的數據你是已經發送了,但接受方並沒有接受到[36,37]數據。所以接收方發送回對報文段 A 的確認:ack = 35, win = 10。
3、發送方收到了 ack = 35, win = 10,對方期望接收 [35, 45) 號數據。那么發送方在發送[35, 45) 。
這里面需要思考一個問題?
第一步發送了[33, 43),如果這次發送[35, 45),那中間重疊部分不是發送了兩次,所以這里要思考: 是全部重新發送還是只發送接收端沒有收到的數據,如果全部發送那么重復
發送的數據接收端怎么處理。這個下面快速重傳會講。
4、接收方接收到了報文段 [35, 41),接收方發送:ack = 41, win = 10. (這是一個累積確認)
5、發送方收到了 ack = 41, win = 10,對方期望接收 [41, 51) 號數據。
6、.......
這樣一直傳輸數據,直到數據發送完成。這么一來就保證數據數據的可靠性,因為如果某數據沒有獲取到,那么ack永遠不會跳過它。
這里也要思考一個問題,如果某一數據一只沒有獲取到,總不能一直這樣堵塞在這里吧,這里就要講接下來有關堵塞的解決方法。
四、TCP擁塞控制
流量控制是通過接收方來控制流量的一種方式;而擁塞控制則是通過發送方來控制流量的一種方式。
TCP發送方可能因為IP網絡的擁塞而被遏制,TCP擁塞控制就是為了解決這個問題(注意和TCP流量控制的區別)。
TCP擁塞控制的幾種方法:慢啟動,擁塞避免,快重傳和快恢復。
這里先理解一個概念: 擁塞窗口
擁塞窗口:發送方維持一個叫做擁塞窗口 cwnd的狀態變量。擁塞窗口的大小取決於網絡的擁塞程度,並且動態變化。
發送方的讓自己的發送窗口=min(cwnd,接受端接收窗口大小)。說明: 發送方取擁塞窗口與滑動窗口的最小值作為發送的上限。
發送方控制擁塞窗口的原則是:只要網絡沒有出現擁塞,擁塞窗口就增大一些,以便把更多的分組發送出去。但只要網絡出現擁塞,擁塞窗口就減小一些,以減少
注入到網絡中的分組數。
下面將討論擁塞窗口cwnd的大小是怎么變化的。
1、慢啟動
TCP在連接過程的三次握手完成后,開始傳數據,並不是一開始向網絡通道中發送大量的數據包。因為假如網絡出現問題,很多這樣的大包會積攢在路由器上,很容易導致網
絡中路由器緩存空間耗盡,從而發生擁塞。因此現在的TCP協議規定了,新建立的連接不能夠一開始就發送大尺寸的數據包,而只能從一個小尺寸的包開始發送,在發送和數據被
對方確認的過程中去計算對方的接收速度,來逐步增加每次發送的數據量(最后到達一個穩定的值,進入高速傳輸階段。相應的,慢啟動過程中,TCP通道處在低速傳輸階段),
以避免上述現象的發生。這個策略就是慢啟動。
畫個簡單的圖從原理上粗略描述一下
我們思考一個慢啟動引起的性能問題?
在海量用戶高並發訪問的大型網站后台,有一些基本的系統維護需求。比如遷移海量小文件,就是從一些機器拷貝海量小碎文件到另一些機器,來完成一些系統維護的基本需求。
慢啟動為什么會對拷貝海量小文件的需求造成重大性能損失?
舉個簡單的例子,我們對每個文件都采用獨立的TCP連接來傳輸(循環使用scp拷貝就是這個例子的實際場景,很常見的用法)。那么工作過程應該是,每傳輸一個文件建立一個
連接,然后連接處於慢啟動階段,傳輸小文件,每個小文件幾乎都處於獨立連接的慢啟動階段被傳輸,這樣傳輸過程所用的TCP包的總量就會增多。更細致的說一說這個事,如果在
慢啟動過程中傳輸一個小文件,我們可能需要2至3個小包,而在一個已經完成慢啟動的TCP通道中(TCP通道已進入在高速傳輸階段),我們傳輸這個文件可能只需要1個大包。
網絡拷貝文件的時間基本上全部消耗都在網絡傳輸的過程中(發數據過去等對端ACK,ACK確認歸來繼續再發,這樣的數據來回交互相比較本機的文件讀寫非常耗時間),撇開三次
握手和四次握手那些包,如果文件的數量足夠大,這個總時間就會被放大到需求難以忍受的地步。
因此,在遷移海量小文件的需求下,我們不能使用“對每個文件都采用獨立的TCP連接來傳輸(循環使用scp拷貝)“這樣的策略,它會使每個文件的傳輸都處於在一個獨立TCP的慢啟
動階段。
如何避免慢啟動,進而提升性能?
很簡單,盡量把大量小文件放在一個TCP連接中排隊傳輸。起初的一兩個文件處於慢啟動過程傳輸,后續的文件傳輸全部處於高速通道中傳輸,用這樣的方式來減少發包的數
目,進而降低時間消耗。同樣,實際上這種傳輸策略帶來的性能提升的功勞不僅僅歸於避免慢啟動,事實上也避免了大量的3次握手和四次握手,這個對海量小文件傳輸的性能消耗
也非常致命。
2、擁塞避免
先補充下: 慢啟動中擁塞窗口的cwnd值,開始是1,接下開是指數型增漲的。1、2、4、8、16.....這樣漲太快了吧。那么就有了堵塞避免。
cwnd不能一直這樣無限增長下去,一定需要某個限制。TCP使用了一個叫慢啟動門限(ssthresh)的變量,一旦cwnd>=ssthresh(大多數TCP的實現,通常大小都是65536),慢
啟動過程結束,擁塞避免階段開始;
擁塞避免:cwnd的值不再指數級往上升,開始加法增加。此時當窗口中所有的報文段都被確認時,cwnd的大小加1,cwnd的值就隨着RTT開始線性增加,這樣就可以避免增長過
快導致網絡擁塞,慢慢的增加調整到網絡的最佳值。(它邏輯很簡單就是到一定值后,cwnd不在是指數增長,而是+1增長。這樣顯然慢多了)。
非ECN環境下的擁塞判斷,發送方RTO超時,重傳了一個報文段,它的邏輯如下:
1)把ssthresh降低為cwnd值的一半。
2)把cwnd重新設置為1。
3)重新進入慢啟動過程。
上面的圖還是蠻好理解的。
3、快速重傳
TCP要保證所有的數據包都可以到達,所以,必需要有重傳機制。
注意: 接收端給發送端的Ack確認只會確認最后一個連續的包,比如,發送端發了1,2,3,4,5一共五份數據,接收端收到了1,2,於是回ack 3,然后收到了4(注意此時3沒收到)
此時的TCP會怎么辦?我們要知道,因為正如前面所說的,SeqNum和Ack是以字節數為單位,所以ack的時候,不能跳着確認,只能確認最大的連續收到的包,不然,發送端
就以為之前的都收到了。
3.1)超時重傳機制
一種是不回ack,死等3,當發送方發現收不到3的ack超時后,會重傳3。一旦接收方收到3后,會ack 回 4——意味着3和4都收到了。
但是,這種方式會有比較嚴重的問題,那就是因為要死等3,所以會導致4和5即便已經收到了,而發送方也完全不知道發生了什么事,因為沒有收到Ack,所以,發送方可能會
悲觀地認為也丟了,所以有可能也會導致4和5的重傳。
對此有兩種選擇:
① 一種是僅重傳timeout的包。也就是第3份數據。
② 另一種是重傳timeout后所有的數據,也就是第3,4,5這三份數據。
這兩種方式有好也有不好。第一種會節省帶寬,但是慢,第二種會快一點,但是會浪費帶寬,也可能會有無用功。但總體來說都不好。因為都在等timeout,timeout可能會很長。
3.2)快速重傳機制
於是,TCP引入了一種叫Fast Retransmit的算法,不以時間驅動,而以數據驅動重傳。也就是說,如果,包沒有連續到達,就ack最后那個可能被丟了的包,如果發送方連續收到
3次相同的ack,就重傳。Fast Retransmit的好處是不用等timeout了再重傳,而是只是三次相同的ack就重傳。
比如:如果發送方發出了1,2,3,4,5份數據,第一份先到送了,於是就ack回2,結果2因為某些原因沒收到,3到達了,於是還是ack回2,后面的4和5都到了,但是還是ack回2
因為2還是沒有收到,於是發送端收到了三個ack=2的確認,知道了2還沒有到,於是就馬上重轉2。然后,接收端收到了2,此時因為3,4,5都收到了,於是ack回6。示意圖如下
Fast Retransmit只解決了一個問題,就是timeout的問題,它依然面臨一個艱難的選擇,就是重轉之前的一個還是重裝所有的問題。對於上面的示例來說,是重傳#2呢還是重傳
#2,#3,#4,#5呢?因為發送端並不清楚這連續的3個ack(2)是誰傳回來的?也許發送端發了20份數據,是#6,#10,#20傳來的呢。這樣,發送端很有可能要重傳從#2到
#20的這堆數據(這就是某些TCP的實際的實現)。可見,這是一把雙刃劍。
總結: 不管是超時重傳還是快速重傳確實能保證數據的可靠性,但它無法解決的問題就是:比如發送端發了1、2、3、4、5,而接收端收到了1、3、4、5,那么這個時候它發送的
ack是2。那么發送端發送的是重傳#2呢還是重傳#2,#3,#4,#5的問題。如果在發送#2,#3,#4,#5,本身資源是一種浪費,因為接受方#3,#4,#5已經緩存下來,只需
#2,所以在發一遍是無意義的。
TCP三次握手和四次揮手
有關TCP協議詳解,請看博客:【TCP協議】(1)---TCP協議詳解
TCP有6種標示:SYN(建立聯機) ACK(確認) PSH(傳送) FIN(結束) RST(重置) URG(緊急)
一、TCP三次握手
第一次握手
客戶端向服務器發出連接請求報文,這時報文首部中的同部位SYN=1,同時隨機生成初始序列號 seq=x,此時,TCP客戶端進程進入了 SYN-SENT(同步已發送狀態)狀
態。TCP規定,SYN報文段(SYN=1的報文段)不能攜帶數據,但需要消耗掉一個序號。這個三次握手中的開始。表示客戶端想要和服務端建立連接。
第二次握手
TCP服務器收到請求報文后,如果同意連接,則發出確認報文。確認報文中應該 ACK=1,SYN=1,確認號是ack=x+1,同時也要為自己隨機初始化一個序列號 seq=y,此
時,TCP服務器進程進入了SYN-RCVD(同步收到)狀態。這個報文也不能攜帶數據,但是同樣要消耗一個序號。這個報文帶有SYN(建立連接)和ACK(確認)標志,詢問客戶端
是否准備好。
第三次握手
TCP客戶進程收到確認后,還要向服務器給出確認。確認報文的ACK=1,ack=y+1,此時,TCP連接建立,客戶端進入ESTABLISHED(已建立連接)狀態。
TCP規定,ACK報文段可以攜帶數據,但是如果不攜帶數據則不消耗序號。這里客戶端表示我已經准備好。
思考:為什么要三次握手呢,有人說兩次握手就好了
舉例:已失效的連接請求報文段。
client發送了第一個連接的請求報文,但是由於網絡不好,這個請求沒有立即到達服務端,而是在某個網絡節點中滯留了,直到某個時間才到達server,本來這已經是一個失效
的報文,但是server端接收到這個請求報文后,還是會想client發出確認的報文,表示同意連接。假如不采用三次握手,那么只要server發出確認,新的建立就連接了,但其實這個
請求是失效的請求,client是不會理睬server的確認信息,也不會向服務端發送確認的請求,但是server認為新的連接已經建立起來了,並一直等待client發來數據,這樣,server的
很多資源就沒白白浪費掉了,采用三次握手就是為了防止這種情況的發生,server會因為收不到確認的報文,就知道client並沒有建立連接。這就是三次握手的作用。
二、TCP數據的傳輸過程
建立連接后,兩台主機就可以相互傳輸數據了。如下圖所示(本篇博客圖片都是引用它人圖片):
1)主機A初始seq為1200,滑動窗體為100,向主機B傳遞數據的過程。
2)假設主機B在完全成功接收數據的基礎上,那么主機B為了確認這一點,向主機A發送 ACK 包,並將 Ack 號設置為 1301。因此按如下的公式確認 Ack 號:
Ack號 = Seq號 + 傳遞的字節數 + 1 (這是在完全接受成功的情況下)
3)主機A獲得B傳來的ack(1301)后,開始發送seq為1301,滑動窗體為100的數據。
......
與三次握手協議相同,最后加 1 是為了告訴對方要傳遞的 Seq 號。上面說了,主機B完全成功接收A發來的數據才是這樣的,如果存在丟包該如何。
下面分析傳輸過程中數據包丟失的情況,如下圖所示:
上圖表示通過 Seq 1301 數據包向主機B傳遞100字節的數據,但中間發生了錯誤,主機B未收到。經過一段時間后,主機A仍未收到對於 Seq 1301 的ACK確認,因此嘗試
重傳數據。為了完成數據包的重傳,TCP套接字每次發送數據包時都會啟動定時器,如果在一定時間內沒有收到目標機器傳回的 ACK 包,那么定時器超時,數據包會重傳。
上面也只是一種可能,比如數據1250丟失,那么Ack返回的就是1250,具體的可以詳細看下博客:【TCP協議】(1)---TCP協議詳解,這里面滑動窗口有說明。
三、TCP的四次揮手
第一次揮手
TCP發送一個FIN(結束),用來關閉客戶到服務端的連接。
客戶端進程發出連接釋放報文,並且停止發送數據。釋放數據報文首部,FIN=1,其序列號為seq=u(等於前面已經傳送過來的數據的最后一個字節的序號加1),
此時,客戶端進入FIN-WAIT-1(終止等待1)狀態。 TCP規定,FIN報文段即使不攜帶數據,也要消耗一個序號。
揮手
服務端收到這個FIN,他發回一個ACK(確認),確認收到序號為收到序號+1,和SYN一樣,一個FIN將占用一個序號。
服務器收到連接釋放報文,發出確認報文,ACK=1,ack=u+1,並且帶上自己的序列號seq=v,此時,服務端就進入了CLOSE-WAIT(關閉等待)狀態。TCP服務器
通知高層的應用進程,客戶端向服務器的方向就釋放了,這時候處於半關閉狀態,即客戶端已經沒有數據要發送了,但是服務器若發送數據,客戶端依然要接受。這個
狀態還要持續一段時間,也就是整個CLOSE-WAIT狀態持續的時間。
客戶端收到服務器的確認請求后,此時,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文(在這之前還需要接受服務器發送的最后的數據)。
揮手
服務端發送一個FIN(結束)到客戶端,服務端關閉客戶端的連接。
服務器將最后的數據發送完畢后,就向客戶端發送連接釋放報文,FIN=1,ack=u+1,由於在半關閉狀態,服務器很可能又發送了一些數據,假定此時的序列號為seq=w,
此時,服務器就進入了LAST-ACK(最后確認)狀態,等待客戶端的確認。
揮手
客戶端發送ACK(確認)報文確認,並將確認的序號+1,這樣關閉完成。
客戶端收到服務器的連接釋放報文后,必須發出確認,ACK=1,ack=w+1,而自己的序列號是seq=u+1,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。注意此時
TCP連接還沒有釋放,必須經過2∗∗MSL(最長報文段壽命)的時間后,當客戶端撤銷相應的TCB后,才進入CLOSED狀態。
服務器只要收到了客戶端發出的確認,立即進入CLOSED狀態。同樣,撤銷TCB后,就結束了這次的TCP連接。可以看到,服務器結束TCP連接的時間要比客戶端早一些。
思考:那么為什么是4次揮手呢?
為了確保數據能夠完成傳輸。
關閉連接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你所有的數據都全部發送給對方了,所以你可以未必會馬上會關閉SOCKET,也
即你可能還需要發送一些數據給對方之后,再發送FIN報文給對方來表示你同意現在可以關閉連接了,所以它這里的ACK報文和FIN報文多數情況下都是分開發送的。
可能有人會有疑問,tcp我握手的時候為何ACK(確認)和SYN(建立連接)是一起發送。揮手的時候為什么是分開的時候發送呢.
因為當Server端收到Client端的SYN連接請求報文后,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到
FIN報文時,很可能並不會立即關閉 SOCKET,所以只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端所有的報文都發送完了,我才能
發送FIN報文,因此不能一起發送。故需要四步揮手。
思考:客戶端突然掛掉了怎么辦?
正常連接時,客戶端突然掛掉了,如果沒有措施處理這種情況,那么就會出現客戶端和服務器端出現長時期的空閑。解決辦法是在服務器端設置保活計時器,每當服務器收到
客戶端的消息,就將計時器復位。超時時間通常設置為2小時。若服務器超過2小時沒收到客戶的信息,他就發送探測報文段。若發送了10個探測報文段,每一個相隔75秒,
還沒有響應就認為客戶端出了故障,因而終止該連接。
四、SYN(洪水)攻擊
背景
初始化連接的 SYN 超時問題Client發送SYN包給Server后掛了,Server回給Client的SYN-ACK一直沒收到Client的ACK確認,這個時候這個連接既沒建立起來,也不能算
失敗。這就需要一個超時時間讓Server將這個連接斷開,否則這個連接就會一直占用Server的SYN連接隊列中的一個位置,大量這樣的連接就會將Server的SYN連接隊列耗盡,
讓正常的連接無法得到處理。
目前,Linux下默認會進行5次重發SYN-ACK包,重試的間隔時間從1s開始,下次的重試間隔時間是前一次的雙倍,5次的重試時間間隔為1s, 2s, 4s, 8s, 16s,總共31s,第
5次發出后還要等32s都知道第5次也超時了,所以,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 63s,TCP才會把斷開這個連接。由於,SYN超時需要63秒,那么就給攻擊者一
個攻擊服務器的機會,攻擊者在短時間內發送大量的SYN包給Server(俗稱SYN flood攻擊),用於耗盡Server的SYN隊列。
什么是 SYN 攻擊
SYN 攻擊指的是,攻擊客戶端在短時間內偽造大量不存在的IP地址,向服務器不斷地發送SYN包,服務器回復確認包,並等待客戶的確認。由於源地址是不存在的,服務器
需要不斷的重發直至超時,這些偽造的SYN包將長時間占用未連接隊列,正常的SYN請求被丟棄,導致目標系統運行緩慢,嚴重者會引起網絡堵塞甚至系統癱瘓。SYN 攻擊是一
種典型的 DoS攻擊。
如何檢測 SYN 攻擊?
檢測 SYN 攻擊非常的方便,當你在服務器上看到大量的半連接狀態時,特別是源IP地址是隨機的,基本上可以斷定這是一次SYN攻擊。在 Linux/Unix 上可以使用系統自帶的
netstats 命令來檢測 SYN 攻擊。
如何防御 SYN 攻擊?
SYN攻擊不能完全被阻止,除非將TCP協議重新設計。我們所做的是盡可能的減輕SYN攻擊的危害,常見的防御 SYN 攻擊的方法有如下幾種:
縮短超時(SYN Timeout)
時間增加最大半連接數
過濾網關防護SYN
cookies技術
四、TCP和UDP的區別
我這里簡單列舉幾個,因為我還沒有研究UDP這個協議。
1、基於連接與無連接;UDP是無連接的,即發送數據之前不需要建立連接
2、TCP保證數據正確性,UDP可能丟包,TCP保證數據順序,UDP不保證。也就是說,通過TCP連接傳送的數據,無差錯,不丟失,不重復,且按序到達;UDP盡最大努力交付
,即不保證可靠交付Tcp通過校驗和,重傳控制,序號標識,滑動窗口、確認應答實現可靠傳輸。如丟包時的重發控制,還可以對次序亂掉的分包進行順序控制。
3、UDP具有較好的實時性,工作效率比TCP高,適用於對高速傳輸和實時性有較高的通信或廣播通信。
4、每一條TCP連接只能是點到點的;UDP支持一對一,一對多,多對一和多對多的交互通信。
5、TCP對系統資源要求較多,UDP對系統資源要求較少。
【TCP協議】(3)---TCP粘包黏包
有關TCP協議之前寫過兩篇博客:
一、TCP粘包、拆包圖解
假設客戶端分別發送了兩個數據包D1和D2給服務端,由於服務端一次讀取到字節數是不確定的,故可能存在以下四種情況:
1)服務端分兩次讀取到了兩個獨立的數據包,分別是D1和D2,沒有粘包和拆包
2)服務端一次接受到了兩個數據包,D1和D2粘合在一起,稱之為TCP粘包
3)服務端分兩次讀取到了數據包,第一次讀取到了完整的D1包和D2包的部分內容,第二次讀取到了D2包的剩余內容,這稱之為TCP拆包
4)服務端分兩次讀取到了數據包,第一次讀取到了D1包的部分內容D1_1,第二次讀取到了D1包的剩余部分內容D1_2和完整的D2包。
特別要注意的是,如果TCP的接受滑窗非常小,而數據包D1和D2比較大,很有可能會發生第五種情況,即服務端分多次才能將D1和D2包完全接受,期間發生多次拆包。
二、 粘包、拆包發生原因
產生原因主要有這3種:滑動窗口、MSS/MTU限制、Nagle算法
1、滑動窗口
TCP流量控制主要使用滑動窗口協議,滑動窗口是接受數據端使用的窗口大小,用來告訴發送端接收端的緩存大小,以此可以控制發送端發送數據的大小,從而達到流量
控制的目的。這個窗口大小就是我們一次傳輸幾個數據。對所有數據幀按順序賦予編號,發送方在發送過程中始終保持着一個發送窗口,只有落在發送窗口內的幀才允許被發送;
同時接收方也維持着一個接收窗口,只有落在接收窗口內的幀才允許接收。這樣通過調整發送方窗口和接收方窗口的大小可以實現流量控制。
現在來看一下滑動窗口是如何造成粘包、拆包的?
粘包:假設發送方的每256 bytes表示一個完整的報文,接收方由於數據處理不及時,這256個字節的數據都會被緩存到SO_RCVBUF(接收緩存區)中。如果接收方的SO_RCVBUF
中緩存了多個報文,那么對於接收方而言,這就是粘包。
拆包:考慮另外一種情況,假設接收方的窗口只剩了128,意味着發送方最多還可以發送128字節,而由於發送方的數據大小是256字節,因此只能發送前128字節,等到接收方ack
后,才能發送剩余字節。這就造成了拆包。
2、MSS和MTU分片
MSS: 是Maximum Segement Size縮寫,表示TCP報文中data部分的最大長度,是TCP協議在OSI五層網絡模型中傳輸層對一次可以發送的最大數據的限制。
MTU: 最大傳輸單元是Maxitum Transmission Unit的簡寫,是OSI五層網絡模型中鏈路層(datalink layer)對一次可以發送的最大數據的限制。
當需要傳輸的數據大於MSS或者MTU時,數據會被拆分成多個包進行傳輸。由於MSS是根據MTU計算出來的,因此當發送的數據滿足MSS時,必然滿足MTU。
為了更好的理解,我們先介紹一下在5層網絡模型中應用通過TCP發送數據的流程:
對於應用層來說,只關心發送的數據DATA,將數據寫入socket在內核中的發送緩沖區SO_SNDBUF即返回,操作系統會將SO_SNDBUF中的數據取出來進行發送。
傳輸層會在DATA前面加上TCP Header,構成一個完整的TCP報文。
當數據到達網絡層(network layer)時,網絡層會在TCP報文的基礎上再添加一個IP Header,也就是將自己的網絡地址加入到報文中。
到數據鏈路層時,還會加上Datalink Header和CRC。
當到達物理層時,會將SMAC(Source Machine,數據發送方的MAC地址),DMAC(Destination Machine,數據接受方的MAC地址 )和Type域加入。
可以發現數據在發送前,每一層都會在上一層的基礎上增加一些內容,下圖演示了MSS、MTU在這個過程中的作用。
MTU是以太網傳輸數據方面的限制,每個以太網幀都有最小的大小64bytes最大不能超過1518bytes。刨去以太網幀的幀頭 (DMAC目的MAC地址48bit=6Bytes
+SMAC源MAC地址48bit=6Bytes+Type域2bytes)14Bytes和幀尾 CRC校驗部分4Bytes(這個部分有時候大家也把它叫做FCS),那么剩下承載上層協議的地方也
就是Data域最大就只能有1500Bytes這個值 我們就把它稱之為MTU。
由於MTU限制了一次最多可以發送1500個字節,而TCP協議在發送DATA時,還會加上額外的TCP Header和Ip Header,因此刨去這兩個部分,就是TCP協議一次可以
發送的實際應用數據的最大大小,也就是MSS。
MSS長度=MTU長度-IP Header-TCP Header
TCP Header的長度是20字節,IPv4中IP Header長度是20字節,IPV6中IP Header長度是40字節,因此:在IPV4中,以太網MSS可以達到1460byte;在IPV6中,以太網
MSS可以達到1440byte。
需要注意的是MSS表示的一次可以發送的DATA的最大長度,而不是DATA的真實長度。發送方發送數據時,當SO_SNDBUF中的數據量大於MSS時,操作系統會將數據進
行拆分,使得每一部分都小於MSS,這就是拆包,然后每一部分都加上TCP Header,構成多個完整的TCP報文進行發送,當然經過網絡層和數據鏈路層的時候,還會分別
加上相應的內容。
需要注意: 默認情況下,與外部通信的網卡的MTU大小是1500個字節。而本地回環地址的MTU大小為65535,這是因為本地測試時數據不需要走網卡,所以不受到1500
的限制。
3、 Nagle算法
TCP/IP協議中,無論發送多少數據,總是要在數據(DATA)前面加上協議頭(TCP Header+IP Header),同時,對方接收到數據,也需要發送ACK表示確認。
即使從鍵盤輸入的一個字符,占用一個字節,可能在傳輸上造成41字節的包,其中包括1字節的有用信息和40字節的首部數據。這種情況轉變成了4000%的消耗,這樣的
情況對於重負載的網絡來是無法接受的。
為了盡可能的利用網絡帶寬,TCP總是希望盡可能的發送足夠大的數據。(一個連接會設置MSS參數,因此,TCP/IP希望每次都能夠以MSS尺寸的數據塊來發送數據)。
Nagle算法就是為了盡可能發送大塊數據,避免網絡中充斥着許多小數據塊。
Nagle算法的基本定義是任意時刻,最多只能有一個未被確認的小段。 所謂“小段”,指的是小於MSS尺寸的數據塊,所謂“未被確認”,是指一個數據塊發送出去后,沒有
收到對方發送的ACK確認該數據已收到。
Nagle算法的規則:
1)如果SO_SNDBUF(發送緩沖區)中的數據長度達到MSS,則允許發送;
2)如果該SO_SNDBUF中含有FIN,表示請求關閉連接,則先將SO_SNDBUF中的剩余數據發送,再關閉;
3)設置了TCP_NODELAY=true選項,則允許發送。TCP_NODELAY是取消TCP的確認延遲機制,相當於禁用了Nagle 算法。
4)未設置TCP_CORK選項時,若所有發出去的小數據包(包長度小於MSS)均被確認,則允許發送;
5)上述條件都未滿足,但發生了超時(一般為200ms),則立即發送。
有關TCP粘包黏包的解決辦法,將在下一篇通過Netty代碼演示。
概念
:OAuth(開放授權)是一個開放標准,允許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如基本消息,照片,聯系人列表),
而無需將 用戶名 和 密碼 提供給第三方應用。
一、應用場景
為了理解OAuth的適用場合,這里舉一個使用第三方賬戶進行登錄的例子。
現在一般登陸都會采用 第三方授權 登陸,比較常見就是微信、qq、微博授權登陸。這里以微信授權登陸為例:
現在我在未注冊的情況下去訪問A網站
,A網站 為了提高用戶體驗,可以省去你在這次網站申請注冊的步驟,讓你通過微信
授權登陸去拿去你在微信上的基本信息。
問題就在這里,如果拿到微信用戶
基本信息給到A網站,直接給A網站我登陸微信的賬號密碼,那么問題可想而知。
1、這個也太不安全了,我只想給A網站我的在微信上的基本信息,而不是所有信息,通過用戶密碼可以獲取我的所有信息。
2、用戶只有修改密碼,才能收回賦予"A網站"的權力。但是這樣做,會使得其他所有獲得用戶授權的第三方應用程序全部失效。
3、只要有一個第三方應用程序被破解,就會導致用戶密碼泄漏,以及所有被密碼保護的數據泄漏。
OAuth就是為了解決上面這些問題而誕生的。
從上面可以看出主要有三個身份
用戶
使用第三方賬戶登錄一個新的網站,對於用戶來說就不需要走復雜的注冊流程。
第三方平台
(微信)
上面來講 微信 就是第三方平台,那么對於第三方如何做才能保證用戶的安全呢?
就在A網站在通過微信授權登陸之前,需要提供資質到微信,微信審核,審核通過后給要求接入的服務商一個唯一憑證,標明服務商身份。
服務商(A網站)
我們要做的就是將這兩者進行連接起來,先到第三方平台資質審核,審核通過后,用戶去第三方平台授權登錄后,就可以獲取用戶基本信息,完成登陸。
二、OAuth的思路
這里還是以 服務商(A網站)
,和 第三方平台(微信)
授權登錄來縷這個思路。
OAuth在 服務商(A網站) 與 第三方平台(微信) 之間,設置了一個授權層(authorization layer)。服務商 不能直接登錄 第三方平台,只能登錄授權層,
以此將用戶與服務商(A網站)區分開來。服務商(A網站) 登錄授權層所用的令牌
(token),與用戶的密碼不同。用戶可以在登錄的時候,指定授權層令牌的權限范圍和有效期。
服務商(A網站) 登錄授權層以后,第三方平台 根據令牌的權限范圍和有效期,向 服務商(A網站) 開放用戶儲存的資料。
這里縷下大致流程
1、接入前准備(資質審核)
如果一個服務商需要使用第三方平台的服務,那么首先是需要向第三方平台提供資料,第三方平台審核通過后,會給服務商一個唯一標識的ID,這樣通過第三方平台授權的時候,
第三方平台就知道是哪個商戶了。
一般來說你會得到如下的兩個參數:
appid 代表你的應用唯一ID
appsecret 對應的密鑰
這個部分每家平台都不一樣,具體如何獲取你的APPID請參考對應平台的指南.
注意
第三方平台給你的不一定是APPID,我的意思不是連名字都完全一樣,有的平台給的參數多有的給的少,總之都是用於驗明身份的.
2、用戶要使用第三方登陸
這里我們以登錄為例.
在這個流程中服務器(A網站)接受到了用戶想要第三方登錄的請求,我們使用之前獲取的APPID(不同平台叫法和參數可能不同),然后拼接為成第三方平台指定的url
然后直接重定向到這個url.
例如在這個例子中我們的地址可能長這個樣子:
www.xxx.com/oauth2.0/authorize?appid=123456&redirect=www.sss.com/login
參數:
appid 我們的應用對於第三方平台的唯一id redirect 用戶同意授權后被重定向的地址,一般來說都是本應用的首頁或者登錄頁面,在本例中就是www.sss.com/login這個地址. 其他參數 根據第三方平會有不同的額外參數.
然后將用戶重定向到這個url中,此時用戶會跳轉到www.xxx.com(因為如果用戶授權成功,你總要回調服務商接口,來告訴它,已經授權成功).
3、用戶授權成功
用戶授權成功后,微信就會請求上面redirect參數中的接口地址,帶上授權成功的參數code
在這個例子中這個url看起來是這個樣子的
www.sss.com/login?code=xxxxx
4、獲取用戶token(令牌)
此時我們的www.sss.com/login接受到了一個含有code的請求,我們知道這個是一個第三方登錄授權后的請求.
我們再次拼接一個url(不同平台地址規則不同),但是一般來說這個請求會有如下的參數:
code 用戶授權后重定向帶回來的code appid 應用唯一id appsecret 應用對應的密鑰
在這個例子中我們請求服務器的url可能是這個樣子的:
www.xxx.com/oauth2/access_token?appid=xxxx&secert=xxxx&code=xxxx
如果一切順利在這個階段我們就可以獲取第三方平台響應的一個accesstoken
,這個accesstoken代表着用戶對於這個應用的授權.
除此以外你還會獲取到用戶的基本信息例如用戶的唯一id之類的,后續的請求用戶的信息需要使用accesstoken進行請求。
5、獲取用戶基本信息
利用accesstoken我們向服務器獲取了用戶的名字,顯示在了我們的應用中, 后續的資源獲取就是這個模式(不同平台資源獲取地址以及方式有可能稍有不同).
6、補充
1、微信認證成功后,我會會把accesstoken存放在cookie
中,這樣不用每次都需要用戶去授權認證,而是我們后台去請問微信,這個時候用戶是不會感知的。
2、accesstoken不是一直有效的,它會有過期時間的,就好比微信掃碼登陸中accesstoken有效時間是2小時。
3、那么accesstoken時效,是不是就要用戶重新授權登陸了,當然也不是,如果沒2小時都要重新授權登陸那體驗也太差了。這里會有個叫refresh_token
,
它是在你第一次獲取accesstoken一起給你的,也就是說如果你的accesstoken時效了,你還可以通過refresh_token去獲取用戶信息。這么說refresh_token的
時效時間肯定要比accesstoken,微信掃碼登陸refresh_token有效時間是30天
4、也就是當refresh_token也時效的時候,才會需要用戶重新授權登陸。
具體的可以看看微信掃碼登陸的官方文檔:網站應用微信登錄開發指南