My multi-thread online Server gets too slow when multiple clients connect, C# - c#

I'm trying to develop an online game just for fun, with a server and at least 2 clients playing in a single "room". My logic goes as follow, first have the server wait for a client to connect, after the client connects to the server I create a thread for this client (so the client can send messages to the server when it interacts with the main screen). After this if the client wants to play he clicks a button that says "start match" and if another client does the same both goes inside another Thread to play the game only between them. So I create a Thread for each client that connects and a Thread for every 2 clients that want to play.
Now, when i have 2 players playing the game everything goes smooth, the problem is when another 2 players want to play the game the server standalone stops responding (but you can still connect to the server and send messages) and the game gets really slow for the clients.I'm trying to learn about multi-threading that's why i'm doing this project so maybe i'm missing something. I should be creating only 2 threads for each client (when they are actually in game if not only 1) and with 4 clients that's at max 8 Threads, so why is my server getting so slow and crashing?.
This is where I accept my clients:
private void AcceptTcpClient(IAsyncResult ar)
{
TcpListener listener = (TcpListener)ar.AsyncState;
ServerClient sc = new ServerClient(listener.EndAcceptTcpClient(ar));
sc.SetisInGame(false); // not in game, just in the main menu
clients.Add(sc);
new Thread(() => MainPanelRoomThread(sc)).Start();
StartListening();
}
This is my Thread when a client connects:
private void MainPanelRoomThread(ServerClient scThread)
{
while (true)
{
if (!serverStarted)
return;
if (!scThread.GetisInGame()) //check if its not in game so it can receive data
{
NetworkStream stream = scThread.tcp.GetStream();
if (stream.DataAvailable)
{
StreamReader streamReader = new StreamReader(stream, true);
string data = streamReader.ReadLine();
if (data != null)
OnIncomingData(scThread, data); // waiting for that from the client
}
}
}
}
In the OnIncomingData function I wait for a Message saying that the client wants to enter a game so i can start the other thread:
private void OnIncomingData(ServerClient c, string data)
{
Debug.Log("Server = " + data);
string[] aData = data.Split('|');
switch (aData[0])
{
case "CMATCH":
if (aData[1].Contains("true"))
{
queueClients.Enqueue(c);
if (queueClients.Count == 2)
new Thread(new ThreadStart(RoomThread)).Start(); //start the Game only if 2 players are waiting for the game to start.
else
BroadCast("SMATCH|false", c);
}else if (aData[1].Contains("false"))
{
queueClients.Dequeue();
BroadCast("SMATCH|true", c);
}
break;
}
}
Finally the in Game room:
private void RoomThread()
{
string msg = "";
List<ServerClient> players = new List<ServerClient>();
List<ServerClient> disconnected = new List<ServerClient>();
int tamanoCola = queueClients.Count;
for(int i = 0; i < tamanoCola; i++)
{
players.Add(queueClients.Dequeue());
msg = players[i].clientName + "|";
players[i].isMainPlayer = (i == 0);
players[i].SetisInGame(true); //its in game
BroadCast("SWHO|" + msg + ((players[i].isMainPlayer) ? 1 : 0).ToString(), players[i]);
}
while (true)
{
if (!serverStarted)
return;
foreach (ServerClient c in players)
{
//check if client is still playing (either because the connection died or someone won)
if (!IsConnected(c.tcp))
{
c.tcp.Close();
disconnected.Add(c);
continue;
}
else
{
NetworkStream stream = c.tcp.GetStream();
if (stream.DataAvailable)
{
StreamReader streamReader = new StreamReader(stream, true);
string data = streamReader.ReadLine();
if (data != null)
OnIncomingData(c, data, players);
}
}
}
if (disconnected.Count >= 1)
return;
}
}
Any help is really appreciated.

Related

No other function is working with timer running in console app

I am using a system.Timers Timer in Asynchronous Socket listener console app.
I want the timer to run every 40 seconds and check connection status of client devices.
Once the timer starts Server socket is not listening any more(not waiting for more connections) and only data can be received all the time by 1st device that was connected. When I removed the device from network and again connected there is nothing happening. No other function is running.
Here is the code for timer-
IPStatusTimer = new System.Timers.Timer(35000);
IPStatusTimer.Elapsed += IPStatusTimer_Elapsed;
IPStatusTimer.AutoReset = true;
IPStatusTimer.Enabled = true;
private static void IPStatusTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
Console.WriteLine("Inside Timer");
if (connectedClient > 0)
{
foreach (KeyValuePair<string, int> i in IPStatus)
{
if (i.Value == 1)
{
IPStatus[i.Key] = 0;
}
else if (i.Value == 0)
{
Decode dr = new Decode();
string m = dr.offlineMessage();
if (Clients.Count > 0)
{
foreach (KeyValuePair<int, StateObject> c in Clients)
{
if (((IPEndPoint)c.Value.workSocket.RemoteEndPoint).Address.ToString() == i.Key)
{
Clients.Remove(c.Key);
}
}
SendMessage(i.Key, m);
}
}
}
}
}
The timer should not affect my other functions. It should quietly run in background till the app is working.
EDIT: (In above code)Added if CLients.Count>0 then only will remove item from CLients So that there wont be any exception there.
Whenever there is a connection I am adding the Stateobject to dictionary so that I can broadcast data to all the clients when require.
public static void AcceptCallback(IAsyncResult ar)
{
connectedClient++;
// Signal the main thread to continue.
allDone.Set();
// Get the socket that handles the client request.
Socket listener = (Socket)ar.AsyncState;
Socket handler = listener.EndAccept(ar);
string ip = ((IPEndPoint)handler.RemoteEndPoint).Address.ToString();
Console.WriteLine("ip " + ip);
// Create the state object.
StateObject state = new StateObject();
state.buffer = new byte[StateObject.BufferSize];
Clients.Add(connectedClient, state);
if (IPStatus.ContainsKey(ip))
IPStatus[ip] = 1;
else
IPStatus.Add(ip, 1);
Console.WriteLine(" Total Connected client : " + connectedClient);
state.workSocket = handler;
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
new AsyncCallback(ReadCallback), state);
}
So as you can see.. If a client is back in network and server is always in listening mode it should accept connection and state object should be added to Client(dictionary object) again. It should not be a problem. And as soon as client gets connected I am giving IPStatus(dictionary object) a value 1 as it means client is online.
Then in my readCallBack(), whenever I receive data from any client, then in my dictionary , I am again setting its value to 1 in IPStatus.
public static void ReadCallback(IAsyncResult ar)
{
string content = string.Empty;
// Retrieve the state object and the handler socket
// from the asynchronous state object.
StateObject state = (StateObject)ar.AsyncState;
Socket handler = state.workSocket;
// Read data from the client socket.
int bytesRead = handler.EndReceive(ar);
int i = 0;
string ip = ((IPEndPoint)handler.RemoteEndPoint).Address.ToString();
if (IPStatus.ContainsKey(ip))
{
IPStatus[ip] = 1;
}
}
And when Timer runs , If Value of any IP is 1 I am setting it to 0. So that next time if timer runs and it gets value 0 that means it had not received any data from client in last 35 seconds which means client is not online anymore. ReadCallback and all is happening using StateObject not with Clients(dictinary Object). So if I am removing any item from Clients, I am adding to it again when Any new connection starts.
I hope my scenario is getting a bit more clear to you guys.
Thanks to #rs232... to point out the cause of exception.
It was dictionary object that was throwing error. So used lock(object) before read and write operation on dictionary.
lock (Clients)
{
if (Clients.Count > 0)
{
foreach (KeyValuePair<string, StateObject> c in Clients)
{
if (c.Value.workSocket == handler)
{
Clients.Remove(c.Key);
Console.WriteLine("Client automatically disconnected");
Decode dr = new Decode();
string m = dr.offlineMessage();
SendMessage(ip, m);
break;
}
}
}
}
And for anyone else who comes on this page thinking about timer issue. The code above(in the question) is perfectly fine, working as required. You can take a reference from that.

Allow main (gui) thread while doing a task then update table

I'm using a winform to try to gather online/offline status of every IP on the network, doing so I'm using a ping request, if it replies it marks the IP as online then moves on. Only issue is waiting for up to 255 replies, after it gets all 255 responses I am wanting it to fill a data grid view.
I've managed to get it all to work but only downside is, gui hangs while doing this process, I figured just use a separate thread with the following expression
Thread T1 = new Thread(PingHost)
T1.run();
PingHost does as the name implies, pings all the hosts and decides if online or offline. Problem is I tried to update the dgv table from the thread, naturally dgv is locked to main thread.
So I tried switching to a Task and just grab the return value and update after everything is finished. Sadly I can't quite get how to get the return value from the task AND have it run on a separate thread.
I've tried googling different methods, but just running in circles at this point, so I humbly come to you guys for assistance
Code of main thread using tasks
List<string> everything = new List<string>();
int i = 0;
Task<List<string>> task1 = Task<List<string>>.Factory.StartNew(PingHost);
everything = task1.Result;
foreach(var item in everything)
{
var index = dataGridView1.Rows.Add();
dataGridView1.Rows[i].Cells["IP"].Value = item;
i++;
}
And this is the thread of my PingHost method
bool pingable = false;
Ping pinger = null;
int i = 0;
string ip;
while (i < 255)
{
ip = "192.168.0." + i;
try
{
pinger = new Ping();
PingReply reply = pinger.Send(ip, 8);
pingable = reply.Status == IPStatus.Success;
}
catch (PingException)
{
MessageBox.Show("ERROR");
// Discard PingExceptions and return false;
}
finally
{
if (pinger != null)
{
pinger.Dispose();
}
}
if (pingable)
{
checkedIP.Add(ip + ": ONLINE");
}
else
{
checkedIP.Add(ip + ": OFFLINE");
}
i++;
}
return checkedIP;
This might be a bit overkill, but I just drafted a solution. Basically I created a new Class for pinging with an event that triggers after each Ping returned, this event uses custom EventArgs to return the IP that was just pinged and if it is online or not. I then subscribed to that Event in my GUI Thread and just update the GUI. Here's some code:
This is my Pinger class responsible for pinging the actual Computers:
class Pinger
{
public event EventHandler<PingReturnedEventArgs> OnPingReturned;
public void PingNetwork()
{
for (int i = 1; i <= 255; i++)
{
string ip = $"192.168.0.{i}";
Ping ping = new Ping();
try
{
PingReply reply = ping.Send(IPAddress.Parse(ip));
TriggerEvent(reply?.Address.ToString(), true);
}
catch (Exception)
{
TriggerEvent(ip, false);
}
}
}
private void TriggerEvent(string ip, bool online)
{
if (OnPingReturned == null) return;
PingReturnedEventArgs args = new PingReturnedEventArgs(ip, online);
OnPingReturned(this, args);
}
}
My custom EventArgs:
class PingReturnedEventArgs : EventArgs
{
public string Ip { get; private set; }
public bool Online { get; private set; }
private PingReturnedEventArgs() { }
public PingReturnedEventArgs(string ip, bool online)
{
Ip = ip;
Online = online;
}
}
And finally here is how I'm actually using all of this:
Pinger pinger = new Pinger();
pinger.OnPingReturned += Your event handler to update the GUI
// I recommend doing it like this so you can stop the Thread at a later time
// Maybe with like a cancel button
Thread pingThread = new Thread(pinger.PingNetwork);
pingThread.Start();
The event handler looks like this private void PingReturnedHandler(object sender, PingReturnedEventArgs args)
The two main benefits of this are that 1. the GUI Thread remains unblocked, meaning the GUI will still respond to user input and 2. this procedurally (on every ping completion) triggers the event, meaning that if it takes a long time to ping all the PCs you don't have to wait for the entirety to finish before the user sees something

Unity3d - Node.js tcp connection, Client did not update immediately

I am testing node.js as TCP server and Unity3d as client for my up coming project.
I create a simple chat interface with unity3d that can send and receive data from the server. Everything seems to work fine until I test with 2 clients, one in Unity editor and another with Unity application, whenever I send the message, the server immediately pickup and display in terminal. But the client side only update the display interface when I click to focus on the client, be it Unity editor or the Unity application itself.
Below are my codes:
Node.js
net = require('net');
var clients = [];
net.createServer(function (socket) {
socket.name = socket.remoteAddress + ":" + socket.remotePort
clients.push(socket);
socket.write("Welcome " + socket.name + "\n");
broadcast(socket.name + " joined the chat\n", socket);
socket.on('data', function (data) {
broadcast(socket.name + "> " + data, socket);
});
socket.on('end', function () {
clients.splice(clients.indexOf(socket), 1);
broadcast(socket.name + " left the chat.\n");
});
function broadcast(message, sender) {
clients.forEach(function (client) {
if (client === sender) return;
client.write(message);
});
process.stdout.write(message)
}
}).listen(5000);
// Put a friendly message on the terminal of the server.
console.log("Chat server running at port 5000\n");
client.cs
using UnityEngine;
using UnityEngine.UI;
using System.Collections;
using System.Net.Sockets;
using System.IO;
public class client : MonoBehaviour {
private bool socketReady;
private TcpClient socket;
private NetworkStream stream;
private StreamWriter writer;
private StreamReader reader;
public Text servermessage;
public InputField input;
// Use this for initialization
void Start () {
ConnectToServer ();
}
// Update is called once per frame
void Update () {
if(socketReady){
if(stream.DataAvailable){
string data = reader.ReadLine ();
if(data != null){
OnIncomingData (data);
}
}
}
}
public void ConnectToServer(){
if(socketReady)
return;
string host = "127.0.0.1";
int port = 5000;
try
{
socket = new TcpClient(host,port);
stream = socket.GetStream();
writer = new StreamWriter(stream);
reader = new StreamReader(stream);
socketReady = true;
Debug.Log("socket :"+ reader.ToString());
}
catch (System.Exception e)
{
Debug.Log("socket error :"+ e);
}
}
private void OnIncomingData(string data){
Debug.Log ("server : "+ data);
servermessage.text = data;
}
public void OnOutgoingData(string data){
if (!socketReady)
return;
if (input.text == "") {
writer.WriteLine (data);
} else {
writer.WriteLine (input.text);
}
writer.Flush ();
input.text = "";
}
}
What is missing? How can I make it that I can see the immediate update on all my client interface?
This is not caused by your code, but due to the Unity editor. By default the unity editor does not run applications in the background.
You can turn run-in-background on inEdit > Project Settings > Settings-Player
There should be a field Run in background. Turn it on, and it should update even when you are not focused
See Also Run In background

Getting a speed-bottleneck in data-communication when using anonymous pipes

I have a program setup where a MCU sends me sensor data via USART to a C# windows forms application. That application upon receiving the data via the serialdatareceived event sends it to a managed c++ application using anonymous pipes. As soon as the data is received it is plotted in an OpenGL 3d enviroment.
My problem is that the 3D application only refreshes a few times per second and the animation is quite slow and not smooth enough. I did my best to improve the USART speed but the result is the same. I believe the animations speed is bottlenecked by the anonymous pipes speed. I was wondering if anyone else encountered this problem before and possibly found ways to speed upthe anonymous pipes data transfer.
So my problem is the low data transfer speed between the two applications. Ideally I would want 20+ messages a second but at the very least the bottleneck of the data transfer should be the USART interface and not the anonymous pipes. I am running a BAUD-rate of 19200 and am transferring the command "get_angle" and receiving data back fairly fast (~20 ms for calculation of data on the MCU), the data received is ~12 chars.
My anonymous pipe client in managed c++ (in WinMain):
try
{
String^ args = gcnew String(lpCmdLine);
PipeStream^ pipeClient = gcnew AnonymousPipeClientStream(PipeDirection::In, args);
StreamReader^ sr = gcnew StreamReader(pipeClient);
String^ temp;
fullscreen = FALSE;
if (!CreateGLWindow("OpenGL Test", 640, 480, 16, fullscreen))
{
return 0; // Quit If Window Was Not Created
}
while (!done) // Loop That Runs While done=FALSE
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) // Is There A Message Waiting?
{
if (msg.message == WM_QUIT) // Was the message a quit message?
{
done = TRUE; // Set Flag to execute program
}
else // If Not, Deal With Window Messages
{
TranslateMessage(&msg); // Translate The Message
DispatchMessage(&msg); // Dispatch The Message
}
}
temp = sr->ReadLine(); // Read text from pipeline
if (temp != "") // Make sure message is not empty/New message has been received
{
try
{
x_an = FLOAT::Parse(temp->Substring(0, 5)); // Parse X value from string to float
y_an = FLOAT::Parse(temp->Substring(7, 12)); // Parse Y value from string to float
}
catch (Exception^)
{
MessageBox(NULL, "Error parsing string to float", "Fatal error", MB_OK);
}
}
if ((!done)&& (1))
{
// Draw The Scene. Watch For ESC Key And Quit Messages From DrawGLScene()
if ((active && !DrawGLScene()) || keys[VK_ESCAPE]) // Active? Was There A Quit Received?
{
done = TRUE; // ESC or DrawGLScene Signalled A Quit
}
else // Not Time To Quit, Update Screen
{
SwapBuffers(hDC); // Swap Buffers (Double Buffering)
}
}
}
// Shutdown
sr->Close();
pipeClient->Close();
KillGLWindow(); // Kill The Window
return (msg.wParam); // Exit The Program
}
catch (Exception^)
{
MessageBox(NULL, "Pipe connection error", "Fatal error", MB_OK);
return 0x01;
}
My code for the C# anonymous pipes server part:
private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
if (Ready_To_Receive)
{
if (Setup_Pipe)
{
try
{
pipeClient.StartInfo.FileName = "client.exe";
pipeClient.StartInfo.Arguments =
pipeServer.GetClientHandleAsString();
pipeClient.StartInfo.UseShellExecute = false;
pipeClient.Start();
pipeServer.DisposeLocalCopyOfClientHandle();
sw = new System.IO.StreamWriter(pipeServer);
sw.AutoFlush = true;
Setup_Pipe = false;
}
catch
{
MessageBox.Show("Error setting up pipeserver.");
button2_Click(this, null); // Resets application
}
}
Debug_String = serialPort1.ReadExisting();
Debug_String = Debug_String.Replace(serialPort1.NewLine, ""); // Delete newline character so all that remains are numbers
if (!(Debug_String == "")) // String is not empty
{
DataReceived = true;
try
{
sw.WriteLine(Debug_String);
}
catch (Exception)
{
MessageBox.Show("Connection to Pipe Client lost.");
button2_Click(this, null);
}
}
}
else if (Shutting_Down)
{
pipeClient.Close();
}
else
{
serialPort1.ReadExisting(); // Flush the data buffer
DataReceived = true;
}
}

Broadcast from bluetooth server to multiple bluetooth clients with 32feet

I have an issue about the server-client communication.
I googled around but I did not find a solution to this.
Right now I am using 32feet in order to get in touch 2 or more (till 7) BT clients to 1 BT server.
I need to broadcast a message from the server to every device in the same time, but I don't know how to do it.
The only way I figured out was to use the list of connection in order to send the message one per time, but it means a delay between each message sent (around 100 ms per device). Unfortunately it means to have a large delay on the last one.
Can someone please give me an advice on how to solve this problem?
Is there a way to broadcast the message to all devices in the same time?
If it can be helpfull, here there is the handle of connection and reading from devices.
Thanks for your help
private void btnStartServer_Click(object sender, EventArgs e)
{
btnStartClient.Enabled = false;
ConnectAsServer();
}
private void ConnectAsServer()
{
connessioniServer = new List<BluetoothClient>();
// thread handshake
Thread bluetoothConnectionControlThread = new Thread(new ThreadStart(ServerControlThread));
bluetoothConnectionControlThread.IsBackground = true;
bluetoothConnectionControlThread.Start();
// thread connessione
Thread bluetoothServerThread = new Thread(new ThreadStart(ServerConnectThread));
bluetoothServerThread.IsBackground = true;
bluetoothServerThread.Start();
}
private void ServerControlThread()
{
while (true)
{
foreach (BluetoothClient cc in connessioniServer)
{
if (!cc.Connected)
{
connessioniServer.Remove(cc);
break;
}
}
updateConnList();
Thread.Sleep(0);
}
}
Guid mUUID = new Guid("fc5ffc49-00e3-4c8b-9cf1-6b72aad1001a");
private void ServerConnectThread()
{
updateUI("server started");
BluetoothListener blueListener = new BluetoothListener(mUUID);
blueListener.Start();
while (true)
{
BluetoothClient conn = blueListener.AcceptBluetoothClient();
connessioniServer.Add(conn);
Thread appoggio = new Thread(new ParameterizedThreadStart(ThreadAscoltoClient));
appoggio.IsBackground = true;
appoggio.Start(conn);
updateUI(conn.RemoteMachineName+" has connected");
}
}
private void ThreadAscoltoClient(object obj)
{
BluetoothClient clientServer = (BluetoothClient)obj;
Stream streamServer = clientServer.GetStream();
streamServer.ReadTimeout=1000;
while (clientServer.Connected)
{
try
{
int bytesDaLeggere = clientServer.Available;
if (bytesDaLeggere > 0)
{
byte[] bytesLetti = new byte[bytesDaLeggere];
int byteLetti = 0;
while (bytesDaLeggere > 0)
{
int bytesDavveroLetti = streamServer.Read(bytesLetti, byteLetti, bytesDaLeggere);
bytesDaLeggere -= bytesDavveroLetti;
byteLetti += bytesDavveroLetti;
}
updateUI("message sent from "+clientServer.RemoteMachineName+": " + System.Text.Encoding.Default.GetString(bytesLetti));
}
}
catch { }
Thread.Sleep(0);
}
updateUI(clientServer.RemoteMachineName + " has gone");
}
private void updateUI(string message)
{
Func<int> del = delegate()
{
textBox1.AppendText(message + System.Environment.NewLine);
return 0;
};
Invoke(del);
}
private void updateConnList()
{
Func<int> del = delegate()
{
listaSensori.Items.Clear();
foreach (BluetoothClient d in connessioniServer)
{
listaSensori.Items.Add(d.RemoteMachineName);
}
return 0;
};
try
{
Invoke(del);
}
catch { }
}
I don't exactly understand how you do it right now (the italian names are not helping...) but maybe my solution can help you.
first of all, bluetooth classic does not support broadcast. so you have to deliver at one at a time.
i do connect to 7 serial port devices at a time, using 7 threads. then i tell every thread to send data. this is very close to same time, but of course not exactly.
let me know if that helps or if you need a code example.

Categories

Resources