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

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

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

發(fā)布時(shí)間:2024/1/1 编程问答 54 豆豆
生活随笔 收集整理的這篇文章主要介紹了 TCP状态转换、半关闭、端口复用 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

1、TCP狀態(tài)轉(zhuǎn)換

1.1 三次握手

1.2 四次揮手

1.3 狀態(tài)轉(zhuǎn)換

1.4 相關(guān)命令

2、半關(guān)閉

3、端口復(fù)用


1、TCP狀態(tài)轉(zhuǎn)換

在 TCP 進(jìn)行三次握手,或者四次揮手的過(guò)程中,通信的服務(wù)器和客戶端內(nèi)部會(huì)發(fā)送狀態(tài)上的變化,發(fā)生的狀態(tài)變化在程序中是看不到的,這個(gè)狀態(tài)的變化也不需要程序猿去維護(hù),但是在某些情況下進(jìn)行程序的調(diào)試會(huì)去查看相關(guān)的狀態(tài)信息,下面是三次握手過(guò)程中的狀態(tài)轉(zhuǎn)換。

1.1 三次握手

  • 在第一次握手之前,服務(wù)器必須先啟動(dòng),并且已經(jīng)開(kāi)始了監(jiān)聽(tīng)
  • --服務(wù)器端先調(diào)用了 listen() 函數(shù),開(kāi)始監(jiān)聽(tīng)
  • --服務(wù)器端啟動(dòng)監(jiān)聽(tīng)前后的狀態(tài)變化:沒(méi)有狀態(tài)---> SYN_SENT
  • 當(dāng)服務(wù)器監(jiān)聽(tīng)啟動(dòng)之后,由客戶端發(fā)起的三次握手過(guò)程中狀態(tài)轉(zhuǎn)換如下:

    第一次握手:

    • 客戶端:調(diào)用了 connect() 函數(shù),狀態(tài)變化:沒(méi)有狀態(tài) -> SYN_SENT
    • 服務(wù)器:收到連接請(qǐng)求 SYN,狀態(tài)變化:LISTEN -> SYN_RCVD

    第二次握手:

    • 服務(wù)器:給客戶端回復(fù) ACK,并且請(qǐng)求和客戶端建立連接,狀態(tài)無(wú)變化,依然是 SYN_RCVD
    • 客戶端:接收數(shù)據(jù),收到了 ACK,狀態(tài)變化:SYN_SENT -> ESTABLISHED

    第三次握手:

    • 客戶端:給服務(wù)器回復(fù) ACK,同意建立連接,狀態(tài)沒(méi)有變化,還是 ESTABLISHED
    • 服務(wù)器:收到了 ACK,狀態(tài)變化:SYN_RCVD -> ESTABLISHED

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

    1.2 四次揮手

    關(guān)于四次揮手對(duì)于客戶端和服務(wù)器哪一端先斷開(kāi)連接沒(méi)有要求,根據(jù)實(shí)際情況處理即可。下面根據(jù)上圖中的實(shí)例描述一下四次揮手過(guò)程中 TCP 的狀態(tài)轉(zhuǎn)換(上圖中主動(dòng)斷開(kāi)連接的一方是客戶端):

    第一次揮手:

    • 客戶端:調(diào)用 close() 函數(shù),將 tcp 協(xié)議中的 FIN 設(shè)置為 1,請(qǐng)求和服務(wù)器斷開(kāi)連接,狀態(tài)變化:ESTABLISHED -> FIN_WAIT_1
    • 服務(wù)器:收到斷開(kāi)連接請(qǐng)求,狀態(tài)變化: ESTABLISHED -> CLOSE_WAIT

    第二次揮手:

    • 服務(wù)器:回復(fù) ACK,同意斷開(kāi)連接的請(qǐng)求,狀態(tài)沒(méi)有變化,還是 CLOSE_WAIT
    • 客戶端:收到 ACK,狀態(tài)變化:FIN_WAIT_1 -> FIN_WAIT_2

    第三次揮手:

    • 服務(wù)器端:調(diào)用 close () 函數(shù),發(fā)送 FIN 給客戶端,請(qǐng)求斷開(kāi)連接,狀態(tài)變化:CLOSE_WAIT -> LAST_ACK
    • 客戶端:收到 FIN,狀態(tài)變化:FIN_WAIT_2 -> TIME_WAIT

    第四次揮手:

    • 客戶端:回復(fù) ACK 給服務(wù)器,狀態(tài)是沒(méi)有變化的,狀態(tài)變化:TIME_WAIT -> 沒(méi)有狀態(tài)
    • 服務(wù)器端:收到 ACK,雙向連接斷開(kāi),狀態(tài)變化:LAST_ACK -> 無(wú)狀態(tài)(沒(méi)有了)

    1.3 狀態(tài)轉(zhuǎn)換

    在下圖中同樣是描述 TCP 通信過(guò)程中的客戶端和服務(wù)器端的狀態(tài)轉(zhuǎn)換,看起來(lái)比較亂,其實(shí)只需要看兩條主線:紅色實(shí)線和綠色虛線。關(guān)于黑色的實(shí)線對(duì)應(yīng)的是一些特殊情況下的狀態(tài)切換,在此不做任何分析。

    因?yàn)槿挝帐质怯煽蛻舳税l(fā)起的,據(jù)此分析紅色的實(shí)線表示的客戶端的狀態(tài),綠色虛線表示的是服務(wù)器端的狀態(tài)。

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

    一倍報(bào)文壽命 (MSL) 大概時(shí)長(zhǎng)為 30s,因此兩倍報(bào)文壽命一般在 1 分鐘作用。

    主動(dòng)關(guān)閉方重新發(fā)送的最終ACK,是因?yàn)楸粍?dòng)關(guān)閉方重傳了它的FIN。事實(shí)上,被動(dòng)關(guān)閉方總是重傳FIN直到它收到一個(gè)最終的ACK。

    1.4 相關(guān)命令

    1? ?$ netstat 參數(shù)
    2? ?$ netstat -apn?? ?| grep 關(guān)鍵字

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

    TCP 連接只有一方發(fā)送了 FIN,另一方?jīng)]有發(fā)出 FIN 包,仍然可以在一個(gè)方向上正常發(fā)送數(shù)據(jù),這種狀態(tài)可以稱之為半關(guān)閉或者半連接。當(dāng)四次揮手完成兩次的時(shí)候,就相當(dāng)于實(shí)現(xiàn)了半關(guān)閉,在程序中只需要在某一端直接調(diào)用 close () 函數(shù)即可。套接字通信默認(rèn)是雙工的,也就是雙向通信,如果進(jìn)行了半關(guān)閉就變成了單工,數(shù)據(jù)只能單向流動(dòng)了。比如下面的這個(gè)例子:

    • 服務(wù)器端:
  • 調(diào)用了 close () 函數(shù),因此不能發(fā)數(shù)據(jù),只能接收數(shù)據(jù)
  • 關(guān)閉了服務(wù)器端的寫(xiě)操作,現(xiàn)在只能進(jìn)行讀操作 –> 變成了讀端
    • 客戶端:
  • 沒(méi)有調(diào)用 close (),客戶端和服務(wù)器的連接還保持著
  • 客戶端可以給服務(wù)器發(fā)送數(shù)據(jù),也可以接收服務(wù)器發(fā)送的數(shù)據(jù) (但是,服務(wù)器已經(jīng)喪失了發(fā)送數(shù)據(jù)的能力),因此客戶端也只能發(fā)送數(shù)據(jù),接收不到數(shù)據(jù) –> 變成了寫(xiě)端
  • 按照上述流程做了半關(guān)閉之后,從雙工變成了單工,數(shù)據(jù)單向流動(dòng)的方向:客戶端 —–> 服務(wù)器端。

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

    SHUT_WR: 關(guān)閉文件描述符對(duì)應(yīng)的寫(xiě)操作

    SHUT_RDWR: 關(guān)閉文件描述符對(duì)應(yīng)的讀寫(xiě)操作

    • 返回值:函數(shù)調(diào)用成功返回 0,失敗返回 - 1

    3、端口復(fù)用

    在網(wǎng)絡(luò)通信中,一個(gè)端口只能被一個(gè)進(jìn)程使用,不能多個(gè)進(jìn)程共用同一個(gè)端口。我們?cè)谶M(jìn)行套接字通信的時(shí)候,如果按順序執(zhí)行如下操作:先啟動(dòng)服務(wù)器程序,再啟動(dòng)客戶端程序,然后關(guān)閉服務(wù)器進(jìn)程,再退出客戶端進(jìn)程,最后再啟動(dòng)服務(wù)器進(jìn)程,就會(huì)出如下的錯(cuò)誤提示信息:bind error: Address already in use

    # 第二次啟動(dòng)服務(wù)器進(jìn)程 $ ./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

    通過(guò) netstat 查看 TCP 狀態(tài),發(fā)現(xiàn)上一個(gè)服務(wù)器進(jìn)程其實(shí)還沒(méi)有真正退出。因?yàn)榉?wù)器進(jìn)程是主動(dòng)斷開(kāi)連接的進(jìn)程,最后狀態(tài)變成了 TIME_WAIT 狀態(tài),這個(gè)進(jìn)程會(huì)等待 2msl(大約1分鐘) 才會(huì)退出,如果該進(jìn)程不退出,其綁定的端口就不會(huì)釋放,再次啟動(dòng)新的進(jìn)程還是使用這個(gè)未釋放的端口,端口被重復(fù)使用,就是提示 bind error: Address already in use 這個(gè)錯(cuò)誤信息。

    如果想要解決上述問(wèn)題,就必須要設(shè)置端口復(fù)用,使用的函數(shù)原型如下:

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

    參數(shù):

    • sockfd:用于監(jiān)聽(tīng)的文件描述符
    • level:設(shè)置端口復(fù)用需要使用 SOL_SOCKET 宏
    • optname:要設(shè)置什么屬性(下邊的兩個(gè)宏都可以設(shè)置端口復(fù)用)

    SO_REUSEADDR
    ? ? ? ?SO_REUSEPORT

    • optval:設(shè)置是去除端口復(fù)用屬性還是設(shè)置端口復(fù)用屬性,實(shí)際應(yīng)該使用 int 型變量

    0:不設(shè)置
    ? ? ? ?1:設(shè)置

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

    這個(gè)函數(shù)應(yīng)該添加到服務(wù)器端代碼中,具體應(yīng)該放到什么位置呢?答:在綁定之前設(shè)置端口復(fù)用

  • 創(chuàng)建監(jiān)聽(tīng)的套接字
  • 設(shè)置端口復(fù)用
  • 綁定
  • 設(shè)置監(jiān)聽(tīng)
  • 等待并接受客戶端連接
  • 通信
  • 斷開(kāi)連接
  • 參考代碼

    #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[]) {// 創(chuàng)建監(jiān)聽(tīng)的套接字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);// 設(shè)置端口復(fù)用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);}// 監(jiān)聽(tīng)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("客戶端斷開(kāi)了連接\n");FD_CLR(i, &reads);close(i);}else{perror("read");exit(0);}}}}return 0; }

    ?

    ?

    ?

    ?

    總結(jié)

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

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。