C# / TCP Sockets/ Blocking Read/ How to close such threads - c#

Recently I had to build a small TCP client app, that connects to an external app's TCP listener and is intended to deal with large amount of data volumes and on high frequencies.
I made a wrapper class around the TCPClient class, just to catch exceptions, and keep reference to some properties of interest (network stream etc.). Here is the wrapper:
public class MyTCPClient
{
private string serverIP;
private int serverPort;
public TcpClient tcpClient = new TcpClient();
private IPEndPoint serverEndPoint;
private NetworkStream stream = null;
public string name;
public MyTCPClient(string serverIp, int serverPort, string parentName)
{
this.serverIP = serverIp;
this.serverPort = serverPort;
this.name = parentName + "_TCPClient";
serverEndPoint = new IPEndPoint(IPAddress.Parse(serverIP), serverPort);
tcpClient.ReceiveBufferSize = 1048576;
this.TryConnect();
}
private bool TryConnect()
{
try
{
tcpClient.Connect(serverEndPoint);
}
catch (SocketException e1)
{
throw new ErrorOnConnectingException(e1, "SocketException while connecting. (see msdn Remarks section for more details. ) Error code: " + e1.ErrorCode);
}
catch (ArgumentNullException e2)
{
throw new ErrorOnConnectingException(e2, "ArgumentNullException while connecting. (The hostname parameter is null.) Message: " + e2.Message);
}
catch (ArgumentOutOfRangeException e3)
{
throw new ErrorOnConnectingException(e3, "ArgumentOutOfRangeException while connecting (The port parameter is not between MinPort and MaxPort. ). Message: " + e3.Message);
}
catch (ObjectDisposedException e4)
{
throw new ErrorOnConnectingException(e4, "ObjectDisposedException while connecting. (TcpClient is closed. ) Message: " + e4.Message);
}
try
{
stream = this.tcpClient.GetStream();
}
catch (ObjectDisposedException e1)
{
throw new ErrorOnGettingStreamException(e1, "ObjectDisposedException while acquiring Network stream. (The TcpClient has been closed. ) Message: " + e1.Message);
}
catch (InvalidOperationException e2)
{
throw new ErrorOnGettingStreamException(e2, "ArgumentOutOfRangeException while acquiring Network stream (The TcpClient is not connected to a remote host. ). Message: " + e2.Message);
}
return true;
}
public string ReadData()
{
try
{
ASCIIEncoding encoder = new ASCIIEncoding();
byte[] dataHeader = new byte[12];
if (this.tcpClient.Connected)
{
stream.Read(dataHeader, 0, 12);
}
else
{
throw new ErrorOnReadingException(null, "The underlying TCP tcpClient is not connected any more");
}
var strHeaderMessage = System.Text.Encoding.Default.GetString(dataHeader);
Utils.logToTimeStampedFile(strHeaderMessage, name);
int bodyAndTailCount = Convert.ToInt32(strHeaderMessage.Replace("#", ""));
byte[] dataBodyAndTail = new byte[bodyAndTailCount];
if (this.tcpClient.Connected)
{
stream.Read(dataBodyAndTail, 0, bodyAndTailCount);
}
else
{
throw new ErrorOnReadingException(null, "The underlying TCP tcpClient is not connected any more");
}
var strBodyAndTailMessage = System.Text.Encoding.Default.GetString(dataBodyAndTail);
Utils.logToTimeStampedFile(strBodyAndTailMessage, name);
return strBodyAndTailMessage;
}
catch (FormatException e0)
{
CloseAllLeft();
throw new ErrorOnReadingException(e0, "FormatException while reading data. (Bytes red are null or does not correspond to specification, happens on closing Server) Message: " + e0.Message);
}
catch (ArgumentNullException e1)
{
CloseAllLeft();
throw new ErrorOnReadingException(e1, "ArgumentNullException while reading data. (The buffer parameter is null.) Message: " + e1.Message);
}
catch (ArgumentOutOfRangeException e2)
{
CloseAllLeft();
throw new ErrorOnReadingException(e2, "ArgumentOutOfRangeException while reading data. (see msdn description) Message: " + e2.Message);
}
catch (IOException e3)
{
CloseAllLeft();
throw new ErrorOnReadingException(e3, "IOException while reading data. (The underlying Socket is closed.) Message: " + e3.Message);
}
catch (ObjectDisposedException e4)
{
CloseAllLeft();
throw new ErrorOnReadingException(e4, "ArgumentOutOfRangeException while reading data. (see msdn description) Message: " + e4.Message);
}
}
public void CloseAllLeft()
{
try
{
stream.Close();
}
catch (Exception e)
{
Console.WriteLine("Exception closing tcp network stream: " + e.Message);
}
try
{
tcpClient.Close();
}
catch (Exception e)
{
Console.WriteLine("Exception closing tcpClient: " + e.Message);
}
}
}
Still, nothing mentioned about the threads using this MyTCPClient. The app should have two such TCP clients, connecting on different ports, and doing different jobs. I was new to TCP programming and after some wandering around the properties I decided to use the blocking read approach - i.e by default the TCPClient.Read() method will block the thread until there is new data. I needed such approach bucause I do not have control on the external app's listener, and the only way to recognize a server's closing was the "zero bytes" sent as per TCP Sockets specs.
So, i build an abstract class that will maintain and control threads that will later make use of the above MyTCPClient class (which by design, eventually might block the parent theads). Here is the code for my abstract TCPManager:
/// <summary>
/// Serves as a dispatcher for the high frequency readings from the TCP pipe.
/// Each time the thread is started it initializes new TCPClients which will attempt to connect to server.
/// Once established a TCP socket connection is alive until the thread is not requested to stop.
///
/// Error hanling level here:
///
/// Resources lke NetworkStream and TCPClients are ensured to be closed already within the myTCPClient class, and the error handling here
/// is steps on top of that - sending proper emails, notifications and logging.
///
/// </summary>
public abstract class AbstractmyTCPClientManager
{
public string name;
public string serverIP;
public int serverPort;
public Boolean requestStop = false;
public Boolean MyTCPClientThreadRunning = false;
public Boolean requestStart = false;
public myTCPClient myTCPClient;
public int sleepInterval;
public Thread MyTCPClientThread;
public AbstractmyTCPClientManager(string name, string serverIP, int serverPort)
{
this.name = name;
this.serverIP = serverIP;
this.serverPort = serverPort;
}
public void ThreadRun()
{
MyTCPClientThreadRunning = false;
bool TCPSocketConnected = false;
bool AdditionalInitializationOK = false;
// keep trying to init requested tcp clients
while (!MyTCPClientThreadRunning && !requestStop) // and we are not suggested to stop
{
while (!TCPSocketConnected && !requestStop) // and we are not suggested to stop)
{
try
{
myTCPClient = new myTCPClient(serverIP, serverPort, name);
TCPSocketConnected = true;
}
catch (ErrorOnConnectingException e0)
{
// nah, too long message
string detail = e0.originalException != null ? e0.originalException.Message : "No inner exception";
//Utils.logToTimeStampedFile("Creating connection attempt failed.(1." + e0.customMessage + " 2." + detail + "). Will retry in 10 seconds...", name);
//Utils.logToTimeStampedFile(e0.customMessage + " (" + detail + "). Will retry in 10 seconds...", name);
Utils.logToTimeStampedFile(detail + ". Will retry in 10 seconds...", name);
Thread.Sleep(10000);
}
catch (ErrorOnGettingStreamException e1)
{
// nah, too long message
string detail = e1.originalException != null ? e1.originalException.Message : "No inner exception";
//Utils.logToTimeStampedFile("Getting network stream attempt failed. (1." + e1.customMessage + " 2." + detail + "). Will retry in 10 seconds...", name);
//Utils.logToTimeStampedFile(e1.customMessage + " (" + detail + "). Will retry in 10 seconds...", name);
Utils.logToTimeStampedFile(detail + ". Will retry in 10 seconds...", name);
Thread.Sleep(10000);
}
}
Utils.logToTimeStampedFile("TCP Communication established", name);
while (!AdditionalInitializationOK && !requestStop) // or we are not suggested to stop
{
try
{
AdditionalInitialization();
AdditionalInitializationOK = true;
}
catch (AdditionalInitializationException e1)
{
string detail = e1.originalException != null ? e1.originalException.Message : "No inner exception";
//Utils.logToTimeStampedFile("Additional initialization failed (1." + e1.customMessage + " 2." + detail + "). Will retry in 10 seconds", name);
Utils.logToTimeStampedFile(e1.customMessage + ". Will retry in 10 seconds", name);
Thread.Sleep(10000);
}
}
MyTCPClientThreadRunning = TCPSocketConnected && AdditionalInitializationOK;
ViewModelLocator.ControlTabStatic.updateUIButtons();
}
Utils.logToTimeStampedFile("Additional Initialization successfully completed, thread started", name);
// while all normal (i.e nobody request a stop) continiously sync with server (read data)
while (!requestStop)
{
try
{
syncWithInterface();
}
catch (ErrorOnReadingException e1)
{
string detail = e1.originalException != null ? e1.originalException.Message : "No inner exception";
//Utils.logToTimeStampedFile("Error ocured while reading data. (1." + e1.customMessage + " 2." + detail + ")", name);
Utils.logToTimeStampedFile(e1.customMessage, name);
if (!requestStop) // i.e if this indeed is an exception, during a normal flow, and nobody requested a thread stop (which migh cause read exceptions as a consequence)
{
Utils.logToTimeStampedFile("There was no external stop request, when the error occured, doing tcp client restart.", name);
requestStop = true;
requestStart = true;
}
}
Thread.Sleep(sleepInterval);
}
// we need to close all after execution, but the execution may be closed before/while resources were still initializing
if (TCPSocketConnected)
{
myTCPClient.CloseAllLeft();
}
if (AdditionalInitializationOK)
{
ReleaseAdditionalResources();
}
// remember that thread is stoped
MyTCPClientThreadRunning = false;
Utils.logToTimeStampedFile("Thread stoped", name);
ViewModelLocator.ControlTabStatic.updateUIButtons();
// this serves as a restart
if (requestStart)
{
Utils.logToTimeStampedFile("Restarting thread...", name);
this.requestStop = false;
this.requestStart = false; // we are already processing a request start event, so reset this flag
this.MyTCPClientThread = new Thread(new ThreadStart(this.ThreadRun));
this.MyTCPClientThread.Name = this.name;
this.MyTCPClientThread.IsBackground = true;
this.MyTCPClientThread.Start();
}
}
/// <summary>
/// this method empties the entire TCP buffer, cycling through it
/// </summary>
private void syncWithInterface()
{
int counter = 0;
// read at most 100 messages at once (we assume that for 3 sec interval there might not be more,
//even if they are, it is still OK, they just will be processed next time)
while (counter < 100)
{
counter++;
string data = myTCPClient.ReadData();
ForwardData(data);
}
// below is left for testing:
/*
* "Sleep(0) or Yield is occasionally useful in production code for
* advanced performance tweaks. It’s also an excellent diagnostic tool
* for helping to uncover thread safety issues: if inserting Thread.Yield()
* anywhere in your code makes or breaks the program, you almost certainly have a bug."*/
Thread.Yield();
}
/// <summary>
/// Left for implementing in the caller that initialized the object. Meaning: one and the same way for receiving market/order data. Different ways of processing this data
/// </summary>
/// <param name="data"></param>
public abstract void ForwardData(string data);
/// <summary>
/// left for implementing in child classes. Its purpose is to initialize any additional resources needed for the thread to operate.
/// If something goes wrong while getting this additional resources,
/// an AdditionalInitialization exception should be thrown, which is than handled from the initialization phase in the caller.
/// </summary>
public abstract void AdditionalInitialization();
// countrapart of AdditionalInitialization method - what is initialized should be then closed
public abstract void ReleaseAdditionalResources();
}
Later, each needed TCP communication channel would have a dedicated implementation for the above abstract class, providing implementation of the methods ForwardData (i.e what to do with this data) and AdditionalInitialization (i.e what else is needed to be initialized before a particular TCP communication processing is run. For example on of my threads required additional storage Thread to be initialized prior receiving data).
Everything was fine, except for closing the TCP processing. I had this requestStop variables to control whther a thread should exit or continue, but the thing is that Read() method may fall in continious blocking, preventing even the requestStop variable from being read (I should say that the two tcp channels I need to process are very different in that one of them is very frequently receiving data and the other one - sporadically). I would like still to have them implementing the same design. So from what I am reading so far I have to implement another, "parent", or "controlling", or "wrapper" thread that will actually take the job on observing requestStop parameter.
I am looking towards solutions like this post, or timers like this post
Any suggestions would be greatly appreciated. Thanks!

I would personally use asynchronous sockets for this:
http://msdn.microsoft.com/en-us/library/bbx2eya8.aspx
If you, however, still want to use blocking reads, you one could simply be to Close() the socket from another thread.
I hope this help.

I would recommend calling the ReadAsync method of NetworkStream and passing a CancellationToken to it. This way, the read operation can be easily cancelled (from another thread) when a request stop event is observed:
public class MyTCPClient : IDisposable
{
...
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource ();
...
public string ReadData()
{
...
byte[] dataHeader = new byte[12];
if (this.tcpClient.Connected)
{
stream.ReadAsync(dataHeader, 0, 12, cancellationTokenSource.Token).Wait();
} ...

Set your 'requestStop' bool and Close the client socket from another thread. This causes the read() call to return 'early' with an error/exception. The client thread can check 'requestStop' after every read() return and clean up/exit if requested.
TBH, I rarely bother with explicitly shutting down such clients anyway. I just leave them until the app exits.

Related

Unexpected error in windows service

I've got a WCF windows service running. It basically registers a number of clients and broadcasts messages to them as and when needed.
Generally it works fine, but recently I've been getting an error trying to register a new client, there are about 24 clients connected already, but when I try to register a 25th, I get "Index was outside the bounds of the array." being returned.
If I restart the service, all the clients reconnect, and the new client is able to register.
The NotifyServer method is called to broadcast a message to all the registered clients. This runs through the clients dictionary creating an async task that sends the message. This is done so that any issue happening with sending to one client does not impact the sending of the message to others.
The service is set up as to use a Reliable Connection.
// List of connected Clients
//
private static Dictionary<string, IBroadcastorCallBack> clients = new Dictionary<string, IBroadcastorCallBack>();
// lock indicator object
//
private static object locker = new object();
public string RegisterClient(string clientName)
{
string returnValue = "";
if (!string.IsNullOrEmpty(clientName))
{
try
{
var callback = OperationContext.Current.GetCallbackChannel<IBroadcastorCallBack>();
lock (locker)
{
// Remove the old client if its already there
//
if (clients.Keys.Contains(clientName))
{
clients.Remove(clientName);
}
// Add the new one
//
clients.Add(clientName, callback);
}
}
catch (Exception ex)
{
// Return any error message
//
returnValue = ex.Message;
}
}
return returnValue;
}
/// <summary>
/// Notify the service of a message that can be broadcast
/// </summary>
/// <param name="eventData">Message details</param>
///
public async void NotifyServer(EventDataType eventData)
{
DateTime Start = DateTime.Now;
// Get a list copy of the dictionary for all clients bar the one sending it. This is so we can't update the list inside the loop.
//
var clientlist = clients.Where(x => x.Key != eventData.ClientName).ToList();
// Logging
//
if (MetricBroadcast.Properties.Settings.Default.LogXML)
{
log.Debug("XML Broadcast from " + eventData.ClientName + " to " + clientlist.Count.ToString() + " clients:\n" + eventData.EventMessage + "\n");
}
// Broadcast to all the valid clients
//
var BroadcastToClientList = clientlist.Select(client => BroadcastMessage(client.Value, client.Key, eventData)).ToList();
// Wait until they are all done
//
await Task.WhenAll(BroadcastToClientList);
// If we are logging and the broadcast time is > 1 second, we make a log entry
//
DateTime End = DateTime.Now;
if (MetricBroadcast.Properties.Settings.Default.LogXML)
{
TimeSpan res = End - Start;
if (res.TotalSeconds > 1)
{
var timetaken = string.Format("XML Broadcast Time : {0,2:00}:{1,2:00}.{2,3:000}", res.Minutes, res.Seconds, res.Milliseconds);
log.Debug(timetaken);
}
}
}
private async Task<bool> BroadcastMessage(IBroadcastorCallBack clientCallback, string ClientKey, EventDataType eventData)
{
bool retval = true;
Exception savedEx = null;
DateTime BroadStart = DateTime.Now;
try
{
// Send the message to the current client
//
clientCallback.BroadcastToClient(eventData);
}
catch (Exception e)
{
// If we can't access the current clients callback method,
// we remove them from the clients list, as they've probably lost their connection.
//
clients.Remove(ClientKey);
savedEx = e;
retval = false;
}
// Log any broadcast that took > .5 seconds
//
DateTime BroadEnd = DateTime.Now;
if (MetricBroadcast.Properties.Settings.Default.LogXML)
{
TimeSpan res = BroadEnd - BroadStart;
if (res.TotalSeconds > .5)
{
var timetaken = string.Format("Single XML Broadcast Time to " + ClientKey + " : {0,2:00}:{1,2:00}.{2,3:000}", res.Minutes, res.Seconds, res.Milliseconds);
log.Debug(timetaken, savedEx);
}
}
return retval;
}
Most likely the problem is incorrect usage of static clients Dictionary in multithreaded environment. New client can register at any time, including in the middle of your BroadcastMessage and NotifyServer functions. Dictionary was not designed for access from multiple threads. Take for example this:
clients.Where(x => x.Key != eventData.ClientName).ToList();
What happens if client is removed in the middle of this enumeration? Who knows, because it was not designed for this, but most likely some exception (like your "index out of bounds") will be thrown. That is why you need to lock your dictionary for both writes and reads, not just for writes:
List<KeyValuePair<string, IBroadcastorCallback>> clientList;
lock (locker)
clientList = clients.Where(x => x.Key != eventData.ClientName).ToList();
Another option is to use ConcurrentDictionary class. It was designed for concurrent access and you can safely read and write to it from multiple threads.

Lost frames on synchrous/asynchrous data receiveing

I have some problem with receiving frames in C#. After few hours from application start, some frames are lost.
Well, at start I'm running two threads which are receiving data. Code:
public void ATSListenerThreadA(MainWindow mainWindow)
{
UdpClient listenerA = new UdpClient(19001);
listenerA.Client.ReceiveBufferSize = 1000;
IPEndPoint remoteIPEndPointA = new IPEndPoint(IPAddress.Any, 1);
Byte[] receivedBytes;
try
{
while (mainWindow.ATSThreadActive)
{
receivedBytes = listenerA.Receive(ref remoteIPEndPointA);
if (receivedBytes.Length > 170)
{
Log.Write(MessageType.Info, "BUG KANAL A: " + BitConverter.ToString(receivedBytes));
}
mainWindow.ATSTimeoutTimer.Start();
ATSDataReceived(Channel.A, mainWindow, receivedBytes);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
public void ATSListenerThreadB(MainWindow mainWindow)
{
UdpClient listenerB = new UdpClient(19002);
listenerB.Client.ReceiveBufferSize = 1000;
IPEndPoint remoteIPEndPointB = new IPEndPoint(IPAddress.Any, 1);
Byte[] receivedBytes;
try
{
while (mainWindow.ATSThreadActive)
{
receivedBytes = listenerB.Receive(ref remoteIPEndPointB);
if (receivedBytes.Length > 170)
{
Log.Write(MessageType.Info, "BUG KANAL B: " + BitConverter.ToString(receivedBytes));
}
mainWindow.ATSTimeoutTimer.Start();
ATSDataReceived(Channel.B, mainWindow, receivedBytes);
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private void ATSDataReceived(Channel channel, MainWindow mainWindow, Byte[] received)
{
try
{
mainWindow.Dispatcher.Invoke(() =>
{
mainWindow.ATSStatusLabel.Foreground = System.Windows.Media.Brushes.Green;
mainWindow.ATSStatusLabel.Content = "OK";
});
ATSInterface.UpdateObjects(channel, received);
}
catch (Exception ex)
{
mainWindow.Dispatcher.Invoke(() =>
{
mainWindow.ATSStatusLabel.Foreground = System.Windows.Media.Brushes.Red;
mainWindow.ATSStatusLabel.Content = "ERR";
});
Log.Write(MessageType.Warning, ex.Message);
}
lock (this)
{
if (mainWindow.ATSTimeoutTimer != null)
{
mainWindow.ATSTimeoutTimer.Stop();
mainWindow.ATSTimeoutTimer.Start();
}
}
}
Then, in ATSDataReceived method, received data are processed with calling method ATSInterface.UpdateObjects(). Of course inside ATSInterface.UpdateObjects() method is lock. I was trying to receiving data synchronously and asynchronously, but it doesn't change anything. Also I was checking if two frames are not sticking together, but it's not this too. As I said, problem araises after few hours from program start, in both threads simultaneously.
I might add that frames are coming very often, once at ~150 ms.
What can be a reason?
some frames are lost.
That's perfectly normal for UDP. By design, you may or may not ever receive a datagram that was sent to you.
Other unreliable behaviors of UDP include the possibility that a given datagram may be received more than once, and that one datagram may be received after a different datagram that was sent later than it (i.e. the datagrams are not guaranteed to be received in the same order in which they were sent).
If you want reliable communications, you will need to use TCP, or add reliability features on top of your UDP protocol.
udp is unreliable protocol to use.
if you want to make sure that everything arrives you need to use TcpClient instead.

Can you read and write with a single Named Pipe client?

I've written a little apllication that creates a named pipe server and a client that connects to it. You can send data to the server, and the server reads it successfully.
The next thing I need to do is receive messages from the server, so I've got another thread that spawns and sits and waits for incoming data.
The problem is that whilst the thread is sat waiting for incoming data, you can no longer send messages to the server as it hangs on the WriteLine call as I assume the pipe is now tied up checking for data.
So is it just that I'm not approaching this properly? Or are named pipes not meant to be used like this? The examples I've seen on named pipes seem to only go one way, a client sends and a server receives, although you can specify the direction of a pipe as In, Out or both.
Any help, pointers or suggestions would be appreciated!
Heres' the code so far:
// Variable declarations
NamedPipeClientStream pipeClient;
StreamWriter swClient;
Thread messageReadThread;
bool listeningStopRequested = false;
// Client connect
public void Connect(string pipeName, string serverName = ".")
{
if (pipeClient == null)
{
pipeClient = new NamedPipeClientStream(serverName, pipeName, PipeDirection.InOut);
pipeClient.Connect();
swClient = new StreamWriter(pipeClient);
swClient.AutoFlush = true;
}
StartServerThread();
}
// Client send message
public void SendMessage(string msg)
{
if (swClient != null && pipeClient != null && pipeClient.IsConnected)
{
swClient.WriteLine(msg);
BeginListening();
}
}
// Client wait for incoming data
public void StartServerThread()
{
listeningStopRequested = false;
messageReadThread = new Thread(new ThreadStart(BeginListening));
messageReadThread.IsBackground = true;
messageReadThread.Start();
}
public void BeginListening()
{
string currentAction = "waiting for incoming messages";
try
{
using (StreamReader sr = new StreamReader(pipeClient))
{
while (!listeningStopRequested && pipeClient.IsConnected)
{
string line;
while ((line = sr.ReadLine()) != null)
{
RaiseNewMessageEvent(line);
LogInfo("Message received: {0}", line);
}
}
}
LogInfo("Client disconnected");
RaiseDisconnectedEvent("Manual disconnection");
}
// Catch the IOException that is raised if the pipe is
// broken or disconnected.
catch (IOException e)
{
string error = "Connection terminated unexpectedly: " + e.Message;
LogError(currentAction, error);
RaiseDisconnectedEvent(error);
}
}
You cannot read from one thread and write on another thread to the same pipe object. So while you could create a protocol where the listening position changes depending on the data you're sending, you cannot do both at the same time. You will need a client and server pipe on both sides to do this.

How to sync between a listening/sending tcp client thread and the main execution?

i have a simple windows service which runs and starts a thread which listen/receive heartbeat via tcp/ip. i'm having a hard time finding ways to sync between getting information from the tcp thread and using that value to update something in the main thread.
i try to use a thread.sleep method and keep on looping it for a few times while awaiting the answer back from the thread and then getting the value, but that method seems to be a bit volatile with the method sometimes working and sometimes not.
so what's a good way to sync between these two?
basically what i want to do is to start the listening tcp thread, get specific value and the update the main program.
attached are the receive function and the function which i used to start the thread.
p.s: i'm a totally noobie when it comes to tcp/ip and c# so any comments on any part of the code or the design is more than welcome :)
public virtual void Receive()
{
string eventMessage = string.Empty;
int bytesRcvd = 0;
int totalBytesRcvd = 0;
byte[] byteBuffer = new byte[maxBufferSize];
NetworkStream listenStream;
try
{
if (client.Connected)
{
listenStream = client.GetStream();
}
else
{
return;
}
while (true)
{
//message that is slot in from the object will get sent here.
if (!string.IsNullOrEmpty(MessageToSend))
{
Send(MessageToSend);
MessageToSend = string.Empty;
}
// must convert it back and look for the delimiter, cannot wait for the three heartbeat to pass
string leftoverMsg = string.Empty;
bytesRcvd = listenStream.Read(byteBuffer, totalBytesRcvd, maxBufferSize - totalBytesRcvd);
totalBytesRcvd += bytesRcvd;
//if more than heart beat size, can process to see if it's a heartbeat and proceed to send
if (totalBytesRcvd > msgHeartbeatSize)
{
eventMessage = Encoding.ASCII.GetString(byteBuffer, 0, totalBytesRcvd);
ProcessMessage(eventMessage, ref leftoverMsg, ref totalBytesRcvd, ref byteBuffer);
}
}
}
catch (ThreadAbortException thEx)
{
//do nothing as main thread has aborted and waiting to close
logger.Info(Thread.CurrentThread.Name + " is stopped. ");
}
catch (Exception exce)
{
bIsActive = false;
logger.Error(exce);
CleanUp();
}
finally
{
logger.Info(String.Format("Thread {0} Exiting. ", Thread.CurrentThread.Name));
}
}
public virtual void StartReceivingThread()
{
Thread thrReceive = new Thread(Receive);
try
{
if (!bIsActive && Connect())
{
//NOTE: exception thrown by a thread can only be captured by that thread itself
//start a listen thread
//wait until heartbeat message is accepted
thrReceive.Name = "thr" + serviceType.Name;
thrReceive.Start();
bIsActive = true;
//wait to get the heartbeat message
for (int i = 0; i < maxRetry; i++)
{
Thread.Sleep(maxTimeOutValue);
if (bIsReceivingHeartbeat)
break;
}
//if nothing happens close the connection and try again
if (!bIsReceivingHeartbeat)
{
bIsActive = false;
CleanUp();
logger.Info("Closing receiver thread - " + thrReceive.Name);
}
else
{
logger.Info("Starting receiver thread - " + thrReceive.Name);
}
}
}
catch(Exception ex)
{
logger.Error(ex);
}
//finally
//{
// logger.Info("Exiting receiver thread - " + thrReceive.Name);
//}
}
I assume bIsReceivingHeartbeat is a bool member variable of the class. If the value changed in one thread (receiver) is not visible in the other thread this is most likely due to memory barrier. I am saying this from my Java background but this is most likely true in .net as well.
Try declaring the variables volatile or use a property and make the getter and setter synchronized:
private bool bIsReceivingHeartbeat;
public bool IsReceivingHeartbeat
{
[MethodImpl(MethodImplOptions.Synchronized)]
get { return bIsReceivingHeartbeat; }
[MethodImpl(MethodImplOptions.Synchronized)]
set { bIsReceivingHeartbeat = value; }
}
And in the calling code:
if (!IsReceivingHeartbeat) ....
I am writing from Java background but the situation most likely similar
(Looks like you also posted this code in refactormycode.com.)
Anyway, instead of the loop with a sleep delay, I recommend using an Event object that pulsed by the code that sets IsReceivingHeartbeat. See the ManualResetEvent and AutoResetEvent classes in MSDN.

Cannot get string data to pass through NamedPipeServerStream and NamedPipeClientStream

I'm trying to achieve bi-directional, named pipe communication on my Win-XP workstation using two simple C# forms solutions. One for the client and one for the server. They appear almost identical and use NamedPipeServerStream and NamedPipeClientStream (.NET 3.5). Both client and server are set to bidirectional comms via PipeDirection.InOut
The order of start-up events is:
1) Start the server. It waits for a connection from the client.
2) Start the client and it immediately finds and connects to the server. The server, likewise, completes its connection to the client.
3) Both client and server launch their "Read" threads which in turn create instances of streamreader. These threads then call ReadLn() and block - waiting for data. In all instances, autoflush is true.
I then use streamwriter.WriteLn() to send string data from the server to the client (or vice-versa). However, the execution never returns from that call. I don't know why and any insights would be greatfully received.
I have spent considerable time studying all that there is on this subject but I'm still missing something.
Client and server code snippets are shown:
SERVER:
private void ListenForClients()
{
// Only one server as this will be a 1-1 connection
m_pipeServerStream = new NamedPipeServerStream(PipeName, PipeDirection.InOut, 1);
// Wait for a client to connect
m_pipeServerStream.WaitForConnection();
// Ccould not create handle - server probably not running
if (!m_pipeServerStream.IsConnected)
return;
// Create a stream writer which flushes after every write
m_pipeServerWriter = new StreamWriter(m_pipeServerStream);
m_pipeServerWriter.AutoFlush = true;
Connected = true;
// Start listening for messages
if (m_pipeServerStream.CanRead)
{
ReadThread = new Thread(new ParameterizedThreadStart(Read));
ReadThread.Start(m_pipeServerStream);
}
}
/// <summary>
/// Reads data from the client
/// </summary>
/// <param name="serverObj"></param>
private void Read(object serverObj)
{
NamedPipeServerStream pipeStream = (NamedPipeServerStream)serverObj;
using (StreamReader sr = new StreamReader(pipeStream))
{
while (true)
{
string buffer;
try
{
buffer = sr.ReadLine();
}
catch
{
//read error has occurred
break;
}
//client has disconnected
if (buffer.Length == 0)
break;
//fire message received event
if (MessageReceived != null)
{
MessageReceived(buffer);
}
}
}
}
/// <summary>
/// Sends a message to the connected client
/// </summary>
/// <param name="message">the message to send</param>
public void SendMessage(string message)
{
if (m_pipeServerWriter != null)
{
m_pipeServerWriter.WriteLine(message);
m_pipeServerWriter.Flush();
}
}
CLIENT:
private void ConnectToServer()
{
// Seek out the one server
m_pipeClientStream = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut);
// Connect to the waiting server
m_pipeClientStream.Connect();
// Ccould not create handle - server probably not running
if (!m_pipeClientStream.IsConnected)
return;
// Create a stream writer which flushes after every write
m_pipeClientWriter = new StreamWriter(m_pipeClientStream);
m_pipeClientWriter.AutoFlush = true;
Connected = true;
// Start listening for messages
if (m_pipeClientStream.CanRead)
{
ReadThread = new Thread(new ParameterizedThreadStart(Read));
ReadThread.Start(m_pipeClientStream);
}
}
/// <summary>
/// Reads data from the server
/// </summary>
private void Read(object serverObj)
{
NamedPipeClientStream pipeStream = (NamedPipeClientStream)serverObj;
using (StreamReader sr = new StreamReader(pipeStream))
{
while (true)
{
string buffer;
try
{
buffer = sr.ReadLine();
}
catch
{
//read error has occurred
break;
}
//client has disconnected
if (buffer.Length == 0)
break;
//fire message received event
if (MessageReceived != null)
{
MessageReceived(buffer);
}
}
}
}
/// <summary>
/// Sends a message to the connected server
/// </summary>
/// <param name="message"></param>
public void SendMessage(string message)
{
if (m_pipeClientWriter != null)
{
m_pipeClientWriter.WriteLine(message);
m_pipeClientWriter.Flush();
}
}
Try setting the Async flag on the streams:
NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
I've now given up and moved to the safe, obvious technique of using two pipes, one for each direction of communication. They work fine.
I am not sure if this will help but I am also experiencing the same problem. First of all, I don't know why any reference to m_pipeServerStream.IsConnected will break the pipe. I tested this with just a simple MessageBox.Show(m_pipeServerStream.IsConnected.ToString()) and that broke my pipe!
Secondly, another weird thing is that your streamreader call will never return if you are using a duplex named pipe. You will need to read it manually like this
const int BufferSize = 4096;
Decoder decoder = Encoding.UTF8.GetDecoder();
StringBuilder msg = new StringBuilder();
char[] chars = new char[BufferSize];
byte[] bytes = new byte[BufferSize];
int numBytes = 0;
MessageBox.Show("before do while loop");
numBytes = pipeServer.Read(bytes, 0, BufferSize);
if (numBytes > 0)
{
int numChars = decoder.GetCharCount(bytes, 0, numBytes);
decoder.GetChars(bytes, 0, numBytes, chars, 0, false);
msg.Append(chars, 0, numChars);
}
MessageBox.Show(numBytes.ToString() + " " + msg.ToString());
MessageBox.Show("Finished reading, now starting writing");
using (StreamWriter swr = new StreamWriter(pipeServer))
{
MessageBox.Show("Sending ok back");
swr.WriteLine("OK");
pipeServer.WaitForPipeDrain();
}
Anyway, it doesn't seem to like the behavior of StreamReader, but this will work for now... I got this off this link http://social.msdn.microsoft.com/forums/en-US/csharpgeneral/thread/23dc2951-8b59-48e4-89fe-d2b435db48c6/
I'm not following every single step because I just needed to find out why it keeps hanging at StreamReader.ReadLine(). it's not returning from this function. StreamWriter does not seem to have this problem.
I am actually communicating between native dll and a managed windows service. Imagine my surprise when I found out that it was the managed part that was the problem, not the unmanaged part since they has such good examples in msdn...
I am no expert on Named Pipes or Anonymous Pipes but I will give it my best shot at trying to help others out even though you have a work around to your problem.
Client Server Communications is the best way to think of how this process should be achieved.
Server Starts and Listens for a Connection --> Client initiates a connection to a Server -->Server accepts the connection -->Client makes a request -->Server makes a response --> Connection is closed.
Server Starts and Listens for a Connection:
try
{
namedPipeServerStream = new NamedPipeServerStream(PipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
// Wait for a connection here...
namedPipeServerStream.BeginWaitForConnection(new AsyncCallback(ConnectionCallBack), namedPipeServerStream);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
Client Connects, then makes a Request:
try
{
namedPipeClientStream = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut, PipeOptions.Asynchronous);
// Connect with timeout...
namedPipeClientStream.Connect(TimeOut);
byte[] buffer = Encoding.UTF8.GetBytes(DataToSend);
namedPipeClientStream.BeginWrite(buffer, 0, buffer.Length, ConnectionCallBack, namedPipeClientStream);
}
catch (TimeoutException ex)
{
Debug.WriteLine(ex.Message);
}
ConnectionCallBack is an Asynchronous CallBack. This Method (this is on the Client) is where the Connection is managed:
private void ConnectionCallBack(IAsyncResult iAsyncResult)
{
try
{
// Get the pipe
NamedPipeClientStream namedPipeClientStream = (NamedPipeClientStream)iAsyncResult.AsyncState;
// End the write
namedPipeClientStream.EndWrite(iAsyncResult);
namedPipeClientStream.Flush();
// Get Server Response...
GetServerResponse(namedPipeClientStream);
// Flush Data and Close Pipe...
namedPipeClientStream.Flush();
namedPipeClientStream.Close();
namedPipeClientStream.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
The Server handles the Client Request and formulates a Response and sends it:
// Response Methods...
public void SendResponse(string ServerResponse)
{
try
{
// Fill Buffer with Server Response Data...
byte[] Buffer = Encoding.UTF8.GetBytes(ServerResponse);
// Begin Async Write to the Pipe...
namedPipeServerStream.BeginWrite(Buffer, 0, Buffer.Length, SendResponseCallBack, namedPipeServerStream);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
private void SendResponseCallBack(IAsyncResult iAsyncResult)
{
try
{
// Get the Pipe Handle...
NamedPipeServerStream namedPipeServerStream = (NamedPipeServerStream)iAsyncResult.AsyncState;
// End the Write and Flush...
namedPipeServerStream.EndWrite(iAsyncResult);
namedPipeServerStream.Flush();
// Close the Connection and Dispose...
namedPipeServerStream.Close();
namedPipeServerStream.Dispose();
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
This is called from the Client Request Handler:
private void ClientRequestHandler(string clientRequest)
{
try
{
if (this.InvokeRequired)
{
this.Invoke(new InvokedDelegate(ClientRequestHandler), clientRequest);
}
else
{
ProcessClientRequest(clientRequest);
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
private void ProcessClientRequest(string clientRequest)
{
// Display the Client Request...
richTextBox1.Text = clientRequest;
PipeServer.SendResponse("Server has received Client Request at: " + DateTime.Now);
}
The Client has initiated a Connection to the Server, at the point where the Asynchronous CallBack Method see's this:
// Get Server Response...
GetServerResponse(namedPipeClientStream);
The Connection is still open. The Client Request was made and the Pipe was Flushed and is ready for the Client to Read the Server Response mentioned above:
private void GetServerResponse(NamedPipeClientStream namedPipeClientStream)
{
byte[] buffer = new byte[255];
namedPipeClientStream.Read(buffer, 0, buffer.Length);
// Convert byte buffer to string
string ResponseData = Encoding.UTF8.GetString(buffer, 0, buffer.Length);
// Pass message back to calling form
ServerResponse.Invoke(ResponseData);
}
The Response is received and then the Connection is again Flushed and Closed ready for the Client to Initiate another Connection.
The code is a little more complex than just this but essentially this is how it works. While you have a connection initiated, use it. Once you close it, and then try to re-initialise it, you will need to wait for a period of time for it to dispose properly or you will get all sorts of semaphore errors and so on. Don't Smoke your connection when you don't need to!!!
Please see: Code Project - C# Async Named Pipes for an excellent example

Categories

Resources