Tomasz Szymański
Tomasz Szymański

Reputation: 1453

Proper way to receive network data in loop

I am writing a C# client application which will connect to the server written in python. My question is about receiving data in loop. The application structure is all about client asks server -> server responds to client. Everything works fine when the message is lower that actual buffer size (set in server). For example: server side buffer: 1024, client buffer size: 256, data length < 1kb. I run my application with following code:

int datacounter = 0;
byte[] recived = new byte[256];
StringBuilder stb = new StringBuilder();
serverStream.ReadTimeout = 1500;
try
{
    while ((datacounter = serverStream.Read(recived, 0, 256)) > 0)
    {
        Console.WriteLine("RECIVED: " + datacounter.ToString());
        stb.append(System.Text.Encoding.UTF8.GetString(recived, 0, datacounter));
    }
}
catch { Console.WriteLine("Timeout!"); }

Then the application receives data in 4 loops (256 bytes each):

RECIVED: 256
RECIVED: 256
RECIVED: 256
RECIVED: 96

And then the timeout ticks, that ends the transmission and pass the complete data to later analysis (from stb object). I don't think using timeout is proper, but i don't know any other way to do this. However, this way it works. Here we go with example, that does not: server side buffer: 1024, client side buffer: 256, data length ~ 8kbytes (python side sends data in loop).

RECIVED: 256
RECIVED: 256
RECIVED: 256
RECIVED: 256

Then the timeout ticks (and obviosly the data is incomplete - got 1kb of 8kb). Sometimes the loop even ends after 1 run with 28 recived bytes and thats all before timeout. Python says that the data has been send properly. Here's the way i create the socket and serverStream object:

TcpClient clientSocket = new TcpClient();
clientSocket.Connect("x.y.z.x", 1234);
NetworkStream serverStream = clientSocket.GetStream();

Its not the TcpClient fault. Tried the same with clear sockets, created like:

new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)

However that works similar. Is there a way, to make my loop work without timeout, receiving all data? I would like to keep the socket synchronous if possible.

Upvotes: 0

Views: 3756

Answers (3)

michael aboah
michael aboah

Reputation: 11

For anyone else who needs help with this

Just to add to Chi_Town_Don's answer, make sure you use stb.ToString() outside of the loop. And I've found that nothing will print out unless the loop breaks out. To do that if(!serverStream.DataAvailable()){break;} works wonders. That way you don't need to pass in the packet size or some other convoluted condition.

Upvotes: 0

Chi_Town_Don
Chi_Town_Don

Reputation: 111

I don't think there's anything wrong with your receive code functionally. I put together a test, and the receiver gets as much as you can send it (eg. 8 MBs), as long as you keep sending without 1.5 seconds pause, before timing out.

So it looks like your server is simply not sending "fast" enough.

To answer your question, timing is not the typical way of knowing when you have received a full message. One common, simple way of determining when a full message is received is to prefix the length of the full message on the sending side (eg. 4-byte int). Then on the receive side, first read 4 bytes, decode to the length, and then read that many more bytes.

You could also consider appending a message termination string, such as Environment.NewLine, to the end of your message. This has the advantage that you could call StreamReader.ReadLine(), which will block until the full message is received. This only works if the termination can NOT be included in the message itself.

If you can't alter the server protocol, is there any other way of knowing you have received a full message? (eg. checking for a NewLine at the end of the message, an XML end tag, or some other pattern.) If not, perhaps you could wait for the server to disconnect, otherwise it looks like you would be forced to find the right timing balance.

I am including the test code below in case you want to play around with it.

Server/Sending Side:

        IPAddress localAddr = IPAddress.Parse("127.0.0.1");
        TcpListener server = new TcpListener(localAddr, 13579);
        server.Start();
        TcpClient clientSocket = server.AcceptTcpClient();
        NetworkStream stream = clientSocket.GetStream();

        int bytesSent = 0;
        int bytesToSend = 1 << 25;
        int bufferSize = 1024;
        string testMessage = new string('X', bufferSize);
        byte[] buffer = UTF8Encoding.UTF8.GetBytes(testMessage);

        while (bytesSent < bytesToSend)
        {
            int byteToSendThisRound = Math.Min(bufferSize, bytesToSend - bytesSent);
            stream.Write(buffer, 0, byteToSendThisRound);
            bytesSent += byteToSendThisRound;
        }

Client/Receiving Side:

        TcpClient client = new TcpClient("127.0.0.1", 13579);
        NetworkStream serverStream = client.GetStream();

        int totalBytesReceived = 0;
        int datacounter = 0;
        byte[] recived = new byte[256];
        StringBuilder stb = new StringBuilder();
        serverStream.ReadTimeout = 1500;
        try
        {
            while ((datacounter = serverStream.Read(recived, 0, 256)) > 0)
            {
                totalBytesReceived += 256;
                Console.WriteLine("RECIVED: {0}, {1}", datacounter, totalBytesReceived);
                stb.Append(System.Text.Encoding.UTF8.GetString(recived, 0, datacounter));
            }
        }
        catch { Console.WriteLine("Timeout!"); }

Upvotes: 1

CharlieBrown
CharlieBrown

Reputation: 4163

Why don't you dump the exception that makes your code go into the catch branch and find out? :)

catch (Exception ex) { Console.WriteLine("Timeout because of... " + ex.Message); }

--EDIT Sorry I didn't see the timout. The question you're asking is if there's a way to do it without timeout. Yes, don't set any timeout and check if received number of bytes is smaller than the buffer size.

That is:

while ((datacounter = serverStream.Read(recived, 0, 256)) > 0)
    {
        Console.WriteLine("RECIVED: " + datacounter.ToString());
        stb.append(System.Text.Encoding.UTF8.GetString(recived, 0, datacounter));
        if(datacounter < 256) //you're good to go
           break; 
    }

Upvotes: 0

Related Questions