Tuesday, July 1, 2008

.NET Sockets - Two Way - Single Client (C# Source Code Included)

  • This post contains generic code that's ready for use.
  • The code was tested and debugged.
  • Full solution is available at the end of the post.

Preface

This post will walk you through the implementation of 'client-server' communication package with which single clients can establish communication with the server, send bytes array, and listen to server messages.

This package can be added with extra layer in order to support typed messages transport - code + details can be found here.

If you need multiple clients support -  code + details can be found here.

Single Client - Socket based Communication

This post reviews the most simple implementation of client-server socket based application. It shows how single client can establish communication with listening server and send/recive bytes array to/from the server.

Implementation

Server-Side
Server Terminal

ServerTerminal opens TCP port, accepts connection from single client, listens to client messages (bytes array) and sends messages (bytes array) to the connected client.

It utilizes the SocketListener class (that will be latter described) to allow incoming messages from connected clients.

public class ServerTerminal
{
    Socket m_socket;
    SocketListener m_listener;
    
    private bool m_Closed;
    private Socket m_socWorker;

    public event TCPTerminal_MessageRecivedDel MessageRecived;
    public event TCPTerminal_ConnectDel ClientConnect;
    public event TCPTerminal_DisconnectDel ClientDisconnect;
    
    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
        {
            m_socWorker = m_socket.EndAccept(asyn);

            RaiseClientConnected(m_socWorker);
            
            m_listener = new SocketListener();
            m_listener.MessageRecived += OnMessageRecived;
            m_listener.Disconnected += OnClientDisconnection;

            m_listener.StartReciving(m_socWorker);
        }
        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);

        // Try to re-establish connection
        m_socket.BeginAccept(new AsyncCallback(OnClientConnection), null);

    }
  public void SendMessage(byte[] buffer)
    {
      if (m_socWorker == null)
      {
          return;
      }
      m_socClient.Send(buffer);

  }
    public void Close()
    {
        try
        {
            if (m_socket != null)
            {
                m_Closed = true;

                if (m_listener != null)
                {
                    m_listener.StopListening();
                }

                m_socket.Close();

                m_listener = null;
                m_socWorker = null;
                m_socket = null;
            }
        }
        catch (ObjectDisposedException odex)
        {
            Debug.Fail(odex.ToString(), "Stop failed");
        }
    }

    private void OnMessageRecived(string message)
    {
        if (MessageRecived != null)
        {
            MessageRecived(message);
        }
    }

    private void RaiseClientConnected(Socket socket)
    {
        if (ClientConnect != null)
        {
            ClientConnect(socket);
        }
    }

    private void RaiseClientDisconnected(Socket socket)
    {
        if (ClientDisconnect != null)
        {
            ClientDisconnect(socket);
        }
    }
}
Server Host (Console)

Server host should instantiate ServerTerminal and call StartListening. After that call - Single client 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 incoming messages from the other side.

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;

            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("Apperently client has been closed and connot answer.");

                OnConnectionDroped(socket);
                return;
            }

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

                OnConnectionDroped(socket);
                return;
            }

            RaiseMessageRecived(theSockId.dataBuffer);

            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

1 comment: