【问题标题】:Sending and receiving over network using TcpClient (socket or Stream) fails after X interactionsX 交互后使用 TcpClient(套接字或流)通过网络发送和接收失败
【发布时间】:2014-03-05 15:50:25
【问题描述】:

我有几个非常简单的套接字程序来发送和接收以长度为前缀的字符串。以下是核心例程:

// Send a string, length-prefixed, to a socket.
public static void SendStringToSocket(Socket socket, string str)
{
    byte[] dataBuffer = Encoding.UTF8.GetBytes(str);
    Console.WriteLine("SendStringToSocket: " + dataBuffer.Length);
    byte[] lengthBuffer = LengthToNetworkBytes(dataBuffer.Length);
    byte[] overallBuffer = new byte[dataBuffer.Length + lengthBuffer.Length];

    for (int b = 0; b < lengthBuffer.Length; ++b)
        overallBuffer[b] = lengthBuffer[b];

    for (int d = 0; d < dataBuffer.Length; ++d)
        overallBuffer[d + lengthBuffer.Length] = dataBuffer[d];

    Console.WriteLine("SendStringToSocket: Sending " + overallBuffer.Length);
    socket.Send(overallBuffer);
    Console.WriteLine("SendStringToSocket: Complete");
}

// Read a length-prefixed string from a socket.
public static string ReadStringFromSocket(Socket socket)
{
    byte[] buffer = new byte[8192];
    bool bReadLength = false;
    int nStrLen = -1;
    MemoryStream memStream = new MemoryStream(buffer.Length);
    while (true)
    {
        Console.WriteLine("ReadStringFromSocket: Reading...");
        int nRead = socket.Receive(buffer, SocketFlags.None);
        if (nRead == 0)
            break;

        int nOffset = 0;
        if (!bReadLength)
        {
            byte[] lenBuffer = new byte[sizeof(int)];
            if (nRead < lenBuffer.Length)
                throw new RuntimeException(ErrorCode.NetworkError, "Reading string length failed.");

            for (int b = 0; b < lenBuffer.Length; ++b)
                lenBuffer[b] = buffer[b];

            nStrLen = NetworkBytesToLength(lenBuffer);
            Console.WriteLine("ReadStringFromSocket: Length: " + nStrLen);
            if (nStrLen < 0)
                throw new RuntimeException(ErrorCode.NetworkError, "Invalid string length: " + nStrLen + " - be sure to convert from host to network");

            bReadLength = true;
            nOffset = lenBuffer.Length;

            if (nStrLen == 0)
            {
                Console.WriteLine("ReadStringFromSocket: Complete with no length");
                if (nRead != lenBuffer.Length)
                    throw new RuntimeException(ErrorCode.NetworkError, "Zero length string has more data sent than expected.");
                return "";
            }
        }

        memStream.Write(buffer, nOffset, nRead - nOffset);

        if (memStream.Length > nStrLen)
            throw new RuntimeException(ErrorCode.NetworkError, "More string data sent than expected.");

        if (memStream.Length == nStrLen)
            break;
    }

    Console.WriteLine("ReadStringFromSocket: Complete with " + memStream.Length + " bytes");
    return Encoding.UTF8.GetString(memStream.GetBuffer(), 0, (int)memStream.Length);
}

我认为这些例程体现了这类事情的最佳实践。

这是客户端应用程序:

static void Main(string[] args)
{
    if (args.Length != 2)
    {
        Console.WriteLine("StringSocketClient <server address> <server port>");
        return;
    }

    TcpClient client = new TcpClient(args[0], int.Parse(args[1]));
    Socket socket = client.Client;

    while (true)
    {
        Console.Write("> ");

        string strInput = Console.ReadLine();

        Console.WriteLine("Sending...");
        Utils.SendStringToSocket(socket, strInput);

        Console.WriteLine("Receiving...");
        string strResponse = Utils.ReadStringFromSocket(socket);

        Console.WriteLine("Response:");
        Console.WriteLine(strResponse);
        Console.WriteLine();
    }
}

这是服务器应用程序:

static void Main(string[] args)
{
    if (args.Length != 1)
    {
        Console.WriteLine("StringSocketEchoServer <TCP port to serve>");
        return;
    }

    TcpListener listener = new TcpListener(IPAddress.Any, int.Parse(args[0]));
    listener.Start();
    while (true)
    {
        TcpClient client = listener.AcceptTcpClient();
        new Thread(ProcessConnection).Start(client);
    }
}

static void ProcessConnection(object state)
{
    TcpClient client = (TcpClient)state;
    Socket socket = client.Client;

    try
    {
        while (true)
        {
            Console.WriteLine("Reading from network...");
            string str = Utils.ReadStringFromSocket(socket);
            Console.WriteLine("Received: " + str);

            Console.WriteLine("Sending back...");
            Utils.SendStringToSocket(socket, str);
        }
    }
    catch (Exception exp)
    {
        Console.WriteLine("EXCEPTION!\r\n" + exp);
        try
        {
            client.Close();
            client = null;
        }
        catch { }
    }
}

真正直截了当、简单的东西,切入问题的根源。

我发现如果我进行五次交互,客户端会挂起以尝试接收响应,而服务器正在尝试接收请求。这是控制台输出:

客户:

foobar 发送... 发送字符串到套接字:6 SendStringToSocket: 发送 10 SendStringToSocket:完成 接收... ReadStringFromSocket:正在读取... ReadStringFromSocket:长度:6 ReadStringFromSocket:6 个字节完成 回复: 富吧

> foobar
Sending...
SendStringToSocket: 6
SendStringToSocket: Sending 10
SendStringToSocket: Complete
Receiving...
ReadStringFromSocket: Reading...
ReadStringFromSocket: Length: 6
ReadStringFromSocket: Complete with 6 bytes
Response:
foobar

> foobar
Sending...
SendStringToSocket: 6
SendStringToSocket: Sending 10
SendStringToSocket: Complete
Receiving...
ReadStringFromSocket: Reading...
ReadStringFromSocket: Length: 6
ReadStringFromSocket: Complete with 6 bytes
Response:
foobar

> foobar
Sending...
SendStringToSocket: 6
SendStringToSocket: Sending 10
SendStringToSocket: Complete
Receiving...
ReadStringFromSocket: Reading...
ReadStringFromSocket: Length: 6
ReadStringFromSocket: Complete with 6 bytes
Response:
foobar

> foobar
Sending...
SendStringToSocket: 6
SendStringToSocket: Sending 10
SendStringToSocket: Complete
Receiving...
ReadStringFromSocket: Reading...
^CPress any key to continue . . .

服务器:

Reading from network...
ReadStringFromSocket: Reading...
Reading from network...
ReadStringFromSocket: Reading...
ReadStringFromSocket: Length: 6
ReadStringFromSocket: Complete with 6 bytes
Received: foobar
Sending back...
SendStringToSocket: 6
SendStringToSocket: Sending 10
SendStringToSocket: Complete
Reading from network...
ReadStringFromSocket: Reading...
ReadStringFromSocket: Length: 6
ReadStringFromSocket: Complete with 6 bytes
Received: foobar
Sending back...
SendStringToSocket: 6
SendStringToSocket: Sending 10
SendStringToSocket: Complete
Reading from network...
ReadStringFromSocket: Reading...
ReadStringFromSocket: Length: 6
ReadStringFromSocket: Complete with 6 bytes
Received: foobar
Sending back...
SendStringToSocket: 6
SendStringToSocket: Sending 10
SendStringToSocket: Complete
Reading from network...
ReadStringFromSocket: Reading...
ReadStringFromSocket: Length: 6
ReadStringFromSocket: Complete with 6 bytes
Received: foobar
Sending back...
SendStringToSocket: 6
SendStringToSocket: Sending 10
SendStringToSocket: Complete
Reading from network...
ReadStringFromSocket: Reading...

当天早些时候,是三个互动,然后是挂起。

我认为代码是正确的。它工作X次。但随后它挂起。

有什么想法吗?

【问题讨论】:

  • 对混乱的客户端:/服务器:输出感到抱歉。让我知道您是否需要在上面倾倒的内容之外进行澄清。
  • 从客户端:/服务器:输出可能不清楚,但在一天结束时,客户端认为发送正常并继续尝试接收服务器响应,但是服务器只是停留在接收请求,就好像客户端发送的数据被卡住或被丢弃一样。
  • 客户在我的办公室。服务器位于 AWS EC2 上。我尝试使用不同的 AWS EC2 禁用我的本地安全软件,禁用服务器上的 Windows 防火墙。那奇怪的五次......
  • 您有几个对象实现了IDisposable,但它们不在using 块中。至少您的MemoryStreamTcpClientSocket 实例。未实现 using 块的一个常见症状是操作“在一段时间后”失败。
  • 感谢您的浏览。我不认为测试应用程序需要使用块......我不明白添加使用块会如何改变我面临的问题......这不是资源泄漏,而是网络问题。另一个线索:Wireshark 在挂起的情况下说 TCP Retransmission 很多次。

标签: c# sockets tcp client-server tcpclient


【解决方案1】:

尝试使用 Stream 类来写和接收数据,它可能比 Socket 类更可靠。示例:

TcpClient conn = new TcpClient();
Stream stream;                    // read and write data to stream
try
{
   string msg = "this is a test";
   conn.Connect("localhost", 50000);
   stream = conn.GetStream();
   byte[] by = Encoding.UTF8.GetBytes(msg.ToCharArray(), 0, msg.Length);
   await stream.WriteAsync(by, 0, by.Length);     // write bytes to buffer
   stream.Flush();                                // send bytes, clear buffer

   by = new byte[2048];  // new byte array to store received data

   //wait until buffer has data, then returns buffer length
   int bytesAvailable = await stream.ReadAsync(by, 0, 2048);
   msg = Encoding.UTF8.GetString(by, 0, bytesAvailable);
} catch (Exception e) { //output exception
}

这就是我为 Win 8 ui 应用程序所做的。正因为如此,您可能会摆脱流的常规 Read 和 Write 方法,在调用之前不需要 await。即

stream.Write(by, 0, by.Length);
stream.Read(by, 0, 2048);

顺便说一句,如果您曾经在 Win 8 商店应用程序中执行此操作,则需要一个 Stream 对象用于读取和另一个用于写入。

【讨论】:

  • 感谢您的建议。不幸的是,使用与我最初发布的非常相似的代码,用流代码替换套接字代码,我得到了相同的行为。我可以进行五次相同的交互,然后在第六次它挂起。控制台指示客户端已完成发送并正在尝试接收,但服务器只是坐在那里尝试接收。关于下一步尝试什么的任何想法?
  • FWIW - 我尝试使用 Begin / End 异步 I/O,使用流而不是套接字,所以它是完全不同的代码,并且在五次成功后仍然挂起。 TCP 级别的东西......我真的在这个问题上撕毁了我的头发!
  • FWIW - 我将症状的简要描述发布到服务器故障:serverfault.com/questions/574216/…
  • 在某些情况下客户端或服务器未成功发送消息。你调试过检查吗?此外,也许这是一个错字,但您的服务器在实际继续之前是否真的重复了输出的前两行?另外,我建议在您的消息中添加一个标识符,将消息硬编码到消息的前面或后面。创建一个应用程序协议,以便客户端知道它正在与正确的服务器通信,反之亦然。
  • 没有引发异常。客户端认为它已经发送了请求,但服务器没有给出请求到达的指示。我能够在(1、2、3 等)中发送不同的消息,并且服务器得到了正确的内容并正确地回显了它。我们现在正在处理第三个请求。
【解决方案2】:

我能够验证客户端和服务器 - 将代码剥离回上面发布的更简单版本 - 当东海岸客户端访问西海岸服务器时,可以完美地进行大量交互。一定是我本地网络中的东西。不是软件问题。感谢大家的见解。

【讨论】:

  • 是的,事实证明我们的 Intranet 不允许传出流量进入服务器端口。不知道为什么它会工作 X 次,但是一旦他们玩弄了自己的比特,一切都很顺利。再次感谢您的帮助。
猜你喜欢
  • 2011-04-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-02-12
  • 2013-09-11
  • 1970-01-01
  • 2016-04-14
相关资源
最近更新 更多