C# Issues with Multi-Threading and Socket - c#

First of all, I just want to let you know that I am not new to programming, it should be easier to help me :)
I am having issues with my Multi-Threaded Chat that I am making in C# with Socket.
I have 3 threads :
void ListenSocketConnection : Check for Socket that could connect. Connected Socket are added into a List<>
void CheckIfClientStillConnectedThread : Check if a Socket disconnected. Disconnected Socket are removed from List<>
void ReceiveDataListener : Check if a Socket received data
Here is the issue. If the first or second thread remove a Socket from the List<>, the 'foreach (ClientManager cManager in clientsList)' will raise an exception.
Here is the second issue. If a socket disconnect during that foreach, 'foreach ClientManager cManager in clientsList)' will raise an exception : DisposedException
Do you have any tips on how I could fix this?
Here is my code :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.ComponentModel;
using System.Threading;
namespace JAChat.Library
{
class SocketServer
{
private Socket socketServer;
private BackgroundWorker bwSocketConnectListener;
private BackgroundWorker bwCheckIfConnected;
private BackgroundWorker bwReceiveDataListener;
private List<ClientManager> clientsList;
#region Constructor
public SocketServer(int port)
{
clientsList = new List<ClientManager>();
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socketServer.Bind(new IPEndPoint(IPAddress.Any, port));
socketServer.Listen(100);
bwSocketConnectListener = new BackgroundWorker();
bwSocketConnectListener.DoWork += new DoWorkEventHandler(ListenSocketConnection);
bwSocketConnectListener.RunWorkerAsync();
bwCheckIfConnected = new BackgroundWorker();
bwCheckIfConnected.DoWork += CheckIfClientStillConnectedThread;
bwCheckIfConnected.RunWorkerAsync();
bwReceiveDataListener = new BackgroundWorker();
bwReceiveDataListener.DoWork += ReceiveDataListener;
bwReceiveDataListener.RunWorkerAsync();
}
#endregion
#region Getter
public List<ClientManager> connectedClients
{
get
{
return clientsList;
}
}
#endregion
#region Public Methods
/// <summary>
/// Parse and send the command object to targets
/// </summary>
public void sendCommand(Command cmd)
{
BackgroundWorker test = new BackgroundWorker();
test.DoWork += delegate {
foreach(ClientManager cManager in clientsList){
cManager.sendCommand(cmd);
}
};
test.RunWorkerAsync();
}
/// <summary>
/// Disconnect and close the socket
/// </summary>
public void Disconnect()
{
socketServer.Disconnect(false);
socketServer.Close();
socketServer = null; //Stop some background worker
}
#endregion
#region Private Methods
private void ListenSocketConnection(object sender, DoWorkEventArgs e)
{
while (socketServer != null)
{
//Get and WAIT for new connection
ClientManager newClientManager = new ClientManager(socketServer.Accept());
clientsList.Add(newClientManager);
onClientConnect.Invoke(newClientManager);
}
}
private void CheckIfClientStillConnectedThread(object sender, DoWorkEventArgs e){
while(socketServer != null){
for(int i=0;i<clientsList.Count;i++){
if(clientsList[i].socket.Poll(10,SelectMode.SelectRead) && clientsList[i].socket.Available==0){
clientsList[i].socket.Close();
onClientDisconnect.Invoke(clientsList[i]);
clientsList.Remove(clientsList[i]);
i--;
}
}
Thread.Sleep(5);
}
}
private void ReceiveDataListener(object unused1, DoWorkEventArgs unused2){
while (socketServer != null){
foreach (ClientManager cManager in clientsList)
{
try
{
if (cManager.socket.Available > 0)
{
Console.WriteLine("Receive Data Listener 0");
//Read the command's Type.
byte[] buffer = new byte[4];
int readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
Console.WriteLine("Receive Data Listener 1");
if (readBytes == 0)
break;
Console.WriteLine("Receive Data Listener 2");
CommandType cmdType = (CommandType)(BitConverter.ToInt32(buffer, 0));
Console.WriteLine("Receive Data Listener 3");
//Read the sender IP size.
buffer = new byte[4];
readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int senderIPSize = BitConverter.ToInt32(buffer, 0);
//Read the sender IP.
buffer = new byte[senderIPSize];
readBytes = cManager.socket.Receive(buffer, 0, senderIPSize, SocketFlags.None);
if (readBytes == 0)
break;
IPAddress cmdSenderIP = IPAddress.Parse(System.Text.Encoding.ASCII.GetString(buffer));
//Read the sender name size.
buffer = new byte[4];
readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int senderNameSize = BitConverter.ToInt32(buffer, 0);
//Read the sender name.
buffer = new byte[senderNameSize];
readBytes = cManager.socket.Receive(buffer, 0, senderNameSize, SocketFlags.None);
if (readBytes == 0)
break;
string cmdSenderName = System.Text.Encoding.Unicode.GetString(buffer);
//Read target IP size.
string cmdTarget = "";
buffer = new byte[4];
readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int targetIPSize = BitConverter.ToInt32(buffer, 0);
//Read the command's target.
buffer = new byte[targetIPSize];
readBytes = cManager.socket.Receive(buffer, 0, targetIPSize, SocketFlags.None);
if (readBytes == 0)
break;
cmdTarget = System.Text.Encoding.ASCII.GetString(buffer);
//Read the command's MetaData size.
string cmdMetaData = "";
buffer = new byte[4];
readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int metaDataSize = BitConverter.ToInt32(buffer, 0);
//Read the command's Meta data.
buffer = new byte[metaDataSize];
readBytes = cManager.socket.Receive(buffer, 0, metaDataSize, SocketFlags.None);
if (readBytes == 0)
break;
cmdMetaData = System.Text.Encoding.Unicode.GetString(buffer);
//Create the command object
Command cmd = new Command(cmdType, cmdSenderIP, cmdSenderName, IPAddress.Parse(cmdTarget), cmdMetaData);
this.onCommandReceived(cmd);
}
}
catch (ObjectDisposedException) {/*Le socket s'est déconnectée pendant le for each. Ignore l'érreur et retourne dans le while*/ }
catch (InvalidOperationException) { /* clientsList a été modifié pendant le foreach et délanche une exception. Retour while*/}
}
}
Console.WriteLine("Receive data listener closed");
}
#endregion
#region Events
public delegate void OnClientConnectEventHandler(ClientManager client);
/// <summary>
/// Events invoked when a client connect to the server
/// </summary>
public event OnClientConnectEventHandler onClientConnect = delegate { };
public delegate void OnClientDisconnectEventHandler(ClientManager client);
/// <summary>
/// Events invoked when a client disconnect from the server
/// </summary>
public event OnClientDisconnectEventHandler onClientDisconnect = delegate { };
public delegate void OnCommandReceivedEventHandler(Command cmd);
/// <summary>
/// Events invoked when a command has been sent to the server
/// </summary>
public event OnCommandReceivedEventHandler onCommandReceived = delegate { };
#endregion
}
}

There are multiple threads but I don't see any synchronization. That's incorrect. Use a lock to protect mutable shared state.
Instead of having this central management of all sockets, the polling and the checking of DataAvailable, why don't you just use either one thread per socket or async IO? That way you only ever manage one socket at a time. No polling necessary. You just call Read (or its async version) and wait for the data to arrive. This is a much better paradigm to deal with sockets. Basically, if your socket code contains DataAvailable or polling, you are going against best-practices (and likely have a bug somewhere). Think about how you would solve this without using the two. It is possible, and better.
In ReceiveDataListener you assume, that if data is available that an entire message is available. That's wrong because TCP is stream-oriented. You can receive the sent data in arbitrarily small chunks. Going with my point (2) fixes this.
To elaborate on (2): this is basically an actor model. One actor per socket. Whether you implement the actor using a thread, using async/await or using legacy async IO does not matter.
Hope this helps. Feel free to ask follow-up questions in the comments below.

The collection is being modified by multiple threads, so the count may very every time it is interrogated. As such, you should set it to a fixed amount; ie before the loop and then iterate through the list. Also, a count down rather than a count up is a better option since you are removing the elements.
Consider the following code :
private void CheckIfClientStillConnectedThread(object sender, DoWorkEventArgs e)
{
while (socketServer != null)
{
int count = clientsList.Count -1;
for (int i=count; i >= 0 ; i--)
{
if (clientsList[i].socket.Poll(10, SelectMode.SelectRead) && clientsList[i].socket.Available == 0)
{
clientsList[i].socket.Close();
onClientDisconnect.Invoke(clientsList[i]);
clientsList.Remove(clientsList[i]);
}
}
Thread.Sleep(5);
}
}

Related

I am trying to get my GUI to receive messages from another application. However, nothing works

As the title says, I have been given a simple flight simulator and I have been tasked to create a GUI which connects to the flight sim (Through Socket Programming). Then I can send Control Updates to the flight sim through a button by using TCPClient and writing to the stream (already done that).
However, the flight Sim I have been given sends telemetry information and my GUI is supposed to receive that information and display it on a datagridView. I have tried running a thread in the background to invoke the ReceiveMessage method and then the DisplayMessage method is supposed to activate but nothing works. The Flight Simulator sends a JSON string and from what I understood, my GUI needs to deserialize it and present it on to the Data Grid View.
Sorry if my explanation is not great. Here is the code:
The process of listening when the programs are connected should work in the following sequence:
public void ConnectProgram(string IPaddress, int port)
{
tcpClient = new TcpClient();
tcpClient.Connect(IPaddress, port);
}
private void start()
{
messageInThread = new Thread(new ThreadStart(ReceiveMessage))
{
IsBackground = true
};
//thank you LarsTech for pointing out to write .Start() but it still doesn't work
messageInThread.Start();
btnListen.Enabled = false;
}
private void ReceiveMessage()
{
//netStream = tcpClient.GetStream();
string JSONmsg = " ";
int i;
using (netStream = tcpClient.GetStream())
{
byte[] buffer = new byte[1024];
using (MemoryStream ms = new MemoryStream())
{
int numBytesRead;
while ((numBytesRead = netStream.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Read(buffer, 0, numBytesRead);
}
JSONmsg = Encoding.ASCII.GetString(ms.ToArray(), 0, (int)ms.Length);
DisplayMessage(JSONmsg);
}
}
}
private void DisplayMessage(string JSONmsg)
{
if (dataTelemetryInfo.InvokeRequired)
{
object[] args = new object[] { JSONmsg };
this.Invoke(new StringArg(DisplayMessage), args); //StringArg is a delegate with a string as a parameter
}
else
{
lblTest.Text += JSONmsg;
telemetryInfo = serializer.Deserialize<TelemetryUpdate>(JSONmsg);
dataTelemetryInfo.Rows.Add(telemetryInfo.Altitude.ToString(), telemetryInfo.ElevatorPitch.ToString(), telemetryInfo.Pitch.ToString(),
telemetryInfo.Speed.ToString(), telemetryInfo.Throttle.ToString(), telemetryInfo.VerticalSpeed.ToString(),
telemetryInfo.WarningCode.ToString());
}
}
Once I connect the programs, and I click on the Listen button of my GUI (which starts the "start()" method), nothing changes and my datagridview isn't updated. Thank you in advance.
Change ReceiveMessage to:
private void ReceiveMessage()
{
//netStream = tcpClient.GetStream();
string JSONmsg = " ";
int i;
using (netStream = tcpClient.GetStream())
{
byte[] buffer = new byte[1024];
using (MemoryStream ms = new MemoryStream())
{
int numBytesRead;
while ((numBytesRead = netStream.Read(buffer, 0, buffer.Length)) > 0)
{
ms.Write(buffer, 0, numBytesRead);
}
JSONmsg = Encoding.ASCII.GetString(ms.ToArray());
DisplayMessage(JSONmsg);
}
}
}
You must .Write to your MemoryStream.

How receive a complete screenshot in Async socket?

I have a Java android code that sends data (image or text) to a C# application, to receive these data I'm using Async socket. But exists a problem that is relative to BeginReceive() function is not receiving the complete data when is sent an image.. Then how I can make a kind of "loop" to receive full data and after show the image on Picturebox (for example)?
Form
private Listener listener;
private Thread startListen;
private Bitmap _buffer;
public frmMain()
{
InitializeComponent();
}
private void serverReceivedImage(Client client, byte[] image)
{
try
{
byte[] newImage = new byte[image.Length - 6];
Array.Copy(image, 6, newImage, 0, newImage.Length);
using (var stream = new MemoryStream(newImage))
{
using (var msInner = new MemoryStream())
{
stream.Seek(2, SeekOrigin.Begin);
using (DeflateStream z = new DeflateStream(stream, CompressionMode.Decompress))
{
z.CopyTo(msInner);
}
msInner.Seek(0, SeekOrigin.Begin);
var bitmap = new Bitmap(msInner);
Invoke(new frmMain.ImageCompleteDelegate(ImageComplete), new object[] { bitmap });
}
}
}
catch (Exception)
{
System.Diagnostics.Process.GetCurrentProcess().Kill();
}
}
private delegate void ImageCompleteDelegate(Bitmap bitmap);
private void ImageComplete(Bitmap bitmap)
{
if (_buffer != null)
_buffer.Dispose();
_buffer = new Bitmap(bitmap);
pictureBox1.Size = _buffer.Size;
pictureBox1.Invalidate();
}
private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
if (_buffer == null) return;
e.Graphics.DrawImage(_buffer, 0, 0);
}
private void startToolStripMenuItem_Click(object sender, EventArgs e)
{
startListen = new Thread(listen);
startListen.Start();
}
private void listen()
{
listener = new Listener();
listener.BeginListen(101);
listener.receivedImage += new Listener.ReceivedImageEventHandler(serverReceivedImage);
startToolStripMenuItem.Enabled = false;
}
Listener
class Listener
{
private Socket s;
public List<Client> clients;
public delegate void ReceivedImageEventHandler(Client client, byte[] image);
public event ReceivedImageEventHandler receivedImage;
private bool listening = false;
public Listener()
{
clients = new List<Client>();
s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
public bool Running
{
get { return listening; }
}
public void BeginListen(int port)
{
s.Bind(new IPEndPoint(IPAddress.Any, port));
s.Listen(100);
s.BeginAccept(new AsyncCallback(AcceptCallback), s);
listening = true;
}
public void StopListen()
{
if (listening == true)
{
s.Close();
listening = false;
}
}
void AcceptCallback(IAsyncResult ar)
{
Socket handler = (Socket)ar.AsyncState;
Socket sock = handler.EndAccept(ar);
Client client = new Client(sock);
clients.Add(client);
sock.BeginReceive(client.buffer, 0, client.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), client);
client.Send("REQUEST_PRINT" + Environment.NewLine);
handler.BeginAccept(new AsyncCallback(AcceptCallback), handler);
}
void ReadCallback(IAsyncResult ar)
{
Client client = (Client)ar.AsyncState;
try
{
int rec = client.sock.EndReceive(ar);
if (rec != 0)
{
string data = Encoding.UTF8.GetString(client.buffer, 0, rec);
if (data.Contains("SCREEN"))
{
byte[] bytes = Encoding.UTF8.GetBytes(data);
receivedImage(client, bytes);
}
else // not is a image, is a text
{
// prepare text to show in TextBox
}
}
else
{
Disconnected(client);
return;
}
client.sock.BeginReceive(client.buffer, 0, client.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), client);
}
catch
{
Disconnected(client);
client.sock.Close();
clients.Remove(client);
}
}
}
Client
class Client
{
public Socket sock;
public byte[] buffer = new byte[8192];
public Client(Socket sock)
{
this.sock = sock;
}
public void Send(string data)
{
byte[] buffer = Encoding.ASCII.GetBytes(data);
sock.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((ar) =>
{
sock.EndSend(ar);
}), buffer);
}
}
Android code
private byte[] compress(byte[] data) {
Deflater deflater = new Deflater();
deflater.setInput(data);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
deflater.finish();
byte[] buffer = new byte[1024];
while (!deflater.finished()) {
int count = deflater.deflate(buffer);
outputStream.write(buffer, 0, count);
}
outputStream.close();
byte[] output = outputStream.toByteArray();
return output;
}
public static DataOutputStream dos;
public static byte[] array;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
array = compress(bos.toByteArray());
//...
dos = new DataOutputStream(SocketBackgroundService.clientSocket.getOutputStream());
byte[] header = ("SCREEN").getBytes(StandardCharsets.UTF_8);
byte[] dataToSend = new byte[header.length + array.length];
System.arraycopy(header, 0, dataToSend, 0, header.length);
System.arraycopy(array, 0, dataToSend, header.length, array.length);
dos.writeInt(dataToSend.length);
dos.write(dataToSend, 0, dataToSend.length);
dos.flush();
EDITION
i'm always getting the error Invalid Parameter in this line
var bitmap = new Bitmap(msInner);
and using compression also happens the same here
z.CopyTo(msInner);
IvalidDataException
on ServerReceivedImage() method respectively.
using this
File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "image.png"), newImage);
i noted that is receiving only 15KB (size of file without use compression).
I was writing a comment but it does not give me enough space to express my frustration with your code.
My main points are
You try to recompress and perfectly compressed image. PNG is portable network graphics. It was designed for network transfers. If it is acceptable you should use something like jpeg.
You just decode received buffer using UTF8.GetString and search for a text, then re-encode that string and try to decompress and read an image from it, by starting from index 6 which is pretty meaningless considering you added a two byte size field to the start of stream and you really do not know position of "SCREEN".
You do not check if you have received ALL of the stream data.
All of the code looks like you have scoured the SO questions and answers and created a copy pasta.
Now my recommendations.
When transferring data from network, do not try to invent wheels. Try something like gRPC which has both android java and c# packages.
If you will use raw data, please, please know your bytes.
I assume you will extend your code by adding new command pairs. Since you have no magic markers of some kind of signal system, it will be very hard for you to distinguish data from header. For a simple implementation add some kind of magic data to your header and search for that data, then read header and then read data. You may need to read from socket again and again until you receive all of the data.
424A72 0600 53435245454E 008E0005 ..... 724A42
B J r 6 S C R E E N 36352 ..... rJB
this sample data shows that we have a valid stream by looking at "BJr". Then read a 2 byte unsigned integer to read command size which is 6 for SCREEN. Read command and then read four bytes unsigned length for command data. For our sample it is 36352. Just to be safe I've added an end of command marker "rJB".
For a bonus point try reducing memory allocations / copies, you can look at System.Span<T>

Fastest possible UDP listener socket in .NET

Hello guys I was wondering what would be the fastest possible reading mechanism for UDP packets.
I was thinking about using socket async recieve, which should be the fastest way to receive data. Then having a processing loop in a different thread.
Is this really the fastest possible way to do it?
static Socket udpListener;
static byte[] buffer = new byte[5000];
static ConcurrentQueue<byte[]> recievedPackets = new ConcurrentQueue<byte[]>();
static void Main(string[] args)
{
udpListener = new Socket(SocketType.Dgram, ProtocolType.Udp);
udpListener.Bind(new IPEndPoint(IPAddress.Any, 50000));
StartRecievingChain();
// Processing loop
Task.Factory.StartNew(() =>
{
int counter = 0;
while (true)
{
if (recievedPackets.Count == 0)
Thread.Sleep(50);
byte[] packet;
if (recievedPackets.TryDequeue(out packet))
{
counter++;
Console.WriteLine("Recieved {0} bytes", packet.Length);
}
}
});
Console.ReadKey();
}
static void StartRecievingChain()
{
SocketAsyncEventArgs recieveArgs = new SocketAsyncEventArgs();
recieveArgs.SetBuffer(buffer, 0, buffer.Length);
recieveArgs.Completed += ProcessRecievedPacket;
if (!udpListener.ReceiveAsync(recieveArgs))
ProcessRecievedPacket(null, recieveArgs);
}
static void ProcessRecievedPacket(object sender, SocketAsyncEventArgs e)
{
try
{
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
StartRecievingChain();
byte[] data = new byte[e.BytesTransferred];
Array.Copy(e.Buffer, data, e.BytesTransferred);
recievedPackets.Enqueue(data);
}
}
finally
{
e.Completed -= ProcessRecievedPacket;
e.Dispose();
}
}

C# Socket(server) wont receive any datas

I am trying to make an advanced chat in C#. I am not new to programming but it is close to my first TCP chat.
The problem is that my Socket (server) looks to not receive any datas. In my void ReceiveDataListener which is a BackgroundWorker, I added some Console.WriteLine(); to check where it locks and it only display the first Console.WriteLine("Receive Data Listener 0"). I know it is normal that Socket.Receive() lock until some datas are received but it seems to stay locked even if I send datas.
I also want to add that my event onClientConnect and onClientDisconnect are invoked fine so I know the clients connects fine.
Here is the code of my Server class :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Net;
using System.Net.Sockets;
using System.ComponentModel;
using System.Threading;
namespace JAChat.Library
{
class SocketServer
{
private Socket socket;
private BackgroundWorker bwSocketConnectListener;
private BackgroundWorker bwCheckIfConnected;
private BackgroundWorker bwReceiveDataListener;
private List<ClientManager> clientsList;
#region Constructor
public SocketServer(int port)
{
clientsList = new List<ClientManager>();
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Any, port));
socket.Listen(100);
bwSocketConnectListener = new BackgroundWorker();
bwSocketConnectListener.DoWork += new DoWorkEventHandler(ListenSocketConnection);
bwSocketConnectListener.RunWorkerAsync();
bwCheckIfConnected = new BackgroundWorker();
bwCheckIfConnected.DoWork += CheckIfClientStillConnectedThread;
bwCheckIfConnected.RunWorkerAsync();
bwReceiveDataListener = new BackgroundWorker();
bwReceiveDataListener.DoWork += ReceiveDataListener;
bwReceiveDataListener.RunWorkerAsync();
}
#endregion
#region Getter
public List<ClientManager> connectedClients
{
get
{
return clientsList;
}
}
#endregion
#region Public Methods
/// <summary>
/// Parse and send the command object to targets
/// </summary>
public void sendCommand(Command cmd)
{
BackgroundWorker test = new BackgroundWorker();
test.DoWork += delegate {
foreach(ClientManager cManager in clientsList){
cManager.sendCommand(cmd);
}
};
test.RunWorkerAsync();
}
/// <summary>
/// Disconnect and close the socket
/// </summary>
public void Disconnect()
{
socket.Disconnect(false);
socket.Close();
socket = null; //Stop some background worker
}
#endregion
#region Private Methods
private void ListenSocketConnection(object sender, DoWorkEventArgs e)
{
while (socket != null)
{
//Get and WAIT for new connection
ClientManager newClientManager = new ClientManager(socket.Accept());
clientsList.Add(newClientManager);
onClientConnect.Invoke(newClientManager);
}
}
private void CheckIfClientStillConnectedThread(object sender, DoWorkEventArgs e){
while(socket != null){
for(int i=0;i<clientsList.Count;i++){
if(clientsList[i].socket.Poll(10,SelectMode.SelectRead) && clientsList[i].socket.Available==0){
clientsList[i].socket.Close();
onClientDisconnect.Invoke(clientsList[i]);
clientsList.Remove(clientsList[i]);
i--;
}
}
Thread.Sleep(5);
}
}
private void ReceiveDataListener(object sender, DoWorkEventArgs e){
while (socket != null){
Console.WriteLine("Receive Data Listener 0");
//Read the command's Type.
byte[] buffer = new byte[4];
int readBytes = this.socket.Receive(buffer, 0, 4, SocketFlags.None);
Console.WriteLine("Receive Data Listener 1");
if (readBytes == 0)
break;
Console.WriteLine("Receive Data Listener 2");
CommandType cmdType = (CommandType)(BitConverter.ToInt32(buffer, 0));
Console.WriteLine("Receive Data Listener 3");
//Read the sender IP size.
buffer = new byte[4];
readBytes = this.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int senderIPSize = BitConverter.ToInt32(buffer, 0);
//Read the sender IP.
buffer = new byte[senderIPSize];
readBytes = this.socket.Receive(buffer, 0, senderIPSize, SocketFlags.None);
if (readBytes == 0)
break;
IPAddress cmdSenderIP = IPAddress.Parse(System.Text.Encoding.ASCII.GetString(buffer));
//Read the sender name size.
buffer = new byte[4];
readBytes = this.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int senderNameSize = BitConverter.ToInt32(buffer, 0);
//Read the sender name.
buffer = new byte[senderNameSize];
readBytes = this.socket.Receive(buffer, 0, senderNameSize, SocketFlags.None);
if (readBytes == 0)
break;
string cmdSenderName = System.Text.Encoding.Unicode.GetString(buffer);
//Read target IP size.
string cmdTarget = "";
buffer = new byte[4];
readBytes = this.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int targetIPSize = BitConverter.ToInt32(buffer, 0);
//Read the command's target.
buffer = new byte[targetIPSize];
readBytes = this.socket.Receive(buffer, 0, targetIPSize, SocketFlags.None);
if (readBytes == 0)
break;
cmdTarget = System.Text.Encoding.ASCII.GetString(buffer);
//Read the command's MetaData size.
string cmdMetaData = "";
buffer = new byte[4];
readBytes = this.socket.Receive(buffer, 0, 4, SocketFlags.None);
if (readBytes == 0)
break;
int metaDataSize = BitConverter.ToInt32(buffer, 0);
//Read the command's Meta data.
buffer = new byte[metaDataSize];
readBytes = this.socket.Receive(buffer, 0, metaDataSize, SocketFlags.None);
if (readBytes == 0)
break;
cmdMetaData = System.Text.Encoding.Unicode.GetString(buffer);
//Create the command object
Command cmd = new Command(cmdType, cmdSenderIP, cmdSenderName, IPAddress.Parse(cmdTarget), cmdMetaData);
this.onCommandReceived(cmd);
}
Console.WriteLine("Receive data listener closed");
}
#endregion
#region Events
public delegate void OnClientConnectEventHandler(ClientManager client);
/// <summary>
/// Events invoked when a client connect to the server
/// </summary>
public event OnClientConnectEventHandler onClientConnect = delegate { };
public delegate void OnClientDisconnectEventHandler(ClientManager client);
/// <summary>
/// Events invoked when a client disconnect from the server
/// </summary>
public event OnClientDisconnectEventHandler onClientDisconnect = delegate { };
public delegate void OnCommandReceivedEventHandler(Command cmd);
/// <summary>
/// Events invoked when a command has been sent to the server
/// </summary>
public event OnCommandReceivedEventHandler onCommandReceived = delegate { };
#endregion
}
}
and here is the code that I use to test the server by sending datas :
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect("localhost", 2000);
networkStream = new NetworkStream(clientSocket);
//CommandType
byte[] buffer = new byte[4];
buffer = BitConverter.GetBytes(1);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();
//Sender IP + Size
byte[] senderIPBuffer = Encoding.ASCII.GetBytes("192.54.67.8");
buffer = new byte[4];
buffer = BitConverter.GetBytes(senderIPBuffer.Length);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();
this.networkStream.Write(senderIPBuffer, 0, senderIPBuffer.Length);
this.networkStream.Flush();
//Sender Name + Size
byte[] senderNameBuffer = Encoding.ASCII.GetBytes("James");
buffer = new byte[4];
buffer = BitConverter.GetBytes(senderNameBuffer.Length);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();
this.networkStream.Write(senderNameBuffer, 0, senderNameBuffer.Length);
this.networkStream.Flush();
//Command Target IP + Size
byte[] targetIPBuffer = Encoding.ASCII.GetBytes("192.43.54.6");
buffer = new byte[4];
buffer = BitConverter.GetBytes(targetIPBuffer.Length);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();
this.networkStream.Write(targetIPBuffer, 0, targetIPBuffer.Length);
this.networkStream.Flush();
//Command MetaData + Size
byte[] metaBuffer = Encoding.Unicode.GetBytes("Metadata contents");
buffer = new byte[4];
buffer = BitConverter.GetBytes(metaBuffer.Length);
this.networkStream.Write(buffer, 0, 4);
this.networkStream.Flush();
this.networkStream.Write(metaBuffer, 0, metaBuffer.Length);
this.networkStream.Flush();
SocketServer has a class-level variable named "socket"
SocketServer's constructor creates a socket for listening on the specified port and saves this as the value of socket
ListenSocketConnection is calling Socket.Accept (good) and keeping a list of connections
ERROR: ReceiveDataListener is trying to read from the socket that is listening on the specified port instead of reading from the individual sockets that were returned by the Accept call in ListenSocketConnection
Note: the server throws an exception right on startup (for me at least) in ReceiveDataListener, before any client has even attempted to connect, since the socket reader worker immediately tries to read from a socket that is in the listen state.
Hope that helps - Harold

Waiting for multiple TcpClients to have data available - WaitHandle or Thread.Sleep?

I am writing a server application that will receive data from multiple TCP connections. We would like to be able to scale to ~200 connections. The first algorithm I wrote for this is as follows:
while (keepListening)
{
foreach (TcpClient client in clientList)
{
if (!client.Connected)
{
client.Close();
deleteList.Add(client);
continue;
}
int dataAvail = client.Available;
if (dataAvail > 0)
{
NetworkStream netstr = client.GetStream();
byte[] arry = new byte[dataAvail];
netstr.Read(arry, 0, dataAvail);
MemoryStream ms = new MemoryStream(arry);
try
{
CommData data = dataDeserializer.Deserialize(ms) as CommData;
beaconTable.BeaconReceived(data);
}
catch
{ }
}
}
foreach (TcpClient clientToDelete in deleteList)
clientList.Remove(clientToDelete);
deleteList.Clear();
while (connectionListener.Pending())
clientList.Add(connectionListener.AcceptTcpClient());
Thread.Sleep(20);
}
This works fine, though I found that I have to add the Thread.Sleep to slow down the loop, otherwise it takes up an entire core, no matter how many or few connections there are. I was advised that Thread.Sleep is generally considered bad, so I looked for some alternatives. In a similar question to this, I was recommended to use BeginRead and BeginAccept using WaitHandles, so I wrote up an algorithm to do the same thing using that, and came up with this:
while (keepListening)
{
int waitResult = WaitHandle.WaitAny(waitList.Select(t => t.AsyncHandle.AsyncWaitHandle).ToArray(), connectionTimeout);
if (waitResult == WaitHandle.WaitTimeout)
continue;
WaitObject waitObject = waitList[waitResult];
Type waitType = waitObject.WaitingObject.GetType();
if (waitType == typeof(TcpListener))
{
TcpClient newClient = (waitObject.WaitingObject as TcpListener).EndAcceptTcpClient(waitObject.AsyncHandle);
waitList.Remove(waitObject);
byte[] newBuffer = new byte[bufferSize];
waitList.Add(new WaitObject(newClient.GetStream().BeginRead(newBuffer, 0, bufferSize, null, null), newClient, newBuffer));
if (waitList.Count < 64)
waitList.Add(new WaitObject(connectionListener.BeginAcceptTcpClient(null, null), connectionListener, null));
else
{
connectionListener.Stop();
listening = false;
}
}
else if (waitType == typeof(TcpClient))
{
TcpClient currentClient = waitObject.WaitingObject as TcpClient;
int bytesRead = currentClient.GetStream().EndRead(waitObject.AsyncHandle);
if (bytesRead > 0)
{
MemoryStream ms = new MemoryStream(waitObject.DataBuffer, 0, bytesRead);
try
{
CommData data = dataDeserializer.Deserialize(ms) as CommData;
beaconTable.BeaconReceived(data);
}
catch
{ }
byte[] newBuffer = new byte[bufferSize];
waitList.Add(new WaitObject(currentClient.GetStream().BeginRead(newBuffer, 0, bufferSize, null, null), currentClient, newBuffer));
}
else
{
currentClient.Close();
}
waitList.Remove(waitObject);
if (!listening && waitList.Count < 64)
{
listening = true;
connectionListener.Start();
waitList.Add(new WaitObject(connectionListener.BeginAcceptTcpClient(null, null), connectionListener, null));
}
}
else
throw new ApplicationException("An unknown type ended up in the wait list somehow: " + waitType.ToString());
}
This also works fine, until I hit 64 clients. I wrote in a limit to not accept more than 64 clients because that's the maximum number of WaitHandles that WaitAny will accept. I can't see any good way around this limit, so I basically can't maintain more than 64 connections like this. The Thread.Sleep algorithm works fine with 100+ connections.
I also don't really like having to pre-allocate a receive array of arbitrary size rather than allocating it at the exact size of the received data after the data is received. And I have to give WaitAny a timeout anyways, or else it prevents the thread running this from Join-ing when I close the application if there are no connections. And it's just generally longer and more complex.
So why is Thread.Sleep the worse solution? Is there some way I can at least get the WaitAny version to handle more than 64 connections? Is there some completely different way of handling this that I'm not seeing?
Jim gave the obvious suggestion of using Async callbacks instead of WaitHandles. I initially thought that this would be too complex, but it got a lot simpler once I realized that I could pass a reference to the calling TcpListener or TcpClient in the state object. With that and a few changes for thread-safety, it's ready to go. It tests fine with over 100 connections and has no problem with exiting cleanly. I would still like an alternative, though, to having to pre-allocate the data buffer. Here's the code for anyone trying something similar:
public class NetworkReceiver : IDisposable
{
private IReceiver beaconTable;
private XmlSerializer dataDeserializer;
private HashSet<TcpClient> ClientTable;
private TcpListener connectionListener;
private int bufferSize = 1000;
public NetworkReceiver(IReceiver inputTable)
{
beaconTable = inputTable;
dataDeserializer = new XmlSerializer(typeof(CommData));
ClientTable = new HashSet<TcpClient>();
connectionListener = new TcpListener(IPAddress.Any, SharedData.connectionPort);
connectionListener.Start();
connectionListener.BeginAcceptTcpClient(ListenerCallback, connectionListener);
}
private void ListenerCallback(IAsyncResult callbackResult)
{
TcpListener listener = callbackResult.AsyncState as TcpListener;
TcpClient client;
try
{
client = listener.EndAcceptTcpClient(callbackResult);
lock (ClientTable)
ClientTable.Add(client);
ClientObject clientObj = new ClientObject() { AsyncClient = client, Buffer = new byte[bufferSize] };
client.GetStream().BeginRead(clientObj.Buffer, 0, bufferSize, ClientReadCallback, clientObj);
listener.BeginAcceptTcpClient(ListenerCallback, listener);
}
catch (ObjectDisposedException)
{
return;
}
}
private void ClientReadCallback(IAsyncResult callbackResult)
{
ClientObject clientObj = callbackResult.AsyncState as ClientObject;
TcpClient client = clientObj.AsyncClient;
if (!client.Connected)
return;
try
{
int bytesRead = client.GetStream().EndRead(callbackResult);
if (bytesRead > 0)
{
MemoryStream ms = new MemoryStream(clientObj.Buffer, 0, bytesRead);
try
{
CommData data;
lock (dataDeserializer)
data = dataDeserializer.Deserialize(ms) as CommData;
lock (beaconTable)
beaconTable.BeaconReceived(data);
}
catch
{ }
client.GetStream().BeginRead(clientObj.Buffer, 0, bufferSize, ClientReadCallback, clientObj);
}
else
{
client.Close();
lock (ClientTable)
ClientTable.Remove(client);
}
}
catch (Exception ex)
{
if (ex.GetType() == typeof(ObjectDisposedException) || ex.GetType() == typeof(InvalidOperationException))
return;
else
throw;
}
}
class ClientObject
{
public TcpClient AsyncClient;
public byte[] Buffer;
}
public void Dispose()
{
connectionListener.Stop();
foreach (TcpClient client in ClientTable)
client.Close();
}
}

Categories