Showing posts with label Networking. Show all posts
Showing posts with label Networking. Show all posts

Friday, July 10, 2009

Multicast Application with Multihomed Support (C# .NET Sample)

In the previous post we’ve reviewed the multicast addressing technology and examined some of its pitfalls.

In this post we’ll dive through the implementation of a simple application that allows sending and receiving multicast data over the LAN, while enabling the selection of the network interfaces (IPEndPoint) through which the multicast traffic will be sent and received.

As illustrated in the previous post, setting the network interface is crucial when the host is connected to two or more networks or have two or more network cards installed and enabled.

The application source code can be downloaded from here.

image 

Configuration

The MulticastConfiguration class encapsulates the configuration entered by the user. It exposes two factory methods that allow instantiating the class for single network hosts and for multi network hosts. The 1st factory method requires only the multicast address/port of the sender and  the receiver, while the 2nd factory method also requires the unicast address of the network card of the sender and  the receiver.

Code Snippet
  1.     class MulticastConfiguration
  2.     {
  3.         readonly IPAddress m_SendMulticastAddress;
  4.         readonly int m_SendPort;
  5.         readonly int m_ReceivePort;
  6.         readonly IPAddress m_ReceiveMulticastAddress;
  7.         // Multihomed Support
  8.         readonly IPAddress m_SendNetworkCardAddress = IPAddress.Any;
  9.         readonly int m_SendNetworkCardPort;
  10.         readonly IPAddress m_ReceiveNetworkCardAddress = IPAddress.Any;
  11.         public MulticastConfiguration(
  12.             IPAddress sendMulticastAddress,
  13.             int sendPort,
  14.             IPAddress receiveMulticastAddress,
  15.             int receivePort)
  16.         {
  17.             m_SendMulticastAddress = sendMulticastAddress;
  18.             m_SendPort = sendPort;
  19.             m_ReceiveMulticastAddress = receiveMulticastAddress;
  20.             m_ReceivePort = receivePort;
  21.         }
  22.         public MulticastConfiguration(
  23.             IPAddress sendMulticastAddress,
  24.             int sendPort,
  25.             IPAddress receiveMulticastAddress,
  26.             int receivePort,
  27.             IPAddress sendExplicitSourceAddressm,
  28.             int sendExplicitSourcePort,
  29.             IPAddress receiveExplicitSourceAddress)
  30.             : this(
  31.             sendMulticastAddress,
  32.             sendPort,
  33.             receiveMulticastAddress,
  34.             receivePort)
  35.         {
  36.             m_SendNetworkCardAddress = sendExplicitSourceAddressm;
  37.             m_SendNetworkCardPort = sendExplicitSourcePort;
  38.             m_ReceiveNetworkCardAddress = receiveExplicitSourceAddress;
  39.         }
  40.         public static MulticastConfiguration CreateForSingleInterfaceNetwork(
  41.             IPAddress sendMulticastAddress,
  42.             int sendPort,
  43.             IPAddress receiveMulticastAddress,
  44.             int receivePort)
  45.         {
  46.             return new MulticastConfiguration(
  47.                 sendMulticastAddress,
  48.                 sendPort,
  49.                 receiveMulticastAddress,
  50.                 receivePort);
  51.         }
  52.         public static MulticastConfiguration CreateForMultiHomedNetwork(
  53.             IPAddress sendMulticastAddress,
  54.             int sendPort,
  55.             IPAddress receiveMulticastAddress,
  56.             int receivePort,
  57.             IPAddress sendExplicitSourceAddressm,
  58.             int sendExplicitSourcePort,
  59.             IPAddress receiveExplicitSourceAddress)
  60.         {
  61.             return new MulticastConfiguration(
  62.                 sendMulticastAddress,
  63.                 sendPort,
  64.                 receiveMulticastAddress,
  65.                 receivePort,
  66.                 sendExplicitSourceAddressm,
  67.                 sendExplicitSourcePort,
  68.                 receiveExplicitSourceAddress);
  69.         }
  70.         public IPAddress SendMulticastAddress
  71.         {
  72.             get { return m_SendMulticastAddress; }
  73.         }
  74.         public int SendPort
  75.         {
  76.             get { return m_SendPort; }
  77.         }
  78.         public IPAddress ReceiveMulticastAddress
  79.         {
  80.             get { return m_ReceiveMulticastAddress; }
  81.         }
  82.         public int ReceivePort
  83.         {
  84.             get { return m_ReceivePort; }
  85.         }
  86.         public IPAddress SendNetworkCardAddress
  87.         {
  88.             get { return m_SendNetworkCardAddress; }
  89.         }
  90.         public int SendNetworkCardPort
  91.         {
  92.             get { return m_SendNetworkCardPort; }
  93.         }
  94.         public IPAddress ReceiveNetworkCardAddress
  95.         {
  96.             get { return m_ReceiveNetworkCardAddress; }
  97.         }
  98.     }

Transport

The TransportAgent class is in charge of creating the sockets that receive and send the data, joining to the multicast group (i.e. sending IGMP package to the router) and binding both sockets to the appropriate network interface.

Code Snippet
  1.     class TransportAgent
  2.     {
  3.         static readonly IPEndPoint AnyAddress = new IPEndPoint(IPAddress.Any, 0);
  4.         private readonly byte[] m_objectStateBuffer = new byte[8192];
  5.         private readonly MulticastConfiguration m_dataEntity;
  6.         private readonly Socket m_sendSocket;
  7.         private readonly Socket m_receiveSocket;
  8.         private  bool m_opened;
  9.         public TransportAgent(
  10.             MulticastConfiguration entity,
  11.             EventHandler<BufferReceivedEventArgs> messageReceived)
  12.             : this(entity)
  13.         {
  14.             MessageReceived += messageReceived;
  15.         }
  16.         public TransportAgent(MulticastConfiguration entity)
  17.         {
  18.             m_dataEntity = entity;
  19.            
  20.             m_sendSocket = CreateUdpSocket();
  21.             EndPoint sendEndPoint = new IPEndPoint(
  22.                 m_dataEntity.SendNetworkCardAddress,
  23.                 m_dataEntity.SendNetworkCardPort);
  24.            
  25.             // define the address of the network card from
  26.             // which the multicast data will be sent. 
  27.             m_sendSocket.Bind(sendEndPoint);
  28.             m_receiveSocket = CreateUdpSocket();
  29.            
  30.             EndPoint receiveEndPoint = new IPEndPoint(
  31.                 m_dataEntity.ReceiveNetworkCardAddress,
  32.                 m_dataEntity.ReceivePort);
  33.             // define the address of the network card from
  34.             // which the multicast data will be received. 
  35.             m_receiveSocket.Bind(receiveEndPoint);
  36.            
  37.             // Send the IGMP message to the router, ask to
  38.             // goin to the multicast group
  39.             JoinMulticast(m_receiveSocket);
  40.             BeginReceive();
  41.             m_opened = true;
  42.         }
  43.         public event EventHandler<BufferReceivedEventArgs> MessageReceived = delegate { };
  44.         public void Close()
  45.         {
  46.             if (!m_opened)
  47.             {
  48.                 return;
  49.             }
  50.             DropMulticast(m_receiveSocket);
  51.             m_receiveSocket.Close();
  52.             m_sendSocket.Close();
  53.             m_opened = false;
  54.         }
  55.         public void Send(byte[] message)
  56.         {
  57.             StateObject stateObject = new StateObject(
  58.                 m_sendSocket, message);
  59.             IPEndPoint endPoint = new IPEndPoint(
  60.                 m_dataEntity.SendMulticastAddress,
  61.                 m_dataEntity.SendPort);
  62.             int offset = 0;
  63.            
  64.             m_sendSocket.BeginSendTo(
  65.                 message,
  66.                 offset,
  67.                 message.Length,
  68.                 SocketFlags.None,
  69.                 endPoint,
  70.                 OnSend,
  71.                 stateObject);           
  72.         }
  73.         private void OnSend(IAsyncResult ar)
  74.         {
  75.             int bytesCount = m_sendSocket.EndSendTo(ar);
  76.             Trace.WriteLine(
  77.                 string.Format("Messages Sent, Bytes: {0}",
  78.                 bytesCount));
  79.         }
  80.         private void BeginReceive()
  81.         {
  82.             Socket receiveSocket = m_receiveSocket;
  83.             if (receiveSocket == null) return;
  84.         
  85.             EndPoint remoteEP = AnyAddress;
  86.             StateObject stateObject = new StateObject(
  87.                 receiveSocket, m_objectStateBuffer);
  88.             int offset = 0;
  89.             receiveSocket.BeginReceiveFrom(
  90.                 stateObject.Buffer,
  91.                 offset,
  92.                 stateObject.Buffer.Length,
  93.                 SocketFlags.None,
  94.                 ref remoteEP,
  95.                 OnReceive,
  96.                 stateObject);
  97.         }
  98.         private void OnReceive(IAsyncResult result)
  99.         {
  100.             EndPoint remoteEP = AnyAddress;
  101.             try
  102.             {
  103.                 int bufferSize = m_receiveSocket.EndReceiveFrom(
  104.                     result, ref remoteEP);
  105.                 StateObject stateObject =
  106.                     (StateObject)result.AsyncState;
  107.                
  108.                 BufferReceivedEventArgs e = new BufferReceivedEventArgs(
  109.                     stateObject.Buffer, bufferSize);
  110.                
  111.                 MessageReceived(this, e);
  112.                
  113.                 BeginReceive();
  114.             }
  115.             catch
  116.             {
  117.                 Trace.WriteLine("Disconnection");
  118.             }
  119.         }
  120.         private static Socket CreateUdpSocket()
  121.         {
  122.             Socket socket = new Socket(
  123.                 AddressFamily.InterNetwork,
  124.                 SocketType.Dgram,
  125.                 ProtocolType.Udp);
  126.            
  127.             return socket;
  128.         }
  129.         private void JoinMulticast(Socket socket)
  130.         {
  131.             MulticastOption multicastOption = GetMulticastOption();
  132.            
  133.             socket.SetSocketOption(
  134.                 SocketOptionLevel.IP,
  135.                 SocketOptionName.MulticastTimeToLive,
  136.                 64);
  137.            
  138.             socket.SetSocketOption(
  139.                 SocketOptionLevel.IP,
  140.                 SocketOptionName.AddMembership,
  141.                 multicastOption);
  142.         }
  143.         private void DropMulticast(Socket socket)
  144.         {
  145.             MulticastOption multicastOption = GetMulticastOption();
  146.            
  147.             socket.SetSocketOption(
  148.                 SocketOptionLevel.IP,
  149.                 SocketOptionName.MulticastTimeToLive,
  150.                 64);
  151.            
  152.             socket.SetSocketOption(
  153.                 SocketOptionLevel.IP,
  154.                 SocketOptionName.DropMembership,
  155.                 multicastOption);
  156.         }
  157.         private MulticastOption GetMulticastOption()
  158.         {
  159.             IPAddress ip = m_dataEntity.ReceiveMulticastAddress;
  160.             // MulticastOption can be constructed with the
  161.             // overload that allows setting the address of
  162.             // the network card from which the IGMP package
  163.             // will be sent to the router.
  164.             MulticastOption mo = new MulticastOption(ip);
  165.             return mo;
  166.         }
  167.     }

Host

All the host has to do is to read the configuration from the UI, instantiate the configuration class, and instantiate the transport class - injecting it with the configuration instance and with delegate to OnMessageReceived callback that will be called when ever data arrive.

Code Snippet
  1.     MulticastConfiguration multicastConfiguration = ReadConfiguration();
  2.     m_TransportAgent = new TransportAgent(
  3.         multicastConfiguration, OnMessageReceived);
Sending Multicast Data
Code Snippet
  1.     ASCIIEncoding asciiEncoding = new ASCIIEncoding();
  2.     string message = m_textBoxMessage.Text;
  3.     byte[] bytes = asciiEncoding.GetBytes(message);
  4.     m_TransportAgent.Send(bytes);
Receiving Multicast Data
Code Snippet
  1.     private void OnMessageReceived(object sender, BufferReceivedEventArgs e)
  2.     {
  3.         string message = Encoding.ASCII.GetString(e.Buffer);
  4.         Print(message);
  5.     }

Debugging

My choice for network protocol analyzer is Wireshark which is the best analyzer available today, it’s released under the GNU General Public License (GPL) so it can be used freely.

To get started, download the source code and run the multicast tester application, download Wireshark, run it and start a capture session,

IGMP

In order to get a feeling about the way that multicast addressing works – let’s review the IGMP message that is sent to the router when the receiver socket joins to the multicast group (see TransportAgent line 166).

Enter ‘igmp’ in Wireshark filter.

image

In  the multicast tester application, press ‘Connect’. The ‘network card interface’ in the receiver configuration is set to my local machine upper network card address.

image

image

As a result, IGMP package was sent to the network, requesting it to add network interface with address 192.168.2.100 (which is my upper network card address) to the multicast group 224.0.0.12. Consequently, the network will deliver any multicast data sent to group 224.0.0.12 to address 192.168.2.100.

Now, In the multicast tester application, press ‘disconnect’.

image

As a result, IGMP package was sent to the network, requesting it to remove the network interface from the group.

Sending Multicast Data – Single Network

Enter ‘udp’ in Wireshark filter.

In  the multicast tester application, leave the ‘Network Card Address’ and ‘Network Card Port’ in the sender configuration blank, press ‘Connect’, enter some string to the message text box and press ‘Send’

image

As a result, the string that was specified (in this case ‘aviad’) is sent form the network card 192.168.2.100, port 3704. Since we didn’t specify the network card address and port – the operation system took the liberty to select network card and port.

Sending Multicast Data – Multihomed Network

In  the multicast tester application, set the sender ‘Network Card Address’ to one of your network cards address and set the ‘Network Card Port’ to 1234, press ‘Connect’, enter some string to the message text box and press ‘Send’.

image

image

As a result, the string is sent form the network card 192.168.2.100, port 1234. Since we did specify the network card address and port – the network card and the port from which the string was sent were NOT the selection of the operation system. Even though in the previous case the operation system selected the same network card that we specified, in cases where there are more than 1 enabled and active network cards in the machine - you must specify the appropriate network address or your multicast may be delivered though the wrong network.

Tuesday, July 7, 2009

Multicast Addressing Pitfalls

The IP multicast model has been described as follows: “You put packets in at one end, and the network conspires to deliver them to anyone who asks”. That sounds pretty simple indeed, but as you spend more time with applications that deliver multicast services you find that in many cases you will ‘put packets in at one end, look at the network protocol analyzer, and see nothing that you expect’. The truth of the matter is that getting multicast applications to work as expected requires deep understanding of the network topology and the underlying routing devices.

This post reviews the multicast addressing technology along with in detail illustrations of some of the pitfalls that are often encountered when deploying multicast applications.

In my next post you can find walkthrough of C# sample application that allows sending and receiving multicast data over the network.

How does it Work?

In multicast applications, the sender defines IP multicast group address (from 224.0.0.0 to 239.255.255.255) to which is send data packets. Receivers inform the network that they are interested in receiving data packets sent to a certain group by sending Internet Group Management Protocol (IGMP) package to the closest network node (router, IGMP querier). The node is in charge of maintaining multicast distribution trees such that data packets sent to a multicast group reach all receivers which have joined the group.

image

Why Use Multicast?

The great thing about multicast addressing is that in order to distribute a message to multiple receivers - the sender have to dispatch only one message over the wire, while it’s the network (e.g. intermediary routers) responsibility to copy and distribute the message to all the receivers in the group. In case there aren’t any receivers that had joined the group – the network drops the message.  The fact that the sender is unaware of the receivers greatly improves the application ability to scale to a large receiver population

The Pitfalls

Developing application that sends and receives multicast streams over the network is considered a fairly simple task. You can get pretty fast to the point where everything seems to work fine in the integration labs, were all the PCs communicate through a single network card and connected through few well known switches. The problems start when the application first meet the client network that is often spitted by VLANs and filled with all kinds of Cisco goodness. Unavoidably, you’ll find yourself steering at the network sniffer trying to figure out why you don’t see packages from group X even though the IGMP package was sent to the router, or why you do see packages from group Y - even though your application had never requested to join this particular group (No IGMP package was sent).

Where is my Multicast?

Multihomed Network

In case a host is connected to two or more networks or have two or more network cards installed and enabled – senders must explicitly state to which network interface (represented by the unicast IP address of the network card) they want to send the multicast traffic, and receivers must explicitly state from which network interface they want to receive multicast traffic. If they don’t – the operation system takes the liberty to choose the network interface, and the multicast traffic has 50% chance of reaching to the intended destination.

image

A common mistake that developers make is not binding the socket used for sending multicast traffic to the appropriate network card endpoint, or binding the socket used for receiving multicast to the address Any (0.0.0.0).

This mistake is so common because developers often assume that UDP unicast socket and UDP multicast sockets that are being used to send data can be implemented alike, and that sockets that are being used to receive multicast data can bind to IPAddress.Any (0.0.0.0). This is true when the host has single network card, but not true for host that lives in multihomed network.

Firewall

In case the machine firewall is turned on, and the firewall is NOT configured to relay transports from a certain multicast group (and corresponding UDP port) – your application will not receive messages from that group.

image

Inter VLAN

In case the sender and the receivers don’t sit on the same VLAN, the receivers will not receive messages from the sender.

image

Switch doesn’t Support IGMP Snooping

In case the sender is connected to a switch that support IGMP snooping (which means that it relay multicast messages only through ports which sent IGMP message), and the receiver is connected to another switch that doesn’t support IGMP snooping - the multicast relaying mechanism will "breaks down" in the absence of an mrouter port, thus the receiver will not receive the messages from the sender.

image

If you want a fix for this solution, you must have the switches somehow learn or know of an mrouter port. When the switches know their mrouter port, the right Switch (that doesn’t support IGMP snooping) relays out the IGMP report that it receives through its mrouter port. From the perspective of the left Switch, it received merely another IGMP report. The left switch adds that port into its IGMP snooping table and begins sending out multicast traffic on that port as well. At this point, the right Switch receive the multicast traffic, and the application works as expected.

Why am I getting this Multicast?

Broadcasting Multicast Traffic

Switches that cannot understand multicast addresses usually broadcast multicast traffic to all the members of a LAN. Machines that are connected to such switches will see multicast traffic from groups that they’ve never registered to.

image 

Port Mirroring

In case the machine is connected to a router port that is configured as ‘port mirroring’ – the machine will receive multicast streams form all the groups.

image

Port mirroring, also known as a roving analysis port, is a method of monitoring network traffic that forwards a copy of each incoming and outgoing packet from one port of a network switch to another port where the packet can be studied. A network administrator uses port mirroring as a diagnostic tool or debugging feature, especially when fending off an attack. It enables the administrator to keep close track of switch performance and alter it if necessary. Port mirroring can be managed locally or remotely.

Multicasting over WAN

Source-specific multicast (SSM) make multicasting packages over the WAN eligible by reducing the amount of multicast routing information that the network must maintain. With SSM receivers supply the sender’s source address to the routers as a part of joining the group (by setting the receiver socket option MCAST_JOIN_SOURCE_GROUP).

A great presentation (ppt) that discusses multicast addressing can be downloaded from here.

Links

http://www.netcraftsmen.net/welcher/papers/multicast01.html

http://www.cisco.com/en/US/products/hw/switches/ps708/products_tech_note09186a008059a9df.shtml

Saturday, July 5, 2008

Sending Typed (Serialized) Messages over .NET Sockets (C# Source Code Included)

Client-server communication via .NET sockets can be established pretty easily by using the 'Sockets communication' package introduced in .NET Socket. The package supports sending/receiving raw array of bytes in two directions and allows multiple clients connection. 

image

In some applications we'll rather transfer structured data in the form of typed messages over raw array of bytes. To accomplish this we need to add another tiny layer on the top of the communication package; the extra layer serialize typed message into bytes array on the sender side and de-serialize bytes array into  typed message in the receiver side.

image

In this post you can find all the code that you need in order to add the referred layer to .NET Socket communication packaged.

Implementation

MessageComposer

MessageComoser is used to convert message (that derive from MessageBase) to bytes[] and to convert bytes to message.

public class MessageComposer
{
    public static byte[] Serialize(int messageKind, MessageBase msg)
    {
        MemoryStream ms = new MemoryStream();
        BinaryFormatter bf1 = new BinaryFormatter();

        bf1.Serialize(ms, messageKind);
        bf1.Serialize(ms, msg);

        return ms.ToArray();
    }

    public static void Deserialize(byte[] buffer, 
        out int messageKind, out MessageBase msg)
    {
        MemoryStream ms = new MemoryStream(buffer);
        BinaryFormatter formatter = new BinaryFormatter();
        messageKind = (int)formatter.Deserialize(ms);
        msg = (MessageBase)formatter.Deserialize(ms);
    }
}

 

Concrete Message

Message should inherit from MessageBase so it can be serialized and deserialized via MesageComposer. The following message carry message string and time. It is used to measure network latency.

[Serializable]
 public class SendingTimeMessage: MessageBase
 {
     private DateTime m_time;
     private string m_message;

     public SendingTimeMessage(DateTime time, string message)
     {
         m_time = time;
         m_message = message;
     }

     public DateTime Time
     {
         get { return m_time; }
     }

     public string Message
     {
         get { return m_message; }
     }

     public TimeSpan CalcSpan()
     {
         return DateTime.Now - m_time;
     }
 }

 

Sending Concrete-Message

MessageComposer is used to covert SendingTimeMessage into bytes, the bytes are than being sent using ClientTerminal.

string mes = "Message content...";

// Create the concrete message
SendingTimeMessage message = new SendingTimeMessage(mes);

int messageKind = (int)MessageKind.SendingTime;

byte[] buffer = MessageComposer.Serialize(messageKind, message);

// Send the message (as bytes) to the server.
m_ClientTerminal.SendMessage(buffer);
Receiving Concrete-Message

MessageComposer is used to covert bytes received from the client into message, the message is being converted to string and presented on the screen. Then, the bytes are being distributed to all connected clients.

void m_Terminal_MessageRecived(Socket socket, byte[] buffer)
{
    string message = ConvertBytesToString(buffer);

    PresentMessage(listMessages, string.Format("Sockets: {0}", message));

    // Send Echo
    m_ServerTerminal.DistributeMessage(buffer);
}


private string ConvertBytesToString(byte[] bytes)
{
    int messageKind;
    MessageBase msg;
    MessageComposer.Deserialize(bytes, out messageKind, out msg);

    MessageKind kind = (MessageKind) messageKind;

    switch(kind)
    {
        case MessageKind.SendingTime:
            SendingTimeMessage sendingTimeMessage = (SendingTimeMessage)msg;
            return "SendingTimeMessage: " + sendingTimeMessage.Message;

        case MessageKind.Simple:
            SimpleMessage simpleMessage = (SimpleMessage)msg;
            return "SimpleMessage: " + simpleMessage.Message;
    }

    return "UnKnown";
}

 

Sample project

Download from here

Tuesday, July 1, 2008

.NET Sockets in Two Directions with Multiple Client Support (C# Source Code Included)

  • 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.

image

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;

         // Start waiting asynchronously for single data packet
         soc.BeginReceive(
                theSocPkt.dataBuffer,
                0,
                theSocPkt.dataBuffer.Length,
                SocketFlags.None,
                pfnWorkerCallBack,
                theSocPkt);
        }
        catch (SocketException sex)
        {
            Debug.Fail(sex.ToString(), "WaitForData: Socket failed");
        }

    }

    private void OnDataReceived(IAsyncResult asyn)
    {
        CSocketPacket theSockId = (CSocketPacket)asyn.AsyncState;
        Socket socket = theSockId.thisSocket;

        if (!socket.Connected)
        {
            return;
        }

        try
        {
            int iRx;
            try
            {
                iRx = socket.EndReceive(asyn);
            }
            catch (SocketException)
            {
                Debug.Write("Client has been closed and cannot answer.");

                OnConnectionDroped(socket);
                return;
            }

            if (iRx == 0)
            {
                Debug.Write("Client socket has been closed.");

                OnConnectionDroped(socket);
                return;
            }

            RaiseMessageRecived(theSockId.dataBuffer);
       // Wait for the next package
            WaitForData(m_socWorker);
        }
        catch (Exception ex)
        {
            Debug.Fail(ex.ToString(), "OnClientConnection: Socket failed");
        }
    }

    public void StopListening()
    {
        if (m_socWorker != null)
        {
            m_socWorker.Close();
            m_socWorker = null;
        }
    }

    private void RaiseMessageRecived(byte[] buffer)
    {
        if (MessageRecived != null)
        {
            MessageRecived(m_socWorker, buffer);
        }
    }

    private void OnDisconnection(Socket socket)
    {
        if (Disconnected != null)
        {
            Disconnected(socket);
        }
    }

    private void OnConnectionDroped(Socket socket)
    {
        m_socWorker = null;
        OnDisconnection(socket);
    }
}

public class CSocketPacket
{
    public System.Net.Sockets.Socket thisSocket;
    public byte[] dataBuffer;

    public CSocketPacket(int buffeLength)
    {
        dataBuffer = new byte[buffeLength];
    }
}
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