I am creating a networking library in C# that I can use in any application, and as part of this library I have a TCP client/server setup. This setup works perfectly in almost every situation; it connects, sends/receives data, and disconnects flawlessly when under minimal and medium stress loads. However, when I send large amounts of data from the client to the server, the client socket works for a varied amount of time (sometimes short, sometimes long) and then just refuses to send data for a while. Specifically, my data rate goes from the 550-750 KBps range to 0 KBps, and sits there for again a varied amount of time. Then the socket will start sending again for a very short time, and get "throttled" again. During the throttling, i was assuming that the socket was disconnected because I couldn't send anything, but Polling returns that the socket IS connected using this code:
public bool IsConnected(Socket socket)
{
try
{
return !(socket.Poll(1, SelectMode.SelectRead) && socket.Available == 0);
}
catch (SocketException) { return false; }
}
I just took a networking class at my college, so I started thinking about the congestion control and flow control mechanisms in TCP, but it seems to me that neither would cause this problem; congestion control only slows the data rate, and a full buffer on the receiver's side wouldn't last nearly the length of time I am getting a 0 KBps data rate. The symptom seems to point towards either some type of heavy data throttling or mass scale dropping of packets.
My question is this: does anyone have any idea what might be causing this data "throttling", for lack of a better term? Also, is it possible that the packets I send are going further than just my router even though they are addressed to a host in the same subnet?
Edit: Just so it is clear, the reason I am trying to fix this problem is because I want to send files over TCP at the highest possible data rate. I understand that UDP can be used as well, and I will also be making a solution using it, but I want TCP to work first.
Specific Information:
I am using blocking read/write operations, and the server is multi-threaded. The client runs on its own thread as well. I am testing on my local subnet, bouncing all packets through my router, which should have a throughput of 54 Mbps. The packets are 8 KB each in size, and at maximum would be sent 1000 times a second (sending thread sleeps 1 ms), but obviously are not reaching that rate. Reducing the size of the packets so the data rate is lower causes the throttling to disappear. Windows 7 machines, 1 server, 1 client. The send operation always completes, it is the receive operation that errors out.
The send operation is below:
//get a copy of all the packets currently in the queue
IPacket[] toSend;
lock (packetQueues[c])
{
if (packetQueues[c].Count > SEND_MAX)
{
toSend = packetQueues[c].GetRange(0, SEND_MAX).ToArray();
packetQueues[c].RemoveRange(0, SEND_MAX);
}
else
{
toSend = packetQueues[c].ToArray();
packetQueues[c].RemoveRange(0, toSend.Length);
}
}
if (toSend != null && toSend.Length > 0)
{ //write the packets to the network stream
try
{
writer.Write(toSend.Length);
}
catch (Exception e)
{
Logger.Log(e);
if (showErrorMessages)
MessageBox.Show("Client " + (int)c + ": " + e, "Error", MessageBoxButtons.OK);
}
for (int i = 0; i < toSend.Length; i++)
{
try
{
toSend[i].Write(writer);
if (onSend != null)
{
object[] args = new object[2];
args[0] = c;
args[1] = toSend[i];
onSend(args);
}
}
catch (Exception e)
{
Logger.Log(e);
if (showErrorMessages)
MessageBox.Show("Client " + (int)c + ": " + e, "Error", MessageBoxButtons.OK);
}
}
}
And this is the receive code:
try
{
//default buffer size of a TcpClient is 8192 bytes, or 2048 characters
if (client.Available > 0)
{
int numPackets = reader.ReadInt32();
for (int i = 0; i < numPackets; i++)
{
readPacket.Clear();
readPacket.Read(reader);
if (owner != null)
{
owner.AcceptPacket(readPacket, c); //application handles null packets itself.
if (onReceive != null)
{
object[] args = new object[2];
args[0] = c;
args[1] = readPacket;
onReceive(args);
}
}
}
timestamps[c] = TimeManager.GetCurrentMilliseconds();
}
else
{
double now = TimeManager.GetCurrentMilliseconds();
if (now - timestamps[c] >= timeToDisconnect)
{ //if timestamp is old enough, check for connection.
connected[c] = IsConnected(client.Client);
if (!connected[c])
{
netStream.Close();
clients[c].Close();
numConnections--;
if (onTimeout != null) onTimeout(c);
}
else
{
timestamps[c] = now;
}
}
}
}
catch (Exception s)
{
Logger.Log(s);
if (showErrorMessages)
MessageBox.Show("Client " + (int)c + ": " + s, "Error", MessageBoxButtons.OK);
}
Packet send/receive:
public void Write(BinaryWriter w)
{
w.Write(command); //byte
w.Write(data.Type); //short
w.Write(data.Data.Length); //int
w.Write(data.Data); //byte array
w.Flush();
}
/// <summary>
/// Reads a command packet from data off a network stream.
/// </summary>
/// <param name="r">The stream reader.</param>
public void Read(BinaryReader r)
{
command = r.ReadByte();
short dataType = r.ReadInt16();
int dataSize = r.ReadInt32();
byte[] bytes = r.ReadBytes(dataSize);
data = new PortableObject(dataType, bytes);
}
Full Server Communication Loop:
public void Communicate(object cl)
{
int c = (int)cl;
timestamps[c] = TimeManager.GetCurrentMilliseconds();
try
{
//Console.Out.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + " has started up. c = " + (int)c);
TcpClient client = clients[c];
client.ReceiveTimeout = 100;
NetworkStream netStream = client.GetStream();
BinaryReader reader = new BinaryReader(netStream);
BinaryWriter writer = new BinaryWriter(netStream);
while (client != null && connected[c])
{
#region Receive
try
{
//default buffer size of a TcpClient is 8192 bytes, or 2048 characters
if (client.Available > 0)
{
int numPackets = reader.ReadInt32();
for (int i = 0; i < numPackets; i++)
{
readPacket.Clear();
readPacket.Read(reader);
if (owner != null)
{
owner.AcceptPacket(readPacket, c); //application handles null packets itself.
if (onReceive != null)
{
object[] args = new object[2];
args[0] = c;
args[1] = readPacket;
onReceive(args);
}
}
}
timestamps[c] = TimeManager.GetCurrentMilliseconds();
}
else
{
double now = TimeManager.GetCurrentMilliseconds();
if (now - timestamps[c] >= timeToDisconnect)
{ //if timestamp is old enough, check for connection.
connected[c] = IsConnected(client.Client);
if (!connected[c])
{
netStream.Close();
clients[c].Close();
numConnections--;
if (onTimeout != null) onTimeout(c);
}
else
{
timestamps[c] = now;
}
}
}
}
catch (Exception s)
{
Logger.Log(s);
if (showErrorMessages)
MessageBox.Show("Client " + (int)c + ": " + s, "Error", MessageBoxButtons.OK);
}
#endregion
Thread.Sleep(threadLatency);
#region Send
//get a copy of all the packets currently in the queue
IPacket[] toSend;
lock (packetQueues[c])
{
if (packetQueues[c].Count > SEND_MAX)
{
toSend = packetQueues[c].GetRange(0, SEND_MAX).ToArray();
packetQueues[c].RemoveRange(0, SEND_MAX);
}
else
{
toSend = packetQueues[c].ToArray();
packetQueues[c].RemoveRange(0, toSend.Length);
}
}
if (toSend != null && toSend.Length > 0)
{ //write the packets to the network stream
try
{
writer.Write(toSend.Length);
}
catch (Exception e)
{
Logger.Log(e);
if (showErrorMessages)
MessageBox.Show("Client " + (int)c + ": " + e, "Error", MessageBoxButtons.OK);
}
for (int i = 0; i < toSend.Length; i++)
{
try
{
toSend[i].Write(writer);
if (onSend != null)
{
object[] args = new object[2];
args[0] = c;
args[1] = toSend[i];
onSend(args);
}
}
catch (Exception e)
{
Logger.Log(e);
if (showErrorMessages)
MessageBox.Show("Client " + (int)c + ": " + e, "Error", MessageBoxButtons.OK);
}
}
}
#endregion
}
}
catch (ThreadAbortException tae)
{
Logger.Log(tae);
MessageBox.Show("Thread " + (int)cl + " was aborted.", "Error", MessageBoxButtons.OK);
}
}
It is probably your code, but it's difficult for us to say as it's incomplete.
I wrote up my own set of best practices in a .NET TCP/IP FAQ - after many, many years of TCP/IP experience. I recommend you start with that.
P.S. I reserve the term "packet" for packets on-the-wire. A TCP app has no control over packets. I use the term "message" for application-protocol-level messages. I think this reduces confusion, especially for newcomers.
If you are trying to create a
networking library in C# that I can use in any application
were you aware of any existing open source libraries out there? networkComms.net is possibly a good start. If you can recreate the same problem with that i'd be very surprised. I've personally used it to maintain over 1000 concurrent connections each sending about 10 packets a second. Otherwise if you want to keep using your code perhaps looking at the source of networkComms.net can point out where you might be going wrong.
Didn't look closely at your code snippets, but I see you have allocation in there - have you checked what pressure you're putting on the garbage collector?
PS: (sending thread sleeps 1 ms) - keep in mind that Sleep() without timeBeginPeriod() isn't going to get your 1ms resolution - probably closer to 10-20ms depending on Windows version and hardware.
Don't really know about C# and this code is incomplete. If I get it right, then
readPacket.Read(reader);
will read whatever is available, and your receiver end for loop will be knocked over. Where are you checking the read amount of bytes ?
Anyway, a good way to check on what's happening at TCP level and lower is wireshark
Related
I'm writing some code for a production line. In particular I need to integrate some barcode reader into the line.
For many (wrong) reason I had to end up like you are about to see.
Also, I was new to that field at the time I wrote that.
The issue: Sometimes, the reader reads the code (and send the image via ftp) but I get no data in my software and I need to understand why.
This is how initialize my reader (I have multiple reader, each with different IP but same port)
server = new TcpListener(IPAddress.Any, scannerPort);
server.Start();
Thread thread = new Thread(() =>
{
log.Debug("Waiting for scanner connection");
server.BeginAcceptTcpClient(HandleAsyncConnection, server);
});
thread.Start();
and this is my main method:
try
{
object _lockObj = new object();
using (TcpClient client = server.EndAcceptTcpClient(res))
{
int i;
server.BeginAcceptTcpClient(HandleAsyncConnection, server);
string address = client.Client.RemoteEndPoint.ToString();
string senderIp = address.Split(':')[0];
NetworkStream stream = client.GetStream();
Byte[] bytes = new Byte[2048];
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
log.Debug("Start");
lock (_lockObj)
{
log.Debug("Lock");
StringBuilder sb = new StringBuilder(string.Empty);
string rawCode = string.Empty;
bool isCrFound = false;
foreach (byte c in bytes.Where(x => x != 0))
{
if (c != (char)13 && !isCrFound)
{
if (c == (char)29)
{
sb.Append("<GS>");
}
else
sb.Append(Convert.ToChar(c));
}
else
{
isCrFound = true;
}
}
rawCode = sb.ToString();
log.Debug("Raw code from {0} -> {1}", senderIp, rawCode);
if (rawCode.StartsWith("ERROR", StringComparison.InvariantCultureIgnoreCase))
{
log.Debug("Operator - Errore reading code {0} from {1}", rawCode, senderIp);
}
else
{
//Process code
}
log.Debug("End");
}
log.Debug("Unlock");
}
}
}
catch (Exception ex)
{
log.Error("Error on the async connect operation: {0}, Stack: {1}", ex.Message, ex.StackTrace);
}
So basically everytime a sensor trigger, data is sent to my socket.
This works pretty well until the line starts to speed up (like 3/4 code/second); that is when data loss happen.
Now, I don't know where the issue is. Could be the devide, my implementation, resources management (byte array was 1024, I still have to try the 2048)
Any help would be much appreciated.
Thanks
EDIT:
I already stripped some log for brevity
I need a lock because when a sensor triggers too close to another, messages got overlapped (to that add different business reason)
When I loose Codes, I don't have anything in my log; the log, usually looks like:
start
Lock
lot of stuff
end
unlock
Lost codes don't leave any trace on my log... so I don't think it's something related to data discarded.
What I'd like to know if there is a way to do this kind of thing better in a context like mine.
I am working on a simple Client to send data to a server at my office. I tested the code locally on my computer using a server called TCPServer, I connect, send data, receive reply, disconnect, send again, rinse and repeat, it all works perfectly, but when I connect to office and do the same thing it connects fine, I can connect/disconnect forever, but when I send data it just hangs. Nothing is received in the log at the office. I can't send a single byte there.
Seems like a firewall issue doesn't it.
But I can run an older program I wrote years ago in Delphi (pascal), and it connects and sends the same data over without issue, same port, everything, so the problem is not a firewall issue. Thoughts on this? Here is the basic code layout.
Connect Button
Disconnect Button
Send Button
At the top of the class I declare the TcpClient Variable
public TcpClient m_client = new TcpClient();
In the _Click for Connect Button and Disconnect Button I have code to connect to server and set some indicators etc
private void ConnectButton_Click(object sender, EventArgs e)
{
string address = FixIP(IPAddressMaskedTextBox.Text);
int Port = Convert.ToInt32(PortNumberMaskedTextBox.Text);
Control control = (Control)sender;
String name = control.Name;
try
{
switch (name)
{
case ("ConnectButton"):
//Connect to server
connect(address, Port);
if (m_client.Connected)
{
SingleConnectionRichTextBox.Clear();
ConnectedLightButton.BackColor = Color.Lime;
SingleConnectionRichTextBox.Text += "Connected at IP " + address + " and Port " + Port.ToString() + "\r\n";
}
break;
case ("DisconnectButton"):
if (m_client.Connected)
{
SingleConnectionRichTextBox.Text += "Connection Terminated\r\n";
ConnectedLightButton.BackColor = Color.Red;
m_client.Client.Disconnect(false);
m_client = new TcpClient();
}
break;
}
}
catch (Exception err)
{
MessageBox.Show(err.ToString());
}
}
public void connect(string address, int port)
{
try
{
if (!m_client.Connected)
{
ConnectedLightButton.BackColor = Color.Yellow;
SingleConnectionRichTextBox.Text += "Attempting to Connect...\r\n";
m_client.Connect(address, port);
}
else
{
SingleConnectionRichTextBox.Text += "Connection Failed...\r\n";
ConnectedLightButton.BackColor = Color.Red;
throw new Exception("Connect: Already connected\r\n");
}
}
catch (Exception err)
{
MessageBox.Show(err.ToString());
}
}
The Send button has it's own event, mostly because when connecting to office it can take a minute for sockets to be created etc.
private void SendButton_Click(object sender, EventArgs e)
{
try
{
if (m_client.Connected)
{
string completeString = "";
for (int cnt = 0; cnt < SingleSendRichTextBox.Lines.Count() - 1; cnt++)
{
string aLine = Regex.Replace(SingleSendRichTextBox.Lines[cnt], #"\e\[(\d+;)*(\d+)?[ABCDHJKfmsu]", "");
if (cnt == 0)
{
//First line gets Start Block, plus a CR on end
aLine = (char)0x0B + aLine + (char)0x0D;
}
else if (cnt == (SingleSendRichTextBox.Lines.Count() -1))
{
//Last line gets CR + End Block + CR on end
aLine += (char)0x0D + (char)0x1C + (char)0x0D;
}
else
{
//All middle lines get CR on end
aLine += (char)0x0D;
}
//MessageBox.Show("Sending line# " + cnt.ToString() + " = " + aLine);
completeString += aLine;
}
Byte[] data = Encoding.ASCII.GetBytes(completeString);
WriteBytes(data);
ReadAllBytes();
}
else
{
MessageBox.Show("Nothing is connected currently...");
}
}
catch (Exception err)
{
MessageBox.Show(err.ToString());
}
}
public void WriteBytes(Byte[] data)
{
try
{
if ((m_client.Connected)&&(data.Count() > 0))
{
// Get access to network stream
NetworkStream stm = m_client.GetStream();
stm.Write(data, 0, data.Length);
stm.Flush();
//MessageBox.Show("Data Sent, length = " + data.Length.ToString());
}
else
{
MessageBox.Show("Either we are not connected, or there is no data to send!!");
}
}
catch (Exception err)
{
MessageBox.Show(err.ToString());
}
}
public void ReadAllBytes()
{
try
{
// Buffer to store the response bytes.
Byte[] readdata = new Byte[256];
// String to store the response ASCII representation.
String responseData = String.Empty;
NetworkStream stm = m_client.GetStream();
// Read the first batch of the TcpServer response bytes.
Int32 bytes = stm.Read(readdata, 0, readdata.Length);
responseData = Encoding.ASCII.GetString(readdata, 0, bytes);
SingleReplyRichTextBox.Text += responseData + "\r\n";
}
catch (Exception err)
{
MessageBox.Show(err.ToString());
}
}
Does anything in here raise a red flag that is obvious? I tried Stream VS NetworkStream. I tried turning off the Reply listener. I took this code and combined it all into one function, and moved the TcpClient creation over to a different Class as static so I could create it in each function, but while all of these worked fine locally, nothing works connecting to office. It won't send a single byte. I set m_client as static at the top too, works fine locally, not to office.
Is GetStream failing maybe? or its sending the data on a different socket?
use a Task and cancellation token to cancel the task. Don't init a "new" TCPclient. In the background the TCPclient will be not closed (socket-timeout of .net)
Use try catches to see the exception and add the log to the conversation to make it more clear pls
I wrote a C# server application. The server utilizes Asynchronous TCP sockets.
The packets are 80-180 bytes of data.
For performance testing I have a single client connect and send packets continuously. With debugging on the first 100 packets (0-100) receive in roughly 5 seconds. By the time the server received packets #300-400 it takes roughly 30 seconds to receive the packets. The performance continues to degrade as more receives occur.
I looked around and have not been able to find a solution. I have tried setting the Socket.NoDelay flag in case the Nagle algorithm was inhibiting the server.
I have disabled all functions within the server; so that it is only receiving to ensure I wasn't losing performance in other code.
I have also checked my CPU utilization and it is ~13%. I have over 2 GB of free memory. When running the application the ram is NOT constantly growing and utilization is minimal.
I am at a loss as to what to debug and look into next...
EDIT: Added Code Sample
public void StartListening()
{
try
{
IPAddress ipAddress = IPAddress.Parse("192.168.2.60");
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, m_Port);
m_MainSocket = new Socket(localEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
m_MainSocket.NoDelay = true;
m_MainSocket.Bind(localEndPoint);
m_MainSocket.Listen(10);
m_MainSocket.BeginAccept(new AsyncCallback(clientConnected), null);
System.Diagnostics.Debug.WriteLine("Listening on:Local IP Address: " + localEndPoint.Address.ToString() + " Port :" + localEndPoint.Port.ToString() + "\n");
}
catch (SocketException se)
{
System.Diagnostics.Debug.WriteLine("Listening Exception \n");
System.Diagnostics.Debug.WriteLine(se.Message);
}
}
void clientConnected(IAsyncResult ar)
{
try
{
SocketState state = new SocketState(m_MainSocket.EndAccept(ar));
Client client = new Client(state);
if (client.SocketState.clientSocket.Connected)
{
System.Diagnostics.Debug.WriteLine("Client #?????? Connected \n");
AddLogText("Client #?????? Connected \r\n\r\n");
waitForData(client);
SetSendButton(true);
}
m_MainSocket.BeginAccept(new AsyncCallback(clientConnected), null);
}
catch (ObjectDisposedException)
{
System.Diagnostics.Debug.WriteLine("Client Connected: Socket has been closed\n");
}
catch (SocketException se)
{
System.Diagnostics.Debug.WriteLine("Client Connected Exception \n");
System.Diagnostics.Debug.WriteLine(se.Message);
}
}
void waitForData(Client client)
{
try
{
SocketState state = new SocketState(client.SocketState.clientSocket);
client.SocketState.clientSocket = null;
client.SocketState = state;
client.SocketState.clientSocket.BeginReceive(client.SocketState.DataBuffer, 0, client.SocketState.DataBuffer.Length, SocketFlags.None, new AsyncCallback(readDataCallback), client);
}
catch (SocketException se)
{
System.Diagnostics.Debug.WriteLine("Wait For Data Exception \n");
System.Diagnostics.Debug.WriteLine(se.Message);
}
}
public void readDataCallback(IAsyncResult ar)
{
Client client = (Client)ar.AsyncState;
try
{
// Read data from the client socket.
int iRx = client.SocketState.clientSocket.EndReceive(ar);
client.SocketState.SB.Append(Encoding.ASCII.GetString(client.SocketState.DataBuffer, 0, iRx));
string sPacketString = client.SocketState.SB.ToString();
Server formServer = this;
Packet_Helper packet_helper = new Packet_Helper(sPacketString, formServer);
Packet packet = new Packet(sPacketString);
client.SerialNumber = packet.SerialNumber;
client.FirmwareVersion = packet.FirmwareVersion;
client.ProductID = packet.ProductID;
client.HardwareVersion = packet.HardwareVersion;
if (!m_Clients.ContainsKey(packet.SerialNumber))
{
m_Clients.Add(packet.SerialNumber, client);
UpdateClientList();
string[] packets = client.refreshAll();
for (int i = 0; i < packets.Length; i++)
{
byte[] byteData = Encoding.ASCII.GetBytes(packets[i]);
client.SocketState.clientSocket.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(SendCallback), client);
AddPacketsSentText(packets[i] + "--" + (iSent++).ToString() + "\r\n\r\n");
}
}
System.Diagnostics.Debug.WriteLine("Read " + sPacketString.Length.ToString() + " bytes from " + client.SerialNumber + "\n" + sPacketString + "\n");
AddLogText("Read " + sPacketString.Length.ToString() + " bytes from " + client.SerialNumber + " \r\n");
AddLogText(sPacketString.ToString() + "\r\n\r\n");
waitForData(client);
}
catch (ObjectDisposedException)
{
System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n");
}
catch (SocketException se)
{
if (se.ErrorCode == 10054) // Error code for Connection reset by peer
{
string sclientSerial = "??????";
if (client.SerialNumber != null || client.SerialNumber != "")
sclientSerial = client.SerialNumber;
AddLogText("Client " + sclientSerial + " Disconnected" + "\r\n\r\n");
System.Diagnostics.Debug.WriteLine("Client " + sclientSerial + " Disconnected" + "\n");
m_Clients.Remove(sclientSerial);
UpdateClientList();
}
else
{
System.Diagnostics.Debug.WriteLine("Read Data Exception \n");
System.Diagnostics.Debug.WriteLine(se.Message);
}
}
}
class SocketState
{
private Socket m_ClientSocket; //Socket connection to the client
private byte[] m_DataBuffer = new byte[256]; //Buffer to store the data sent by the client
private StringBuilder m_SB = new StringBuilder(); //for building recieved data into a string
/// <summary>
/// Gets or Sets the client Socket
/// </summary>
public Socket clientSocket
{
get { return m_ClientSocket; }
set { m_ClientSocket = value; }
}
/// <summary>
/// Gets the DataBuffer
/// </summary>
public byte[] DataBuffer
{
get { return m_DataBuffer; }
set { DataBuffer = value; }
}
/// <summary>
/// Gets or Sets the SB
/// </summary>
public StringBuilder SB
{
get { return m_SB; }
set { m_SB = value; }
}
public SocketState(Socket socket)
{
m_ClientSocket = socket;
m_ClientSocket.ReceiveBufferSize = 256;
m_ClientSocket.NoDelay = true;
//m_DataBuffer = Enumerable.Repeat((byte)0, 256).ToArray();
}
}
Edit: AddLogText() function added. This function is used to add text to a Text Box that is in the UI.
//Delegate - enables asychronous calls for setting the text property of the tb_ListeningLog
delegate void AddLogTextCallback(string text);
private void AddLogText(string text)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.tb_ListeningLog.InvokeRequired)
{
AddLogTextCallback d = new AddLogTextCallback(AddLogText);
this.Invoke(d, new object[] { text });
}
else
{
this.tb_ListeningLog.Text += text;
tb_ListeningLog.SelectionStart = tb_ListeningLog.Text.Length;
tb_ListeningLog.ScrollToCaret();
}
}
I'm taking a bit of a shot in the dark with this answer, but the code you've posted certainly helps.
The reason you're probably seeing slow performance as time goes on is because of the code in your readDataCallback method. The way you have it set up, the processing of the data is done before you go for another receive. This means that as the length of the processing increases, the duration between receiving your data increases.
I don't know what code is in a lot of your methods, but you should generally look at any loops that may be taking a while to finish. If you're having trouble finding the bottleneck by looking through your code, try finding which methods take the longest to finish and continue to narrow your code down.
For instance (I'm guessing that the bottleneck is in this area of code):
if (!m_Clients.ContainsKey(packet.SerialNumber))
{
m_Clients.Add(packet.SerialNumber, client);
AddLogText("Running UpdateClientList\r\n");
UpdateClientList();
AddLogText("Doing client.refreshAll\r\n");
string[] packets = client.refreshAll();
AddLogText("Doing for loop\r\n");
for (int i = 0; i < packets.Length; i++)
{
byte[] byteData = Encoding.ASCII.GetBytes(packets[i]);
client.SocketState.clientSocket.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(SendCallback), client);
AddPacketsSentText(packets[i] + "--" + (iSent++).ToString() + "\r\n\r\n");
}
}
Just observe the amount of time between each method with your eyes, or make it easier and use a Stopwatch or DateTime to show exact time.
Also, if you find that the behavior of the code cannot be made more efficient, you could toy around with the idea of processing the data in a separate thread. I'm assuming that this behavior isn't desired, though, because of the question at hand.
For your AddLogText method, try using tb_ListeningLog.Text.AppendText instead of +=.
I am not sure why you have such a long piece of code to read more data. Also, try placing the message in a Queue which can be processed by a different thread.
Here is an implementation I use:
// Read data from the client
private void ReadCallback(IAsyncResult ar)
{
StateObject state = (StateObject)ar.AsyncState;
Socket socket = state.workSocket;
try
{
if (socket.Connected)
{
// Read the socket
int bytesRead = socket.EndReceive(ar);
// Deserialize objects
foreach (MessageBase msg in MessageBase.Receive(socket, bytesRead, state))
{
// Add objects to the message queue
lock (this.messageQueue)
messageQueue.Enqueue(msg);
}
// Notify any event handlers
if (DataRecieved != null)
DataRecieved(socket, bytesRead);
// Asynchronously read more client data
socket.BeginReceive(state.Buffer, state.readOffset, state.BufferSize - state.readOffset, 0,
ReadCallback, state);
}
else
{
HandleClientDisconnect(socket);
}
}
catch (SocketException)
{
HandleClientDisconnect(socket);
}
}
I'm working on a client/server application where the connections from the client to the server stay open until the client application is closed.
If the server application goes down unexpectedly, while the client is reading data, I want the client to treat this as an exception, but then to catch the exception and raise an event with the exception as the argument.
I've written a test that I think should test that this system works, but the object I'm testing doesn't seem to register that the socket is closed unless I put in a break point and then continue.
The important part of the test looks like this:
StreamingMonitor sm = new StreamingMonitor();
bool errored = false;
string msg = "";
sm.ErrorOccurred += (s, a) =>
{
errored = true;
msg = a.Exception.Message;
};
sm.Enabled = true;
client = listener.AcceptTcpClient();
client.GetStream().Write(BitConverter.GetBytes(10000), 0, 4);
client.Close();
while(!errored)
{}
Assert.AreEqual("A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call", msg);
The TcpListener object listener is listening to the loopback address.
The StreamingMonitor begins listening for the length of the data to retrieve when it is enabled. The length of data is always assumed to fit into a signed 32 bit integer.
When the message length is received then this methods is called.
private void GotMessageLength(IAsyncResult asyncResult)
{
try
{
client.Client.EndReceive(asyncResult);
if(firstMessage)
{
firstMessage = false;
if (Connected != null)
{
Connected(this, new EventArgs());
}
}
int msgLen = BitConverter.ToInt32(messageLength, 0);
byte[] message = new byte[msgLen];
List<byte> lbMessage = new List<byte>();
int bytesReturned = client.Client.Receive(message);
int remaining = (msgLen < bytesReturned) ? bytesReturned - msgLen : msgLen - bytesReturned;
if(remaining > 0)
{
if (bytesReturned > 0)
{
for (int i = 0; i < bytesReturned; i++)
{
lbMessage.Add(message[i]);
}
}
while(remaining > 0)
{
if(!client.Connected)
{
throw new SocketException((int)SocketError.Shutdown);
}
bytesReturned = client.Client.Receive(message);
remaining = (remaining < bytesReturned) ? bytesReturned - remaining : remaining - bytesReturned;
if (bytesReturned > 0)
{
for (int i = 0; i < bytesReturned; i++)
{
lbMessage.Add(message[i]);
}
}
}
message = lbMessage.ToArray();
}
MessageReceived(this, new MessageReceivedEventArgs(message));
if (Enabled)
{
client.Client.BeginReceive(messageLength, 0, 4, SocketFlags.None, GotMessageLength, null);
}
}
catch (SocketException ex)
{
if(ErrorOccurred != null)
{
ErrorOccurred(this, new ErrorEventArgs(ex));
}
}
catch (ObjectDisposedException)
{
}
}
The method reads data from the network stream until it has read the specified number of bytes. If the remote connection closes then it should raise a socket exception.
However, the unit test gets caught in a infinite loop, waiting for the error to occur, because the socket in the StreamingMonitor never realises that the other end has closed.
How can I make the SteamingMonitor realise that the server has gone?
Is this possible on the loopback address?
Sorry for all the code, I couldn't think how to cut the method down.
I can give some general pointers on the area that might help.
Loopback (or just using localhost) in general does not act the same way as a real network. Scenarios like how much data is send/received in each call to the socket api. So always test or real network connections.
The socket api will only find out if the other side is disconnected upon trying to send to it ( i think that is correct). So some sort of heartbeat functionality comes in handy =)
Edit: You can also get the SocketException to determine if the the other side is disconnected by trying to receive (did some basic test on some old code of mine).
protected void ReceiveCallback(IAsyncResult ar)
{
var so = (StateObject)ar.AsyncState;
if (!so.Socket.Connected) return;
try
{
int read = so.Socket.EndReceive(ar);
if (read > 0)
ProcessBuffer(so, so.Buffer, read);
so.Socket.BeginReceive(so.Buffer, 0, so.Buffer.Length, SocketFlags.None, ReceiveCallback, so);
}
catch (SocketException e)
{
Trace.WriteLine("[Networking]::NetBase.ReceiveCallback: SocketException");
Output.WriteLine(e.Message);
RaiseDisconnected();
}
catch (ObjectDisposedException e)
{
Trace.WriteLine("[Networking]::NetBase.ReceiveCallback: ObjectDisposedException");
Output.WriteLine(e.Message);
RaiseDisconnected();
}
}
This will call my disconnect function if the other side crashes for some reason.
Hope it helps
I've got some network code to process an arbitary TCP connection.
It all seems to work as expected but seems slow. When i've profiled the code the it seems to spend a good 600 ms in NetworkStream.Read() and I'm wondering how to improve it. I've fiddled with the buffer sizes and alternated between a massive buffer to read all of the data in one go or a small one which should concatenate the data into a StringBuilder. Currently the client i'm using is a web-browser but this code is generic and it may well not be HTTP data that is being sent to it. Any ideas?
My code is this:
public void StartListening()
{
try
{
lock (oSyncRoot)
{
oTCPListener = new TcpListener(oIPaddress, nPort);
// fire up the server
oTCPListener.Start();
// set listening bit
bIsListening = true;
}
// Enter the listening loop.
do
{
// Wait for connection
TcpClient newClient = oTCPListener.AcceptTcpClient();
// queue a request to take care of the client
oThreadPool.QueueUserWorkItem(new WaitCallback(ProcessConnection), newClient);
}
while (bIsListening);
}
catch (SocketException se)
{
Logger.Write(new TCPLogEntry("SocketException: " + se.ToString()));
}
finally
{
// shut it down
StopListening();
}
}
private void ProcessConnection(object oClient)
{
TcpClient oTCPClient = (TcpClient)oClient;
try
{
byte[] abBuffer = new byte[1024];
StringBuilder sbReceivedData = new StringBuilder();
using (NetworkStream oNetworkStream = oTCPClient.GetStream())
{
// set initial read timeout to nInitialTimeoutMS to allow for connection
oNetworkStream.ReadTimeout = nInitialTimeoutMS;
int nBytesRead = 0;
do
{
try
{
bool bDataAvailable = oNetworkStream.DataAvailable;
while (!bDataAvailable)
{
Thread.Sleep(5);
bDataAvailable = oNetworkStream.DataAvailable;
}
nBytesRead = oNetworkStream.Read(abBuffer, 0, abBuffer.Length);
if (nBytesRead > 0)
{
// Translate data bytes to an ASCII string and append
sbReceivedData.Append(Encoding.UTF8.GetString(abBuffer, 0, nBytesRead));
// decrease read timeout to nReadTimeoutMS second now that data is coming in
oNetworkStream.ReadTimeout = nReadTimeoutMS;
}
}
catch (IOException)
{
// read timed out, all data has been retrieved
nBytesRead = 0;
}
}
while (nBytesRead > 0);
//send the data to the callback and get the response back
byte[] abResponse = oClientHandlerDelegate(sbReceivedData.ToString(), oTCPClient);
if (abResponse != null)
{
oNetworkStream.Write(abResponse, 0, abResponse.Length);
oNetworkStream.Flush();
}
}
}
catch (Exception e)
{
Logger.Write(new TCPLogEntry("Caught Exception " + e.StackTrace));
}
finally
{
// stop talking to client
if (oTCPClient != null)
{
oTCPClient.Close();
}
}
}
Edit: I get roughly the same figures on two entirely seperate machines (my XP development machine and a 2003 box in a colo). I've put some timing into the code around the relevant parts (using System.Diagnostic.StopWatch) and dump it to a log:
7/6/2009 3:44:50 PM : Debug : While DataAvailable took 0 ms
7/6/2009 3:44:50 PM : Debug : Read took 531 ms
7/6/2009 3:44:50 PM : Debug : ProcessConnection took 577 ms
I recommend you use Microsoft Network Monitor or something like it to see what's going on in terms of those 600ms. NetworkStream is a piece of networking software - when looking at its behavior, always consider what the network is doing.
Another vote for the use of network monitoring software. Either Network Monitor or WireShark should do. Make sure you record what time the networkstream.read call begins and ends in your program so you can know where in the recorded network traffic your program events happened.
Also, I'd recommend waiting for the NetworkStream.DataAvailable property to become true before you call the Read method, and record the time it becomes true as well. If your network monitor shows data arriving 600 ms before your program indicates it can be read, something else on your computer may be holding up the packet - e.g. antivirus or your firewall.
Addendum 2009/7/6 3:12 PM EDT:
The extra timing information you posted is interesting. If data is available, why is it taking so long to read? I ran your code on my development machine, and both waiting for dataavailable and the read function itself comes out as 0 milliseconds. Are you sure you have the latest service packs, etc. installed? I'm running Visual Studio Professional 2005 with .NET 2.0.50727. I also have .NET 3.0 and 3.5 installed, but I don't think VS 2005 is using those. Do you have a fresh OS installation (real or virtual machine) with no extra programs (even/especially ones "required" by corporate IT) that you could try this on?
Here's the code I ran:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.IO;
using System.Threading;
using System.Diagnostics;
namespace stackoverflowtest
{
class Program
{
static private object oSyncRoot = new object();
static private TcpListener oTCPListener;
static private IPAddress oIPaddress = IPAddress.Parse("10.1.1.109");
static private int nPort = 8009;
static bool bIsListening = true;
static void Main(string[] args)
{
StartListening();
Thread.Sleep(500000);
bIsListening = false;
}
public static void StartListening()
{
try
{
lock (oSyncRoot)
{
oTCPListener = new TcpListener(oIPaddress, nPort);
// fire up the server
oTCPListener.Start();
// set listening bit
bIsListening = true;
}
// Enter the listening loop.
do
{
// Wait for connection
TcpClient newClient = oTCPListener.AcceptTcpClient();
// queue a request to take care of the client
ThreadPool.QueueUserWorkItem(new WaitCallback(ProcessConnection), newClient);
}
while (bIsListening);
}
catch (SocketException se)
{
Console.WriteLine("SocketException: " + se.ToString());
}
finally
{
// shut it down
//StopListening();
}
}
private static void ProcessConnection(object oClient)
{
TcpClient oTCPClient = (TcpClient)oClient;
try
{
byte[] abBuffer = new byte[1024];
StringBuilder sbReceivedData = new StringBuilder();
using (NetworkStream oNetworkStream = oTCPClient.GetStream())
{
int nInitialTimeoutMS = 1000;
// set initial read timeout to nInitialTimeoutMS to allow for connection
oNetworkStream.ReadTimeout = nInitialTimeoutMS;
int nBytesRead = 0;
do
{
try
{
bool bDataAvailable = oNetworkStream.DataAvailable;
Stopwatch sw = new Stopwatch();
while (!bDataAvailable)
{
Thread.Sleep(5);
bDataAvailable = oNetworkStream.DataAvailable;
}
Console.WriteLine("DataAvailable loop took " + sw.ElapsedMilliseconds);
sw.Reset();
nBytesRead = oNetworkStream.Read(abBuffer, 0, abBuffer.Length);
Console.WriteLine("Reading " + nBytesRead + " took " + sw.ElapsedMilliseconds);
if (nBytesRead > 0)
{
// Translate data bytes to an ASCII string and append
sbReceivedData.Append(Encoding.UTF8.GetString(abBuffer, 0, nBytesRead));
// decrease read timeout to nReadTimeoutMS second now that data is coming in
int nReadTimeoutMS = 100;
oNetworkStream.ReadTimeout = nReadTimeoutMS;
}
}
catch (IOException)
{
// read timed out, all data has been retrieved
nBytesRead = 0;
}
}
while (nBytesRead > 0);
byte[] abResponse = new byte[1024];
for (int i = 0; i < abResponse.Length; i++)
{
abResponse[i] = (byte)i;
}
oNetworkStream.Write(abResponse, 0, abResponse.Length);
oNetworkStream.Flush();
//send the data to the callback and get the response back
//byte[] abResponse = oClientHandlerDelegate(sbReceivedData.ToString(), oTCPClient);
//if (abResponse != null)
//{
// oNetworkStream.Write(abResponse, 0, abResponse.Length);
// oNetworkStream.Flush();
//}
}
}
catch (Exception e)
{
Console.WriteLine("Caught Exception " + e.StackTrace);
}
finally
{
// stop talking to client
if (oTCPClient != null)
{
oTCPClient.Close();
}
}
}
}
}
After some more research it seems that the only way to speed this up is to break after the first x bytes have been read. The delay seems to be on the second read. If I change the buffer to be 8096 bytes (probably the max my application will be sent at any one go) and break here:
if (nBytesRead > 0)
{
// Translate data bytes to an ASCII string and append
sbReceivedData.Append(Encoding.UTF8.GetString(abBuffer, 0, nBytesRead));
if (bTurboMode)
{
break;
}
else
{
// decrease read timeout to nReadTimeoutMS second now that data is coming in
oNetworkStream.ReadTimeout = nReadTimeoutMS;
}
}
Then the response time goes from 600ms to about 80ms. This is an acceptable solution for me currently. I can toggle the bTurboMode from the calling application and speed things up substantially for this case