簡體   English   中英

C# 異步 TCP sockets:處理緩沖區大小和大量傳輸

[英]C# Async TCP sockets: Handling buffer size and huge transfers

當使用阻塞 TCP 套接字時,我不必指定緩沖區大小。 例如:

using (var client = new TcpClient())
{
    client.Connect(ServerIp, ServerPort);

    using (reader = new BinaryReader(client.GetStream()))
    using (writer = new BinaryWriter(client.GetStream()))
    {
        var byteCount = reader.ReadInt32();
        reader.ReadBytes(byteCount);
    }
}

請注意遠程主機如何發送任意數量的字節。

但是,當使用異步 TCP sockets 時,我需要創建一個緩沖區並因此硬編碼最大大小:

 var buffer = new byte[BufferSize];
 socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, callback, null);

我可以簡單地將緩沖區大小設置為 1024 字節。 如果我只需要接收小塊數據,這將起作用。 但是如果我需要接收一個 10 MB 的序列化 object 怎么辦? 我可以將緩沖區大小設置為 10*1024*1024... 但只要應用程序正在運行,這將浪費恆定的 10 MB RAM。 這很愚蠢。

所以,我的問題是:如何使用異步 TCP sockets 有效地接收大塊數據?

兩個示例不相同 - 您的阻塞代碼假定遠程端發送要跟隨的32位數據長度。 如果相同的協議對異步有效 - 只需讀取該長度(阻塞或不阻塞),然后分配緩沖區並啟動異步IO。

編輯0:

我還要補充一點,分配用戶輸入的緩沖區,特別是網絡輸入大小的緩沖區是災難的收據。 一個明顯的問題是當客戶端請求一個巨大的緩沖區並保持它 - 比如非常緩慢地發送數據 - 並阻止其他分配和/或減慢整個系統時的拒絕服務攻擊。

這里的常識是一次接受固定數量的數據並隨時解析。 這當然會影響您的應用程序級協議設計。

經過長時間的分析,我發現解決此問題的最佳方法如下:

  • 首先,您需要設置緩沖區大小以便從服務器/客戶端接收數據。

  • 其次,您需要找到該連接的上傳/下載速度。

  • 第三,您需要根據要發送或接收的 package 的大小來計算連接超時應該持續多少秒。

設置緩沖區大小


緩沖區大小可以通過兩種方式設置,任意的或客觀的。 如果要接收的信息是基於文本的,它不大並且不需要字符比較,那么任意預設緩沖區大小是最佳的。 如果要接收的信息需要逐個字符地處理,和/或很大,則目標緩沖區大小是最佳選擇

// In this example I used a Socket wrapped inside a NetworkStream for simplicity
// stability, and asynchronous operability purposes.

// This can be done by doing this:
//
//  For server:
//
//  Socket server= new Socket();
//  server.ReceiveBufferSize = 18000;
//  IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Any, port);
//  server.Bind(iPEndPoint);
//  server.Listen(3000);
// 
// 
//  NetworkStream ns = new NetworkStream(server);

//  For client:
//  
//  Socket client= new Socket();
//  client.Connect("127.0.0.1", 80);
//  
//  NetworkStream ns = new NetworkStream(server);

// In order to set an objective buffer size based on a file's size in order not to
// receive null characters as extra characters because the buffer is bigger than
// the file's size, or a corrupted file because the buffer is smaller than
// the file's size.

//   The TCP protocol follows the Sys, Ack and Syn-Ack paradigm,  
//   so within a TCP connection if the client or server began the 
//   connection by sending a message, the next message within its
//   connection must be read, and if the client or server began 
//   the connection by receiving a message, the next message must
//   be sent.

// [SENDER]

byte[] file = new byte[18032];

byte[] file_length = Encoding.UTF8.GetBytes(file.Length.ToString());

await Sender.WriteAsync(file_length, 0, file_length.Length);

byte[] receiver_response = new byte[1800];

await Sender.ReadAsync(receiver_response, 0, receiver_response.Length);

await Sender.WriteAsync(file, 0, file.Length);

// [SENDER]

// [RECEIVER]

byte[] file_length = new byte[1800];

await Receiver.ReadAsync(file_length, 0, file_length.Length);

byte[] encoded_response = Encoding.UTF8.GetBytes("OK");

await Receiver.WriteAsync(encoded_response, 0, encoded_response.Length);

byte[] file = new byte[Convert.ToInt32(Encoding.UTF8.GetString(file_length))];

await Receiver.ReadAsync(file, 0, file.Length);

// [RECEIVER]

用於接收有效負載長度的緩沖區使用任意緩沖區大小。 將要發送的有效載荷的長度轉換為字符串,然后將字符串轉換為 UTF-8 編碼的字節數組。 然后將接收到的有效負載長度轉換回字符串格式,然后轉換為 integer 以設置將接收有效負載的緩沖區的長度。 將長度轉換為字符串,然后轉換為int ,然后轉換為byte[] ,以避免由於與有效負載長度相關的信息不會發送到與信息具有相同大小的緩沖區而導致數據損壞. 當接收者將byte[]內容轉換為字符串,然后轉換為int時,多余的字符將被刪除,信息將保持不變。

獲取連接的上傳/下載速度並計算Socket接收和發送緩沖區大小


  • 首先,制作一個負責計算每個連接的緩沖區大小的 class。
// In this example I used a Socket wrapped inside a NetworkStream for simplicity
// stability, and asynchronous operability purposes.

// This can be done by doing this:
//
//  For server:
//
//  Socket server= new Socket();
//  server.ReceiveBufferSize = 18000;
//  IPEndPoint iPEndPoint = new IPEndPoint(IPAddress.Any, port);
//  server.Bind(iPEndPoint);
//  server.Listen(3000);
// 
//  NetworkStream ns = new NetworkStream(server);

//  For client:
//  
//  Socket client= new Socket();
//  client.Connect("127.0.0.1", 80);
//
//  NetworkStream ns = new NetworkStream(server);

    class Internet_Speed_Checker
    {
        public async Task<bool>> Optimum_Buffer_Size(System.Net.Sockets.NetworkStream socket)
        {
             System.Diagnostics.Stopwatch traffic_per_second_counter = new System.Diagnostics.Stopwatch();

             byte[] test_payload = new byte[2048];

             //   The TCP protocol follows the Sys, Ack and Syn-Ack paradigm,  
             //   so within a TCP connection if the client or server began the 
             //   connection by sending a message, the next message within its
             //   connection must be read, and if the client or server began 
             //   the connection by receiving a message, the next message must
             //   be sent.
             //
             //   In order to test the connection, the client and server must 
             //   send and receive a package of the same size. If the client 
             //   or server began the connection by sending a message, the 
             //   client or server must do the this connection test by 
             //   initiating a write-read sequence, else it must do this
             //   connection test initiating a read-write sequence.

             traffic_per_second_counter.Start();

             await client_secure_network_stream.ReadAsync(test_payload, 0, test_payload.Length);

             traffic_per_second_counter.Stop();

             int download_bytes_per_second = (int)(test_payload.Length * (1000 / traffic_per_second_counter.Elapsed.TotalMilliseconds));
             
             int download_optimal_buffer_size = (int)(bytes_per_second * traffic_per_second_counter.Elapsed.TotalMilliseconds);
             
             traffic_per_second_counter.Restart();
             
             await client_secure_network_stream.WriteAsync(test_payload, 0, test_payload.Length);

             traffic_per_second_counter.Stop();
             
             int upload_bytes_per_second = (int)(test_payload.Length * (1000 / traffic_per_second_counter.Elapsed.TotalMilliseconds));

             int upload_optimal_buffer_size = (int)(bytes_per_second * traffic_per_second_counter.Elapsed.TotalMilliseconds);
             
             // If you want to download data from the client/server -->  client.SendBufferSize = download_optimal_buffer_size;
             // client.SendTimeout = (payload_array.Length / bytes_per_second___and___optimal_buffer_size.Item1) * 1000 + 100;
             
             // If you want to upload data to the client/server -->  client.ReceiveBufferSize = download_optimal_buffer_size;
             // client.SendTimeout = (payload_array.Length / bytes_per_second___and___optimal_buffer_size.Item1) * 1000 + 100;
        }
    }

上述方法是確保在客戶端緩沖區和服務器緩沖區之間傳輸的數據使用適當的套接字緩沖區大小和套接字連接超時,以避免數據損壞和碎片。 當數據通過帶有異步讀/寫操作的套接字發送時,要發送的信息的長度將以數據包的形式分段。 數據包大小有一個默認值,但它不包括連接的上傳/下載速度變化的事實。 為了避免數據損壞和連接的最佳下載/上傳速度,必須根據連接速度設置數據包大小。 在上述示例中,我還展示了如何計算與連接速度相關的超時。 上傳/下載的數據包大小可以分別使用socket.ReceiveBufferSize =... / socket.SendBufferSize =...來設置。

有關使用的方程式和原理的更多信息,請查看:
使用 Ping 計算上傳/下載速度

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM