Can I read the stream from TcpClient in a better way? - c#

I have a system where in one end I have a device communicating with a module via RS-232.
The module is connected to a PC via TCP, and translates TCP messages to RS-232 and vice versa.
This is how I do it:
I read out every byte on the stream
build a string
compare the latter part of the string to a delimiter
then I stop reading the stream (and fire an event, though not shown here).
My current code for handling this is
string delimiter = "\r\n";
byte[] reply = new byte[1];
string replyString = string.Empty;
bool readOk = true;
int dl = delimiter.Length;
bool delimiterReached = false;
do
{
try
{
stream.Read(reply, 0, 1);
}
catch (Exception e)
{
readOk = false;
break;
}
replyString += Encoding.ASCII.GetString(reply, 0, reply.Length);
int rl = replyString.Length;
if (rl > dl)
{
string endString = replyString.Substring(rl-dl, dl);
if (endString.Equals(delimiter))
delimiterReached = true;
}
} while (!delimiterReached);
where stream is the TcpClient.GetStream()
I don't much care for the constant string building, so I was wondering if there is a better way of doing this?

Once the TCP connection is established, wrap the TcpClient stream in a StreamReader:
var reader = new StreamReader( stream, Encoding.ASCII );
Since your delimiter is \r\n, StreamReader.ReadLine will read a complete message:
var reply = reader.ReadLine();
Be careful to create the StreamReader once per TCP connection; it may read and buffer additional data from the underlying TCP stream for efficiency.

Related

C# Read from SslStream continuously (long connection, last for up to days) and Efficiently without infinite loop

I am completely new to C#, and need to encrypt the data sent and received between client and server, after googled it for two days, learnt the best way is to use SslStream, some answers I found give good examples but they all somehow assume we just need to read one message and then close the connection, which is totally not my case, I have to read whenever a user triggers his device to send a message through the persistent connection.
one example from Microsoft documentation:
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. <------ In my case,I don't have EOF
if (messageData.ToString().IndexOf("<EOF>") != -1)
{
break;
}
} while (bytes !=0);
return messageData.ToString();
}
and other answers actually tell me how to continuously read from a SslStream, but they are using infinite loop to do it, on the server side, there could be thousands clients connected to it, so the possible poor performance concerns me,like this one :
Read SslStream continuously in C# Web MVC 5 project
So I want to know if there is a better way to continuously read from a persistent SslStream connection.
I know with bare socket I can use SocketAsyncEventArgs to know when there is new data ready, I hope I could do this with SslStream, probably I misunderstand something, any ideas would be appreciated, thanks in advance.
Here's my shot at it. Instead of looping forever, I chose recursion. This method will return immediately but will fire an event when EOF is hit and continue to keep reading:
public static void ReadFromSSLStreamAsync(
SslStream sslStream,
Action<string> result,
Action<Exception> error,
StringBuilder stringBuilder = null)
{
const string EOFToken = "<EOF>";
stringBuilder = stringBuilder ?? new StringBuilder();
var buffer = new byte[4096];
try
{
sslStream.BeginRead(buffer, 0, buffer.Length, asyncResult =>
{
// Read all bytes avaliable from stream and then
// add them to string builder
{
int bytesRead;
try
{
bytesRead = sslStream.EndRead(asyncResult);
}
catch (Exception ex)
{
error?.Invoke(ex);
return;
}
// Use Decoder class to convert from bytes to
// UTF8 in case a character spans two buffers.
var decoder = Encoding.UTF8.GetDecoder();
var buf = new char[decoder.GetCharCount(buffer, 0, bytesRead)];
decoder.GetChars(buffer, 0, bytesRead, buf, 0);
stringBuilder.Append(buf);
}
// Find the EOFToken, if found copy all data before the token
// and send it to event, then remove it from string builder
{
int tokenIndex;
while((tokenIndex = stringBuilder.ToString().IndexOf(EOFToken)) != -1)
{
var buf = new char[tokenIndex];
stringBuilder.CopyTo(0, buf, 0, tokenIndex);
result?.Invoke(new string(buf));
stringBuilder.Remove(0, tokenIndex + EOFToken.Length);
}
}
// Continue reading...
ReadFromSSLStreamAsync(sslStream, result, error, stringBuilder);
}, null);
}
catch(Exception ex)
{
error?.Invoke(ex);
}
}
You could call it as so:
ReadFromSSLStreamAsync(sslStream, sslData =>
{
Console.WriteLine($"Finished: {sslData}");
}, error =>
{
Console.WriteLine($"Errored: {error}");
});
It's not TaskAsync, so you don't have to await on it. But it is asynchronous so your thread can go on to do other things.
Consider checking out the following asnwer. SSLStream was derived from the Stream class therefore the ReadAsnyc method can be used. Code below, read until the <EOF> delimiter characters then return with the received message as string.
internal static readonly byte[] EOF = Encoding.UTF8.GetBytes("<EOF>");
internal static async Task<string> ReadToEOFAsync(Stream stream)
{
byte[] buffer = new byte[8192];
using (MemoryStream memoryStream = new MemoryStream())
{
long eofLength = EOF.LongLength;
byte[] messageTail = new byte[eofLength];
while (!messageTail.SequenceEqual(EOF))
{
int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
await memoryStream.WriteAsync(buffer, 0, bytesRead);
Array.Copy(memoryStream.GetBuffer(), memoryStream.Length - eofLength, messageTail, 0, eofLength);
}
// Truncate the EOF tail from the data stream
byte[] result = new byte[memoryStream.Length - eofLength];
Array.Copy(memoryStream.GetBuffer(), 0, result, 0, result.LongLength);
return Encoding.UTF8.GetString(result);
}
}
The received messages was appended to the memoryStream. The first Array.Copy copies the message tail from the buffer. If the message tail is euqals to the <EOF> then it stops reading from the stream. Second copy is to ensure truncating the delimiter characters from the message.
Note: There is a more sophisticated way of slicing using Span introduced in .NET Core 2.1.

NetworkStream Read Extremely Slow

I'm using a C# NetworkStream to read/write to an IP address (not a DNS address).
My program is replacing a very old assembly language program that was on a mainframe. Now it's on Windows.
I'm writing/reading less than 200 bytes. The strings end with a LineFeed character so I'm using a StreamReader.Readline() to read a response, after my Stream.Write(). On the IBM a write/read cycle took 300ms.
Now about after every 2nd or 3 read, it takes 15 seconds for the read. When I read the log of the sender it is sending the data in less than a second. For some reason I get these 15 second delays.
I'm clueless on what's happening.
p.s.
One weird thing I noticed if I set the stream read timeout to 4 seconds, it times out around 4 seconds. If I set the timeout to 10 seconds or no timeout, it times out after 15 seconds.
TcpClient tcpc = null;
NetworkStream stream = null;
StreamReader sr = null;
tcpc = new TcpClient();
tcpc.NoDelay = true;
tcpc.ExclusiveAddressUse = false;
tcpc.Connect("172.18.10.100", 4004);
stream = tcpc.GetStream();
sr = new StreamReader(stream, Encoding.ASCII);
sr.Peek();
string Message = null;
Message = "IX3543543" + '\r';
stream.Write(Encoding.ASCII.GetBytes(Message), 0, Message.Length);
string readmsg = null;
for (int i = 0; i < 4; i++)
readmsg = sr.ReadLine();
Your connection stays open as your code never free your IDisposable resources.
I think that your code should run faster if you add the using constructure.
Also you can merge declaration and assignment, like this (this is only a style comment, the main concern is `IDisposable usage)
And you can ReadToEnd your message in return, and examine it by youself after releasing the resources.
So your code could look something like this:
string response = null;
using(var tcpc = new TcpClient())
{
tcpc.NoDelay = true;
tcpc.ExclusiveAddressUse = false;
tcpc.Connect("172.18.10.100", 4004);
using (var stream = tcpc.GetStream())
using (var sr = new StreamReader(stream, Encoding.ASCII))
{
sr.Peek();
var Message = "IX3543543" + '\r';
stream.Write(Encoding.ASCII.GetBytes(Message), 0, Message.Length);
response = sr.ReadToEnd();
}
}
// examine message here
var lines = response.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);

XPS Files Received Through TcpClient are Corrupt

I am trying to create a "virtual printer" application in C# that receives print jobs over the network, parses the raw print data for certain information, and then saves the document into a database. A modified version of the following class is working for postscript print jobs (it saves the incoming data to a valid .prn file, just as though the printer was set to print to the "FILE:" port.) When I try to capture .XPS documents from Microsoft XPS Document Writer, though, the documents cannot be opened. Valid XPS files should also be valid ZIP files if the extension is renamed, and this doesn't work either. When I print the same document to the FILE: port and then to my application, and I compare the results in Notepad++, there is a 5-character difference in the length of the data, but it looks identical (it is not plaintext so it's difficult to look at, but the first few characters and last few characters appear to be the same). The file saved the "normal" way works fine, but the file generated by my code does not.
More generally speaking, I'm trying to receive arbitrary data through a TCP port and write it to a file. My solution is "close" but not working. I don't know what kind of encoding XPS uses, but I am using ASCII for postscript and I have tried ASCII and UTF8 for this XPS version.
Any help is greatly appreciated! Here is the relevant part of my code:
class XPSListener
{
private TcpListener tcpListener;
private Thread listenThread;
private string instanceName = "";
private string fileShare = (Settings.Default.SharedPath.Substring(Settings.Default.SharedPath.Length - 1) == #"\") ? Settings.Default.SharedPath : Settings.Default.SharedPath + #"\"; // use SharedPath setting value - append backslash if it isn't already there.
public XPSListener(string initInstanceName, Int32 initPort)
{
this.instanceName = initInstanceName;
this.tcpListener = new TcpListener(IPAddress.Any, initPort);
this.listenThread = new Thread(new ThreadStart(ListenForClients));
this.listenThread.Start();
}
private void ListenForClients()
{
try
{
this.tcpListener.Start();
}
catch (Exception e)
{
MessageBox.Show("Socket Error 1 - " + e.StackTrace);
}
while (true)
{
//blocks until a client has connected to the server
TcpClient client = this.tcpListener.AcceptTcpClient();
//create a thread to handle communication with connected client
Thread clientThread = new Thread(new ParameterizedThreadStart(AcceptXPSData));
clientThread.Start(client);
}
}
private void AcceptXPSData(object client)
{
TcpClient tcpClient = (TcpClient)client;
NetworkStream clientStream = tcpClient.GetStream();
string tempFilePath = fileShare + "XPStemp_" + instanceName + ".oxps";
byte[] message = new byte[65536];
int bytesRead;
string input;
while (true)
{
bytesRead = 0;
try
{
//blocks until a client sends a message
bytesRead = clientStream.Read(message, 0, 65536);
Debug.WriteLine("Bytes read: " + bytesRead.ToString());
}
catch
{
//a socket error has occured
break;
}
if (bytesRead == 0)
{
//the client has disconnected from the server
break;
}
//message has successfully been received
if (instanceName != "DontPrint")
{
Debug.WriteLine(instanceName + " Receiving Data");
//ASCIIEncoding encoder = new ASCIIEncoding();
UTF8Encoding encoder = new UTF8Encoding();
using (FileStream fs = new FileStream(tempFilePath, FileMode.Append, FileAccess.Write))
{
using (StreamWriter sw = new StreamWriter(fs))
{
input = encoder.GetString(message, 0, bytesRead);
sw.Write(input);
// first capture this input and write it to an xps file. This file can be converted to PDF at a later time by Ghostscript
// but we will still have access to the temp file for parsing purposes.
}
}
}
}
tcpClient.Close();
// processXPS();
}
You have at least two problems in your code, one of them almost certainly the reason the file you write is incorrect:
You keep reopening the file you're writing to, rather than just opening it once.
You are interpreting the bytes you receive as text and then re-encoding them.
The first issue is more of an efficiency/file-locking issue than a correctness problem. But the second is a big problem.
As you seem to be aware, an XPS file is basically a .zip file. That means that while the underlying data is XML (i.e. UTF8), the file itself is a compressed binary file. You can't interpret that as text in any meaningful way.
You should simply write the bytes you read straight to the file. A better version of your code would look like this:
private void AcceptXPSData(object client)
{
string tempFilePath = fileShare + "XPStemp_" + instanceName + ".oxps";
using (TcpClient tcpClient = (TcpClient)client)
using (NetworkStream clientStream = tcpClient.GetStream())
using (FileStream fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write))
{
clientStream.CopyTo(fs);
}
// processXPS();
}
If you actually want to monitor the I/O as it occurs, you can deal with it explicitly, but still much more simply than your code was:
private void AcceptXPSData(object client)
{
string tempFilePath = fileShare + "XPStemp_" + instanceName + ".oxps";
using (TcpClient tcpClient = (TcpClient)client)
using (NetworkStream clientStream = tcpClient.GetStream())
using (FileStream fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write))
{
byte[] message = new byte[65536];
int bytesRead;
while ((bytesRead = clientStream.Read(message, 0, message.Length)) > 0)
{
fs.Write(message, 0, bytesRead);
// Add logging or whatever here
}
}
// processXPS();
}
Note that if you want to handle exceptions, you need to handle only those you specifically expect might happen, and for which you have a reasonable way to deal with. Bare catch clauses, or broad catch (Exception) should be avoided in code like this.

Akka IO Flush buffer

I use Akka IO for connection between Scala and C# application. On scala side I write data to stream using Akka IO.
case data: ByteString => connection ! Write(data)
But I want to flush buffer because on C# side I read data from buffer and sometimes I read two message instead one and programs fails on parsing.
On C# side I read in such way:
byte[] myReadBuffer = new byte[2048];
StringBuilder message = new StringBuilder();
int numberOfBytesRead = 0;
do
{
numberOfBytesRead = srReceiver.Read(myReadBuffer, 0, myReadBuffer.Length);
message.AppendFormat("{0}", Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead));
} while (srReceiver.DataAvailable);
processMessage(message.ToString());
I think I should flush buffer after message sending but I don't know how to do it.
Maybe someone knows how to resolve this problem?
Peter Duniho was right. I have added delimiter to every message and split it using this delimiter in processMessage method.
private void ProcessMessage(String message)
{
string[] messages = message.Split(new String[] { END_PACKET }, StringSplitOptions.RemoveEmptyEntries);
foreach(String msg in messages)
{
string singleMessage = msg;
if (msg.Contains(END_PACKET))
singleMessage = msg.Substring(0, msg.LastIndexOf(END_PACKET));
notify("Received : " + singleMessage);
Request request = JsonConvert.DeserializeObject<Request>(singleMessage);
onMessageReseived(request);
}
}

NetworkStream BeginRead only reads once?

I have some pretty simple code that reads lines from a network stream that it connects to. In the code example only one line is every read and it doesn't carry on getting more from the server.
What is wrong?
byte[] readBuffer = new byte[1024];
byte[] tempBuff = new byte[1024];
int tempBuffSize = 0;
private void btnConnect_Click(object sender, EventArgs e)
{
TcpClient tcpClient = new TcpClient("192.168.1.151", 5505);
NetworkStream stream = tcpClient.GetStream();
stream.BeginRead(readBuffer, 0, 1024, readHandler, tcpClient);
}
void readHandler(IAsyncResult result)
{
TcpClient tcpClient = (TcpClient)result.AsyncState;
int dataLen = tcpClient.GetStream().EndRead(result);
int currStart = 0;
int currEnd = -1;
for (int i = 0; i < dataLen; i++)
{
if (readBuffer[i] == '\r' && i < (readBuffer.Length - 1) &&
readBuffer[i + 1] == '\n')
{
// Set the end of the data
currEnd = i - 1;
// If we have left overs from previous runs:
if (tempBuffSize != 0)
{
byte[] joinedData = new byte[tempBuffSize + (currEnd - currStart + 1)];
Array.Copy(tempBuff, 0, joinedData, 0, tempBuffSize);
Array.Copy(readBuffer, currStart, joinedData, tempBuffSize, (currEnd - currStart + 1));
System.Text.Encoding enc = System.Text.Encoding.ASCII;
string myString = enc.GetString(joinedData);
System.Diagnostics.Debug.Write(myString);
tempBuffSize = 0;
}
else
{
System.Text.Encoding enc = System.Text.Encoding.ASCII;
string myString = enc.GetString(readBuffer);
System.Diagnostics.Debug.Write(myString);
// HandleData(readBuffer, currStart, currEnd);
}
// Set the new start - after our delimiter
currStart = i + 2;
}
}
// See if we still have any leftovers
if (currStart < dataLen)
{
Array.Copy(readBuffer, currStart, tempBuff, 0, dataLen - currStart);
tempBuffSize = dataLen - currStart;
}
}
Why do you expect it to read the whole information in the first place? I am not an expert but it seems to me that neither the synchronous nor the asynchronous methods guarantee reading all the data (whatever that means because as long as a socket is open more data can arrive). After the code in your EndRead method you should call Read or BeginRead again if you expect more data. You should know if more data is expected based on the protocol you've established with the client.
I have faced similar issues when i developed tcp apps for an embedded device. In my case the problem was the device was giving out data in a delayed time and hence before the rest of the data can come in the control moves to the next line in the program fetching only the initial data from the server. I got around this by introducing a delay.
Just after the line where you read data from the server introduce a delay and for that reason it would be better to run this on a separate thread
thread.sleep(3000)
This should be your problem most probably.
maybe your stream object got disposed when it got out of scope, before the readHandler could be called again. try promoting tcpClient and stream to class scope instead of method scope, or move the reading to a separate thread that exits when the operation has finished.

Categories

Resources