How can I efficiently receive & send data simultaneously using c# sockets - c#

I have a C# .net server & client (TCP). The server listens for multiple clients in an asynchronous manner. The clients can send data ranging from screenshots, to messages, to a videostream (really just multiple screenshots sent extremely fast which never works...), to system information. The problem that I'm facing is that when all of these are possibilites, the server can only receive so little. I can't seem to download a file from a client & receive a screenshot at the same time. Or when receiving multiple screenshots really fast to emulate screensharing, I can't download a file (this usually crashes the data receiving process.)
I don't even think my server is correctly receiving data for clients, as data often is corrupt and throws exceptions. I'll start with my server listening code itself:
public void Listen()
{
_socket.Listen(100);
_socket.BeginAccept(new AsyncCallback(AcceptCallback), null);
CSCLog("Server started || Listening...");
void AcceptCallback(IAsyncResult AR)
{
Socket socket = _socket.EndAccept(AR);
clienthandler client = new clienthandler(socket, _dash);
_socketClientList.Add(client);
// Repeat the listening process...
_socket.BeginAccept(new AsyncCallback(AcceptCallback), null);
}
}
The server holds a custom class, clienthandler, that requires a socket and provides individual socket "control." Here is the basic run down of that class:
public class clienthandler
{
public Socket _clientSocket;
public clienthandler(Socket paramSocket)
{
_clientSocket = paramSocket;
receiveAll();
}
public void receiveAll()
{
int BUFF_SIZE = 4024 * 2;
byte[] _lengthBuffer = new byte[BUFF_SIZE];
_clientSocket.BeginReceive(_lengthBuffer, 0, _lengthBuffer.Length, 0, new AsyncCallback(LengthCallBack), _clientSocket);
void LengthCallBack(IAsyncResult lengthAR)
{
try
{
Socket buffSocket = (Socket)lengthAR.AsyncState;
int lengthreceived = _clientSocket.EndReceive(lengthAR);
byte[] lengthdata = new byte[lengthreceived];
Array.Copy(_lengthBuffer, lengthdata, lengthreceived);
// Handle the received incoming data length
DataInformation datai = (DataInformation)ObjectHandler.Deserialize(_lengthBuffer);
int datalength = datai.datalength;
Array.Clear(_lengthBuffer, 0, _lengthBuffer.Length);
PrepCol(datai, buffSocket);
// Repeat the data length listening process...
_clientSocket.BeginReceive(_lengthBuffer, 0, _lengthBuffer.Length, 0, new AsyncCallback(LengthCallBack), _clientSocket);
}
catch (Exception ex) { // handle exception... }
}
}
}
To further explain, the receiveAll() function tries to asynchronously listen for data. I've created a custom class (these are all stored in a .DLL that the client and server share) called DataInformation that acts as a header for data being sent through the stream. It only includes an enum of an objectType & int datalength that signifies the amount of incoming data my client sends right afterwards (as any data sent from the client has its own DataInformation sent first, then immediately after is the actual data. Both are serialized.)
The PropCol() methods takes in the deserialized DataInformation class and socket object. It then collects data using the datainformation variables provided (the Enum & int datalength.):
public void PrepCol(DataInformation paramDatainformation, Socket paramBuffSocket)
{
int datalength = paramDatainformation.datalength;
object streamobj;
MemoryStream ms = new MemoryStream();
if(paramDatainformation.objectType == ObjectType.Universal)
{
// Prepare & collect the incoming data
while (datalength > 0)
{
byte[] buffer;
if (datalength < paramBuffSocket.ReceiveBufferSize)
{
buffer = new byte[datalength];
}
else
buffer = new byte[paramBuffSocket.ReceiveBufferSize];
int rec = paramBuffSocket.Receive(buffer, 0, buffer.Length, 0);
datalength -= rec;
ms.Write(buffer, 0, rec);
}
// Handle the collected data
ms.Close();
byte[] data = ms.ToArray(); ms.Dispose();
streamobj = ObjectHandler.Deserialize(data);
Array.Clear(data, 0, data.Length);
// Check the data that is received & determine type
CheckData(streamobj);
}
if(paramDatainformation.objectType == ObjectType.Screenshot)
{ // Receive data certain way }
if(paramDatainformation.objectType == ObjectType.TransferFile)
{ // Receive data certain way }
}
It utilizes a while loop to synchronously receive data until it has received the amount that the DataInformation says it needs. It takes the data object and passes it into the CheckData() method that requires an object. It takes the object and checks if it is any object it can handle (such as a screenshot, or message, ect...), if so then it does.
The problem I find the most is that when receiving large data, or data really fast, my DataInformation deseralization in the receiveAll() method returns corrupt/ invalid data which isn't good at all because I'm losing something that I could be needing.
My question really boils down to what am I doing wrong? Or how should I take the approach to receive multiple data simultaneously?
Any more information I could provide is that the Serialization & Desserialization methods are in the .DLL. That's really all. I apologize for dumping large amounts of code out, but felt like those were the most relevent things. Thank you for your time.

First of all, as mentioned in comments, you should consider your TCP socket as continous stream of data. Eventually, you'll end up in situation where you read uncomplete set of bytes, and being unable to deserialize it.
Said all the above, when you receive next portion of data from the socket, you need to know two things:
how much data you received so far;
how much data you need to begin the deserialization process.
Another point is that you are using old-fashioned callback-style asynchronous operations.
You can rewrite your code to use async/await-friendly methods, like Socket.ReceiveAsync().
And finally, don't use Socket in your logic. Use Stream instead.
Here is the approach I'd choose to receive and parse binary data according to some protocol. I assume that your protocol consists of some fixed-size header followed by the data (file/screenshot/video etc.). The size and type of data is stored in the header.
using System;
using System.IO;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
namespace Test1
{
public interface IData
{
}
public interface IHeader
{
int DataSize { get; }
}
public interface IHeaderParser
{
int HeaderSize { get; }
IHeader Parse(byte[] buffer, int offset);
}
public interface IDataParser
{
IData Parse(byte[] buffer, int offset);
}
public interface IDataParserSelector
{
IDataParser GetParser(IHeader header);
}
public class StreamReceiver : IDisposable
{
public StreamReceiver(
Stream stream,
IHeaderParser headerParser,
IDataParserSelector dataParserSelector)
{
_stream = stream;
_headerParser = headerParser;
_dataParserSelector = dataParserSelector;
}
public virtual void Dispose()
{
_stream.Dispose();
}
public async Task<IData> ReceiveAsync(CancellationToken token)
{
const int headerOffset = 0;
await ReadAsync(headerOffset, _headerParser.HeaderSize, token).ConfigureAwait(false);
var header = _headerParser.Parse(_buffer, headerOffset);
var dataOffset = headerOffset + _headerParser.HeaderSize;
await ReadAsync(dataOffset, header.DataSize, token).ConfigureAwait(false);
var dataParser = _dataParserSelector.GetParser(header);
var data = dataParser.Parse(_buffer, dataOffset);
return data;
}
private async Task ReadAsync(int offset, int count, CancellationToken token)
{
if (_buffer.Length < offset + count)
{
var oldBuffer = _buffer;
_buffer = new byte[offset + count];
Array.Copy(oldBuffer, _buffer, oldBuffer.Length);
}
var nread = 0;
while (nread < count)
{
nread += await _stream.ReadAsync(
_buffer, offset + nread, count - nread, token)
.ConfigureAwait(false);
}
}
private readonly Stream _stream;
private readonly IHeaderParser _headerParser;
private readonly IDataParserSelector _dataParserSelector;
private byte[] _buffer = new byte[0];
}
public class TcpReceiver : StreamReceiver
{
public TcpReceiver(
TcpClient tcpClient,
IHeaderParser headerParser,
IDataParserSelector dataParserSelector)
: base(tcpClient.GetStream(), headerParser, dataParserSelector)
{
_tcpClient = tcpClient;
}
public override void Dispose()
{
base.Dispose();
_tcpClient.Dispose();
}
private readonly TcpClient _tcpClient;
}
}
This is just a stub, I leave interface implementations up to you, if you ever consider using this approach.
Also, I didn't cover the connection process here, so it is up to you too :).

Related

Read buffer from NetworkStream in async callback

See also Understand the NetworkStream.EndRead() example from MSDN.
I'm trying to read asynchronously from a NetworkStream using the BeginRead and EndRead methods (specifically, I want to open a NetworkStream, do various other tasks, then process the data that's been received on the NetworkStream). The MSDN example for EndRead (here) has various issues and doesn't work as it stands, as noted in the linked question. The answer to that question shows how the basic code should be structured, but it doesn't include the actual reading of the data from the NetworkStream in the callback.
In the callback function, how do I transfer the data that's been read from the NetworkStream into a buffer?
EDIT
This is the original MSDN example.
public static void myReadCallBack(IAsyncResult ar )
{
NetworkStream myNetworkStream = (NetworkStream)ar.AsyncState;
byte[] myReadBuffer = new byte[1024];
String myCompleteMessage = "";
int numberOfBytesRead;
numberOfBytesRead = myNetworkStream.EndRead(ar);
myCompleteMessage = String.Concat(myCompleteMessage,Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead));
// message received may be larger than buffer size so loop through until you have it all.
while(myNetworkStream.DataAvailable)
{
myNetworkStream.BeginRead(myReadBuffer, 0, myReadBuffer.Length, new AsyncCallback(NetworkStream_ASync_Send_Receive.myReadCallBack), myNetworkStream);
}
// Print out the received message to the console.
Console.WriteLine("You received the following message : " + myCompleteMessage);
}
The basic problem with this example is that no data is transferred from the NetworkStream to myReadBuffer.
EDIT 1
Thanks to Dmytro Mukalov, the following code works:
internal class CallbackArgs
{
public NetworkStream Stream { get; set; }
public byte[] Buffer { get; set; }
public CallbackArgs(NetworkStream stream, byte[] buffer)
{
Stream = stream;
Buffer = buffer;
}
}
// In the caller:
// (Create and open thisNetworkStream, and check thisNetworkStream.CanRead)
byte[] thisReadBuffer = new byte[1024];
CallbackArgs args = new CallbackArgs(thisNetworkStream, thisReadBuffer);
thisNetworkStream.BeginRead(thisReadBuffer, 0, thisReadBuffer.Length, new AsyncCallback(myReadCallBack), args);
// The callback function:
public static void myReadCallBack(IAsyncResult ar)
{
CallbackArgs args = (CallbackArgs)ar.AsyncState;
NetworkStream myNetworkStream = args.Stream;
byte[] myReadBuffer = args.Buffer;
// myReadBuffer now contains the data read from the network stream.
int bytesRead = myNetworkStream.EndRead(ar);
// Do work on myReadBuffer, etc.
}
When callback is being executed, data is already transferred into a buffer passed to preceding BeginRead call. The problem with given example that it's trying to use local myReadBuffer buffer to read initial message. Instead you should make the buffer passed to BeginRead availaible for EndRead. You can do it by making it instance member of a class for this buffer, by passing it along with NetworkStream as state argument of BeginRead, using closure variable in some method which would initiate the reading loop, etc.

How to write to MemoryStream if many messages are sent

Here is how i read data from my stream now:
public List<ServerClient> clients = new List<ServerClient>();
while (true)
{
Update();
}
private void Update()
{
//Console.WriteLine("Call");
if (!serverStarted)
{
return;
}
foreach (ServerClient c in clients.ToList())
{
// Is the client still connected?
if (!IsConnected(c.tcp))
{
c.tcp.Close();
disconnectList.Add(c);
Console.WriteLine(c.connectionId + " has disconnected.");
CharacterLogout(c.connectionId);
continue;
//Console.WriteLine("Check for connection?\n");
}
else
{
// Check for message from Client.
NetworkStream s = c.tcp.GetStream();
if (s.DataAvailable)
{
string data = c.streamReader.ReadLine();
if (data != null)
{
OnIncomingData(c, data);
}
}
//continue;
}
}
for (int i = 0; i < disconnectList.Count - 1; i++)
{
clients.Remove(disconnectList[i]);
disconnectList.RemoveAt(i);
}
}
When data is read it is send to OnIncomingData function which is processing the data. I don't have problems there.
Here is how i send data to the stream:
public void Send(string header, Dictionary data)
{
if (stream.CanRead)
{
socketReady = true;
}
if (!socketReady)
{
return;
}
JsonData SendData = new JsonData();
SendData.header = "1x" + header;
foreach (var item in data)
{
SendData.data.Add(item.Key.ToString(), item.Value.ToString());
}
SendData.connectionId = connectionId;
string json = JsonConvert.SerializeObject(SendData);
var howManyBytes = json.Length * sizeof(Char);
writer.WriteLine(json);
writer.Flush();
Debug.Log("Client World:" + json);
}
Here is my:
public class ServerClient
{
public TcpClient tcp;
public int accountId;
public StreamReader streamReader;
public int connectionId;
public ServerClient(TcpClient clientSocket)
{
tcp = clientSocket;
}
}
Here is my OnConnection function:
private void OnConnection(IAsyncResult ar)
{
connectionIncrementor++;
TcpListener listener = (TcpListener)ar.AsyncState;
NetworkStream s = clients[clients.Count - 1].tcp.GetStream();
clients.Add(new ServerClient(listener.EndAcceptTcpClient(ar)));
clients[clients.Count - 1].connectionId = connectionIncrementor;
clients[clients.Count - 1].streamReader = new StreamReader(s, true);
StartListening();
//Send a message to everyone, say someone has connected!
Dictionary<string, string> SendDataBroadcast = new Dictionary<string, string>();
SendDataBroadcast.Add("connectionId", clients[clients.Count - 1].connectionId.ToString());
Broadcast("001", SendDataBroadcast, clients, clients[clients.Count - 1].connectionId);
Console.WriteLine(clients[clients.Count - 1].connectionId + " has connected.");
}
Normally everything works fine. However if i try to send more request per 1 second the problem occurs. The message received is not full and complete. It just receives a portion of the message sent.
From Debug.Log("Client World:" + json); i can see that the message is full and complete but on the server i see that it is not.
This is not happening if i send less requests.
So for that reason i think i should create a MemoryStream and puts a message there and read it after. However i'm really not sure how i can do that. Can you help ?
The whole code is not very good, but I'll concentrate on your specific problem. It's most likely related to data buffering by StreamReader. StreamReader has buffer size (which you can pass to constructor) which defaults to 1024 bytes. When you call ReadLine - it's perfectly possible for stream reader to read more than one line from the underlying stream. In your case - you have while loop in which you enumerate connected clients and in every iteration of the loop you create new StreamReader and read one line from it. When message rate is low - all looks fine, because between your loop iterations only one line arrives. Now suppose client quickly sent 2 json messages, each of which is 800 bytes, and they both arrived into your socket. Now you call StreamReader.ReadLine. Because buffer size is 1024 - it will read 1024 bytes from socket (NetworkStream) and return first 800 to you (as a line). You process that line and discard StreamReader going to the next iteration of your while loop. By doing that you also discard part of the message (224 bytes of the next message), because they were already read from the socket into StreamReader buffer. I think from that it should be clear how to solve that problem - don't create new StreamReader every time but create one per client (for example store as a member of ServerClient) and use that.
The client looks more suspicious to me than the server.
StreamWriter is not thread-safe. Are you calling it in a thread-safe manner when using ClientWorldServer.Send? Lock up or queue your calls to ClientWorldServer.Send using a lock or BlockingCollection or some other synchronisation primitive. There is also a thread-safe wrapper of streamwriter you might be able to use.

Most Efficient Way To Consolidate Multiple Byte Arrays Received Over Async Network?

Basically, what I'm looking to do is find an effective means of consolidating large amounts of data that would be too large for a suitable buffer size.
For something like an instant messenger setting a fixed buffer size is a fine solution as most people accept that instant messages tend to have a limit.
However if I were to want to send a whole text document multiple pages long, you would not want to have to send it 2048 characters at a time. Or whatever you define as the limit.
I've represented my current solution in some pseudo code:
public class Pseudo
{
public const int m_BufferSize = 255;
public void SendAllData(Socket socket, byte[] data)
{
int byteCount = data.Length;
if (byteCount <= m_BufferSize)
{
Socket.Send(data);
}
else if (byteCount > m_BufferSize)
{
int wholeSegments = Math.Floor(byteCount / m_BufferSize);
int remainingBytes = byteCount % m_BufferSize;
int bytesSent = 0;
int currentSegment = 1;
string id = Guid.NewGuid();
byte[] tempData = data;
//Send initial packet creating the data handler object.
Socket.SendInitial(id);
while (bytesSent < byteCount)
{
if (currentSegment <= wholeSegments)
{
Socket.Send(tempData[m_BufferSize]);
tempData.CopyTo(tempData, m_BufferSize);
bytesSent += m_BufferSize;
}
else
{
Socket.Send(tempData[remainingBytes]);
bytesSent += remainingBytes;
}
currentSegment++;
}
//Let The Server Know Send Is Over And To Consolidate;
Socket.SendClose(id);
}
}
internal class DataHandler
{
string m_Identity;
List<byte[]> m_DataSegments = new List<byte[]>();
static Dictionary<string, DataHandler>
m_HandlerPool = new Dictionary<string, DataHandler>();
public DataHandler(string id)
{
m_Identity = id;
if (!m_HandlerPool.ContainsKey(id))
{
m_HandlerPool.Add(this);
}
}
public void AddDataSegment(byte[] segment)
{
m_DataSegments.Add(segment);
}
public byte[] Consolidate(string id)
{
var handler = m_HandlerPool(id);
List<byte> temp = new List<byte[]>();
for (int i = handler.m_DataSegments.Length; i >= 0; i--)
{
temp.Add(handler.m_DataSegments[i]);
}
Dispose();
return temp;
}
void Dispose()
{
m_DataSegments = null;
m_HandlerPool.Remove(this);
}
}
}
Basically what this is doing is assigning an identifier to individual packets so that they can be using AsyncEventArgs, as the may not necessarily all be received without being interrupted so I can't really rely on index.
These are then stored in the object 'DataHandler' and consolidated into a single byte array.
The problem is, as you can tell, it's going to add a lot of overhead in what I had hoped to be a high-performance socket server. Even if I were to pool the handler objects, the whole thing feels crufty.
Edit: It's also going to require a delimiter which I really don't want to use.
So, what would be the most efficient way of accomplishing this?
Edit: Example code for the method of processing data, this came from one of the async code projects.
internal void ProcessData(SocketAsyncEventArgs args)
{
// Get the message received from the client.
String received = this.sb.ToString();
Console.WriteLine("Received: \"{0}\". The server has read {1} bytes.", received, received.Length);
Byte[] sendBuffer = Encoding.ASCII.GetBytes(received);
args.SetBuffer(sendBuffer, 0, sendBuffer.Length);
// Clear StringBuffer, so it can receive more data from a keep-alive connection client.
sb.Length = 0;
this.currentIndex = 0;
}
This is populating the user token data which is what is referenced by this.
// Set return buffer.
token.ProcessData(e);
I assume you're using TCP/IP - and if so, I don't understand the problem you're trying to fix. You can keep sending data as long as the connection is stable. Under the hood, TCP/IP will automatically create numbered packets for you, and ensure they arrive in the same order they were sent.
On the receiving end, you will have to read to a buffer of a certain size, but you can immediately write the received data to a MemoryStream (or FileStream if you intend to store the data on disk).
Usually high performance server:
use async send/receive methods
don't store all received data in memory (if you want to store many data use some blob storage services)
if you need, you can store some unordered pieces in some reusable context, and resend it to storage as soon as posible
reuse buffers, don't allocate new buffer on each receive/send action
etc...

How to allow a Server to accept both SSL and plain text (insecure) connections?

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

Is it possible to detect if a Stream has been closed by the client?

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.

Categories