A multi-threaded chat server using c# .net. It accepts connections from the client and manage data reception in separate threads virtually unlimited clients can connect to this server depending on the machine capacity. This is just a server that will broadcast messages from the client to all other clients. also have some events to show server status and client count change.
Detecting client disconnect in abnormal situations like wire cut, improper shutdown,power failure are not supported by framework, we will have to periodically check for the client whether they are connected or not.
Detecting client disconnect in abnormal situations like wire cut, improper shutdown,power failure are not supported by framework, we will have to periodically check for the client whether they are connected or not.
public class Server { //thread safe dictionary to store list of tcp client connection that will be used to broadcast ConcurrentDictionary AllClients { get; set; } #region Events public delegate void ClientCount(int count); private event ClientCount _ClientCountUpdated; public event ClientCount ClientCountUpdated { add { _ClientCountUpdated += value; } remove { _ClientCountUpdated -= value; } } private void RaiseCount(int count) { if (_ClientCountUpdated != null) { _ClientCountUpdated(count); } } public delegate void ServerStatus(string status); private event ServerStatus _StatusChanged; public event ServerStatus StatusChanged { add { _StatusChanged += value; } remove { _StatusChanged -= value; } } private void RaiseStatusChanged(string status) { if (_StatusChanged != null) { _StatusChanged(status); } } #endregion #region Properties private TcpListener tcpListener; private Thread listenThread; private int _TotalClientCount; private int TotalClientCount { get { lock (this) { return _TotalClientCount; } } set { lock (this) { _TotalClientCount = value; RaiseCount(_TotalClientCount); } } } #endregion public Server() { AllClients = new ConcurrentDictionary(); MonitorDisconnectedClients(); } // this will monitor abnormal network disconnections private void MonitorDisconnectedClients() { new Thread(() => { while (true) { //an empty broadcast message similar to ping BroadcastMessage(" "); var clientsToRemove = AllClients.Values.Where(r => r.LastUpdate < DateTime.Now.AddSeconds(-5)).ToArray(); MyTcpClient temp = null; foreach (var client in clientsToRemove) { client.Tcp.Close(); AllClients.TryRemove(client.GetHashCode().ToString(), out temp); } RaiseCount(AllClients.Count); Thread.Sleep(5000); } }).Start(); } public void Start() { RaiseStatusChanged("Starting..."); this.tcpListener = new TcpListener(IPAddress.Any, 3000); this.listenThread = new Thread(new ThreadStart(ListenForClients)); this.listenThread.Start(); RaiseStatusChanged("Listening..."); } //start to listen to tcp connections private void ListenForClients() { this.tcpListener.Start(); while (true) { //blocks until a client has connected to the server TcpClient client = this.tcpListener.AcceptTcpClient(); var myClient = new MyTcpClient(client); //add client connection to to the thread safe dictionary AllClients.AddOrUpdate(myClient.GetHashCode().ToString(), myClient, (key, value) => { return value; }); //create a thread to handle communication //with connected client Thread clientThread = new Thread(new ParameterizedThreadStart(HandleClientComm)); clientThread.Start(myClient); RaiseCount(AllClients.Count); } } //this will handle messages comming from the client in a seperate thread private void HandleClientComm(object client) { MyTcpClient tcpClient = (MyTcpClient)client; NetworkStream clientStream = tcpClient.Tcp.GetStream(); byte[] message = new byte[4096]; int bytesRead; while (true) { bytesRead = 0; try { //blocks until a client sends a message bytesRead = clientStream.Read(message, 0, 4096); } catch { //a socket error has occured break; } if (bytesRead == 0) { AllClients.TryRemove(tcpClient.GetHashCode().ToString(), out tcpClient); RaiseCount(AllClients.Count); //the client has disconnected from the server break; } //message has successfully been received ASCIIEncoding encoder = new ASCIIEncoding(); var msg = encoder.GetString(message, 0, bytesRead); System.Diagnostics.Debug.WriteLine(msg); tcpClient.LastUpdate = DateTime.Now; if(!string.IsNullOrWhiteSpace(msg)) BroadcastMessage(msg); } tcpClient.Tcp.Close(); } // this is used to broadcast messages to all other clients, also in another thread private void BroadcastMessage(string msg) { new Thread(() => { foreach (var item in AllClients) { var tcpClient = item.Value; if (tcpClient.Tcp.Connected) { NetworkStream clientStream = tcpClient.Tcp.GetStream(); ASCIIEncoding encoder = new ASCIIEncoding(); byte[] buffer = encoder.GetBytes(msg); clientStream.Write(buffer, 0, buffer.Length); clientStream.Flush(); } } }).Start(); } //used to close down all tcp connections public void Close() { foreach (var client in AllClients) { client.Value.Tcp.Close(); } } } //MyTcpClent is just a wrapper to hold updates of a tcp client connection public class MyTcpClient { public MyTcpClient(TcpClient client) { Tcp = client; LastUpdate = DateTime.Now; } public TcpClient Tcp { get; set; } public DateTime LastUpdate { get; set; } }
6 comments:
Very nice example! thanks.
Could you also provide the code for the "MyTcpClient" class as well?
hi sharper,
I have just added code for MyTcpClient. MyTcpClient is just a wrapper to hold last update datetime because .net don't offer to detect broken links due to switch/router/power failure. i do a ping/pong check and have update datetime associated with every client to drop zombie connections.
Regards.
Hey,
All looks niec but quite new to ConcurrentDictionary,
And im getting some problems pretty sure i got the right using's included.
Yet i get for this line
ConcurrentDictionary AllClients { get; set; }
Using the generic type 'System.Collections.Concurrent.ConcurrentDictionary' requires 2 type arguments.
Not quite sure how to fix using using VCSharp 2010 btw. thanks a lot!
Geez, this is the third time I'm trying to post this. Blogger removes all caret symbols to prevent javascript injections.
However, you need to give the concurrent dictionary it's key pair datatypes (string and MyTcpClient)
The code is below:
Global Variable:
ConcurrentDictionary[OPENCARET]string, MyTcpClient[CLOSECARET] AllClients { get; set; }
Server Constructor:
AllClients = new ConcurrentDictionary[OPENCARE]string, MyTcpClient[CLOSECARET]();
Post a Comment