文章目錄
- 2.4 TCP/UDP對比
- 2.4.1 用UDP實現可靠傳輸(經典面試題)
- 2.5 TCP 相關實驗
- 2.5.1 理解 listen 的第二個參數
2.4 TCP/UDP對比
- 我們說了TCP是可靠連接, 那么是不是TCP一定就優于UDP呢? TCP和UDP之間的優點和缺點, 不能簡單, 絕對的進行比較
- TCP用于可靠傳輸的情況, 應用于文件傳輸, 重要狀態更新等場景;
- UDP用于對高速傳輸和實時性要求較高的通信領域, 例如, 早期的QQ, 視頻傳輸等. 另外UDP可以用于廣播;
- 歸根結底, TCP和UDP都是程序員的工具, 什么時機用, 具體怎么用, 還是要根據具體的需求場景去判定
2.4.1 用UDP實現可靠傳輸(經典面試題)
參考TCP的可靠性機制, 在應用層實現類似的邏輯;
例如:
- 引入序列號, 保證數據順序;
- 引入確認應答, 確保對端收到了數據;
- 引入超時重傳, 如果隔一段時間沒有應答, 就重發數據;
- …
2.5 TCP 相關實驗
2.5.1 理解 listen 的第二個參數
基于剛才封裝的 TcpSocket 實現以下測試代碼
對于服務器, listen 的第二個參數設置為 2, 并且不調用 accept
test_server.cc
#include "tcp_socket.hpp"
int main(int argc, char* argv[]) {if (argc != 3) {printf("Usage ./test_server [ip] [port]\n");return 1;}TcpSocket sock;bool ret = sock.Bind(argv[1], atoi(argv[2]));if (!ret) {return 1;}ret = sock.Listen(2);if (!ret) {return 1;}// 客戶端不進行 acceptwhile (1) {sleep(1);}return 0; }
test_client.cc
#include "tcp_socket.hpp" // 包含TCP套接字類的頭文件int main(int argc, char* argv[]) {// 檢查命令行參數數量是否正確if (argc != 3) {// 如果參數數量不正確,打印使用說明并退出程序printf("Usage ./test_client [ip] [port]\n");return 1;}// 創建TCP套接字對象TcpSocket sock;// 嘗試連接到指定IP和端口的服務器// argv[1]是IP地址字符串,atoi(argv[2])將端口號字符串轉換為整數bool ret = sock.Connect(argv[1], atoi(argv[2])); // 根據連接結果打印相應信息if (ret) {printf("connect ok\n"); // 連接成功} else {printf("connect failed\n"); // 連接失敗}// 無限循環,保持程序運行// 每秒休眠一次,防止CPU占用過高while (1) {sleep(1);}return 0; // 程序正常結束(實際上永遠不會執行到這里)
}
此時啟動 3 個客戶端同時連接服務器, 用 netstat 查看服務器狀態, 一切正常.
但是啟動第四個客戶端時, 發現服務器對于第四個連接的狀態存在問題了
tcp 3 0 0.0.0.0:9090 0.0.0.0:* LISTEN
9084/./test_server
tcp 0 0 127.0.0.1:9090 127.0.0.1:48178 SYN_RECV - tcp 0 0 127.0.0.1:9090 127.0.0.1:48176 ESTABLISHED - tcp 0 0 127.0.0.1:48178 127.0.0.1:9090 ESTABLISHED
9140/./test_client
tcp 0 0 127.0.0.1:48174 127.0.0.1:9090 ESTABLISHED
9087/./test_client
tcp 0 0 127.0.0.1:48176 127.0.0.1:9090 ESTABLISHED
9088/./test_client
tcp 0 0 127.0.0.1:48172 127.0.0.1:9090 ESTABLISHED
9086/./test_client
tcp 0 0 127.0.0.1:9090 127.0.0.1:48174 ESTABLISHED - tcp 0 0 127.0.0.1:9090 127.0.0.1:48172 ESTABLISHED -
客戶端狀態正常, 但是服務器端出現了 SYN_RECV 狀態, 而不是 ESTABLISHED 狀態
這是因為, Linux內核協議棧為一個tcp連接管理使用兩個隊列:
- 半鏈接隊列(用來保存處于SYN_SENT和SYN_RECV狀態的請求)
- 全連接隊列(accpetd隊列)(用來保存處于established狀態,但是應用層沒有調用accept取走的請求)
而全連接隊列的長度會受到 listen 第二個參數的影響.
全連接隊列滿了的時候, 就無法繼續讓當前連接的狀態進入 established 狀態了.
這個隊列的長度通過上述實驗可知, 是 listen 的第二個參數 + 1.