1. 程式人生 > >為什麼正確的關閉TCP連線

為什麼正確的關閉TCP連線

收到的資料不完整,

先看一個例子,如下

#include "Acceptor.h"
#include "InetAddress.h"
#include "TcpStream.h"

#include <thread>
#include <unistd.h>

void sender(const char* filename, TcpStreamPtr stream)
{
  FILE* fp = fopen(filename, "rb");
  if (!fp)
    return;

  printf("Sleeping 10 seconds.\n");
  //讓我們有機會從另一端輸入資料
  sleep
(10); printf("Start sending file %s\n", filename); char buf[8192]; size_t nr = 0; while ( (nr = fread(buf, 1, sizeof buf, fp)) > 0) { stream->sendAll(buf, nr); } fclose(fp); printf("Finish sending file %s\n", filename); // Safe close connection,可先註釋掉,復現出發資料不完整的情況 /* printf
("Shutdown write and read until EOF\n"); stream->shutdownWrite(); while ( (nr = stream->receiveSome(buf, sizeof buf)) > 0) { // do nothing } printf("All done.\n"); */ // TcpStream destructs here, close the TCP socket. } int main(int argc, char* argv[]) { if (argc < 3) { printf
("Usage:\n %s filename port\n", argv[0]); return 0; } int port = atoi(argv[2]); Acceptor acceptor((InetAddress(port))); printf("Accepting... Ctrl-C to exit\n"); int count = 0; while (true) { TcpStreamPtr tcpStream = acceptor.accept(); printf("accepted no. %d client\n", ++count); std::thread thr(sender, argv[1], std::move(tcpStream)); thr.detach(); } }

此程式再port埠監聽,併發送一個檔案,f傳送下面檔案

[root@localhost tpc]# ll ttcp
-rwxr-xr-x. 1 root root 1236464 May  6 17:40 ttcp

試驗1

[root@localhost tpc]# ./sender ttcp 1234
Accepting... Ctrl-C to exit
accepted no. 1 client
Sleeping 10 seconds.
Start sending file ttcp
Finish sending file ttcp
All done.

[root@localhost ~]# nc localhost 1234 | wc -c 
1236464

可以看出能夠完整收到資料

試驗2,在nc命令列下輸入一點資料

[root@localhost tpc]# ./sender ttcp 1234
Accepting... Ctrl-C to exit
accepted no. 1 client
Sleeping 10 seconds.
Start sending file ttcp
Finish sending file ttcp
All done.

[root@localhost ~]# nc localhost 1234 | wc -c 
nishighds
Ncat: Connection reset by peer.
581156

可以看出收到的資料小了,

注意

如果協議棧的接受緩衝區還有資料,你沒有去讀,直接close,那麼就會導致TCP協議棧傳送RST分節,強行的斷開連線;如果這時協議棧的傳送緩衝區還有資料,對方沒收到,那麼資料就丟失了,close太早,SO_LINGER此選項無助解決此問題。

正確的做法

sender:shudown(write),對方read返回0,這樣的話沒有資料send,就會close.反過來導致sender的read返回0.

總結起來read返回0才去close。read返回0表示遇到EOF,此時close是安全的。

這裡需要注意是read返回0,才會close關閉連線,如果遇到噁心的客戶端,不close,則read會阻塞;因此可能需要加一個超時控制,如果在shutdown()之後若干秒後還沒有read返回0,需要自己去close連線。

上面的辦法是根據shutdown(write),read返回0判斷資料有沒有讀完,更好辦法設計協議的時候把長度包含進去。這樣接收方能主動判斷資料是否收完畢,收完就可以斷開連線。
因為Netcat是無格式的協議,所以需要這種比較繞的方法,
正確關閉TCP連線,

傳送完後,關閉寫端,一直read直到返回0(EOF).表明對方已經關閉連線;這時呼叫close,這樣一來邏輯就正確了;對於接收方比較簡單,read返回0就可以了。read返回0才去close, read返回0表明遇到了EOF,這時候clsoe是安全的。一個因果關係。

這裡寫圖片描述

總結 陳碩老師的課程。

TCP是個可靠協議,為什麼常見的還要在應用層加個ACK響應?
答:TCP協議棧的ACK表現對方協議棧已經收到了你的資料,但是並不代表對方應用程式已經處理了你的資料,對方可能已經阻塞了或者死鎖了,那麼資料還是儲存在接受緩衝區裡,會給你一個TCP的ACK。應用層看不見這個ACK,所以需要加一個TCP的ACK。