日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

TCP状态转换、半关闭、端口复用

發布時間:2024/1/1 编程问答 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TCP状态转换、半关闭、端口复用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

1、TCP狀態轉換

1.1 三次握手

1.2 四次揮手

1.3 狀態轉換

1.4 相關命令

2、半關閉

3、端口復用


1、TCP狀態轉換

在 TCP 進行三次握手,或者四次揮手的過程中,通信的服務器和客戶端內部會發送狀態上的變化,發生的狀態變化在程序中是看不到的,這個狀態的變化也不需要程序猿去維護,但是在某些情況下進行程序的調試會去查看相關的狀態信息,下面是三次握手過程中的狀態轉換。

1.1 三次握手

  • 在第一次握手之前,服務器必須先啟動,并且已經開始了監聽
  • --服務器端先調用了 listen() 函數,開始監聽
  • --服務器端啟動監聽前后的狀態變化:沒有狀態---> SYN_SENT
  • 當服務器監聽啟動之后,由客戶端發起的三次握手過程中狀態轉換如下:

    第一次握手:

    • 客戶端:調用了 connect() 函數,狀態變化:沒有狀態 -> SYN_SENT
    • 服務器:收到連接請求 SYN,狀態變化:LISTEN -> SYN_RCVD

    第二次握手:

    • 服務器:給客戶端回復 ACK,并且請求和客戶端建立連接,狀態無變化,依然是 SYN_RCVD
    • 客戶端:接收數據,收到了 ACK,狀態變化:SYN_SENT -> ESTABLISHED

    第三次握手:

    • 客戶端:給服務器回復 ACK,同意建立連接,狀態沒有變化,還是 ESTABLISHED
    • 服務器:收到了 ACK,狀態變化:SYN_RCVD -> ESTABLISHED

    三次握手完成之后,客戶端和服務器都變成了同一種狀態,這種狀態叫:ESTABLISHED,表示雙向連接已經建立, 可以通信了。在數據傳輸過程中,正常的通信狀態就是 ESTABLISHED。

    1.2 四次揮手

    關于四次揮手對于客戶端和服務器哪一端先斷開連接沒有要求,根據實際情況處理即可。下面根據上圖中的實例描述一下四次揮手過程中 TCP 的狀態轉換(上圖中主動斷開連接的一方是客戶端):

    第一次揮手:

    • 客戶端:調用 close() 函數,將 tcp 協議中的 FIN 設置為 1,請求和服務器斷開連接,狀態變化:ESTABLISHED -> FIN_WAIT_1
    • 服務器:收到斷開連接請求,狀態變化: ESTABLISHED -> CLOSE_WAIT

    第二次揮手:

    • 服務器:回復 ACK,同意斷開連接的請求,狀態沒有變化,還是 CLOSE_WAIT
    • 客戶端:收到 ACK,狀態變化:FIN_WAIT_1 -> FIN_WAIT_2

    第三次揮手:

    • 服務器端:調用 close () 函數,發送 FIN 給客戶端,請求斷開連接,狀態變化:CLOSE_WAIT -> LAST_ACK
    • 客戶端:收到 FIN,狀態變化:FIN_WAIT_2 -> TIME_WAIT

    第四次揮手:

    • 客戶端:回復 ACK 給服務器,狀態是沒有變化的,狀態變化:TIME_WAIT -> 沒有狀態
    • 服務器端:收到 ACK,雙向連接斷開,狀態變化:LAST_ACK -> 無狀態(沒有了)

    1.3 狀態轉換

    在下圖中同樣是描述 TCP 通信過程中的客戶端和服務器端的狀態轉換,看起來比較亂,其實只需要看兩條主線:紅色實線和綠色虛線。關于黑色的實線對應的是一些特殊情況下的狀態切換,在此不做任何分析。

    因為三次握手是由客戶端發起的,據此分析紅色的實線表示的客戶端的狀態,綠色虛線表示的是服務器端的狀態。

    • 客戶端:
  • 第一次握手:發送 SYN,沒有狀態 -> SYN_SENT
  • 第二次握手:收到回復的 ACK,SYN_SENT -> ESTABLISHED
  • 主動斷開連接,第一次揮手發送 FIN,狀態 ESTABLISHED -> FIN_WAIT_1
  • 第二次揮手,收到 ACK,狀態 FIN_WAIT_1 -> FIN_WAIT_2
  • 第三次揮手,收到 FIN,狀態 FIN_WAIT_2 -> TIME_WAIT
  • 第四次揮手,回復 ACK,等待 2 倍報文時長之后,狀態 TIME_WAIT -> 沒有狀態
    • 服務器端:
  • 啟動監聽,沒有狀態 -> LISTEN
  • 第一次握手,收到 SYN,狀態 LISTEN -> SYN_RCVD
  • 第三次握手,收到 ACK,狀態 SYN_RCVD -> ESTABLISHED
  • 收到斷開連接請求,第一次揮手狀態 ESTABLISHED -> CLOSE_WAIT
  • 第三次揮手,發送 FIN 請求和客戶端斷開連接,狀態 CLOSE_WAIT -> LAST_ACK
  • 第四次揮手,收到 ACK,狀態 LAST_ACK -> 無狀態(沒有了)
  • 在 TCP 通信的時候,當主動斷開連接的一方接收到被動斷開連接的一方發送的 FIN 和最終的 ACK 后(第三次揮手完成),連接的主動關閉方必須處于 TIME_WAIT 狀態并持續 2MSL(Maximum Segment Lifetime)時間,這樣就能夠讓 TCP 連接的主動關閉方在它發送的 ACK 丟失的情況下重新發送最終的 ACK。

    一倍報文壽命 (MSL) 大概時長為 30s,因此兩倍報文壽命一般在 1 分鐘作用。

    主動關閉方重新發送的最終ACK,是因為被動關閉方重傳了它的FIN。事實上,被動關閉方總是重傳FIN直到它收到一個最終的ACK。

    1.4 相關命令

    1? ?$ netstat 參數
    2? ?$ netstat -apn?? ?| grep 關鍵字

    • 參數:
  • -a (all) 顯示所有選項
  • -p 顯示建立相關鏈接的程序名
  • -n 拒絕顯示別名,能顯示數字的全部轉化成數字。
  • -l 僅列出有在 Listen (監聽) 的服務狀態
  • -t (tcp) 僅顯示 tcp 相關選項
  • -u (udp) 僅顯示 udp 相關選項
  • 2、半關閉

    TCP 連接只有一方發送了 FIN,另一方沒有發出 FIN 包,仍然可以在一個方向上正常發送數據,這種狀態可以稱之為半關閉或者半連接。當四次揮手完成兩次的時候,就相當于實現了半關閉,在程序中只需要在某一端直接調用 close () 函數即可。套接字通信默認是雙工的,也就是雙向通信,如果進行了半關閉就變成了單工,數據只能單向流動了。比如下面的這個例子:

    • 服務器端:
  • 調用了 close () 函數,因此不能發數據,只能接收數據
  • 關閉了服務器端的寫操作,現在只能進行讀操作 –> 變成了讀端
    • 客戶端:
  • 沒有調用 close (),客戶端和服務器的連接還保持著
  • 客戶端可以給服務器發送數據,也可以接收服務器發送的數據 (但是,服務器已經喪失了發送數據的能力),因此客戶端也只能發送數據,接收不到數據 –> 變成了寫端
  • 按照上述流程做了半關閉之后,從雙工變成了單工,數據單向流動的方向:客戶端 —–> 服務器端。

    1? ?// 專門處理半關閉的函數 2? ?#include <sys/socket.h> 3? ?// 可以有選擇的關閉讀/寫, close()函數只能關閉寫操作 4? ?int shutdown(int sockfd, int how);
    • 參數:
  • sockfd: 要操作的文件描述符
  • how:
  • SHUT_RD: 關閉文件描述符對應的讀操作

    SHUT_WR: 關閉文件描述符對應的寫操作

    SHUT_RDWR: 關閉文件描述符對應的讀寫操作

    • 返回值:函數調用成功返回 0,失敗返回 - 1

    3、端口復用

    在網絡通信中,一個端口只能被一個進程使用,不能多個進程共用同一個端口。我們在進行套接字通信的時候,如果按順序執行如下操作:先啟動服務器程序,再啟動客戶端程序,然后關閉服務器進程,再退出客戶端進程,最后再啟動服務器進程,就會出如下的錯誤提示信息:bind error: Address already in use

    # 第二次啟動服務器進程 $ ./server bind error: Address already in use$ netstat -apn|grep 9999 (Not all processes could be identified, non-owned process infowill not be shown, you would have to be root to see it all.) tcp 0 0 127.0.0.1:9999 127.0.0.1:50178 TIME_WAIT

    通過 netstat 查看 TCP 狀態,發現上一個服務器進程其實還沒有真正退出。因為服務器進程是主動斷開連接的進程,最后狀態變成了 TIME_WAIT 狀態,這個進程會等待 2msl(大約1分鐘) 才會退出,如果該進程不退出,其綁定的端口就不會釋放,再次啟動新的進程還是使用這個未釋放的端口,端口被重復使用,就是提示 bind error: Address already in use 這個錯誤信息。

    如果想要解決上述問題,就必須要設置端口復用,使用的函數原型如下:

    // 這個函數是一個多功能函數, 可以設置套接字選項 int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

    參數:

    • sockfd:用于監聽的文件描述符
    • level:設置端口復用需要使用 SOL_SOCKET 宏
    • optname:要設置什么屬性(下邊的兩個宏都可以設置端口復用)

    SO_REUSEADDR
    ? ? ? ?SO_REUSEPORT

    • optval:設置是去除端口復用屬性還是設置端口復用屬性,實際應該使用 int 型變量

    0:不設置
    ? ? ? ?1:設置

    • optlen:optval 指針指向的內存大小 sizeof (int)

    這個函數應該添加到服務器端代碼中,具體應該放到什么位置呢?答:在綁定之前設置端口復用

  • 創建監聽的套接字
  • 設置端口復用
  • 綁定
  • 設置監聽
  • 等待并接受客戶端連接
  • 通信
  • 斷開連接
  • 參考代碼

    #include <stdio.h> #include <ctype.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/select.h>// server int main(int argc, const char* argv[]) {// 創建監聽的套接字int lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd == -1){perror("socket error");exit(1);}// 綁定struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(9999);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 本地多有的IP// 127.0.0.1// inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr.s_addr);// 設置端口復用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));// 綁定端口int ret = bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));if(ret == -1){perror("bind error");exit(1);}// 監聽ret = listen(lfd, 64);if(ret == -1){perror("listen error");exit(1);}fd_set reads, tmp;FD_ZERO(&reads);FD_SET(lfd, &reads);int maxfd = lfd;while(1){tmp = reads;int ret = select(maxfd+1, &tmp, NULL, NULL, NULL);if(ret == -1){perror("select");exit(0);}if(FD_ISSET(lfd, &tmp)){int cfd = accept(lfd, NULL, NULL);FD_SET(cfd, &reads);maxfd = cfd > maxfd ? cfd : maxfd;}for(int i=lfd+1; i<=maxfd; ++i){if(FD_ISSET(i, &tmp)){char buf[1024];int len = read(i, buf, sizeof(buf));if(len > 0){printf("client say: %s\n", buf);write(i, buf, len);}else if(len == 0){printf("客戶端斷開了連接\n");FD_CLR(i, &reads);close(i);}else{perror("read");exit(0);}}}}return 0; }

    ?

    ?

    ?

    ?

    總結

    以上是生活随笔為你收集整理的TCP状态转换、半关闭、端口复用的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。