Transfering items over NetworkStream causes some data to get clobbered
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 = "";
开发者_Go百科 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);
}
}
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.
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.
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)
A better solution would probably be to serialize all your data (into JSON for example) and pass everything over the network stream.
精彩评论