說明
- 這篇主要接上一篇Android Studio — > [學習筆記]RadioButton、CheckBox、ImageView、ListView、TCP的三次握手
- 對上面回答的細解,并用JS偽代碼,對TCP三次握手和四次揮手的簡單實現.
- Android的基本了解到此篇結束,后續會根據具體情況深度學習.
2.y TCP的三次握手和四次揮手(第2彈)
- 參考
- 下面娓娓道來(逐字敲)上一篇提出問題的解析
2.y.1 三次握手
-
三次握手(Three-way Handshake)其實就是指建立一個TCP連接時,需要客戶端和服務器總共發送3個包。進行三次握手的主要作用就是為了確認雙方的接收能力和發送能力是否正常、指定自己的初始化序列號為后面的可靠傳送做準備。實質上其實就是連接服務器指定端口,建立TCP連接,并同步連接雙方的序列號和確認號,交換TCP窗口大小信息.
-
剛開始客戶端處于Closed的狀態,服務器處于Listen狀態.進行三次握手:
- (1)第一次握手: 客戶端給服務端發一個SYN報文,并指明客戶端的初始化序列號ISN?.此時客戶端處于SYN_SENT狀態;首部的同步位SYN=1,初始序號seq=x,SYN=1的報文段不能攜帶數據,但要消耗掉一個序號
- (2)第二次握手: 服務器收到客戶端的SYN報文后,會以自己的SYN報文作為應答,并且也是指定了自己的初始化序列號ISN(s)。同時會把客戶端的ISN+1作為ACK的值,表示自己已經收到了客戶端的SYN,此時服務器處于SYN_RCVD的狀態;在確認報文段中SYN=1,ACK=1,確認號ack=x+1(表示希望收到下一個的序號),初始序號seq=y
- (3)第三次握手:客戶端收到SYN報文之后,會發送一個ACK報文,當然,也是一樣把服務器的ISN+1作為ACK的值,表示已經收到了服務端的SYN報文,此時客戶端處于ESTABLISHED狀態。服務器收到ACK報文之后,也處于ESTABLISHED狀態,此時,雙方已建立起了連接;確認報文段ACK=1,確認號ack=y+1,序號seq=x+1,ACK報文段可以攜帶數據,不攜帶數據則不消耗序號
-
發送第一個SYN的一端將執行主動打開(active open),接收這個SYN并發回下一個SYN的另一端執行被動打開(passive open).
-
在socket編程中,客戶端執行connect()時,將觸發三次握手。
2.y.2 為什么需要三次握手,兩次不行嗎?
-
為了弄清這個問題,我們需要先弄明白三次握手的目的是什么,能不能只用兩次握手來達到同樣的目的.
- (1)第一次握手: 客戶端發送網絡包,服務端收到,這樣服務端就能得出結論 -> 客戶端的發送能力、服務端的接收能力是正常的
- (2)第二次握手: 服務端發包,客戶端收到了,這樣客戶端就能得出結論: 服務端的接收、發送能力,客戶端的接收、發送能力是正常的。不過此時服務器并不能確認客戶端的接收能力是否正常。
- (3)第三次握手: 客戶端發包,服務端收到,這樣服務器就能得出結論: 客戶端的接收、發送能力正常,服務器自己的發送、接收能力也正常。
-
因此,需要三次握手才能確認雙方的接收與發送能力是否正常。
-
如果是用兩次握手,則會出現下面這種情況:
如客戶端發出連接請求,但因連接請求報文丟失而未收到確認,于是客戶端再重傳一次連接請求。后來收到了確認,建立了連接。數據傳輸完畢后,就釋放了連接,客戶端共發出了兩個連接請求報文段,其中第一個丟失,第二個到達了服務端,但是第一個丟失的報文段只是再某些網絡結點長時間滯留了,延誤到連接釋放以后的某個時間才到達服務端,此時服務端誤認為客戶端又發出了一次新的連接請求,于是就向客戶端發出確認報文段,同意建立連接,就建立新的連接了,此時客戶端忽略服務器發來的確認,也不發送數據,則服務端一致等待客戶端發送數據,造成資源浪費.
2.y.3 什么是半連接隊列?
-
服務器第一次收到客戶端的SYN之后,就處于SYN_RCVD狀態,此時雙方還沒有完全建立其連接,服務器會把此種狀態下請求連接放在一個隊列里,我們把這種隊列稱之為半連接隊列
-
當然還有一個全連接隊列,就是已經完成三次握手,建立起連接的就會放到全連接隊列種。如果隊列滿了就有可能會出現丟包現象。
-
SYN-ACK重傳次數:
服務器發送完SYN-ACK包,如果未收到客戶確認包,服務器進行首次重傳,等待一段時間仍未收到客戶確認包,進行第二次重傳。如果重傳次數超過系統規定的最大重傳次數,系統將該連接信息從半連接隊列中刪除。注意,每次重傳等待的時間不一定相同,一般會是指數增長,例如間隔時間1s,2s,4s,8s…
2.y.4 ISN(Initial Sequence Number)是固定的嗎?
-
當一端為建立連接而發送它的SYN時,它為連接選擇一個初始序號。ISN隨時間而變化,因此每個連接都將具有不同的ISN。ISN可以看作是一個32比特的計數器,每4ms加1.這樣選擇序號的目的在于防止在網絡延遲的分組在以后被傳送,而導致某個連接的一方對它做錯誤的解釋。
-
三次握手的其中一個重要功能是客戶端和服務端交換ISN(Initial Sequence Number),以便讓對方直到接下來接收數據的時候如何按序列號組裝數據。如果ISN是固定的,攻擊者很容易猜出后續的確認號,因此ISN是動態生成的.
2.y.5 三次握手過程中可以攜帶數據嗎?
- 其實第三次握手的時候,是可以攜帶數據的。但是,第一次、第二次握手不可以攜帶數據
- 為什么這樣呢? 假如第一個握手可以攜帶數據的話,如果有人要惡意攻擊服務器,那他每次都在第一次握手中的SYN報文中放入大量的數據。因為攻擊者根本就不理服務器的接收、發送能力是否正常,然后瘋狂著重復發SYN報文的話,這會讓服務器花費很多時間、內存控件來接收這些報文。
- 也就是說,第一次握手不可以放數據,其中一個簡單的原因就是會讓服務器更加容易受到攻擊。而對于第三次的話,此時客戶端已經處于ESTABLISHED狀態。對于客戶端來說,他已經建立起連接了,并且也已經直到服務器的接收、發送能力是正常的了,所以能攜帶數據也沒啥毛病。
2.y.6 SYN攻擊是什么?
- 服務器端的資源分配是在二次握手時分配的,而客戶端的資源分配是在完成三次握手時分配的,所以服務器容易收到SYN洪泛攻擊。SYN攻擊就是Client在短時間內偽造大量不存在的IP地址,并向Server不斷地發送SYN包,Server則回復確認包,并等待Client確認,由于源地址不存在,因此Server需要不斷重發至超時,這些偽造地SYN包將長時間占用未連接隊列,導致正常的SYN請求因為隊列滿而被丟棄,從而引起網絡擁塞甚至系統癱瘓。SYN攻擊是一種典型的DoS/DDoS攻擊。
- 檢測SYN攻擊非常的方便,當你在服務器上看到大量的半連接狀態時,特別是源IP地址是隨機的,基本上可以斷定這是一次SYN攻擊。在Linux/Unix上可以使用系統自帶的netstats命令來檢測SYN攻擊。
netstat -n -p TCP|grep SYN_RECV
- 常見的防御SYN攻擊的方法有如下幾種:
- 縮短超時(SYN Timeout)時間
- 增加最大半連接數
- 過濾網關防護
- SYN cookies技術
2.y.7 四次揮手
-
建立一個連接需要三次握手,而終止一個連接要經過四次揮手。這由TCP的半關閉(half-close)造成的。所謂的半關閉,其實就是TCP提供了連接的一端在結束它的發送后還能接收來自另一端數據的能力。
-
TCP的連接的拆除需要發送四個包,因此成為四次揮手(Four-way handshake),客戶端或服務器均可主動發送揮手動作。
-
剛開始雙方都處于ESTABLISHED狀態,假如是客戶端先發起關閉請求。四次揮手的過程如下:
-
第一次揮手: 客戶端發送一個FIN報文,報文中會指定一個序列號。此時客戶端處于
FIN_WAIT1狀態.即發出連接釋放報文段(FIN=1,序號seq=u),并停止發送數據,主動關閉TCP連接,進入FIN)WAIT1(終止等待1)狀態,等待服務端的確認.
-
第二次揮手: 服務端收到FIN之后,會發送ACK報文,且把客戶端的序列號值+1作為ACK報文的序列號值,表明已經收到客戶端的報文了,此時服務端處于CLOSE_WAIT狀態
即服務器收到連接釋放報文段后即發出確認報文段(ACK=1,確認號ack=u+1,序號seq=v),服務器進入CLOST_WAIT(關閉等待)狀態,此時的TCP處于半關閉狀態,客戶端到服務端連接釋放。客戶端收到服務端的確認后,進入FIN_WATI2(終止等待2)狀態,等待服務端發出的連接釋放報文段.
-
第三次揮手: 如果服務端也想斷開連接了,和客戶端的第一次揮手一樣,發送FIN報文,且指定一個序列號。此時服務器處于LAST_ACK狀態
即服務端沒有要向客戶端發送的數據,服務端發出連接釋放報文段(FIN=1, ACK=1, 序號 seq=w, 確認號 ack=u+1), 服務端進入LAST_ACK(最后確認)狀態,等待客戶端的確認.
-
第四次揮手: 客戶端收到FIN之后,一樣發送一個ACK報文作為應答,且把服務器端的程序列號值+1作為自己ACK報文的序列號值,此時客戶端處于TIME_WAIT狀態。需要過一陣子以確保服務端收到自己的ACK報文之后才會進入CLOSED狀態,服務端收到ACK報文之后,就處于關閉連接了,處于CLOSED狀態
即客戶端收到服務器的連接釋放報文后,對此發出確認報文段(ACK=1,seq=u+1,ack=w+1),客戶端進入TIME_WAIT(時間等待)狀態.此時TCP未釋放掉,需要經過時間等待計時器設置的時間2MSL后,客戶端進入
-
-
收到一個FIN只意味著在這一方向上沒有數據流動。客戶端執行主動關閉并進入TIME_WAIT是正常的,服務端通常執行被動關閉,不會進入TIME_WAIT狀態
2.y.8 為什么需要四次
- 因為當服務端收到客戶端的SYN連接請求報文后可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當服務器收到FIN報文時,很可能并不會立即關閉(因為還有數據要傳輸),所以只能先回復一個ACK報文,告訴客戶端,“你發的FIN報文我收到了”。只有等到我服務端所有的報文都發送完了,我才能發送FIN報文,因此不能一起發送。故需要四次揮手。
2.y.9 2MSL等待狀態
TIME_WAIT狀態也成為了2MSL等待狀態。每個具體TCP實現必須選擇一個報文段最大生存時間(Maximum Segment Lifetime),它是任何報文段被丟棄前在網絡內的最長時間。這個時間是有限的,因為TCP報文段以IP數據包在網絡內傳輸,而IP數據報則有限制其生存時間的TTL字段。
對一個具體實現所給定的MSL值,處理的原則是: 當TCP執行一個主動關閉,并發回最后一個ACK,該連接必須在TIME_WATI狀態停留的時間為2倍的MSL。這樣可讓TCP再次發送最好的ACK以防這個ACK丟失(另一端超時并重發最后的FIN)。
這種2MSL等待的另一個結果是這個TCP連接在2MSL等待期間,定義這個連接的插口(客戶的IP地址和端口號,服務器的IP地址和端口號)不能再被使用。這個連接只能在2MSL結束后才能再被使用。
2.y.10 四次揮手釋放連接時,等待2MSL的意義
MSL是Maximum Segment Lifetime的英文縮寫,可以譯為"最長 報文段壽命",它是任何報文在網絡上存在的最長的時間,超過這個時間報文將被丟棄。
為了保證客戶端發送的最后一個ACK報文端報文段能夠到達服務器。因為這個ACK有可能丟失,從而導致處在LAST-ACK狀態的服務器收不到對FIN-ACK的確認報文。服務器會超時重傳這個FIN-ACK,接著客戶端再重傳一次確認,重新啟動時間等待計時器。最后客戶端和服務器都能正常的關閉,一但這個ACK丟失的話,服務器就無法正常的進入關閉連接狀態。
兩個理由
- 保證客戶端發送的最后一個ACK報文段能夠到達服務端。
?
這個ACK報文段有可能丟失,使得處于LAST-ACK狀態的B收不到已發送的FIN+ACK報文段的確認,服務端超時重傳FIN+ACK報文段,而客戶端能在2MSL時間內收到這個重傳的FIN+ACK報文段,接著客戶端重傳一次確認,重新啟動2MSL計時器,最后客戶端和服務端都進入CLOSED狀態,若客戶端再TIME-WAIT狀態不等待一段時間,而是發送完ACK報文段后立即釋放連接,則無法收到服務端重傳的FIN+ACK報文段,所以不會再發送一次確認報文段,則服務端無法正常進入到CLOSED狀態。 - 防止"已失效的連接請求報文段"出現在本連接中。
?
客戶端在發送完最后一個ACK報文段后,再經過2MSL,就可以使本連接持續的時間內所產生的所有報文段都從網絡中消失,使下一個新的連接中不會出現這種舊的連接請求報文段。
2.y.11 為什么TIME_WAIT狀態需要經過2MSL才能返回到CLOSE狀態?
理論上,四個報文都發送完畢,就可以直接進入CLOSE狀態了,但是可能網絡是不可靠的,有可能最后一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文
2.9 網格視圖GridView(略)
- 常用屬性
- Adapter接口
- Demo演示
2.10 滾動視圖ScrollView
- 垂直滾動: ScrollView
- 水平滾動: HorizontalScrollView
?
ScrollView的子元素只允許有一個根結點
2.10.1 豎直方向滾動的基本使用
activity_main.xml
<ScrollView xmlns:android:="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayout><!-- <Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="300dp"android:text="Test "android:textAllCaps="false" /> * N --></LinearLayout>
</ScrollView>
2.10.2 水平方向的滾動
<HorizontalScroll><LinearLayout><!-- <Buttonandroid:layout_width="200dp"android:layout_height="300dp"android:text="Test"android:textAllCaps="false"/> * N --></LinearLayout>
</HorizontalScroll>