I'm having some issues when I really stress test my networking code. Essentially once the socket is set up it calls this:
NetworkStream networkStream = mClient.GetStream();
networkStream.BeginRead(buffer, 0, buffer.Length, ReadCallback, buffer);
private void ReadCallback(IAsyncResult result)
{
try
{
int read;
NetworkStream networkStream;
try
{
networkStream = mClient.GetStream();
read = networkStream.EndRead(result);
}
catch
{
return;
}
if (read == 0)
{
//The connection has been closed.
return;
}
var readBuffer = (byte[])result.AsyncState;
var readCount = readBuffer.Length;
while (readCount < 4)
{
readCount += networkStream.Read(readBuffer, 0, readBuffer.Length - readCount);
}
var length = BitConverter.ToInt32(readBuffer, 0);
var messageBuffer = new byte[length];
readCount = 0;
while (readCount < length)
{
readCount += networkStream.Read(messageBuffer, 0, messageBuffer.Length - readCount);
}
else
{
RaiseMessageReceived(this, messageBuffer);
}
//Then start reading from the network again.
readBuffer = new byte[4]; //may not need to reset, not sure
networkStream.BeginRead(readBuffer, 0, readBuffer.Length, ReadCallback, readBuffer);
}
catch(Exception)
{
//Connection is dead, stop trying to read and wait for a heal to retrigger the read queue
return;
}
}
Then the below is my send methods
private byte[] GetMessageWithLength(byte[] bytes)
{
//Combine the msg length to the msg
byte[] length = BitConverter.GetBytes(bytes.Length);
var msg = new byte[length.Length + bytes.Length];
Buffer.BlockCopy(length, 0, msg, 0, length.Length);
Buffer.BlockCopy(bytes, 0, msg, length.Length, bytes.Length);
return msg;
}
public override bool Send(byte[] bytes)
{
lock (sendQueue)
{
sendQueue.Enqueue(bytes);
Interlocked.Increment(ref sendQueueSize);
}
if (!mClient.Connected)
{
if (Connect())
{
RaiseConnectionChanged(this, true, Localisation.TCPConnectionEstablished);
}
else
{
RaiseConnectionChanged(this, false, (bytes.Length > 0 ? Localisation.TCPMessageFailed : Localisation.TCPMessageConnectionLost));
}
}
try
{
NetworkStream networkStream = mClient.GetStream();
lock (sendQueue)
{
if (sendQueue.Count == 0)
{
return true;
}
bytes = sendQueue.Dequeue();
}
var msg = GetMessageWithLength(bytes);
//Start async write operation
networkStream.BeginWrite(msg, 0, msg.Length, WriteCallback, null);
}
catch (Exception ex)
{
RaiseConnectionChanged(this, false, (bytes.Length > 0 ? Localisation.TCPMessageFailed : Localisation.TCPMessageConnectionLost));
}
return true;
}
/// <summary>
/// Callback for Write operation
/// </summary>
/// <param name="result">The AsyncResult object</param>
private void WriteCallback(IAsyncResult result)
{
try
{
NetworkStream networkStream = mClient.GetStream();
while (sendQueue.Count > 0)
{
byte[] bytes;
lock (sendQueue)
{
if (sendQueue.Count == 0)
{
break;
}
bytes = sendQueue.Dequeue();
}
var msg = GetMessageWithLength(bytes);
networkStream.Write(msg, 0, msg.Length);
Interlocked.Decrement(ref sendQueueSize);
}
networkStream.EndWrite(result);
mLastPacketSentAt = Environment.TickCount;
Interlocked.Decrement(ref sendQueueSize);
}
catch (Exception ex)
{
RaiseConnectionChanged(this, false, Localisation.TCPMessageConnectionLost);
}
}
But yea, at some point when I stress test the system (say 500 or so clients sending lots of messages at once), I notice maybe 1 packet in every 4 million to just not get recieved. I'm not sure if the issue lies in the sending or the recieving, which is why I have included both methods. However I will point out that if I choose to send another packet from the client, it still sends and receives correctly, so it is not just queued or something.
Can anyone see something I am missing?
The two read loops (e.g. while (readCount < length)) are buggy. You always read at offset zero. You should read at an ever-increasing offset.
This lead to overwriting of already-read data.
Also, I'm not sure if it is a good idea to mix synchronous and asynchronous reads. You lose the benefit of asynchronous code that way and still have to deal with callbacks and such. I think you should decide on one style and stick to it.
Related
I'm working on a multiplayer game and I'm having an issue with the the way I parse the packets from the connection. When I'm debugging the game it runs at a lower performance and the packets are received, when I'm not, packets aren't fully received and the ParsePacket method isn't called.
My packet structure is this:
2 Bytes Short Command, 2 Bytes Short Payload Size, (Optional) Payload Bytes
IInputStream inputStream = null;
DataReader dataReader = null;
byte[] data = new byte[1024];
IBuffer buffer = data.AsBuffer();
try
{
inputStream = StreamSocket.InputStream;
dataReader = DataReader.FromBuffer(buffer);
dataReader.InputStreamOptions = InputStreamOptions.Partial;
dataReader.ByteOrder = ByteOrder.LittleEndian;
while (connected)
{
await inputStream.ReadAsync(buffer, 1024, InputStreamOptions.Partial);
Debug.WriteLine("Buffer " + buffer.Length);
if (buffer.Length >= PacketHeaderSize)
{
short command = dataReader.ReadInt16();
short payloadSize = dataReader.ReadInt16();
byte[] payload = null;
if (payloadSize == 0)
{
UpdateBuffer(buffer, (uint)(PacketHeaderSize + payloadSize));
Packet packet = new Packet(command, payloadSize, payload);
ParsePacket(packet);
}
else if (payloadSize > 0)
{
if (buffer.Length >= (PacketHeaderSize + payloadSize))
{
payload = new byte[payloadSize];
dataReader.ReadBytes(payload);
UpdateBuffer(buffer, (uint)(PacketHeaderSize + payloadSize));
Packet packet = new Packet(command, payloadSize, payload);
ParsePacket(packet);
}
}
}
}
}
catch (Exception e) {
// ...
}
private void UpdateBuffer(IBuffer buffer, uint bytesRead)
{
if (buffer.Length > bytesRead)
{
byte[] bufferBytes = new byte[buffer.Length - bytesRead];
System.Buffer.BlockCopy(buffer.ToArray(), (int)bytesRead, bufferBytes, 0, (int)(buffer.Length - bytesRead));
buffer = bufferBytes.AsBuffer();
}
else
{
byte[] bufferBytes = new byte[1024];
buffer = bufferBytes.AsBuffer();
}
}
What I'm doing wrong?
Things fixed to make this work:
Take important variables outside the parsing packets loop as we need the value next time we need to parse another packet.
Read the packet header once if a incomplete app or game packet is received.
Load only the bytes we need
Read the header and payload once it has been fully received using UnconsumedBufferLength.
Code:
short command = 0;
short payloadSize = 0;
byte[] payload = null;
bool packetHeaderRead = false;
while (connected)
{
if (!packetHeaderRead)
{
if (dataReader.UnconsumedBufferLength < PacketHeaderSize)
{
int headerBytesLeft = PacketHeaderSize - (int)dataReader.UnconsumedBufferLength;
if (headerBytesLeft > 0)
{
await dataReader.LoadAsync((uint)headerBytesLeft);
continue;
}
}
else
{
command = dataReader.ReadInt16();
payloadSize = dataReader.ReadInt16();
packetHeaderRead = true;
continue;
}
}
else
{
int payloadBytesLeft = payloadSize - (int)dataReader.UnconsumedBufferLength;
if (payloadBytesLeft > 0)
{
await dataReader.LoadAsync((uint)payloadBytesLeft);
}
if (payloadSize == 0)
{
Packet packet = new Packet(command, payloadSize, payload);
ParsePacket(packet);
packetHeaderRead = false;
}
else if (dataReader.UnconsumedBufferLength >= payloadSize)
{
payload = new byte[payloadSize];
dataReader.ReadBytes(payload);
Packet packet = new Packet(command, payloadSize, payload);
ParsePacket(packet);
packetHeaderRead = false;
}
}
}
I have the following method :
private void ClientReadCallback(IAsyncResult asyncResult)
{
try
{
var networkStream = _client.GetStream();
var read = networkStream.EndRead(asyncResult);
if (read == 0)
{
Disconnected?.Invoke(this, new EventArgs());
}
byte[] buffer = asyncResult.AsyncState as byte[];
if (buffer != null)
{
byte[] data = new byte[read];
Buffer.BlockCopy(buffer, 0, data, 0, read);
networkStream.BeginRead(buffer, 0, buffer.Length, ClientReadCallback, buffer);
DataRead?.Invoke(this, new DataReadEventArgs(data));
}
}
catch (Exception ex)
{
GetLog().Error(ex);
ClientReadException?.Invoke(this, new ExceptionEventArgs(ex));
}
}
That triggers a : System.ObjectDisposedException: Cannot access a disposed object at some points.
My question is : in which situation does this happens? network issue? How to handle this properly ?
thanks
I am reading a .bin file and writing it into a Stream. Later, I am reading that stream object and then writing it into a Network Stream. Code is as following:
public async Task<bool> UploadFirmware(Stream _stream)
{
bool success = false;
try
{
_tcpclient = new TcpClient();
_tcpclient.Connect(_deviceip, port);
_stream.Seek(0, SeekOrigin.Begin);
m_NetworkStream = _tcpclient.GetStream();
byte[] buffer = new byte[1024];
m_ReadBuffer = new byte[1024];
int readcount = 0;
m_NetworkStream.BeginRead(m_ReadBuffer, 0, m_ReadBuffer.Length, new AsyncCallback(EndReceive), null);
await Task.Run(() =>
{
while ((readcount = _stream.Read(buffer, 0, buffer.Length)) > 0)
{
m_NetworkStream.Write(buffer, 0, readcount);
m_NetworkStream.Flush();
}
});
success = true;
}
catch (Exception ex)
{}
return success;
}
Normally, this code works fine, but sometimes on an IP Address, the code gets stuck at m_NetworkStream.Write(buffer, 0, readcount);. The thing is, I am updating the status in UI based on success value, but the code gets hanged at above mentioned line and doesn't come out at all. No exception is thrown at all to identify the issue. So, in UI the status is not updated, and unexpected result is produced. I am not able to identify the issue. Help of any kind will be highly appreciated.
EDIT:
Also, I have to do an operation in parallel. The code for EndReceive is as follows:
private void EndReceive(IAsyncResult ar)
{
try
{
int nBytes;
nBytes = m_NetworkStream.EndRead(ar);
if (nBytes > 0)
{
string res = Encoding.UTF8.GetString(m_ReadBuffer, 0, nBytes);
DeviceStatus status = new DeviceStatus();
string[] readlines = res.Split(new string[] { CRLF }, StringSplitOptions.RemoveEmptyEntries);
foreach (string readline in readlines)
{
if (readline.StartsWith("CODE"))
{
status.code = Convert.ToInt32(readline.Replace("CODE=", ""));
break;
}
}
status.deviceip = this._deviceip;
status.port = this.port;
status.DeviceID = this._DeviceID;
status.FirmwareID = this._FirmwareID;
status.FilePath = this._Path;
StatusUpdate(status);
m_ReadBuffer = new byte[1024];
}
}
catch (ObjectDisposedException ods)
{
return;
}
if (_tcpclient.Connected)
m_NetworkStream.BeginRead(m_ReadBuffer, 0, m_ReadBuffer.Length, new AsyncCallback(EndReceive), null);
}
I'm not sure you should have a while loop in order to write bytes read from a file (from disk) to a network stream ... you can just read all the bytes and write to the stream and flush in one move.
You can also add a write timeout to specify how much time can pass before the stream write operation fails, to prevent any possibility of 'hanging'.
With these modifications, the code would look something like this:
// make the tcp connection to the remote endpoint
_tcpclient = new TcpClient();
_tcpclient.Connect(_deviceip, port);
// read the file bytes in one operation
var allBytes = File.ReadAllBytes(fileNameOnDisk);
// get the network stream
m_NetworkStream = _tcpclient.GetStream();
// wait a max of 500ms for the write operation to happen
m_NetworkStream.WriteTimeout = 500;
// write the file bytes to the stream and flush without while/stream/seek etc.
m_NetworkStream.Write(allBytes, 0, allBytes.Length);
m_NetworkStream.Flush();
And when you've finished with the stream:
m_NetworkStream.Close();
m_NetworkStream.Dispose();
It seem odd that you starting to read from network stream (m_NetworkStream.BeginRead(...)) and right away in another thread starting to write into same stream (m_NetworkStream.Write(...)). I would suggest to finish reading first and then start writing. Also you could use Stream.CopyTo to copy data between streams.
public async Task<bool> UploadFirmware(Stream fileStream, IPEndPoint deviceEP)
{
bool success = false;
try
{
TcpClient client = new TcpClient();
client.Connect(deviceEP);
NetworkStream networkStream = client.GetStream();
BeginReadFromDevice(networkStream);
// send bin data to device
await fileStream.CopyToAsync(networkStream);
success = true;
}
catch (Exception)
{
}
return success;
}
private void BeginReadFromDevice(Stream networkStream)
{
byte[] buffer = new byte[1024];
networkStream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(EndReceive), null);
}
Alright so for my game, Ive set up a server / client peer to peer connection, to send positions and etc back and forth.
Although my messages arent actually sending that fast, and not reliably either. As in parts of the strings are missing, and sometimes the sending just halts and the thread doesnt continue ( not sure why ).t
Anyways my Recieving code is here :
public void RecieveAsync()
{
if (netStream == null) netStream = Server.GetStream();
if (netStream.DataAvailable == false) return;
netStream.BeginRead(ReadBuffer, 0, ReadBuffer.Length, new AsyncCallback(recieveCallBack), netStream);
}
public void recieveCallBack(IAsyncResult ar)
{
//try
//{
String content = String.Empty;
Console.WriteLine("Stuck trying to get data");
int rec = netStream.EndRead(ar);
if (rec > 0)
{
Console.WriteLine(Encoding.ASCII.GetString(
ReadBuffer, 0, rec));
string packet = Encoding.ASCII.GetString(
ReadBuffer, 0, rec);
bool completedPacket = false;
int appendTo = rec;
if (packet.Contains("<eof>"))
{
appendTo = packet.IndexOf("<eof>");
packet.Replace("<eof>", "");
completedPacket = true;
}
SB.Append(packet, 0, appendTo);
// Check for end-of-file tag. If it is not there, read
// more data.
content = SB.ToString();
if (completedPacket)
{
// All the data has been read from the
// client. Display it on the console.
if (DataRecieved != null)
{
string RecievedData = SB.ToString();
DataRecieved(RecievedData);
netStream.Flush();
Array.Clear(ReadBuffer, 0, ReadBuffer.Length);
ReadBuffer = new byte[1024];
}
SB.Clear();
// Echo the data back to the client.
}
else
{
// Not all data received. Get more.
Array.Clear(ReadBuffer, 0, ReadBuffer.Length);
ReadBuffer = new byte[1024];
netStream.BeginRead(ReadBuffer, 0, ReadBuffer.Length, recieveCallBack, netStream);
}
}
}
And my sending code here :
public void Send(byte[] data, int index, int length)
{
//add data as state
//socket.NoDelay = true;
if (netStream == null) netStream = TcpClient.GetStream();
netStream.BeginWrite(data, 0, length, sendCallback, netStream);
}
private void sendCallback(IAsyncResult ar)
{
//try
//{
netStream.EndWrite(ar);
//if (ar.AsyncState != null)
//{
// byte[] buffer = (byte[])ar.AsyncState;
// socket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, sendCallback, null);
// return;
//}
if (OnSend != null)
{
OnSend(this);
}
netStream.Flush();
//catch (Exception ex)
//{
// System.Windows.Forms.MessageBox.Show(ex.ToString());
// return;
//}
}
The packets are under the Encoding.ASCII.Getbytes.
And both the server and client are updating in while (true) threads with a Thread.Sleep(1).
Because you're trying to reconstitute a string bit by bit (this approach will break if you use more common multibyte encoding such as UTF8), your approach to this is fragile.
As stated in my comment, you might well miss your <eof> because it is split over two reads.
IMO, a preferable approach is to tag your message with a preceding length field (4 byte int) so that you don't have to tiptoe around at the end of the message trying to figure out if it's finished yet.
Just pour all your reads into a MemoryStream until you reach the indicated message length, then decode the contents of the MemoryStream with whatever encoding you see fit.
I have a simple tcp/ip chat program with a server and client. The first time I send a packet, it makes it to the client but during the NetworkStream.Read it stops execution and doesn't throw an exception. The next packet I send is read and processed perfectly. Another weird thing I noticed is that MyNetworkStream.DataAvailable is always false even if I get information from the server so I have to put a debug symbol and skip over it. I wish I could post all my code but it is long so I will post where I read and write to the network stream.
public void Listen(int byteLength)
{
var buffer = new byte[byteLength];
MySocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback(Read), buffer);
}
private void Read(IAsyncResult ar)
{
while (MySocket.Connected)
{
MyNetworkStream = new NetworkStream(MySocket);
var buffer = new byte[((byte[])ar.AsyncState).Length];
if (!MyNetworkStream.DataAvailable)
throw new Exception("Data not available");
MyNetworkStream.Read(buffer, 0, buffer.Length); <------Here it stops execution without throwing an exception
string content = Encoding.ASCII.GetString(buffer);
if(OnRead == null)
continue;
var e = new CommandEventArgs( null, content);
Control target = null;
if (OnRead.Target is Control)
target = (Control)OnRead.Target;
if (target != null && target.InvokeRequired)
target.Invoke(OnRead, this, e);
else
OnRead(this,e);
}
}
public void Write(string message)
{
try
{
var buffer = Encoding.ASCII.GetBytes(message);
MySocket.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, null, null);
if (OnWrite != null)
{
var target = (Control)OnWrite.Target;
if (target != null && target.InvokeRequired)
{
target.Invoke(OnWrite, this, new EventArgs());
}
else
{
OnWrite(this, new EventArgs());
}
}
}
catch
{
}
}
BeginReceive asynchronously waits for a message and fills your buffer. You then start synchronously reading from the socket, overwriting the first message in the process.
You should call EndReceive which returns the number of bytes read, then process your buffer before trying to read more bytes.
I'm not sure if it's directly related to the problem, but you are using the Read method wrong. You are reading data into the buffer, but you are ignoring how much data was actually read assuming that the Read call always returns as much data as you request, so you are decoding the entire buffer eventhough it might not be completely filled.
Get the return value of the Read call so that you know how much of the buffer is actually filled with data:
int len = MyNetworkStream.Read(buffer, 0, buffer.Length);
string content = Encoding.ASCII.GetString(buffer, 0, len);
You need to implement EndRecieve to get the complete data from the stream. Checkout the following example from MSDN :
public static void Read_Callback(IAsyncResult ar){
StateObject so = (StateObject) ar.AsyncState;
Socket s = so.workSocket;
int read = s.EndReceive(ar);
if (read > 0) {
so.sb.Append(Encoding.ASCII.GetString(so.buffer, 0, read));
s.BeginReceive(so.buffer, 0, StateObject.BUFFER_SIZE, 0,
new AsyncCallback(Async_Send_Receive.Read_Callback), so);
}
else{
if (so.sb.Length > 1) {
//All of the data has been read, so displays it to the console
string strContent;
strContent = so.sb.ToString();
Console.WriteLine(String.Format("Read {0} byte from socket" +
"data = {1} ", strContent.Length, strContent));
}
s.Close();
}
}