Sending and receiving over network using TcpClient (socket or Stream) fails after X interactions

StackOverflow https://stackoverflow.com/questions/21641219

Вопрос

I have a couple very simple socket programs to do length-prefixed string sending and receiving. Here's the core routines:

// 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);
}

I think these routines embody the best practices for this sort of thing.

Here's the client app:

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();
    }
}

Here's the server app:

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 { }
    }
}

Real straightforward, simple stuff, cut down to the bone of the problem.

What I'm finding is that if I do five interactions, the client hangs trying to receive a response, and the server is trying to receive a request. Here's the console outputs:

Client:

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...
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 . . .

Server:

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...

Earlier in the day, it was three interactions then the hang.

I think the code's correct. It works X times. But then it hangs.

Any ideas?

Это было полезно?

Решение 2

I was able to verify that the client and server - with code stripped back to even simpler versions that posted above - interact perfectly fine for lots and lots of interactions when an East Coast client hits a West Coast server. Must be something in my local networking. Not a software problem. Thanks to everybody for your insights.

Другие советы

Try using the Stream class to write and receive data, it might be more reliable then the Socket class. example:

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
}

This is what I did for a Win 8 ui app. Because of that, you could probably get away with the regular Read and Write methods of stream, which do not need await before calling. i.e.

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

By the way, if you ever do this in Win 8 store app, you need a Stream object for reading and another for writing.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top