有很多小夥伴私聊我說更新太慢,奪命吹吹吹。一週一次,還不能滿足你們嗎?
好,那我不寫了。╭(╯^╰)╮!!!
額,儘量保持完成稽核後就交貨釋出啦。實際上前面的章節都是週末完成等到週三才發的。
上一節我們細說了網路模型分層。知道了七層分法實際現在只有五層了。今天我們展開來學習傳輸層UDP和TCP的協議。因為這兩個協議關聯性比較大,且篇幅不高。故合併層一個小節學習。
章節
- Android與物聯網裝置通訊-概念入門
- Android與物聯網裝置通訊-資料傳遞的本質
- Android與物聯網裝置通訊-網路模型分層
- Android與物聯網裝置通訊-UDP協議原理
- Android與物聯網裝置通訊-TCP協議原理
- Android與物聯網裝置通訊-基於TCP/IP自定義報文
- Android與物聯網裝置通訊-什麼是位元組序
- Android與物聯網裝置通訊- 位元組報文組裝與解析
- Android與物聯網裝置通訊-利用UDP廣播來做裝置查詢
- Android與物聯網裝置通訊-實現遠端控制Android客戶端
- Android與物聯網裝置通訊-Android做小型伺服器
- Android與物聯網裝置通訊-除錯技巧
- Android與物聯網裝置通訊-並行序列與佇列
- Android與物聯網裝置通訊-資料安全
- Android與物聯網裝置通訊-心跳
- Android與物聯網裝置通訊-網路IO模型
目錄
- UDP
- TCP
UDP (User Datagram Protocol)
它是一種高速但資料不可靠的協議,為什麼說它不可靠呢?是因為與之對應的TCP是非常可靠和穩定的。我們先看一下UDP的報文結構再講解傳遞資料過程的原理。
UDP報文
可以看到很簡單,沒有什麼東西需要特別說明的,根據前幾節的知識就可以推斷出來。UDP的效率高並非在報文上,而是它的傳輸機制就是隻管往外發,對方能不能接收到(是否存在),並不關心。
你一定還記得網路不通的時候ping一下,這種小秀的騷操作的把?沒有錯它的底層就是依靠UDP協議直接實現的,ping的過程就是客戶端組裝UDP報文,和DNS伺服器接收解析UDP並響應客戶端的過程。當ping不通的時候就會出現超時,即DNS伺服器不回覆客戶端。
所以UDP的傳輸可理解成來資料了,就不管三七二一就是一個走你,拜拜了您勒。也不去校驗到底有沒有收到。
正常情況下一條報文會被路由、交換機經過一層層轉發,最後到該接收的位置。他們依靠上一節提到的資料鏈路層和網路層來尋找主機。UDP協議只關心埠。這種最典型的UDP稱為單播UDP,除此之外還有組播和廣播。關於組播和廣播,我們後續的章節講到做查詢裝置時會詳細講解,並做一個小demo。
TCP(Transmission Control Protocol)
相對UDP來說TCP協議就要穩定很多了。同樣我們先看報文結構,再學習傳輸原理。
TCP報文
我們重點看幾個欄位說明:
-
序號: 表示傳送端當前包位於整組資料的第幾個位元組,也叫流水號。用於確保資料組的穩定性。
-
ack號: 也叫確認號,表示接收方收到了多少個位元組,表示期待下一組資料的位元組序號。
-
資料偏移: 一般不用,但是出現在TCP頭裡如果可選欄位增加後,就要在資料偏移中指明偏移的位置,最多可以將20位元組的頭擴充套件到60個位元組。
-
控制位: 6個標誌位URG ACK PSH RST SYN FIN每一個表示一個控制功能,也就是告訴對方當前的包是做什麼用的標識。
-
視窗: 接收端告訴傳送端自身的快取大小的。避免過大的資料包導致接收端接受不過來而丟失資料。
控制位詳解
-
URG: 緊急指標標誌是否有效。
-
ACK: 確認序號是否有效。
-
PSH: 重新整理快取標誌1有效,0忽略,要求把資料儘快給應用,而不要放在快取裡。(java裡的flush方法)
-
RST: 異常標誌,表示強制斷開連線,在異常的情況下。
-
SYN: 連線過程同步序號標誌。
-
FIN: finish標誌,用於釋放連線1表示關閉連線。
咋一看你會發現TCP的報文比UDP複雜了好多,一大堆東西。我的老夥計,別擔心。那麼底層的事情又不要你處理。你只需要知道它們在幹嘛。並且值得說的是在Java上做socket程式設計時,你根本就感知不到底層欄位的狀態。到上層在處理資料的時候僅僅只有一個輸入流和輸出流了。這樣極大的方便了應用層的簡便開發。
TCP協議分析
我們知道TCP是面向連線的,它嚴格的把控住了每一次傳輸的資料的穩定性。那麼它是如何做到的呢?乾巴巴的說太過於抽象,現在我們實際動手寫一下程式碼(kotlin
)搭配利刃wireshark
分析一下。
服務端
fun main(args: Array<String>) {
val ss= ServerSocket(22222);
while (true){
val accept = ss.accept()
val dataInputStream = DataInputStream(accept.getInputStream())
val dataOutputStream = DataOutputStream(accept.getOutputStream())
val s = dataInputStream.readUTF()
dataOutputStream.writeUTF("hello Server")
dataInputStream.close()
dataOutputStream.close()
accept.close()
println(s)
}
}
複製程式碼
客戶端
fun main(args: Array<String>) {
val socket = Socket("192.168.0.5", 22222);
val dataOutputStream = DataOutputStream(socket.getOutputStream())
val dataInputStream = DataInputStream(socket.getInputStream())
dataOutputStream.writeUTF("hello")
val s = dataInputStream.readUTF()
dataInputStream.close()
dataOutputStream.close()
socket.close()
println(s)
}
複製程式碼
值得注意的是wireshark無法直接抓到TCP在本地的迴路包,但是我們可以使用命令列配置一下就可以了,不然抓不到本機的迴路包。
-
給路由表新增一條
route add 本機IP mask 255.255.255.255 路由IP metric 1
-
抓完刪除
route delete 本機IP
為了方便演示,我這裡使用了兩塊網路卡。所以往路由裡新增了兩次迴路IP。
這裡首先我們開啟服務端,進入了迴圈監聽狀態,隨後開啟了客戶端連線上後傳送一個hello
,服務端收到後回覆了一個hello Server
。再沒有做更多的事情了。我們來看一下通過wireshark抓到的包。
- 客戶端:
192.168.0.4
埠:55705
(作業系統協議棧分配) - 服務端:
192.168.0.5
埠:22222
(我們定義的)
1.啟動服務端後,作業系統將開始對22222
埠的SYN
包進行監聽。
2.客戶端啟動,並嘗試連線服務端,由作業系統協議棧隨機分配一個傳送埠。
握手:
3.客戶端組裝一組SYN
報文傳送至服務端22222
號端,標記客戶端seq
為0
。(完成第一次握手)
4.服務端收到SYN
包,並組裝一組SYN、ACK
報文,內容是告訴客戶端我確認了你的SYN
包,並期待你的下一組資料從1
開始,標記服務端seq
為0
。(完成第二次握手)
5.客戶端收到服務端返回的SYN、ACK
包報文,,標記客戶端seq
為1
,並組裝一組ACK
報文,內容是告訴服務端期望收到的下一組ack
從1
開始。(完成第三次握手)
發包:
6.客戶端組裝了一組PSH、ACK
包,並帶上ack=1
,內容為hello
,傳送個服務端。
7.服務端收到了內容為hello
的包,確認後將ack
加上收到的內容的長度。組裝一組PSH、ACK
並帶上ack=8
傳送給客戶端。
揮手:
8.客戶端收到服務端的訊息,更新自己的seq=8
,並組裝一組FIN、ACK
包,並帶上ack=15
確認號,嘗試請求斷開連線。(客戶端第一次主動揮手)
9.服務端收到客戶端的FIN
包,更新自己的seq=15
,並組裝一組FIN、ACK
包,並帶上ack=8
傳送給客戶端。(服務端確認,開始第二次揮手。)
10.客戶端收到服務端的ack
包並檢測seq
是否一致,組裝一組ACK
包傳送給服務端。(第三次揮手)
11.服務端收到客戶端的ack
並檢測seq
是否一致,組裝一組ACK
包傳送給客戶端。(第四次揮手)
上面描述的這個過程從字面上去看並沒有太大意義,我希望你真正開啟wireshark和程式碼,除錯著玩玩。
回顧過程:
握手: 客戶端和服務端會先進行三次握手,握手的時候會告訴對方自身的窗體大小和ack確認號。這個過包的功能是從ACK、SYN
來達到認識的。
傳輸資料: 在接受對方的資料時,在下一組包裡帶上ACK
標記告知對方我已經收到了多少內容,期待下一組內容的起始位置。
揮手: 相互確認包是否傳送完整,再雙方確認了ACK
後。才真正揮手完畢。
丟包或包校驗不對情況: 作業系統協議棧會根據ACK
和SEQ
號對控制位為ACK
的包進行校驗,如果對不上則要求重發上一組包。
到此UDP&TCP
的協議我們就學習完了。這裡沒有去演示UDP
包的抓取過程,因為如果TCP
都會了UDP
就不是什麼難題。再一個沒有展示wireshark
的具體使用過程,因為網上實在有很多優秀的教程了。
即便如此,我敢打賭的是如果讀者是第一次接觸TCP/IP
絕對會因為很多概念和發來發去的欄位搞得頭暈或者一知半解。因為我的能力還不足以讓你看看文章就完全懂了。(原話出自凱哥 ^o^
)
聽我一句勸,這個時候不要放棄,成熱打鐵趕快開啟分析工具,照著我前面的程式碼跑起來。再拿出紙筆寫寫畫一畫。深挖一下真正的去感受它們的巧妙之處。你會感覺這個設計是非常棒的。
話說大家看得真的有收穫嗎?如果覺得不錯,又想獎勵我一下戳這裡打賞(聽說打賞有吹更效果噢)。