5

I am writing an app that will require to make hundreds of socket connections over tcp to read/write data.

I have come across this code snippet here and I'm wondering how I can make this more robust.

This is currently how I am calling the code:

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);
}
  1. Is there anything wrong with the above, and how can it be optimized such that it runs concurrently?

    In the code snippet, the buffer size is set to 1000. Just as a simple illustration, if I were to attempt to print out only the bytes received, and not the remaining 0x00s, I have to do something like this:

    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));
    }
    
  2. Is there a more efficient way of doing this? Previously, I would iterate the whole buffer and print out the data, but that will give me a whole bunch of trailing 0x00s as my protocol is anywhere between 60 to 70 bytes long.

4

1 回答 1

18

我正在编写一个应用程序,它需要通过 tcp 建立数百个套接字连接来读取/写入数据。

为此,您不需要“高性能套接字”。使用常规性能套接字,代码要简单得多。

对于初学者,不要使用您发布的链接中的自定义等待项。它们对某些人来说非常好(并且完全“健壮”),但你不需要它们,没有它们你的代码会更简单。

  1. 以上是不是有什么问题,能否进一步优化?

是的。您不应该混合使用阻塞 ( 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);
  ...
}

标准 TAP-over-APM 包装器ConnectTaskAsync在哪里:

public static Task ConnectTaskAsync(this Socket socket, EndPoint endpoint)
{
  return TaskFactory.FromAsync(socket.BeginConnect, socket.EndConnect, endpoint, null);
}

正如 Marc Gravell 指出的那样,这段代码(和您的原始代码)一次连接一个套接字。您可以使用Task.WhenAll它们同时连接它们。

2)有没有更有效的方法来做到这一点?

ReceiveTaskAsync首先,您应该定义一个与上述类似的 TAP-over-APM包装器。在处理二进制数据时,我还喜欢在字节数组上有一个扩展方法用于转储:

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 字节 little-endian 长度前缀)。

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);
  }
}
于 2013-06-13T18:05:27.557 回答