当使用阻塞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套接字时,我需要创建一个缓冲区,从而硬编码最大大小:
var buffer = new byte[BufferSize];
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, callback, null);我可以简单地将缓冲区大小设置为1024字节。如果我只需要接收一小块数据就行了。但是,如果我需要接收一个10 MB的序列化对象呢?我可以把缓冲区设置为10*1024*1024.但是,只要应用程序运行,就会浪费10 MB的恒定RAM。这太傻了。
因此,我的问题是:如何使用异步TCP套接字有效地接收大块数据?。
发布于 2011-04-10 03:39:46
两个示例并不等价--您的阻塞代码假设远程端发送数据的32位长度。如果相同的协议对异步有效,只需读取该长度(阻塞与否),然后分配缓冲区并启动异步IO。
编辑0:
我还要补充一点,分配用户输入的缓冲区,特别是网络输入的缓冲区,大小是灾难的收据。一个明显的问题是,当客户端请求一个巨大的缓冲区并持有它(比如发送数据非常慢)并阻止其他分配和/或减缓整个系统的速度时,就会发生拒绝服务攻击。
这里的常识是一次接受固定数量的数据,并在进行分析时进行解析。当然,这会影响应用程序级协议的设计。
发布于 2022-08-21 17:45:51
编辑的
经过长时间的分析,我发现解决这一问题的最佳办法如下:
设置缓冲区大小
缓冲区大小可以通过两种方式设置,任意的或客观的。如果要接收的信息是基于文本的,那么它并不大,并且不需要进行字符比较,而任意预先设置的缓冲区大小是最佳的。如果要接收的信息需要逐字符处理,并且/或较大,则目标缓冲区大小是最佳选择。
// 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(client);
// 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编码字节数组中转换字符串。然后将接收到的有效载荷长度转换回字符串格式,然后转换为整数,以设置接收有效负载的缓冲区的长度。长度被转换为字符串,然后转换为int,然后转换为byte[],以避免数据损坏,因为与有效负载长度相关的信息不会发送到与信息大小相同的缓冲区中。当接收方将byte[]内容转换为字符串,然后转换为int时,多余的字符将被删除,信息将保持不变。
获取连接的上传/下载速度,并计算套接字接收和发送缓冲区大小
// 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(client);
class Internet_Speed_Checker
{
public async Task<bool>> Optimum_Buffer_Size(System.Net.Sockets.NetworkStream socket)
{
System.Diagnostics.Stopwatch latency_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.
latency_counter .Start();
await client_secure_network_stream.ReadAsync(test_payload, 0, test_payload.Length);
await client_secure_network_stream.WriteAsync(test_payload, 0, test_payload.Length);
latency_counter .Stop();
int bytes_per_second = (int)(test_payload.Length * (1000 / latency_time_counter.Elapsed.TotalMilliseconds));
int optimal_connection_timeout = (Convert.ToInt32(payload_length) / download_bytes_per_second) * 1000 + 1000;
double optimal_buffer_size_double = ((download_bytes_per_second / 125000) * (latency_time_counter.Elapsed.TotalMilliseconds / 1000)) * 1048576;
int optimal_buffer_size = (int)download_optimal_buffer_size_double + 1024;
// If you want to upload data to the client/server --> client.SendBufferSize = optimal_buffer_size;
// client.SendTimeout = optimal_connection_timeout;
// If you want to download data from the client/server --> client.ReceiveBufferSize = optimal_buffer_size;
// client.ReceiveTimeout = optimal_connection_timeout;
}
}上述方法是确保客户端缓冲区和服务器缓冲区之间传输的数据使用适当的套接字缓冲区大小和套接字连接超时,以避免数据损坏和碎片。当数据通过带有异步读写操作的套接字发送时,要发送的信息的长度将在数据包中分段。数据包大小有一个默认值,但它不包括连接的上载/下载速度是变化的事实。为了避免数据损坏和连接的最佳下载/上传速度,必须根据连接的速度设置数据包大小。在上述示例中,我还演示了如何计算与连接速度相关的超时。可以分别使用socket.ReceiveBufferSize = ... / socket.SendBufferSize = ...设置上载/下载的数据包大小。
有关所使用的方程式和原则的更多信息,请查阅:
https://www.baeldung.com/cs/calculate-internet-speed-ping
%20 01/html/E37476/gnkor.html#:~:text=You%20can%20calculate%20the%20correct,值%20连接%20延迟。
https://stackoverflow.com/questions/5609576
复制相似问题