Android與物聯網裝置通訊-UDP&TCP協議

香脆的大雞排發表於2019-03-02

有很多小夥伴私聊我說更新太慢,奪命吹吹吹。一週一次,還不能滿足你們嗎?

好,那我不寫了。╭(╯^╰)╮!!!

額,儘量保持完成稽核後就交貨釋出啦。實際上前面的章節都是週末完成等到週三才發的。

上一節我們細說了網路模型分層。知道了七層分法實際現在只有五層了。今天我們展開來學習傳輸層UDP和TCP的協議。因為這兩個協議關聯性比較大,且篇幅不高。故合併層一個小節學習。

章節

目錄

  • UDP
  • TCP

UDP (User Datagram Protocol)

它是一種高速但資料不可靠的協議,為什麼說它不可靠呢?是因為與之對應的TCP是非常可靠和穩定的。我們先看一下UDP的報文結構再講解傳遞資料過程的原理。

UDP報文

image.png

可以看到很簡單,沒有什麼東西需要特別說明的,根據前幾節的知識就可以推斷出來。UDP的效率高並非在報文上,而是它的傳輸機制就是隻管往外發,對方能不能接收到(是否存在),並不關心。

你一定還記得網路不通的時候ping一下,這種小秀的騷操作的把?沒有錯它的底層就是依靠UDP協議直接實現的,ping的過程就是客戶端組裝UDP報文,和DNS伺服器接收解析UDP並響應客戶端的過程。當ping不通的時候就會出現超時,即DNS伺服器不回覆客戶端。

所以UDP的傳輸可理解成來資料了,就不管三七二一就是一個走你,拜拜了您勒。也不去校驗到底有沒有收到。

正常情況下一條報文會被路由、交換機經過一層層轉發,最後到該接收的位置。他們依靠上一節提到的資料鏈路層和網路層來尋找主機。UDP協議只關心埠。這種最典型的UDP稱為單播UDP,除此之外還有組播廣播。關於組播和廣播,我們後續的章節講到做查詢裝置時會詳細講解,並做一個小demo。

TCP(Transmission Control Protocol)

相對UDP來說TCP協議就要穩定很多了。同樣我們先看報文結構,再學習傳輸原理。

TCP報文

image.png

我們重點看幾個欄位說明:

  • 序號: 表示傳送端當前包位於整組資料的第幾個位元組,也叫流水號。用於確保資料組的穩定性。

  • 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抓到的包。

image.png
  • 客戶端: 192.168.0.4埠:55705(作業系統協議棧分配)
  • 服務端: 192.168.0.5 埠:22222(我們定義的)

1.啟動服務端後,作業系統將開始對22222埠的SYN包進行監聽。

2.客戶端啟動,並嘗試連線服務端,由作業系統協議棧隨機分配一個傳送埠。

握手:

3.客戶端組裝一組SYN報文傳送至服務端22222號端,標記客戶端seq0(完成第一次握手)

4.服務端收到SYN包,並組裝一組SYN、ACK報文,內容是告訴客戶端我確認了你的SYN包,並期待你的下一組資料從1開始,標記服務端seq0(完成第二次握手)

5.客戶端收到服務端返回的SYN、ACK包報文,,標記客戶端seq1,並組裝一組ACK報文,內容是告訴服務端期望收到的下一組ack1開始。(完成第三次握手)

發包:

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後。才真正揮手完畢。

丟包或包校驗不對情況: 作業系統協議棧會根據ACKSEQ號對控制位為ACK的包進行校驗,如果對不上則要求重發上一組包。


到此UDP&TCP的協議我們就學習完了。這裡沒有去演示UDP包的抓取過程,因為如果TCP都會了UDP就不是什麼難題。再一個沒有展示wireshark的具體使用過程,因為網上實在有很多優秀的教程了。

即便如此,我敢打賭的是如果讀者是第一次接觸TCP/IP絕對會因為很多概念和發來發去的欄位搞得頭暈或者一知半解。因為我的能力還不足以讓你看看文章就完全懂了。(原話出自凱哥 ^o^

聽我一句勸,這個時候不要放棄,成熱打鐵趕快開啟分析工具,照著我前面的程式碼跑起來。再拿出紙筆寫寫畫一畫。深挖一下真正的去感受它們的巧妙之處。你會感覺這個設計是非常棒的。

話說大家看得真的有收穫嗎?如果覺得不錯,又想獎勵我一下戳這裡打賞(聽說打賞有吹更效果噢)。

相關文章