我正在编写一个应用程序,需要通过tcp建立数百个套接字连接来读取/写入数据。
我在这里看到了这段代码片段,我想知道如何使它更健壮。
这是我当前调用代码的方式:
foreach (var ip in listofIps)
{
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(ip), 4001);
Socket client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
client.Connect(remoteEP);
await ReadAsync(client);
}
上面有什么问题吗,如何优化使其并发运行?
在代码片段中,缓冲区大小设置为1000。作为一个简单的示例,如果我试图只输出接收到的字节,而不输出剩余的0x00s,我必须这样做:
while (true) { await s.ReceiveAsync(awaitable); int bytesRead = args.BytesTransferred; if (bytesRead <= 0) break; var hex = new StringBuilder(bytesRead * 2); var msg = new byte[bytesRead]; for (int i = 0; i < bytesRead; i++) msg[i] = args.Buffer[i]; foreach (byte b in msg) hex.AppendFormat("{0:x2} ", b); AppendLog(string.Format("RX: {0}", hex)); }
有更有效的方法吗?以前,我会迭代整个缓冲区并打印出数据,但这会给我一大堆尾随0x00,因为我的协议长度在60到70字节之间。
我正在编写一个应用程序,需要通过tcp建立数百个套接字连接来读取/写入数据。
你不需要"高性能套接字"。使用常规性能的套接字代码要简单得多。
对于初学者来说,不要使用你发布的链接中的自定义可等待对象。它们对某些人来说是完美的(并且完全"健壮"),但是你不需要它们,没有它们你的代码会更简单。
- 以上是否有问题,是否可以进一步优化?
是的。您不应该混合阻塞(Connect
)和异步(ReadAsync
)代码。我建议这样写:
foreach (var ip in listofIps)
{
IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse(ip), 4001);
Socket client = new Socket(AddressFamily.InterNetwork,
SocketType.Stream, ProtocolType.Tcp);
await client.ConnectTaskAsync(remoteEP);
...
}
其中ConnectTaskAsync
是标准的TAP-over-APM封装器:
public static Task ConnectTaskAsync(this Socket socket, EndPoint endpoint)
{
return TaskFactory.FromAsync(socket.BeginConnect, socket.EndConnect, endpoint, null);
}
正如Marc Gravell所指出的,这段代码(以及您的原始代码)每次连接一个套接字。您可以使用Task.WhenAll
同时连接它们。
2)是否有更有效的方法来做这件事?
首先,您应该定义一个与上面类似的TAP-over-APM ReceiveTaskAsync
包装器。在处理二进制数据时,我还喜欢在字节数组上有一个用于转储的扩展方法:
public string DumpHex(this ArraySegment<byte> data)
{
return string.Join(" ", data.Select(b => b.ToString("X2")));
}
你可以写这样的代码:
while (true)
{
int bytesRead = await socket.ReceiveTaskAsync(buffer);
if (bytesRead == 0) break;
var data = new ArraySegment<byte>(buffer, 0, bytesRead);
AppendLog("RX: " + data.HexDump());
...
}
如果你做了很多二进制操作,你可能会发现我的ArraySegments库很有帮助。
3)我应该在哪里以及如何包含逻辑来检查我的整个数据是否在单个读取
中到达
哦,比这更复杂。:)套接字是流抽象,而不是消息抽象。因此,如果您希望在协议中定义"消息",则需要包含长度前缀或分隔符字节,以便检测消息边界。然后需要编写解析消息的代码,记住从套接字读取的数据块可能只包含部分消息(因此必须对其进行缓冲)、完整消息、多个完整消息,还可能以部分消息结束(同样需要缓冲)。当接收到新块时,你还必须考虑你现有的缓冲区。
我在我的博客上有一个TCP/IP . net套接字常见问题解答,专门解决这个问题,并有一些示例代码使用我个人默认的消息帧偏好(4字节小端长度前缀)。
4)我应该如何包含一个writeasync方法,这样我就可以在读取过程中通过套接字发送数据。
这个出奇的棘手:
public static Task<int> SendTaskAsync(this Socket socket, byte[] buffer, int offset, int size, SocketFlags flags)
{
return Task<int>.Factory.FromAsync(socket.BeginSend, socket.EndSend, buffer, offset, size, flags, null);
}
public static Task WriteAsync(this Socket socket, byte[] buffer)
{
int bytesSent = 0;
while (bytesSent != buffer.Length)
{
bytesSent += await socket.SendTaskAsync(data, bytesSent, buffer.Length - bytesSent, SocketFlags.None);
}
}