Click here to Skip to main content
15,891,431 members
Articles / Programming Languages / XML

A Full Library for a Socket Client/Server System

Rate me:
Please Sign up or sign in to vote.
4.81/5 (42 votes)
14 Jan 2015CPOL5 min read 161.6K   11.3K   149  
My article shows a library that everyone can use to create their socket communication. Also, it explains how the library is developed.
using System;
using SocketServerLib.SocketHandler;
using System.Security.Cryptography.X509Certificates;
using System.Net.Sockets;
using System.Threading;
using System.Net;
using System.Diagnostics;
using System.Net.Security;
using SocketServerLib.Message;
using System.Security.Authentication;
using SocketServerLib.Threads;

namespace SocketServerLib.Server
{
    /// <summary>
    /// This abstract class represent a Socket Server.
    /// Implements the method GetHandler to define the socket client handler used in your Socket Server.
    /// </summary>
    public abstract class AbstractSocketServer : AbstractThread
    {
        /// <summary>
        /// Server certificate for the SSL
        /// </summary>
        private X509Certificate2 serverCertificate = null;
        /// <summary>
        /// Socket listener
        /// </summary>
        private Socket listener = null;
        /// <summary>
        /// TcpListener for SSL
        /// </summary>
        private TcpListener sslListener = null;
        /// <summary>
        /// The thread notification for complete a connection on listener
        /// </summary>
        private ManualResetEvent listenerCompleteConnectionEvent = new ManualResetEvent(false);
        /// <summary>
        /// The Client List
        /// </summary>
        private ClientInfoList clientList = new ClientInfoList();
        /// <summary>
        /// Delegate for a receive message event
        /// </summary>
        private ReceiveMessageDelegate receiveMessageEvent = null;
        /// <summary>
        /// Delegate for a new connection event
        /// </summary>
        private SocketConnectionDelegate connectionEvent = null;
        /// <summary>
        /// Delegate for a close connection event
        /// </summary>
        private SocketConnectionDelegate closeConnectionEvent = null;
        // Lock object for raise event
        private readonly object raiseLock = new object();
        /// <summary>
        /// Keep Alive
        /// </summary>
        public bool KeepAlive { get; set; }

        /// <summary>
        /// Constructor
        /// </summary>
        public AbstractSocketServer()
        {
            this.KeepAlive = false;
        }

        #region Properties

        public virtual ClientInfo this[AbstractTcpSocketClientHandler abstractTcpSocketClientHandler]
        {
            get
            {
                return this.clientList[abstractTcpSocketClientHandler];
            }
        }

        public virtual ClientInfo this[string clientUID]
        {
            get
            {
                return this.clientList[clientUID];
            }
        }

        #endregion

        #region Events

        /// <summary>
        /// Event for receive a message.
        /// </summary>
        public event ReceiveMessageDelegate ReceiveMessageEvent
        {
            add { lock (raiseLock) { receiveMessageEvent += value; } }
            remove { lock (raiseLock) { receiveMessageEvent -= value; } }
        }

        /// <summary>
        /// Event for a new connection.
        /// </summary>
        public event SocketConnectionDelegate ConnectionEvent
        {
            add { lock (raiseLock) { connectionEvent += value; } }
            remove { lock (raiseLock) { connectionEvent -= value; } }
        }

        /// <summary>
        /// Event for a close connection.
        /// </summary>
        public event SocketConnectionDelegate CloseConnectionEvent
        {
            add { lock (raiseLock) { closeConnectionEvent += value; } }
            remove { lock (raiseLock) { closeConnectionEvent -= value; } }
        }
        
        #endregion

        #region Methods for Init

        /// <summary>
        /// Init a listener for a SSL communication.
        /// </summary>
        /// <param name="endPoint">The listener IP/Port</param>
        /// <param name="serverCertificateFilename">The server certification file</param>
        /// <param name="certificatePassword">The server certification password</param>
        public void Init(IPEndPoint endPoint, string serverCertificateFilename, string certificatePassword)
        {
            serverCertificate = new X509Certificate2(serverCertificateFilename, certificatePassword);
            Trace.WriteLine(string.Format("Init ssl listener on {0}", endPoint));
            sslListener = new TcpListener(endPoint);
            sslListener.Start();
            Trace.WriteLine(string.Format("Start ssl listener on {0}", endPoint));
            Init();
        }

        /// <summary>
        /// Init a lister for a not SSL communication
        /// </summary>
        /// <param name="endPoint">The listener IP/Port</param>
        public void Init(IPEndPoint endPoint)
        {
            Trace.WriteLine(string.Format("Init listener on {0}", endPoint));
            listener = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
            listener.Bind(endPoint);
            listener.Listen(10);
            Trace.WriteLine(string.Format("Start listener on {0}", endPoint));
            Init();
        }

        #endregion

        #region Accept incoming connection

        /// <summary>
        /// The thread loop is used to accecpt new incoming connection. The Accept method is asynchronous.
        /// </summary>
        protected override void ThreadLoop()
        {
            listenerCompleteConnectionEvent.Reset();
            try
            {
                // Check if it's a not SSL listener
                if (listener != null)
                {
                    Trace.WriteLine("Waiting for a connection...");
                    listener.BeginAccept(new AsyncCallback(AcceptCallback), this);
                }
                // Check if it's a SSL listener
                else if (sslListener != null)
                {
                    Trace.WriteLine("Waiting for a ssl connection...");
                    sslListener.BeginAcceptTcpClient(new AsyncCallback(AcceptSSLCallback), this);
                }
                else
                {
                    Trace.WriteLine("None listener is started");
                }
            }
            catch (Exception ex)
            {
                Trace.WriteLine(string.Format("Failed to start listener on {0}. Exception {1}", listener.LocalEndPoint, ex));
                // Set semaphore to go back to the start of thread loop and wait for a incoming connection
                listenerCompleteConnectionEvent.Set();
            }
            listenerCompleteConnectionEvent.WaitOne();
        }

        /// <summary>
        /// Accept callback method for not SSL connection
        /// </summary>
        /// <param name="ar">The socket server</param>
        private void AcceptCallback(IAsyncResult ar)
        {
            AbstractSocketServer server = (AbstractSocketServer)ar.AsyncState;
            try
            {
                // Get the socket that handles the client request.
                Socket handler = server.listener.EndAccept(ar);
                Trace.WriteLine("Start incoming connection ...");
                handler.Blocking = true;
                AbstractTcpSocketClientHandler clientHandler = this.GetHandler(handler, null);
                clientHandler.KeepAlive = this.KeepAlive;
                clientHandler.ReceiveMessageEvent += new ReceiveMessageDelegate(server.OnReceiveMessage);
                clientHandler.CloseConnectionEvent += new SocketConnectionDelegate(server.clientHandler_CloseConnectionEvent);
                // Add the clilent to the client list
                server.clientList.AddClient(this.GetClientInfo(clientHandler));
                // Notify the connection event
                server.OnConnection(clientHandler);
                // Start to receive data from this client
                clientHandler.StartReceive();
                Trace.WriteLine("New connection completed");
            }
            catch (Exception ex)
            {
                Trace.WriteLine(string.Format("Failed to accept incoming connection. Exception", ex));
            }
            finally
            {
                // Signal the main thread to continue.
                server.listenerCompleteConnectionEvent.Set();
            }
        }

        /// <summary>
        /// Accept callback method for SSL connection.
        /// </summary>
        /// <param name="ar">The socket server</param>
        private void AcceptSSLCallback(IAsyncResult ar)
        {
            AbstractSocketServer server = (AbstractSocketServer)ar.AsyncState;
            SslStream sslStream = null;
            try
            {
                // Get the socket that handles the client request.
                TcpClient sslTcpClient = server.sslListener.EndAcceptTcpClient(ar);
                Trace.WriteLine("Start incoming ssl connection ...");
                sslStream = new SslStream(sslTcpClient.GetStream(), false, new RemoteCertificateValidationCallback(server.OnVerifyClientCertificate));
                sslStream.AuthenticateAsServer(server.serverCertificate, true, SslProtocols.Ssl3, false);
                Socket handler = sslTcpClient.Client;
                handler.Blocking = true;
                AbstractTcpSocketClientHandler clientHandler = this.GetHandler(handler, sslStream);
                clientHandler.KeepAlive = this.KeepAlive;
                clientHandler.ReceiveMessageEvent += new ReceiveMessageDelegate(server.OnReceiveMessage);
                clientHandler.CloseConnectionEvent += new SocketConnectionDelegate(server.clientHandler_CloseConnectionEvent);
                server.OnConnection(clientHandler);
                server.clientList.AddClient(this.GetClientInfo(clientHandler));
                clientHandler.StartReceive();
                Trace.WriteLine("New connection completed");
            }
            catch (Exception ex)
            {
                Trace.WriteLine(string.Format("Failed to accept incoming connection. Exception", ex));
                try
                {
                    if (sslStream != null)
                    {
                        sslStream.Close();
                        sslStream.Dispose();
                    }
                }
                catch (Exception ex2)
                {
                    Trace.WriteLine(ex2);
                }
            }
            finally
            {
                // Signal the main thread to continue.
                server.listenerCompleteConnectionEvent.Set();
            }
        }

        #endregion

        /// <summary>
        /// Implement this method to return a socket client handler instance
        /// </summary>
        /// <param name="handler">The socket handler</param>
        /// <param name="sslStream">The ssl stream</param>
        /// <param name="sendHandleTimeout">The send time out</param>
        /// <param name="socketSendTimeout">The socket send time out</param>
        /// <returns></returns>
        protected abstract AbstractTcpSocketClientHandler GetHandler(Socket handler, SslStream sslStream);

        /// <summary>
        /// Return a clones client list.
        /// </summary>
        /// <returns>A cloned client list</returns>
        public ClientInfo[] GetClientList()
        {
            return this.clientList.CloneClientList();
        }

        /// <summary>
        /// Override this method to change the instace used for the Client Info
        /// </summary>
        /// <param name="abstractTcpSocketClientHandler">The socket client handler for the cient info</param>
        /// <returns>The client info contains the socket clilent handler</returns>
        protected virtual ClientInfo GetClientInfo(AbstractTcpSocketClientHandler abstractTcpSocketClientHandler)
        {
            return new ClientInfo(abstractTcpSocketClientHandler);
        }

        /// <summary>
        /// Override this method to chage the vVerification of the client certificate
        /// </summary>
        /// <param name="sender">The sender</param>
        /// <param name="certificate">The cliet certificate</param>
        /// <param name="chain">The chain</param>
        /// <param name="sslPolicyErrors">The policy</param>
        /// <returns></returns>
        protected virtual bool OnVerifyClientCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
        {
            lock (this)
            {
                if (sslPolicyErrors != SslPolicyErrors.None)
                {
                    return false;
                }
                return true;
            }
        }

        #region Methods to raise events

        /// <summary>
        /// Raise the close connection event.
        /// </summary>
        /// <param name="handler">The socket client handler disconnected</param>
        protected virtual void clientHandler_CloseConnectionEvent(AbstractTcpSocketClientHandler handler)
        {
            clientList.RemoveClient(clientList[handler]);
            if (closeConnectionEvent != null)
            {
                closeConnectionEvent(handler);
            }
        }

        /// <summary>
        /// Raise the receive message event.
        /// </summary>
        /// <param name="handler">The socket client handler for the received message</param>
        /// <param name="abstractMessage">The message received</param>
        protected virtual void OnReceiveMessage(AbstractTcpSocketClientHandler handler, AbstractMessage abstractMessage)
        {
            if (receiveMessageEvent != null)
            {
                receiveMessageEvent(handler, abstractMessage);
            }
        }

        /// <summary>
        /// Raise the new conection event.
        /// </summary>
        /// <param name="handler">The socket client handler connected</param>
        protected virtual void OnConnection(AbstractTcpSocketClientHandler handler)
        {
            if (connectionEvent != null)
            {
                connectionEvent(handler);
            }
        }

        #endregion

        #region Thread methods overridden

        /// <summary>
        /// Shutdown the server and stop the main thread.
        /// </summary>
        public override void Shutdown()
        {
            Trace.WriteLine("Stop listener");
            // Check if it's a not SSL listener
            if (this.listener != null)
            {
                this.listener.Close();
                this.listener.Dispose();
                this.listener = null;
            }
            // Check if it's a SSL listener
            if (this.sslListener != null)
            {
                this.sslListener.Stop();
                this.sslListener = null;
            }
            Trace.WriteLine("Disconnect pending client...");
            foreach (ClientInfo clientInfo in this.clientList.CloneClientList())
            {
                try
                {
                    clientInfo.TcpSocketClientHandler.Close();
                }
                catch (Exception ex)
                {
                    Trace.WriteLine(string.Format("Exception in disconnet pending client. Exception {0}", ex));
                }
            }
            Trace.WriteLine("All pending client are disconnected");
            base.Shutdown();
        }

        /// <summary>
        /// Dispose
        /// </summary>
        public override void Dispose()
        {
            this.clientList.Dispose();
            base.Dispose();
        }

        #endregion

    }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Team Leader Mediatech Solutions
Italy Italy
I’m an IT Project Manager for an Italian Betting Company and over the last 2 years I acquired experience in Betting area.
I have developed code in different object oriented languages (C#, C++, Java) for more than 10 years using a set of technology such as .Net, J2EE, multithreading, etc…

Comments and Discussions