Imagine a server application is connected via TCP to some client device. The user of the server decides to send some message to the client device with NetworkStream.BeginWrite, but due to a slow connection or some other unknown factor, the server calls BeginWrite, but BeginWrite has not yet finished its write and performed its callback. In the meantime, the user decides to disconnect the client device in the main thread, causing an ObjectDisposedException to be immediately thrown in the callback due to the underlying connection no longer being available.
Here's a pseudo example of what I mean:
/// bad pseudo of possible code happening in main server thread
while(!quit){
if( command has been inputted) {
switch (command) {
case send:
string m = getAStringFromGUI();
Send(m);
break;
case disconnect:
ClientDevice.Close();
break;
}
}
}
private void Send(string msg){
NetworkStream stream;
byte[ ] packetBuffer = Encoding.ASCII.GetBytes(msg);
stream = clientDevice.GetStream();
stream.BeginWrite(packetBuffer, 0, packetBuffer.Length, new AsyncCallback(StreamWriteCompleteCallback), stream);
}
private void StreamWriteCompleteCallback(IAsyncResult ar) {
try {
NetworkStream stream = (NetworkStream)ar.AsyncState;
stream.EndWrite(ar);
}
catch (ObjectDisposedException) {
// client device was disconnected by the server before write completed
}
}
If a disconnect command is entered fast enough after the send command, the exception will be thrown. Obviously in this simple example, you could block until the write is complete, but what if you wanted to have the writes to occur asynchronously but still prevent disconnect commands from executing until any write commands have completed (or timed out)? I believe you could keep track of all the BeginWrite calls with waithandles, and utilize those to ensure writes complete before disconnecting, but that seems like a lot of work. Is there any way to know whether any thread is attempting to write to a given network stream?
You need to track manually the request, but it's more easy than you think, a simple concurrent dictionary would be enough for this:
//This class will store ongoing requests and also will be used as the async parameter
public class ExecutingRequest
{
public Guid Id { get; set; }
public NetworkStream Stream { get; set; }
}
//Somewhere in your server class
ConcurrentDictionary<Guid, ExecutingRequest> pendingRequests = new ConcurrentDictionary<Guid, ExecutingRequest>();
private void Send(string msg)
{
NetworkStream stream;
byte[ ] packetBuffer = Encoding.ASCII.GetBytes(msg);
stream = clientDevice.GetStream();
var request = new ExecutingRequest{ Stream = stream, Id = Guid.NewGuid() };
pendingRequests.AddOrUpdate(request.Id, request, (a,b) => request);
stream.BeginWrite(packetBuffer, 0, packetBuffer.Length, new AsyncCallback(StreamWriteCompleteCallback), request);
}
private void StreamWriteCompleteCallback(IAsyncResult ar)
{
try {
ExecutingRequest req = (ExecutingRequest)ar.AsyncState;
pendingRequests.TryRemove(req.Id, out ExecutingRequest dummy);
req.stream.EndWrite(ar);
}
catch (ObjectDisposedException)
{
// client device was disconnected by the server before write completed
}
}
Then, you can check if there is any request running by checking the length of the dictionary.
Related
I need to build an application (a server) that handles data sent from a Client via TCP. I must be able to support (at least) 2000 connections. I've made an attempt to write the TCP Server, but I find when I start to reach 600/700 connections, that the response from my server slows greatly (it actually slows down over time as more and more connections are received). I don't normally write networking code so I'm sure there are many concepts I've not fully comprehended and could be improved upon.
The main purpose of my server is to:
Handle data sent from clients and store it in a sql database.
Decide (based
upon the last message received) what the correct response should be to the client.
Queue up a list of responses and
send them to the client one at a time.
This needs to happen for all clients. Below is the code I have implemented:
private readonly TcpListener tcpListener;
private readonly Thread listenThread;
private bool run = true;
public Server()
{
this.tcpListener = new TcpListener(IPAddress.Any, AppSettings.ListeningPort); //8880 is default for me.
this.listenThread = new Thread(new ThreadStart(ListenForClients));
this.listenThread.Start();
}
private void ListenForClients()
{
this.tcpListener.Start();
while (run) {
TcpClient client = this.tcpListener.AcceptTcpClient();
//create a thread to handle communication with connected client
Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm));
clientThread.Start(client);
}
}
private void HandleClientComm(object client)
{
Queue responseMessages = new Queue();
while (run)
{
try
{
lastMessage = clientStream.GetMessage();
if (lastMessage.Length > 0)
{
// Process logic here...
//an item may be added to the response queue, depending on logic.
}
if (responseMessages.Count > 0)
{
clientStream.WriteMessage(msg);
clientStream.Flush();
// sleep for 250 milliseconds (or whats in the config).
Thread.Sleep(AppSettings.MessageSendDelayMilliseconds);
}
}
catch (Exception ex)
{
break;
}
}
tcpClient.Close();
}
And finally, here's an extension class I wrote to help me out:
namespace System.Net.Sockets
{
using System.Text;
public static class NetworkSteamExtension
{
private static readonly ASCIIEncoding Encoder = new ASCIIEncoding();
public static string GetMessage(this NetworkStream clientStream)
{
string message = string.Empty;
try
{
byte[] bMessage = new byte[4068];
int bytesRead = 0;
while (clientStream.DataAvailable)
{
bytesRead = clientStream.Read(bMessage, 0, 4068);
message += Encoder.GetString(bMessage, 0, bytesRead);
}
}
catch (Exception)
{
}
return message;
}
public static void WriteMessage(this NetworkStream clientStream, string message)
{
byte[] buffer = Encoder.GetBytes(message);
clientStream.Write(buffer, 0, buffer.Length);
clientStream.Flush();
}
}
}
Lots of articles on the subject people are using sockets instead. I've also read that .net 4.5 async / await is the best way to implement a solution.
I would like to make sure I take advantage of the newest features in .net (even 4.5.2 if it will help) and build a server that can handle at least 2000 connections. Can someone advise?
Many thanks!
OK, we need to fix some API usage errors and then the main problem of creating too many threads. It is well established that many connections can only be handled efficiently with async IO. Hundreds of connections counts as "too many".
Your client processing loop must be async. Rewrite HandleClientComm so that it uses async socket IO. This is easy with await. You need to search the web for ".net socket await".
You can continue to use synchronous accept calls. No downside there. Keep the simple synchronous code. Only make async those calls that have a significant avgWaitTime * countPerSecond product. That will be all socket calls, typically.
You are assuming that DataAvailable returns you the number of bytes in any given message. TCP does not preserve message boundaries. You need to do that youself. The DataAvailable value is almost meaningless because it can underestimate the true data that will arrive in the future.
It's usually better to use a higher level serialization protocol. For example, protobuf with length prefix. Don't use ASCII. You probably have done that only because you didn't know how to do it with a "real" encoding.
Dispose all resources using using. Don't use the non-generic Queue class. Don't flush streams, this does nothing with sockets.
Why are you sleeping?
I'm new with socket and trying to write a Client-Server application
My applicationhas those two main methods :
SERVER running on separate Thread :
public void socketListener()
{
byte[] StreamMessage = new byte[9632];
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint localEndPoint =new IPEndPoint(IPAddress.Any , ControlLayer.GlobalParam.PEER2PEER_PORT);
listener.Bind(localEndPoint);
listener.Listen(10);
while (true)
{
Socket Handler = listener.Accept();
//int ByteRec = Handler.Receive(StreamMessage);
int MessageLength;
MessageLength = Handler.Receive(StreamMessage, 0, StreamMessage.Length, SocketFlags.None);
//return MessageLength;
// string message = System.Text.Encoding.Default.GetString(StreamMessage);
string message = System.Text.Encoding.UTF8.GetString(StreamMessage);
OnDataRecievedFromRemotePeer(this, message, "TcpServer");//send data to screen
Task.Run(() => { ParseMessage(message, Handler); });
}
}
once data arrives I prase it collect data and send it using Client
CLIENT :
public void Write(string message)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(CreateClient), message);
}
private void CreateClient(object message)
{
try
{
peerClient = new TcpClient();
peerClient.Connect(remoteIP, 6001/*TODO remove this */);
netStream = peerClient.GetStream();//<- Exception
StreamWriter sw = new StreamWriter(netStream);
sw.Write((string)(message));
netStream.Close();
peerClient.Close();
}
catch(Exception ex)
{
//TODO :
}
}
Each station is symmetrical and have those two methods
I can tell that the server is working and accepting socket and data
but once I want to respond back I get exception in the Line marked in the CreateClient
stream was not writable and when looking on the netStream it is written that I have ObjectDisposed Exception .
What can be the cause of that ?
Also please inform me if more code is needed
You have a classical race here between the server closing the connection before the client has processed the response of the server.
TCP is a "polite" protocol, which means that you can not perform a fire and forget action on the server. The connection needs to be alive on both ends until both sides have processed all messages. Thus either the client needs to send an acknowledge/logout, so that the server can close the connection or at least the server has to wait x seconds until closing it.
I am trying to create a server that can accept both secure SSL and insecure plain text connection (for backwards compatibility). My code is almost working except the first transmitted data received from an insecure client loses the first 5 bytes (chars) at the server. More specificially if I transmit 30 bytes on an insecure connection, when the server gets to the OnClientDataReceived() function, the line "int iRx = nwStream.EndRead(asyn);", then iRx = 25. Any subsequent messages transmitted from the client contain all sent bytes/chars as expected. I suspect that the initial assumption of the connection being an SSLStream may be stripping the first 5 bytes and then when it fails, those 5 bytes have already been extracted from the buffer and are no longer available. Does any body know of another approach I could take to write the code so that the server automatically can switch on the fly?
I am trying to avoid doing the following:
Require that a client connect using a plain text NetworkStream and then request to upgrade to an SSL stream
Setting up two TcpListeners on two different ports (one for secure, one for insecure)
Here is my code:
/// Each client that connects gets an instance of the ConnectedClient class.
Class Pseudo_ConnectedClient
{
//Properties
byte[] Buffer; //Holds temporary buffer of read bytes from BeginRead()
TcpClient TCPClient; //Reference to the connected client
Socket ClientSocket; //The outer Socket Reference of the connected client
StringBuilder CurrentMessage; //concatenated chunks of data in buffer until we have a complete message (ends with <ETX>
Stream Stream; //SSLStream or NetworkStream depending on client
ArrayList MessageQueue; //Array of complete messages received from client that need to be processed
}
/// When a new client connects (OnClientConnection callback is executed), the server creates the ConnectedClient object and stores its
/// reference in a local dictionary, then configures the callbacks for incoming data (WaitForClientData)
void OnClientConnection(IAsyncResult result)
{
TcpListener listener = result.AsyncState as TcpListener;
TcpClient clnt = null;
try
{
if (!IsRunning) //then stop was called, so don't call EndAcceptTcpClient because it will throw and ObjectDisposedException
return;
//Start accepting the next connection...
listener.BeginAcceptTcpClient(this.onClientConnection, listener);
//Get reference to client and set flag to indicate connection accepted.
clnt = listener.EndAcceptTcpClient(result);
//Add the reference to our ArrayList of Connected Clients
ConnectedClient conClnt = new ConnectedClient(clnt);
_clientList.Add(conClnt);
//Configure client to listen for incoming data
WaitForClientData(conClnt);
}
catch (Exception ex)
{
Trace.WriteLine("Server:OnClientConnection: Exception - " + ex.ToString());
}
}
/// WaitForClientData registers the AsyncCallback to handle incoming data from a client (OnClientDataReceieved).
/// If a certificate has been provided, then it listens for clients to connect on an SSLStream and configures the
/// BeginAuthenticateAsServer callback. If no certificate is provided, then it only sets up a NetworkStream
/// and prepares for the BeginRead callback.
private void WaitForClientData(ConnectedClient clnt)
{
if (!IsRunning) return; //Then stop was called, so don't do anything
SslStream sslStream = null;
try
{
if (_pfnClientDataCallBack == null) //then define the call back function to invoke when data is received from a connected client
_pfnClientDataCallBack = new AsyncCallback(OnClientDataReceived);
NetworkStream nwStream = clnt.TCPClient.GetStream();
//Check if we can establish a secure connection
if (this.SSLCertificate != null) //Then we have the ability to make an SSL connection (SSLCertificate is a X509Certificate2 object)
{
if (this.certValidationCallback != null)
sslStream = new SslStream(nwStream, true, this.certValidationCallback);
else
sslStream = new SslStream(nwStream, true);
clnt.Stream = sslStream;
//Start Listening for incoming (secure) data
sslStream.BeginAuthenticateAsServer(this.SSLCertificate, false, SslProtocols.Default, false, onAuthenticateAsServer, clnt);
}
else //No certificate available to make a secure connection, so use insecure (unless not allowed)
{
if (this.RequireSecureConnection == false) //Then we can try to read from the insecure stream
{
clnt.Stream = nwStream;
//Start Listening for incoming (unsecure) data
nwStream.BeginRead(clnt.Buffer, 0, clnt.Buffer.Length, _pfnClientDataCallBack, clnt);
}
else //we can't do anything - report config problem
{
throw new InvalidOperationException("A PFX certificate is not loaded and the server is configured to require a secure connection");
}
}
}
catch (Exception ex)
{
DisconnectClient(clnt);
}
}
/// OnAuthenticateAsServer first checks if the stream is authenticated, if it isn't it gets the TCPClient's reference
/// to the outer NetworkStream (client.TCPClient.GetStream()) - the insecure stream and calls the BeginRead on that.
/// If the stream is authenticated, then it keeps the reference to the SSLStream and calls BeginRead on it.
private void OnAuthenticateAsServer(IAsyncResult result)
{
ConnectedClient clnt = null;
SslStream sslStream = null;
if (this.IsRunning == false) return;
try
{
clnt = result.AsyncState as ConnectedClient;
sslStream = clnt.Stream as SslStream;
if (sslStream.IsAuthenticated)
sslStream.EndAuthenticateAsServer(result);
else //Try and switch to an insecure connections
{
if (this.RequireSecureConnection == false) //Then we are allowed to accept insecure connections
{
if (clnt.TCPClient.Connected)
clnt.Stream = clnt.TCPClient.GetStream();
}
else //Insecure connections are not allowed, close the connection
{
DisconnectClient(clnt);
}
}
}
catch (Exception ex)
{
DisconnectClient(clnt);
}
if( clnt.Stream != null) //Then we have a stream to read, start Async read
clnt.Stream.BeginRead(clnt.Buffer, 0, clnt.Buffer.Length, _pfnClientDataCallBack, clnt);
}
/// OnClientDataReceived callback is triggered by the BeginRead async when data is available from a client.
/// It determines if the stream (as assigned by OnAuthenticateAsServer) is an SSLStream or a NetworkStream
/// and then reads the data out of the stream accordingly. The logic to parse and process the message has
/// been removed because it isn't relevant to the question.
private void OnClientDataReceived(IAsyncResult asyn)
{
try
{
ConnectedClient connectClnt = asyn.AsyncState as ConnectedClient;
if (!connectClnt.TCPClient.Connected) //Then the client is no longer connected >> clean up
{
DisconnectClient(connectClnt);
return;
}
Stream nwStream = null;
if( connectClnt.Stream is SslStream) //Then this client is connected via a secure stream
nwStream = connectClnt.Stream as SslStream;
else //this is a plain text stream
nwStream = connectClnt.Stream as NetworkStream;
// Complete the BeginReceive() asynchronous call by EndReceive() method which
// will return the number of characters written to the stream by the client
int iRx = nwStream.EndRead(asyn); //Returns the numbers of bytes in the read buffer
char[] chars = new char[iRx];
// Extract the characters as a buffer and create a String
Decoder d = ASCIIEncoding.UTF8.GetDecoder();
d.GetChars(connectClnt.Buffer, 0, iRx, chars, 0);
//string data = ASCIIEncoding.ASCII.GetString(buff, 0, buff.Length);
string data = new string(chars);
if (iRx > 0) //Then there was data in the buffer
{
//Append the current packet with any additional data that was already received
connectClnt.CurrentMessage.Append(data);
//Do work here to check for a complete message
//Make sure two complete messages didn't get concatenated in one transmission (mobile devices)
//Add each message to the client's messageQueue
//Clear the currentMessage
//Any partial messsage at the end of the buffer needs to be added to the currentMessage
//Start reading again
nwStream.BeginRead(connectClnt.Buffer, 0, connectClnt.Buffer.Length, OnClientDataReceived, connectClnt);
}
else //zero-length packet received - Disconnecting socket
{
DisconnectClient(connectClnt);
}
}
catch (Exception ex)
{
return;
}
}
What works:
If the server doesn't have a certificate, a NetworkStream is only used, and all bytes are received from the client for all messages.
If the server does have a certificate (an SSLStream is setup) and a secure connection can be established (web-browser using https://) and the full message is received for all messages.
What doesn't work:
If the server does have a certificate (an SSLStream is setup) and an insecure connection is made from a client, when the first message is received from that client, the code does correctly detect the SSLStream is not authenticated and switches to the NetworkStream of the TCPClient. However, when EndRead is called on that NetworkStream for the first message, the first 5 chars (bytes) are missing from the message that was sent, but only for the first message. All remaining messages are complete as long as the TCPClient is connected. If the client disconnects and then reconnects, the first message is clipped, then all subsequent messages are good again.
What is causing those first 5 bytes to be clipped, and how can I avoid it?
My project is currently using .NET v3.5... I would like to remain at this version and not step up to 4.0 if I can avoid it.
Follow-up Question
Damien's answer below does allow me to retain those missing 5 bytes, however, I would prefer to stick with the BeginRead and EndRead methods in my code to avoid blocking. Are there any good tutorials showing a 'best practices' when override(ing) these? More specifically, how to work with the IAsyncResult object. I get that I would need to add any content that is stored in the RestartableStream buffers, then fall through to the inner stream (base) to get the rest and return the toral. But since the IAsyncResult object is a custom class, I can't figure out the generic way that I can combine the buffs of RestartableStream with those of the inner stream before returning. Do I need to also implement BeginRead() so that I know the buffers the user wants the content stored into? I guess the other solution is, since the dropped bytes problem is only with the first message from the client (after that I know whether to use it as a SSLStream or a NetworkStream), would be to handle that first message by directly calling the Read() method of RestartableStream (temporarily blocking the code), then for all future messages use the Async callbacks to Read the contents as I do now.
Okay, I think the best you can do is place your own class in between SslStream and NetworkStream where you implement some customized buffering. I've done a few tests on the below, but I'd recommend a few more before you put in in production (and probably some more robust error handling). I think I've avoided any 4.0 or 4.5isms:
public sealed class RestartableReadStream : Stream
{
private Stream _inner;
private List<byte[]> _buffers;
private bool _buffering;
private int? _currentBuffer = null;
private int? _currentBufferPosition = null;
public RestartableReadStream(Stream inner)
{
if (!inner.CanRead) throw new NotSupportedException(); //Don't know what else is being expected of us
if (inner.CanSeek) throw new NotSupportedException(); //Just use the underlying streams ability to seek, no need for this class
_inner = inner;
_buffering = true;
_buffers = new List<byte[]>();
}
public void StopBuffering()
{
_buffering = false;
if (!_currentBuffer.HasValue)
{
//We aren't currently using the buffers
_buffers = null;
_currentBufferPosition = null;
}
}
public void Restart()
{
if (!_buffering) throw new NotSupportedException(); //Buffering got turned off already
if (_buffers.Count == 0) return;
_currentBuffer = 0;
_currentBufferPosition = 0;
}
public override int Read(byte[] buffer, int offset, int count)
{
if (_currentBuffer.HasValue)
{
//Try to satisfy the read request from the current buffer
byte[] rbuffer = _buffers[_currentBuffer.Value];
int roffset = _currentBufferPosition.Value;
if ((rbuffer.Length - roffset) <= count)
{
//Just give them what we have in the current buffer (exhausting it)
count = (rbuffer.Length - roffset);
for (int i = 0; i < count; i++)
{
buffer[offset + i] = rbuffer[roffset + i];
}
_currentBuffer++;
if (_currentBuffer.Value == _buffers.Count)
{
//We've stopped reading from the buffers
if (!_buffering)
_buffers = null;
_currentBuffer = null;
_currentBufferPosition = null;
}
return count;
}
else
{
for (int i = 0; i < count; i++)
{
buffer[offset + i] = rbuffer[roffset + i];
}
_currentBufferPosition += count;
return count;
}
}
//If we reach here, we're currently using the inner stream. But may be buffering the results
int ncount = _inner.Read(buffer, offset, count);
if (_buffering)
{
byte[] rbuffer = new byte[ncount];
for (int i = 0; i < ncount; i++)
{
rbuffer[i] = buffer[offset + i];
}
_buffers.Add(rbuffer);
}
return ncount;
}
public override bool CanRead
{
get { return true; }
}
public override bool CanSeek
{
get { return false; }
}
public override bool CanWrite
{
get { return false; }
}
//No more interesting code below here
public override void Flush()
{
throw new NotSupportedException();
}
public override long Length
{
get { throw new NotSupportedException(); }
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
}
Usage:
Construct a RestartableReadStream around your NetworkStream. Pass that instance to SslStream. If you decide that SSL was the wrong way to do things, call Restart() and then use it again however you want to. You can even try more than two strategies (calling Restart() between each one).
Once you've settled on which strategy (e.g. SSL or non-SSL) is correct, call StopBuffering(). Once it's finished replaying any buffers it had available, it will revert to just calling Read on its inner stream. If you don't call StopBuffering, then the entire history of reads from the stream will be kept in the _buffers list, which could add quite a bit of memory pressure.
Note that none of the above particularly accounts for multi-threaded access. But if you've got multiple threads calling Read() on a single stream (especially one that's network based), I wouldn't expect any sanity anyway.
I spent hours searching to not write a stream wrapper around NetworkStream and finally came across this and it worked for me.
MSDN SocketFlag.Peek
I kept finding suggestions to just write a wrapper or use separate ports, but I have a problem listening to authority or reason.
Here's my code. NLOLOL (No laughing out loud or lectures)
I haven't completely figured out if I need to Peek at more than the first byte for all scenarios.
Private Async Sub ProcessTcpClient(__TcpClient As Net.Sockets.TcpClient)
If __TcpClient Is Nothing OrElse Not __TcpClient.Connected Then Return
Dim __RequestBuffer(0) As Byte
Dim __BytesRead As Integer
Using __NetworkStream As Net.Sockets.NetworkStream = __TcpClient.GetStream
__BytesRead = __TcpClient.Client.Receive(__RequestBuffer, 0, 1, SocketFlags.Peek)
If __BytesRead = 1 AndAlso __RequestBuffer(0) = 22 Then
Await Me.ProcessTcpClientSsl(__NetworkStream)
Else
Await Me.ProcessTcpClientNonSsl(__NetworkStream)
End If
End Using
__TcpClient.Close()
End Sub
A brief synopsis of the situation:
I have a service that takes information in and sends replies out over Sockets. The connections are unsecured. I want to setup another service that can provide TLS to these connections - this new service would provide a single port and distribute the connections based on the client certificate provided. I don't want to use stunnel for a couple reasons, one being that it would require one forwarding port per receiving port.
The solution I'm currently trying to implement:
Essentially, I'm trying to couple an SslStream (incoming) with a NetworkStream (outgoing - could be a Socket, but I put it into a NetworkStream to match the incoming) and have the read/write operations linked for the two. This link would provide the flow between the client (over SSL/TLS) and the service (over an unsecured connection).
Here's the class I came up with to link these Streams:
public class StreamConnector
{
public StreamConnector(Stream s1, Stream s2)
{
StreamConnectorState state1 = new StreamConnectorState(s1, s2);
StreamConnectorState state2 = new StreamConnectorState(s2, s1);
s1.BeginRead(state1.Buffer, 0, state1.Buffer.Length, new AsyncCallback(ReadCallback), state1);
s2.BeginRead(state2.Buffer, 0, state2.Buffer.Length, new AsyncCallback(ReadCallback), state2);
}
private void ReadCallback(IAsyncResult result)
{
// Get state object.
StreamConnectorState state = (StreamConnectorState)result.AsyncState;
// Finish reading data.
int length = state.InStream.EndRead(result);
// Write data.
state.OutStream.Write(state.Buffer, 0, length);
// Wait for new data.
state.InStream.BeginRead(state.Buffer, 0, state.Buffer.Length, new AsyncCallback(ReadCallback), state);
}
}
public class StreamConnectorState
{
private const int BYTE_ARRAY_SIZE = 4096;
public byte[] Buffer { get; set; }
public Stream InStream { get; set; }
public Stream OutStream { get; set; }
public StreamConnectorState(Stream inStream, Stream outStream)
{
Buffer = new byte[BYTE_ARRAY_SIZE];
InStream = inStream;
OutStream = outStream;
}
}
The problem:
When the client is done sending information and disposes of the SslStream, the server doesn't have any sort of indication of whether or not this has happened. This StreamConnector class happily keeps running into eternity without throwing any sort of error, and I can't find any indicator that it should stop. (There is, of course, the fact that I get 0 length every time in ReadCallback, but I need to be able to provide long-running connections, so this isn't a good way to judge.)
Another potential issue is that the ReadCallback gets called even if no data is available. Not sure if that would be different if I were using a Socket directly instead of a stream, but it seems inefficient to keep running that code over and over again.
My questions:
1) Is there a way to tell if a Stream has been closed from the client side?
2) Is there a better way to do what I am trying to do?
2a) Is there a more efficient way to run the asynchronous read/write loop?
EDIT: Thanks, Robert. Turns out the loop kept getting called because I wasn't closing the Streams (due to not knowing how to tell when the Streams needed to be closed). I'm including the full code solution in case someone else runs into this issue:
/// <summary>
/// Connects the read/write operations of two provided streams
/// so long as both of the streams remain open.
/// Disposes of both streams when either of them disconnect.
/// </summary>
public class StreamConnector
{
public StreamConnector(Stream s1, Stream s2)
{
StreamConnectorState state1 = new StreamConnectorState(s1, s2);
StreamConnectorState state2 = new StreamConnectorState(s2, s1);
s1.BeginRead(state1.Buffer, 0, state1.Buffer.Length, new AsyncCallback(ReadCallback), state1);
s2.BeginRead(state2.Buffer, 0, state2.Buffer.Length, new AsyncCallback(ReadCallback), state2);
}
private void ReadCallback(IAsyncResult result)
{
// Get state object.
StreamConnectorState state = (StreamConnectorState)result.AsyncState;
// Check to make sure Streams are still connected before processing.
if (state.InStream.IsConnected() && state.OutStream.IsConnected())
{
// Finish reading data.
int length = state.InStream.EndRead(result);
// Write data.
state.OutStream.Write(state.Buffer, 0, length);
// Wait for new data.
state.InStream.BeginRead(state.Buffer, 0, state.Buffer.Length, new AsyncCallback(ReadCallback), state);
}
else
{
// Dispose of both streams if either of them is no longer connected.
state.InStream.Dispose();
state.OutStream.Dispose();
}
}
}
public class StreamConnectorState
{
private const int BYTE_ARRAY_SIZE = 4096;
public byte[] Buffer { get; set; }
public Stream InStream { get; set; }
public Stream OutStream { get; set; }
public StreamConnectorState(Stream inStream, Stream outStream)
{
Buffer = new byte[BYTE_ARRAY_SIZE];
InStream = inStream;
OutStream = outStream;
}
}
public static class StreamExtensions
{
private static readonly byte[] POLLING_BYTE_ARRAY = new byte[0];
public static bool IsConnected(this Stream stream)
{
try
{
// Twice because the first time will return without issue but
// cause the Stream to become closed (if the Stream is actually
// closed.)
stream.Write(POLLING_BYTE_ARRAY, 0, POLLING_BYTE_ARRAY.Length);
stream.Write(POLLING_BYTE_ARRAY, 0, POLLING_BYTE_ARRAY.Length);
return true;
}
catch (ObjectDisposedException)
{
// Since we're disposing of both Streams at the same time, one
// of the streams will be checked after it is disposed.
return false;
}
catch (IOException)
{
// This will be thrown on the second stream.Write when the Stream
// is closed on the client side.
return false;
}
}
}
You have to attempt to read or write to a socket -- or anything based on it -- to detect a disconnect.
Attempting to write will throw an exception/return an error (depending on your language's paradigm) or possibly just write 0 bytes. Attempting to read will either throw an exception/return an error (again depending on your language's paradigm) or return null.
It's worth noting that if you're using a select-based server model, the disconnected socket shows up -- i.e. returns select -- as readable when it disconnects, then you attempt to read from it and get the error or null.
I can't help but think your client ought to tell the server when it's done with some kind of message. It's always best to be prepared for a wire being cut or power failing or a plug being pulled, but generally you want to terminate a connection with some sort of end-of-message marker. Save the exceptions for real problems, not for normal conversations.
I have a TCP client/server app to communicate with a Windows CE device over an ActiveSync connection. Both the client and server utilize Asynchronous sockets (i.e. the Socket.Begin* and Socket.End* functions). When both the client and server are running on my desktop everything functions exactly as expected, but when the client is running on the Windows CE device that's connected over ActiveSync, I always get a SocketException on the ReceiveCallback after calling Socket.Shutdown (when the device is initiating the disconnect). The full exception is:
System.Net.Sockets.SocketException: An error message cannot be displayed because an optional resource assembly containing it cannot be found
at System.Net.Sockets.Socket.ReceiveNoCheck()
at ReceiveAsyncRequest.doRequest()
at AsyncRequest.handleRequest()
at WorkerThread.doWork()
at WorkerThread.doWorkI()
at WorkItem.doWork()
at System.Threading.Timer.ring()
Everything also seems to work correctly if the server (running on the desktop) initiates the disconnect. I have a couple ideas on how to avoid this, including disallowing device initiated disconnects and ignoring all exceptions after initiating a disconnect. However, I'd like to know why this is happening and if there's a better way of handling it.
The Disconnect and ReceiveCallbacks (operationally identical on both the server and client) are:
public bool Disconnect(StateObject state)
{
try{
if(state.isDisconnecting) return false;
state.isDisconnecting = true;
state.sock.Shutdown(SocketShutdown.Both);
//Wait for sending and receiving to complete, but don't wait more than 10 seconds
for(int i=0; i<100 && (state.isSending || state.isReceiving); i++){
System.Threading.Thread.Sleep(100);
}
/* Log the disconnect */
state.sock.Close();
return true;
} catch (Exception e){
/* Log the exception */
return false;
}
}
private void ReceiveCallback(IAsyncResult iar)
{
StateObject state = (StateObject)iar.AsyncState;
try{
//handle the new bytes in the buffer.
int recv = state.sock.EndReceive(iar);
//Handle the buffer, storing it somewhere and verifying complete messages.
if(recv > 0){
//start listening for the next message
state.sock.BeginReceive(state.recvBuffer, 0, StateObject.BUFFER_SIZE, SocketFlags.None, new AsyncCallback(ReceiveCallback), state);
} else {
state.isReceiving = false;
Disconnect(state);
}
} catch (Exception e){
/* Log the exception */
}
}
//For reference, A StateObject looks kinda like this:
public class StateObject
{
public const int BUFFER_SIZE = 1024;
public Socket sock = null;
public bool isSending = false;
public bool isReceiving = false;
public bool isDisconnecting = false;
public byte[] recvBuffer = new byte[BUFFER_SIZE];
public byte[] sendBuffer = new byte[BUFFER_SIZE];
//Other stuff that helps handle the buffers and ensure complete messages,
// even when TCP breaks up packets.
}
It in order to get the actual exception maybe try figure out which library it needs and deploy it?
If a message connot be shown on your device, because the message is not installed on your device. You have to install a netcf.messages.cab. You will find it here:
C:\Program Files (x86)\Microsoft.NET\SDK\CompactFramework\v3.5\WindowsCE\Diagnostics
After installing this CAB-File, run your application again and post the new error you get.