views:

56

answers:

4

I am sending a filename(string), filesize(int), and the file(byte[]). What is happening is that in certain cases, depending on how quickly the data gets processed on the server side, the NetworkStream has read the data that I dont need yet.

Example: I do a .Read to get the filename, I will get the data for filename,filesize and the files raw data. I assume this happens because the server just does a .Write and writes the data to the stream when the first .Read hasnt executed yet. This ends up clobbering my filesize .Read. Now when I do a .Read for my filesize I am showing a HUGE number and when I go to read for the file itself and allocation a new byte[] based on the read files size i get an OutOfMemory exception.

How do I synchronize the Read properly? The examples I am finding on the net do it the way I do.

Some code:

   private void ReadandSaveFileFromServer(TcpClient clientATF, NetworkStream currentStream, string locationToSave)
    {
        int fileSize = 0;
        string fileName = "";
        int readPos = 0;
        int bytesRead = -1;

        fileName = ReadStringFromServer(clientATF, currentStream);

        fileSize = ReadIntFromServer(clientATF, currentStream);


        byte[] fileSent = new byte[fileSize];

        while (bytesRead != 0)
        {
            if (currentStream.CanRead && clientATF.Connected)
            {

                bytesRead = currentStream.Read(fileSent, readPos, fileSent.Length);

                readPos += bytesRead;
                if (readPos == bytesRead)
                {
                    break;
                 }

            }
            else
            {
                WriteToConsole("Log Transfer Failed");
                break;
            }
        }
        WriteToConsole("Log Recieved");

        File.WriteAllBytes(locationToSave + "\\" + fileName, fileSent);


    }


 private string ReadStringFromServer(TcpClient clientATF, NetworkStream currentStream)
    {
        int i = -1;
        string builtString = "";
        byte[] stringFromClient = new byte[256];



            if (clientATF.Connected && currentStream.CanRead)
            {

                i = currentStream.Read(stringFromClient, 0, stringFromClient.Length);
                builtString = System.Text.Encoding.ASCII.GetString(stringFromClient, 0, i);

            }

            else
            {
                return "Connection Error";
            }



        return builtString;

    }

    private int ReadIntFromServer(TcpClient clientATF, NetworkStream currentStream)
    {
        int i = -1 ;
        int builtInteger = 0;
        byte[] integerFromClient = new byte[256];
        int offset = 0;


            if (clientATF.Connected && currentStream.CanRead)
            {

                i = currentStream.Read(integerFromClient, offset, integerFromClient.Length);

                builtInteger = BitConverter.ToInt32(integerFromClient, 0);

            }

            else
            {
                return -1;
            }



        return builtInteger;
    }

I have tried using a offset...with no luch. Your help is appreciated.

I started another question but it relates to something else.

Thanks in advance Sean

EDIT: Here is my send string code:

  private void SendToClient( TcpClient clientATF,  NetworkStream currentStream, string messageToSend)
    {
        byte[] messageAsByteArray = new byte[256];

        messageAsByteArray = Encoding.ASCII.GetBytes(messageToSend);

        if (clientATF.Connected && currentStream.CanWrite)
        {
            //send the string to the client

                currentStream.Write(messageAsByteArray, 0, messageAsByteArray.Length);

        }

    }
+1  A: 

TCP/IP is streaming, not datagrams, so the behavior you want is just not there. You can work around it by having the stream contain enough information to be parsed out.

In other words, you could use delimiters, such as a CR/LF after a line of text, or you could give the length of an upcoming piece of data. You can also use fixed-size fields, where appropriate.

Steven Sudit
+1  A: 

you can not rely on how many bytes are read from a stream at a time, there's a bug in your ReadStringFromServer method, assuming the string is of a fixed length (256).

instead of:

 i = currentStream.Read(stringFromClient, 0, stringFromClient.Length);
 builtString = System.Text.Encoding.ASCII.GetString(stringFromClient, 0, i);

try:

 do
 {
    i = currentStream.Read(stringFromClient, 0, 256 - builtString.Length);
    builtString+=(System.Text.Encoding.ASCII.GetString(stringFromClient, 0, i));
 } while(builtString.Length < 256)
BrokenGlass
I have added my send string code above
Sean P
@Sean: It's not correct. First, it'll fail silently when the string turns into more than 256 bytes. Second, you allocate a buffer, but then throw it away. Third, you do not pass the length in any way, so there's no method by which the reader can determine it.
Steven Sudit
The above code did not work.
Sean P
@Steven, Where is that at?
Sean P
@Sean: I'm talking about `SendToClient`. You intialize `messageAsByteArray`, and then immediately overwrite it.
Steven Sudit
+1  A: 

A better solution would probably be to serialize all your data (into JSON for example) and pass everything over the network stream.

Danra
That's not a terrible idea, although it's probably overkill.
Steven Sudit
+1  A: 

Read() the way you're calling it will pull 256 bytes; that's what stringFromClient.Length was set up to be.

You have two options for accurately chopping up a stream of data, both of which involve knowing or creating some way of figuring out the border between data. They are delimited-stream, and fixed-length stream.

For a delimited format, you choose a character that you will not use as a valid part of the filename or size (a pipe, a space, a tab character, newline, etc) and insert one in between the filename and size, and between size and file contents. Then, read one byte at a time into the array until you hit a delimiter. The bytes you've read so far except the delimiter are your data; extract into a useable form and return. This generally makes streams shorter, but requires one possible character to never be used.

For a fixed-size format, determine a number of bytes that any reasonable value for the data would not exceed. Filenames, for instance, cannot be more than 256 chars (and more likely no more than about 50; some older/simpler OSes still limit to 8). File sizes cannot be more than 2^64 bytes in any Windows environment, and that number can be expressed in 4 bytes of raw numeric data, or 20 characters of a string. So, whatever limits you choose, pad the data using an appropriate buffer; for raw numbers, cast to an Int64 and chop it into bytes, while for strings, pad with spaces. Then, you know the first X bytes exactly will be the filename, the next Y bytes exactly will be the file size, and anything after that is content. This makes the stream larger, but the contents can be anything as there are no special or reserved byte values.

KeithS
That sounds like a good idea.. I will try that!
Sean P
How do i add a space to a byte []???
Sean P
@Sean: You might want to pad with ascii 0 here.
Steven Sudit
You can use number 32 (the ASCII value of a space character). a zero byte value is "better" as it is more obvious that it is padding, but some file frameworks read a zero code as an EOF, which can interfere with the basic workings of the file transmission.
KeithS
A zero is more commonly end-of-string, as in C/C++. The tradition EOF is ASCII 26, Ctrl+Z. My concern with using spaces here is that they're legal in filenames.
Steven Sudit