Why does SSL Stream use a byte array of 2048? - c#

After reading the TCP SSL example from MSDN, they use a byte array to read data into the stream. Is there a reason the array is limited to 2048? What if TCP sends a longer array than 2048? Also, how does the buffer.Length property continue to read the stream, as it is changing. It doesn't make complete sense to me. Why read the length of the buffer, wouldn't you want to read the length of the incremental bytes coming into the stream?
static string ReadMessage(SslStream sslStream)
{
// Read the message sent by the client.
// The client signals the end of the message using the
// "<EOF>" marker.
byte [] buffer = new byte[2048];
StringBuilder messageData = new StringBuilder();
int bytes = -1;
do
{
// Read the client's test message.
bytes = sslStream.Read(buffer, 0, buffer.Length);
// Use Decoder class to convert from bytes to UTF8
// in case a character spans two buffers.
Decoder decoder = Encoding.UTF8.GetDecoder();
char[] chars = new char[decoder.GetCharCount(buffer,0,bytes)];
decoder.GetChars(buffer, 0, bytes, chars,0);
messageData.Append (chars);
// Check for EOF or an empty message.
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes !=0);
return messageData.ToString();
}

When reading from any kind of Stream (not just SslStream) just because you asked for X bytes they are allowed to give you anywhere between 1 and X bytes back. that is what the bytes returned from Read is for, to see how many you read.
If you give Read a giant buffer it is only going to fill in the first few 1000ish bytes, giving it a bigger buffer is a waste of space. If you need more data than the buffer is large you make multiple requests for data.
A important thing to remember when dealing with network streams, one write on the sender does not mean one read on the receiver. Writes can be combined together or split apart at arbitrary points, that is why it is to have some form of Message Framing to be able to tell when you have "all the data". In the example you posted they use the marker <EOF> to signal "end of message".

Related

How to set a correct buffer size for TCP message responses

This is how I currently send data to an external TCP server
byte[] data = new byte[0] /* the data to send */;
TcpClient client = new TcpClient("127.0.0.1", 3000); // connect to the tcp server
NetworkStream stream = client.GetStream();
await stream.WriteAsync(data, 0, data.Length);
data = new byte[256]; // set the buffer size
int responseBytes = await stream.ReadAsync(data, 0, data.Length); // store the response to the buffer
string responseData = System.Text.Encoding.ASCII.GetString(data, 0, responseBytes);
stream.Close();
client.Close();
For the response I have to setup the buffer size here new byte[256]. But what if the response is greater than this size? I can't determine the correct size because I'm just connecting to his external server, send a message to it and expect a response. Is there a way I can make this dynamic?
As a sidenote: I'm sending various HL7 messages to clinic servers and they will send back HL7 ACK messages as a response. This gives some information about HL7 ACK messages
https://healthstandards.com/blog/2007/02/01/ack-message-original-mode-acknowledgement/
An example ACK could be
MSH|^~&|CATH|StJohn|AcmeHIS|StJohn|20061019172719||ACK^O01|MSGID12349876|P|2.3
MSA|AA|MSGID12349876
For the response I have to setup the buffer size here new byte[256]. But what if the response is greater than this size?
Then you call stream.ReadAsync() and append your buffer (or the decoded string) to a larger buffer until you know you have received the entire message, which you need to do anyway: the Write() from one end of the socket does not need to correspond to one Read() on the other end. Multiple writes can be read in a single read, or the other way around.
So something like this:
data = new byte[256]; // set the buffer size
var builder = new StringBuilder();
do
{
int responseBytes = await stream.ReadAsync(data, 0, data.Length); // store the response to the buffer
string responseData = System.Text.Encoding.ASCII.GetString(data, 0, responseBytes);
builder.Append(responseData);
} while (responseBytes > 0)
Do note that this happens to work with ASCII, as it doesn't have multibyte characters. Were it UTF-8 or a similar encoding, the 256th byte could be the start of a character which continues into the next read, i.e. byte 1 (and perhaps 2) of the next read.
This code also assumes you want to keep reading until the connection is closed (then responseBytes = 0). If this protocol has a length prefix or message terminator, you have to handle those.
Usually you don't want to implement this low-level stuff yourself, aren't there libraries available that handle the HL7 protocol?

What is the correct method to read from a NetworkStream?

I'm developing a software that will constantly receive requests of various types and different lengths.
In particular, the length is the part that interest me because I don't know the length of the message that I will go to receive.
For the moment I wrote this code that goes to write the received bytes in a byte[] of a very large size, and then copies in a second byte[] only the length of the data.
//get the network stream
NetworkStream networkStream = tcpClient.GetStream();
//initialize an bytes array of size 5 MB
byte[] largeArray = new byte[5242880];
//write the bytes in the largeArray
//determines the length of the received message
int lenght = networkStream.Read(largeArray, 0, largeArray.Length);
//initialize an bytes array with the correct message length
byte[] messageArray = new byte[lenght];
//make a copy from the largeArray to the messageArray with the right lenght
Buffer.BlockCopy(largeArray, 0, messageArray, 0, messageArray.Length);
I wanted to know if this method can be corrected or are there better alternatives to receive messages of unknown lengths ?

IInputStream.ReadAsync() and DataReader.LoadAsync() only work for fixed length reads (UWP C#)

I'm communicating with a serial device and I want to be able to read data as it comes in the stream. The incoming data will not be of fixed length. However, the aforementioned functions only return a value when "count" number of bytes have been received. According to the documentation, it should return when any number of bytes have been received and load up "count" number of bytes into the intermediate buffer to read.
IInputStream stream = EventHandlerForDevice.Current.Device.InputStream;
var buffer = new Byte[100];
IBuffer bufferResult = await stream.ReadAsync(
buffer.AsBuffer(),
(uint)buffer.Length,
InputStreamOptions.Partial).AsTask().ConfigureAwait(false);
buffer = bufferResult.ToArray();
if (bufferResult.Length == 0)
{
LoggingServices.Instance.WriteLine<App>("something ", LogLevel.Info);
}
or
DataReaderObject.InputStreamOptions = InputStreamOptions.Partial;
bytesRead = await DataReaderObject.LoadAsync(ReadBufferLength);
Both of these only return when the buffer length number of bytes have been received. Otherwise it only works when I pass a count of 1 and handle the input on every byte.

How do you fill a Byte header with empty characters?

Im currently trying to add additional bytes to a byte array.
Im trying to send a header to a server that contains the computer name. However because the computer name could change for every machine im trying to create a byte array that is a specific length like 100 bytes.
Which means once i have my string header "rdmstreamĀ§" + Dns.GetHostName()" I need to add x amounts of bytes at the end or start as padding so the overall byte length = 100.
I was wondering if this was possible?
Below is an example of my code for having a set header length:
public static void SendMultiScreen(byte[] img)
{
try
{
//string command = ("rdmstreamĀ§" + Dns.GetHostName()); //This is what I want to add.
byte[] send = new byte[img.Length + 16]; //Create a new buffer to send to the server
byte[] header = Encoding.Unicode.GetBytes("rdmstrea"); //Get the bytes of the header
Buffer.BlockCopy(header, 0, send, 0, header.Length); //Copy the header to the main buffer
fps = 800;
Buffer.BlockCopy(img, 0, send, header.Length, img.Length); //Copy the image to the main buffer
_clientSocket.Send(send, 0, send.Length, SocketFlags.None); //Send the image to the server
}
As you can see as long as the message is only 8 Characters long this works fine. However I want the characters in the message to be variable.
I don't have much knowledge on bytes if im honest so any additional help would be much appreciated.
Thankyou in advance.
One can argue about it if padding is the right way to go, but you could pad the name of your host
string hostName = "OhWhatEver".PadRight(100)
then use this as input for your GetBytes call.
Edit:
If you can't live with the spaces use that:
byte[] header = new byte[100];
byte[] hostname = System.Text.Encoding.Unicode.GetBytes("rdmstreamĀ§" + System.Net.Dns.GetHostName());
Array.Copy(hostname, header, hostname.Length);
If your concern is packet fragmentation: Socket has overloads to send a list of buffer segments in a single operation. That means you can do something like:
var segments = new List<ArraySegment<byte>>();
segments.Add(header);
segments.Add(img);
Note that it is not necessary for the header to be the full array; you can send a part of an array, which allows you to re-use the same buffer; for example:
byte[] buffer = new byte[MaxLength];
var segments = new List<ArraySegment<byte>>();
segments.Add(default); // placeholder
segments.Add(img);
foreach(...) {
string val = ...
int len = encoding.GetBytes(val, 0, val.Length, buffer, 0);
segments[0] = new ArraySegment<byte>(buffer, 0, len);
thisSocket.Send(segments);
}
However! to do this usually requires some kind of framing on the header - either a sentinel value (perhaps a trailing CR/LF/CRLF), or a prefix of the number of bytes that are the string - len here.
If that really isn't possible... just loop over the unused part of the array and set it to what you want, or use Array.Clear if zero is OK.

String concat failed in tcp thread

This is my code
while (true)
{
byte[] btServerReceive = new byte[256];
TcpClient tcpclient = tcp.AcceptTcpClient();
NetworkStream ns = tcpclient.GetStream();
int intReceiveLength = ns.Read(btServerReceive, 0, btServerReceive.Length);
string recv = Encoding.GetEncoding("GB2312").GetString(btServerReceive) + "_01";
tcpclient.Close();
MessageBox.Show(recv.ToString());
// Create a new thread to handle the data associate with recv
Thread sendUpThread = new Thread(new ParameterizedThreadStart(SendThread));
sendUpThread.Start(recv);
}
The string recv only get the value of Encoding.GetEncoding("GB2312").GetString(btServerReceive), can't add "_01".
You can add the "_01". It's just that you don't notice, because the string is being displayed in a context where the embedded nulls prevent you from seeing it.
I.e. you've passed a 256-byte array to the GetString() method, where only the first N bytes have actually been modified and the rest still have their initial value of 0. So GetString() interprets those as '\0' characters and faithfully includes those in the returned string.
At a minimum, you would have to do something like this:
string recv = Encoding.GetEncoding("GB2312")
.GetString(btServerReceive, 0, intReceiveLength) + "_01";
I.e. take into account the number of bytes you actually received and only decode that many.
Now that said, even that doesn't solve your problem entirely. The above will probably work most of the time, but TCP could return to you only part of a whole string that was sent. Since you're using UTF8 encoding, some characters are represented by more than one byte and so of course the last byte in the data received could be only part of a character.
To fix this, you need to have some way to know when you're done reading a string (e.g. send null-terminated strings, send length-prefixed strings, fixed-length strings, etc.), or maintain a single instance of a Decoder which you use to decode the text as it comes in (Decoder maintains an internal buffer of incompletely decoded data so that on subsequent calls to decode text, it can correctly handle the partial character).

Categories