- This post contains generic code that's ready for use.
- Full solution is available at the end of the post.
Preface
This post will walk you through the implementation of a simple client-server application that establishes two way communication via .NET sockets while using infrastructure package that extracts the low level sockets API from the application.
The basic package can be added with an extra layer which will allow the transport of typed messages, please refer to ‘Sending Typed (Serialized) Messages’ for in detail review and case study.
If you don’t need multiple client support, please refer to ".NET Sockets - Single Client"
Implementation
Server-Side
Server Terminal
ServerTerminal opens TCP port, waits for clients connection, accepts multiple connections, listen to clients messages (bytes array) and allow to broadcast messages (bytes array) to all connected client.
Every client that connect to the server is wrapped-up in ConnectedClient object and added to clients collection.
public class ServerTerminal { public event TCPTerminal_MessageRecivedDel MessageRecived; public event TCPTerminal_ConnectDel ClientConnect; public event TCPTerminal_DisconnectDel ClientDisconnect; private Socket m_socket; private bool m_Closed; private Dictionary<long, ConnectedClient> m_clients = new Dictionary<long, ConnectedClient>(); public void StartListen(int port) { IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port); m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { m_socket.Bind(ipLocal); } catch(Exception ex) { Debug.Fail(ex.ToString(), string.Format("Can't connect to port {0}!", port)); return; } m_socket.Listen(4); // Assign delegate that will be invoked when client connect. m_socket.BeginAccept(new AsyncCallback(OnClientConnection), null); } private void OnClientConnection(IAsyncResult asyn) { if (m_Closed) { return; } try { Socket clientSocket = m_socket.EndAccept(asyn); RaiseClientConnected(clientSocket); ConnectedClient connectedClient = new ConnectedClient(clientSocket); connectedClient.MessageRecived += OnMessageRecived; connectedClient.Disconnected += OnClientDisconnection; connectedClient.StartListen(); long key = clientSocket.Handle.ToInt64(); if (m_clients.ContainsKey(key)) { Debug.Fail(string.Format( "Client with handle key '{0}' already exist!", key)); } m_clients[key] = connectedClient; // Assign delegate that will be invoked when next client connect. m_socket.BeginAccept(new AsyncCallback(OnClientConnection), null); } catch (ObjectDisposedException odex) { Debug.Fail(odex.ToString(), "OnClientConnection: Socket has been closed"); } catch (Exception sex) { Debug.Fail(sex.ToString(), "OnClientConnection: Socket failed"); } } private void OnClientDisconnection(Socket socket) { RaiseClientDisconnected(socket); long key = socket.Handle.ToInt64(); if (m_clients.ContainsKey(key)) { m_clients.Remove(key); } else { Debug.Fail(string.Format( "Unknown client '{0}' has been disconnected!", key)); } }public void DistributeMessage(byte[] buffer) { try { foreach (ConnectedClient connectedClient in m_clients.Values) { connectedClient.Send(buffer); } } catch (SocketException se) { Debug.Fail(se.ToString(), string.Format( "Buffer could not be sent")); } }public void Close() { try { if (m_socket != null) { m_Closed = true; // Close the clients foreach (ConnectedClient connectedClient in m_clients.Values) { connectedClient.Stop(); } m_socket.Close(); m_socket = null; } } catch (ObjectDisposedException odex) { Debug.Fail(odex.ToString(), "Stop failed"); } } private void OnMessageRecived(Socket socket, byte[] buffer) { if (MessageRecived != null) { MessageRecived(socket, buffer); } } private void RaiseClientConnected(Socket socket) { if (ClientConnect != null) { ClientConnect(socket); } } private void RaiseClientDisconnected(Socket socket) { if (ClientDisconnect != null) { ClientDisconnect(socket); } } } |
ConnectedClient
This class is instantiated for each client that connect to the server. It utilizes the SocketListener class (will be reviewed shortly) which listen and delegate the messages coming from the client.
public class ConnectedClient { // Hold reference to client socket to allow sending messages to client private Socket m_clientSocket; SocketListener m_listener; public ConnectedClient(Socket clientSocket) { m_clientSocket = clientSocket; m_listener = new SocketListener(); } // Register directly to SocketListener event public event TCPTerminal_MessageRecivedDel MessageRecived { add { m_listener.MessageRecived += value; } remove { m_listener.MessageRecived -= value; } } // Register directly to SocketListener event public event TCPTerminal_DisconnectDel Disconnected { add { m_listener.Disconnected += value; } remove { m_listener.Disconnected -= value; } } public void StartListen() { m_listener.StartReciving(m_clientSocket); } public void Send(byte[] buffer) { if (m_clientSocket == null) { throw new Exception("Can't send data. ConnectedClient is Closed!"); } m_clientSocket.Send(buffer); } public void Stop() { m_listener.StopListening(); m_clientSocket = null; } } |
Server Host (Console)
The server host instantiate the ServerTerminal, register to the appropriate events and call StartListening. As a result, multiple clients can connect to its port and start sending/receiving messages.
m_ServerTerminal = new ServerTerminal(); m_ServerTerminal.MessageRecived += m_Terminal_MessageRecived; m_ServerTerminal.ClientConnect += m_Terminal_ClientConnected; m_ServerTerminal.ClientDisconnect += m_Terminal_ClientDisConnected; m_ServerTerminal.StartListen(alPort); |
Both-Sides
Socket Listener
SocketListener allows both ServerTerminal and ClientTetminal to listen to messages coming a socket. When a message arrives – the SocketListener figures out whether it represents new data or whether it represents 'connection dropped' message. In case the message represents new data it raises the MessageReceived event and waits for the next message. In case the message indicate that the connection has been dropped - it raises the Disconnected event and exits.
public class SocketListener { private const int BufferLength = 1000; AsyncCallback pfnWorkerCallBack; Socket m_socWorker; public event TCPTerminal_MessageRecivedDel MessageRecived; public event TCPTerminal_DisconnectDel Disconnected; public void StartReciving(Socket socket) { m_socWorker = socket; WaitForData(socket); } private void WaitForData(System.Net.Sockets.Socket soc) { try { if (pfnWorkerCallBack == null) { pfnWorkerCallBack = new AsyncCallback(OnDataReceived); } CSocketPacket theSocPkt = new CSocketPacket(BufferLength); theSocPkt.thisSocket = soc; |
Client-Side
Client Terminal
ClientTerminal connects to TCP port, sends messages (bytes array) to the server and listens to server messages (bytes array).
public class ClientTerminal { Socket m_socClient; private SocketListener m_listener; public event TCPTerminal_MessageRecivedDel MessageRecived; public event TCPTerminal_ConnectDel Connected; public event TCPTerminal_DisconnectDel Disconncted; public void Connect(IPAddress remoteIPAddress, int alPort) { m_socClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); IPEndPoint remoteEndPoint = new IPEndPoint(remoteIPAddress, alPort); m_socClient.Connect(remoteEndPoint); OnServerConnection(); } public void SendMessage(byte[] buffer) { if (m_socClient == null) { return; } m_socClient.Send(buffer); } public void StartListen() { if (m_socClient == null) { return; } if (m_listener != null) { return; } m_listener = new SocketListener(); m_listener.Disconnected += OnServerConnectionDroped; m_listener.MessageRecived += OnMessageRecvied; m_listener.StartReciving(m_socClient); } public void Close() { if (m_socClient == null) { return; } if (m_listener != null) { m_listener.StopListening(); } m_socClient.Close(); m_listener = null; m_socClient = null; } private void OnServerConnection() { if (Connected != null) { Connected(m_socClient); } } private void OnMessageRecvied(Socket socket, byte[] buffer) { if (MessageRecived != null) { MessageRecived(socket, buffer); } } private void OnServerConnectionDroped(Socket socket) { Close(); RaiseServerDisconnected(socket); } private void RaiseServerDisconnected(Socket socket) { if (Disconncted != null) { Disconncted(socket); } } } |
Client Host (Console)
Client host should instantiate ClientTerminal and call 'Connect' with server-name/IP-address and port. After that call - m_terminal can be used to send/receive messages to/from the server.
m_ClientTerminal = new ClientTerminal(); m_ClientTerminal.Connected += m_TerminalClient_Connected; m_ClientTerminal.Disconncted += m_TerminalClient_ConnectionDroped; m_ClientTerminal.MessageRecived += m_TerminalClient_MessageRecived; m_ClientTerminal.Connect(remoteIPAddress, alPort); |
Sample project
Download from here
Hi,
ReplyDeletehow do I make sure that all the bytes of my message has arrived correclty, or that there were more than 1 message that arrived.
thank you for this nice code
ReplyDeletethnx a lot
ReplyDeleteit helped me lot
This a very nice code. Helped a lot. On request, Can you provide the same implementation for UDP protocol and SocketType.Dgram used. If you cannot post and still can send me to help me out this is my add - jc_mca atTHErate yahoo.com. Thanks in advance.
ReplyDeleteAdding support for UDP is not so simple, since UDP is connection less etc.
ReplyDeleteThe following link may help, the post there includes implementation of UDP communication with support for Multicast.
http://aviadezra.blogspot.com/2009/07/multicast-igmp-c-code-sample-net.html
Good luck.
Hi Aviade,, a wonderful post i have come through after 8 yrs,thanks for the demonstration and a helpful work for taking in pain to develop.
ReplyDeleteI have a novice question, can the server support more than 1000 connections, assuming am hosting this on Win 2012 Server ?
Why i am asking this is, am developing a server based s/w which will be connecting ~1500 clients, thought of developing a Client Manager which will connect with the server.
Idea is to develop a client manager which will block until the server starts listening.
Any other ideas are also appreciated.
Thanks,
Ronald