C# 异步 TCP 套接字:处理缓冲区大小和大量传输
当使用阻塞 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 套接字有效地接收大块数据?
When using a blocking TCP socket, I don't have to specify a buffer size. For example:
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);
}
}
Notice how the remote host could have sent any number of bytes.
However, when using async TCP sockets, I need to create a buffer and thus hardcode a maximum size:
var buffer = new byte[BufferSize];
socket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, callback, null);
I could simply set the buffer size to, say, 1024 bytes. That'll work if I only need to receive small chunks of data. But what if I need to receive a 10 MB serialized object? I could set the buffer size to 10*1024*1024... but that would waste a constant 10 MB of RAM for as long as the application is running. This is silly.
So, my question is: How can I efficiently receive big chunks of data using async TCP sockets?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
两个示例并不等效 - 您的阻塞代码假设远程端发送了后续数据的 32 位长度。如果相同的协议对异步有效 - 只需读取该长度(是否阻塞),然后分配缓冲区并启动异步 IO。
编辑0:
让我还补充一点,分配用户输入的缓冲区,尤其是网络输入的缓冲区大小是灾难的收据。一个明显的问题是当客户端请求巨大的缓冲区并保留时,会发生拒绝服务攻击它 - 说发送数据非常慢 - 并阻止其他分配和/或减慢整个系统。
这里的常识是一次接受固定数量的数据并进行解析。这当然会影响您的应用程序级协议设计。
Two examples are not equivalent - your blocking code assumes the remote end sends the 32-bit length of the data to follow. If the same protocol is valid for the async - just read that length (blocking or not) and then allocate the buffer and initiate the asynchronous IO.
Edit 0:
Let me also add that allocating buffers of user-entered, and especially of network-input, size is a receipt for disaster. An obvious problem is a denial-of-service attack when client requests a huge buffer and holds on to it - say sends data very slowly - and prevents other allocations and/or slows the whole system.
Common wisdom here is accepting a fixed amount of data at a time and parsing as you go. That of course affects your application-level protocol design.
已编辑
经过长时间的分析,我发现解决这个问题的最佳方法如下:
首先,您需要设置缓冲区大小以便从服务器/客户端接收数据。< /p>
其次,您需要找到该连接的上传/下载速度。
第三,根据要发送或接收的包的大小,计算连接超时应该持续多少秒。
设置缓冲区大小
缓冲区大小可以通过两种方式设置:任意设置或客观设置。如果要接收的信息是基于文本的,它并不大并且不需要字符比较,那么任意预设的缓冲区大小是最佳的。如果要接收的信息需要逐字符处理和/或很大,则目标缓冲区大小是最佳选择。
用于接收有效负载长度的缓冲区使用任意缓冲区大小。将要发送的有效负载的长度转换为字符串,然后将字符串转换为 UTF-8 编码的字节数组。然后,接收到的有效负载长度被转换回字符串格式,然后转换为整数,以便设置将接收有效负载的缓冲区的长度。将长度转换为字符串,然后转换为
int
,然后转换为byte[]
,以避免由于与有效负载长度相关的信息被破坏而导致数据损坏。不会被发送到与信息大小相同的缓冲区中。当接收方将byte[]
内容转换为字符串,然后转换为int
时,多余的字符将被删除,信息将保持不变。获取连接的上传/下载速度并计算Socket接收和发送缓冲区大小
上述方法是确保客户端缓冲区和服务器缓冲区之间传输的数据使用适当的套接字缓冲区大小和套接字连接超时,以避免数据损坏和碎片。当通过具有异步读/写操作的套接字发送数据时,要发送的信息的长度将以数据包为单位进行分段。数据包大小有一个默认值,但它没有涵盖连接的上传/下载速度变化的事实。为了避免数据损坏和最佳的连接下载/上传速度,必须根据连接速度设置数据包大小。在上述示例中,我还展示了如何计算与连接速度相关的超时。上传/下载的数据包大小可以分别使用
socket.ReceiveBufferSize = ... / socket.SendBufferSize = ...
设置。有关所用方程和原理的更多信息,请查看:
https://www. baeldung.com/cs/calculate-internet-speed-ping
https://docs.oracle.com/cd/E36784_01/html/E37476/gnkor.html#: ~:text=您%20可以%20计算%20%20正确的%20%20连接%20延迟的值%20。
EDITED
The best approach for this problem found by me, after a long analysis was the following:
First, you need to set the buffer size in order to receive data from the server/client.
Second, you need to find the upload/download speed for that connection.
Third, you need to calculate how many seconds should the connection timeout last in accordance with the size of package to be sent or received.
Set the buffer size
The buffer size can be set in two ways, arbitrary or objectively. If the information to be received is text based, it is not large and it does not require character comparison, than an arbitrary pre-set buffer size is optimal. If the information to be received needs to be processed character by character, and/or large, an objective buffer size is optimal choice
The buffers that are used to receive the payload length are using an arbitrary buffer size. The length of the payload to be sent is converted to string and then the string is converted in a UTF-8 encoded byte array. The received length of the payload is then converted back into a string format and then converted to an integer in order to set the length of the buffer that will receive the payload. The length is converted to string, then to
int
and then tobyte[]
, in order to avoid data corruption due to the fact that the information related to the payload length will not be sent into a buffer that has the same size as the information. When the receiver will convert thebyte[]
content to a string and then to anint
, the extra characters will be removed and the information will remain the same.Get the upload/download speed of the connection and calculate the Socket receive and send buffer size
The aforementioned method is ensuring that the data transmitted between the client buffer and server buffer uses an appropriate socket buffer size and socket connection timeout in order to avoid data corruption and fragmentation. When the data is sent through a socket with an async Read/Write operation, the length of the information to be sent will be segmented in packets. The packet size has a default value but it does not cover the fact that the upload/download speed of the connection is varying. In order to avoid data corruption and an optimal download/upload speed of the connection, the packet size must be set in accordance with the speed of the connection. In the aforementioned example I also showcased also the how to calculate the timeout in relation with the connection speed. The packet size for upload/download can be set by using the
socket.ReceiveBufferSize = ... / socket.SendBufferSize = ...
respectively.For more information related to the equations and principles used check:
https://www.baeldung.com/cs/calculate-internet-speed-ping
https://docs.oracle.com/cd/E36784_01/html/E37476/gnkor.html#:~:text=You%20can%20calculate%20the%20correct,value%20of%20the%20connection%20latency.