《TCP/IP網絡編程》第 5 章 基於 TCP 的服務端/客戶端(2) 筆記

第 5 章 基於 TCP 的服務端/客戶端(2)

本章代碼,在TCP-IP-NetworkNote中可以找到。

上一章僅僅是從編程角度學習實現方法,並未詳細討論 TCP 的工作原理。因此,本章將想次講解 TCP 中必要的理論知識,還將給出第 4 章客戶端問題的解決方案。

5.1 回聲客戶端的完美實現

5.1.1 回聲服務器沒有問題,只有回聲客戶端有問題?

問題不在服務器端,而在客戶端,只看代碼可能不好理解,因爲 I/O 中使用了相同的函數。先回顧一下服務器端的 I/O 相關代碼:

while ((str_len = read(clnt_sock, message, BUF_SIZE)) != 0)
    write(clnt_sock, message, str_len);

接着是客戶端代碼:

write(sock, message, strlen(message));
str_len = read(sock, message, BUF_SIZE - 1);

二者都在村換調用 read 和 write 函數。實際上之前的回聲客戶端將 100% 接受字節傳輸的數據,只不過接受數據時的單位有些問題。擴展客戶端代碼回顧範圍,下面是,客戶端的代碼:

while (1)
{
    fputs("Input message(Q to quit): ", stdout);
    fgets(message, BUF_SIZE, stdin);

    if (!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
        break;

    write(sock, message, strlen(message));
    str_len = read(sock, message, BUF_SIZE - 1);
    message[str_len] = 0;
    printf("Message from server: %s", message);
}

現在應該理解了問題,回聲客戶端傳輸的是字符串,而且是通過調用 write 函數一次性發送的。之後還調用一次 read 函數,期待着接受自己傳輸的字符串,這就是問題所在。

5.1.2 回聲客戶端問題的解決辦法

這個問題其實很容易解決,因爲可以提前接受數據的大小。若之前傳輸了20字節長的字符串,則再接收時循環調用 read 函數讀取 20 個字節即可。既然有了解決辦法,那麼代碼如下:

這樣修改爲了接收所有傳輸數據而循環調用 read 函數。測試及運行結果可參考第四章。

5.1.3 如果問題不在於回聲客戶端:定義應用層協議

回聲客戶端可以提前知道接收數據的長度,這在大多數情況下是不可能的。那麼此時無法預知接收數據長度時應該如何手法數據?這是需要的是應用層協議的定義。在收發過程中定好規則(協議)以表示數據邊界,或者提前告知需要發送的數據的大小。服務端/客戶端實現過程中逐步定義的規則集合就是應用層協議。

現在寫一個小程序來體驗應用層協議的定義過程。要求:

  1. 服務器從客戶端獲得多個數組和運算符信息。
  2. 服務器接收到數字候對齊進行加減乘運算,然後把結果傳回客戶端。

例:

  1. 向服務器傳遞3,5,9的同事請求加法運算,服務器返回3+5+9的結果
  2. 請求做乘法運算,客戶端會收到3*5*9的結果
  3. 如果向服務器傳遞4,3,2的同時要求做減法,則返回4-3-2的運算結果。

請自己實現一個程序來實現功能。

我自己的實現:

編譯:

gcc My_op_client.c -o myclient
gcc My_op_server.c -o myserver

結果:

其實主要是對程序的一點點小改動,只需要再客戶端固定好發送的格式,服務端按照固定格式解析,然後返回結果即可。

書上的實現:

閱讀代碼要注意一下,int*char之間的轉換。TCP 中不存在數據邊界。

編譯:

gcc op_client.c -o opclient
gcc op_server.c -o opserver

運行:

./opserver 9190
./opclient 127.0.0.1 9190

結果:

5.2 TCP 原理

5.2.1 TCP 套接字中的 I/O 緩衝

TCP 套接字的數據收發無邊界。服務器即使調用 1 次 write 函數傳輸 40 字節的數據,客戶端也有可能通過 4 次 read 函數調用每次讀取 10 字節。但此處也有一些一問,服務器一次性傳輸了 40 字節,而客戶端竟然可以緩慢的分批接受。客戶端接受 10 字節後,剩下的 30 字節在何處等候呢?

實際上,write 函數調用後並非立即傳輸數據, read 函數調用後也並非馬上接收數據。如圖所示,write 函數滴啊用瞬間,數據將移至輸出緩衝;read 函數調用瞬間,從輸入緩衝讀取數據。

I/O 緩衝特性可以整理如下:

  • I/O 緩衝在每個 TCP 套接字中單獨存在
  • I/O 緩衝在創建套接字時自動生成
  • 即使關閉套接字也會繼續傳遞輸出緩衝中遺留的數據
  • 關閉套接字將丟失輸入緩衝中的數據

假設發生以下情況,會發生什麼事呢?

客戶端輸入緩衝爲 50 字節,而服務器端傳輸了 100 字節。

因爲 TCP 不會發生超過輸入緩衝大小的數據傳輸。也就是說,根本不會發生這類問題,因爲 TCP 會控制數據流。TCP 中有滑動窗口(Sliding Window)協議,用對話方式如下:

  • A:你好,最多可以向我傳遞 50 字節
  • B:好的
  • A:我騰出了 20 字節的空間,最多可以接受 70 字節
  • B:好的

數據收發也是如此,因此 TCP 中不會因爲緩衝溢出而丟失數據。

write 函數在數據傳輸完成時返回。

5.2.2 TCP 內部工作原理 1:與對方套接字的連接

TCP 套接字從創建到消失所經過的過程分爲如下三步:

  • 與對方套接字建立連接
  • 與對方套接字進行數據交換
  • 斷開與對方套接字的連接

首先講解與對方套接字建立連接的過程。連接過程中,套接字的對話如下:

  • 套接字A:你好,套接字 B。我這裏有數據給你,建立連接吧
  • 套接字B:好的,我這邊已就緒
  • 套接字A:謝謝你受理我的請求

TCP 在實際通信中也會經過三次對話過程,因此,該過程又被稱爲 Three-way handshaking(三次握手)。接下來給出連接過程中實際交換的信息方式:

套接字是全雙工方式工作的。也就是說,它可以雙向傳遞數據。因此,收發數據前要做一些準備。首先請求連接的主機 A 要給主機 B 傳遞以下信息:

[SYN] SEQ : 1000 , ACK:-

該消息中的 SEQ 爲 1000 ,ACK 爲空,而 SEQ 爲1000 的含義如下:

現在傳遞的數據包的序號爲 1000,如果接收無誤,請通知我向您傳遞 1001 號數據包。

這是首次請求連接時使用的消息,又稱爲 SYN。SYN 是 Synchronization 的簡寫,表示收發數據前傳輸的同步消息。接下來主機 B 向 A 傳遞以下信息:

[SYN+ACK] SEQ: 2000, ACK: 1001

此時 SEQ 爲 2000,ACK 爲 1001,而 SEQ 爲 2000 的含義如下:

現傳遞的數據包號爲 2000 ,如果接受無誤,請通知我向您傳遞 2001 號數據包。

而 ACK 1001 的含義如下:

剛纔傳輸的 SEQ 爲 1000 的數據包接受無誤,現在請傳遞 SEQ 爲 1001 的數據包。

對於主機 A 首次傳輸的數據包的確認消息(ACK 1001)和爲主機 B 傳輸數據做準備的同步消息(SEQ 2000)捆綁發送。因此,此種類消息又稱爲 SYN+ACK。

收發數據前向數據包分配序號,並向對方通報此序號,這都是爲了防止數據丟失做的準備。通過項數據包分配序號並確認,可以在數據包丟失時馬上查看並重傳丟失的數據包。因此 TCP 可以保證可靠的數據傳輸。

通過這三個過程,這樣主機 A 和主機 B 就確認了彼此已經準備就緒。

5.2.3 TCP 內部工作原理 2:與對方主機的數據交換

通過第一步三次握手過程完成了數據交換準備,下面就開始正式收發數據,其默認方式如圖所示:

圖上給出了主機 A 分成 2 個數據包向主機 B 傳輸 200 字節的過程。首先,主機 A 通過 1 個數據包發送 100 個字節的數據,數據包的 SEQ 爲 1200 。主機 B 爲了確認這一點,向主機 A 發送 ACK 1301 消息。

此時的 ACK 號爲 1301 而不是 1201,原因在於 ACK 號的增量爲傳輸的數據字節數。假設每次 ACK 號不加傳輸的字節數,這樣雖然可以確認數據包的傳輸,但無法明確 100 個字節全都正確傳遞還是丟失了一部分,比如只傳遞了 80 字節。因此按照如下公式傳遞 ACK 信息:

ACK 號 = SEQ 號 + 傳遞的字節數 + 1

與三次握手協議相同,最後 + 1 是爲了告知對方下次要傳遞的 SEQ 號。下面分析傳輸過程中數據包丟失的情況:

上圖表示了通過 SEQ 1301 數據包向主機 B 傳遞 100 字節數據。但中間發生了錯誤,主機 B 未收到,經過一段時間後,主機 A 仍然未收到對於 SEQ 1301 的 ACK 的確認,因此試着重傳該數據包。爲了完成該數據包的重傳,TCP 套接字啓動計時器以等待 ACK 應答。若相應計時器發生超時(Time-out!)則重傳。

5.2.4 TCP 內部工作原理 3:斷開套接字的連接

TCP 套接字的結束過程也非常優雅。如果對方還有數據需要傳輸時直接斷掉該連接會出問題,所以斷開連接時需要雙方協商,斷開連接時雙方的對話如下:

  • 套接字A:我希望斷開連接
  • 套接字B:哦,是嗎?請稍後。
  • 套接字A:我也準備就緒,可以斷開連接。
  • 套接字B:好的,謝謝合作。

先由套接字 A 向套接字 B 傳遞斷開連接的信息,套接字 B 發出確認收到的消息,然後向套接字 A 傳遞可以斷開連接的消息,套接字 A 同樣發出確認消息。

圖中數據包內的 FIN 表示斷開連接。也就是說,雙方各發送 1 次 FIN 消息後斷開連接。此過過程經歷 4 個階段,因此又稱四次握手(Four-way handshaking)。SEQ 和 ACK 的含義與之前講解的內容一致,省略。圖中,主機 A 傳遞了兩次 ACK 5001,也許這裏會有困惑。其實,第二次 FIN 數據包中的 ACK 5001 只是因爲接收了 ACK 消息後未接收到的數據重傳的。

5.3 基於 Windows 的實現

暫略

5.4 習題

答案僅代表本人個人觀點,可能不是正確答案。

  1. 請說明 TCP 套接字連接設置的三次握手過程。尤其是 3 次數據交換過程每次收發的數據內容。

    答:三次握手主要分爲:①與對方套接字建立連接②與對方套接字進行數據交換③斷開與對方套接字的連接。每次收發的數據內容主要有:①由主機1給主機2發送初始的SEQ,首次連接請求是關鍵字是SYN,表示收發數據前同步傳輸的消息。②主機2收到報文以後,給主機 1 傳遞信息,用一個新的SEQ表示自己的序號,然後ACK代表已經接受到主機1的消息,希望接受下一個消息③主機1收到主機2的確認以後,還需要給主機2給出確認,此時再發送一次SEQ和ACK。

  2. TCP 是可靠的數據傳輸協議,但在通過網絡通信的過程中可能丟失數據。請通過 ACK 和 SEQ 說明 TCP 通過和何種機制保證丟失數據的可靠傳輸。

    答:通過超時重傳機制來保證,如果報文發出去的特定時間內,發送消息的主機沒有收到另一個主機的回覆,那麼就繼續發送這條消息,直到收到回覆爲止。

  3. TCP 套接字中調用 write 和 read 函數時數據如何移動?結合 I/O 緩衝進行說明。

    答:TCP 套接字調用 write 函數時,數據將移至輸出緩衝,在適當的時候,傳到對方輸入緩衝。這時對方將調用 read 函數從輸入緩衝中讀取數據。

  4. 對方主機的輸入緩衝剩餘 50 字節空間時,若本主機通過 write 函數請求傳輸 70 字節,請問 TCP 如何處理這種情況?

    答:TCP 中有滑動窗口控制協議,所以傳輸的時候會保證傳輸的字節數小於等於自己能接受的字節數。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章