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.
Related
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 :).
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...
There is a code of async server. Client sends Header - size of Data Block + Data Block.
Server reads asynchronously first Header and then Data Block.
I need, after I read Data Block run the BeginRead for Header reading part, to make threads async.
PROBLEM:
When I got DataCallBack, in line:
int bytesRead = ns.EndRead(result);
I get not all buffer i asked to read in
mc.Client.GetStream().BeginRead(mc.DataBuffer, 0, size, new AsyncCallback(DataCallBack), mc);
If client send 1MB of Data I can get different number of "bytesRead".
QUESTION:
How to force "BeginRead" to read all data from connection. It should cause the new loop of Header - Data.
MyClient - simply wrapper over TcpClient;
CODE:
public void DoAcceptTcpClientCallback(IAsyncResult ar)
{
TcpListener listener = (TcpListener)ar.AsyncState;
TcpClient client = listener.EndAcceptTcpClient(ar);
client.NoDelay = false;
// client.ReceiveBufferSize = 1024*1024;
listener.BeginAcceptTcpClient(new AsyncCallback(DoAcceptTcpClientCallback), listener);
MyClient mc = new MyClient(client);
ContinueRead(0,mc);
}
public void ContinueRead(int size, MyClient mc)
{
if (size != 0)
{
mc.DataBuffer = new byte[size];
mc.Client.GetStream().BeginRead(mc.DataBuffer, 0, size, new AsyncCallback(DataCallBack), mc);
}
mc.Client.GetStream().BeginRead(mc.HeaderBuffer, 0, 4, new AsyncCallback(HeaderCallBack), mc);
}
private void HeaderCallBack(IAsyncResult result)
{
MyClient mc = (MyClient)result.AsyncState;
NetworkStream ns = mc.Stream;
int bytesRead = ns.EndRead(result);
if (bytesRead == 0)
throw new Exception();
mc.TotalLengs = BitConverter.ToInt32(mc.HeaderBuffer, 0);
ContinueRead(mc.TotalLengs, mc);
}
private void DataCallBack(IAsyncResult result)
{
MyClient mc = (MyClient)result.AsyncState;
NetworkStream ns = mc.Stream;
int bytesRead = ns.EndRead(result);
if (bytesRead == 0)
throw new Exception();
BAD CODE - MAKES ASYNC READING - SYNC
while (bytesRead < mc.TotalLengs)
{
bytesRead += ns.Read(mc.DataBuffer, bytesRead, mc.TotalLengs - bytesRead);
}
END BAD CODE
ContinueRead(0, mc);
ProcessPacket(mc.DataBuffer, mc.IP);
}
"If client send 1MB of Data I can get different number of "bytesRead"."
Yes...this is simply how TCP works under the hood. You can't change this. TCP guarantees the order of packets, not how they are grouped. The hardware and traffic conditions along the route the packets travel determine how that data is grouped (or un-grouped).
"How to force "BeginRead" to read all data from connection."
TCP has no idea how much data is being sent. As far as it is concerned, the connection is simply an endless stream of bytes; therefore it cannot read "all data" since there is no end to the data (from its perspective). TCP also has no notion of what a "complete message" is with respect to your application. It is up to you, the programmer, to develop a protocol that allows your application to know when all data has been sent.
If you are expecting a certain number of bytes, then keep a running sum of the values returned by EndRead() and stop when that magic number is hit.
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
hey I'm writing on an Server-Client program
but when my client sends something, it never reaches my server!
I'm sending like this:
public void Send(string s)
{
char[] chars = s.ToCharArray();
byte[] bytes = chars.CharToByte();
nstream.Write(bytes, 0, bytes.Length);
nstream.Flush();
}
and Receiving in a background thread like this
void CheckIncoming(object dd)
{
RecievedDelegate d = (RecievedDelegate)dd;
try
{
while (true)
{
List<byte> bytelist = new List<byte>();
System.Threading.Thread.Sleep(1000);
int ssss;
ssss = nstream.ReadByte();
if (ssss > 1)
{
System.Diagnostics.Debugger.Break();
}
if (bytelist.Count != 0)
{
d.Invoke(bytelist.ToArray());
}
}
}
catch (Exception exp)
{
MSGBOX("ERROR:\n" + exp.Message);
}
}
the ssss int is never > 1
whats happening here???
NetworkStream.Flush() actually has no effect:
The Flush method implements the Stream..::.Flush method; however, because NetworkStream is not buffered, it has no affect [sic] on network streams. Calling the Flush method does not throw an exception
How much data is being sent?
If you don't send enough data it may remain buffered at the network level, until you close the stream or write more data.
See the TcpClient.NoDelay property for a way to disable this, if you are only going to be sending small chunks of data and require low latency.
You should change the check of the return value to if (ssss >= 0).
ReadByte returns a value greater or equal 0 if it succeeds to read a byte (source).
To elaborate on Marc's comment: How is nstream created? Maybe there is an underlying class that does not flush.
well, Im creating a TcpClient, and use GetStream(); to get the NetworkStream