When working with TCP client sockets I often find myself frustrated with the lack of event-driven support. I usually end up writing a whole bunch of code to determine disconnects, reconnecting, etc and I figuired it's time to just write my own class for this stuff. I figuired I'd share it with all of you. The implementation is quite simple, I'll provide an example below. I have XML commented all the public properties and methods, the rest is up to you to figuire out if you want to make any changes!

public partial class Form1 : Form
{
    EventDrivenTCPClient client;
    public Form1()
    {
        InitializeComponent();
        //Initialize the event driven client
        client = new EventDrivenTCPClient(IPAddress.Parse(textBox1.Text), int.Parse(textBox2.Text));
        //Initialize the events
        client.DataReceived += new EventDrivenTCPClient.delDataReceived(client_DataReceived);
        client.ConnectionStatusChanged += new EventDrivenTCPClient.delConnectionStatusChanged(client_ConnectionStatusChanged);
    }

    //Fired when the connection status changes in the TCP client - parsing these messages is up to the developer
    //I'm just adding the .ToString() of the state enum to a richtextbox here
    void client_ConnectionStatusChanged(EventDrivenTCPClient sender, EventDrivenTCPClient.ConnectionStatus status)
    {
        //Check if this event was fired on a different thread, if it is then we must invoke it on the UI thread
        if (InvokeRequired)
        {
            Invoke(new EventDrivenTCPClient.delConnectionStatusChanged(client_ConnectionStatusChanged), sender, status);
            return;
        }
        richTextBox1.Text += "Connection: " + status.ToString() + Environment.NewLine;
    }

    //Fired when new data is received in the TCP client
    void client_DataReceived(EventDrivenTCPClient sender, object data)
    {
        //Again, check if this needs to be invoked in the UI thread
        if (InvokeRequired)
        {
            try
            {
                Invoke(new EventDrivenTCPClient.delDataReceived(client_DataReceived),sender, data);
            }
            catch
            { }
            return;
        }
        //Interpret the received data object as a string
        string strData = data as string;
        //Add the received data to a rich text box
        richTextBox1.Text += strData + Environment.NewLine;
    }
    //Button1 is a "Connect" button
    private void button1_Click(object sender, EventArgs e)
    {
        client.Connect();
    }
    //Button 2 is a 'Send Data' button
    private void button2_Click(object sender, EventArgs e)
    {
        client.Send("Hello!");
    }
}

This example requires a connect button called button1, a send data button called button2, a textbox for the IP address, a textbox for the port, and a richtextbox to display output. You will also (obviously) need a server to connect to that will send us data.

Edited 4 Years Ago by skatamatic

/// <summary>
    /// Event driven TCP client wrapper
    /// </summary>
    public class EventDrivenTCPClient : IDisposable
    {
        #region Consts/Default values
        const int DEFAULTTIMEOUT = 5000; //Default to 5 seconds on all timeouts
        const int RECONNECTINTERVAL = 2000; //Default to 2 seconds reconnect attempt rate
        #endregion

        #region Components, Events, Delegates, and CTOR
        //Timer used to detect receive timeouts
        private System.Timers.Timer tmrReceiveTimeout = new System.Timers.Timer();
        private System.Timers.Timer tmrSendTimeout = new System.Timers.Timer();
        private System.Timers.Timer tmrConnectTimeout = new System.Timers.Timer();

        public delegate void delDataReceived(EventDrivenTCPClient sender, object data);
        public event delDataReceived DataReceived;

        public delegate void delConnectionStatusChanged(EventDrivenTCPClient sender, ConnectionStatus status);
        public event delConnectionStatusChanged ConnectionStatusChanged;

        public enum ConnectionStatus
        {
            NeverConnected,
            Connecting,
            Connected,
            AutoReconnecting,
            DisconnectedByUser,
            DisconnectedByHost,
            ConnectFail_Timeout,
            ReceiveFail_Timeout,
            SendFail_Timeout,
            SendFail_NotConnected,
            Error
        }

        public EventDrivenTCPClient(IPAddress ip, int port, bool autoreconnect = true)
        {
            this._IP = ip;
            this._Port = port;
            this._AutoReconnect = autoreconnect;
            this._client = new TcpClient(AddressFamily.InterNetwork);
            this._client.NoDelay = true; //Disable the nagel algorithm for simplicity

            ReceiveTimeout = DEFAULTTIMEOUT;
            SendTimeout = DEFAULTTIMEOUT;
            ConnectTimeout = DEFAULTTIMEOUT;
            ReconnectInterval = RECONNECTINTERVAL;

            tmrReceiveTimeout.AutoReset = false;
            tmrReceiveTimeout.Elapsed += new System.Timers.ElapsedEventHandler(tmrReceiveTimeout_Elapsed);

            tmrConnectTimeout.AutoReset = false;
            tmrConnectTimeout.Elapsed += new System.Timers.ElapsedEventHandler(tmrConnectTimeout_Elapsed);

            tmrSendTimeout.AutoReset = false;
            tmrSendTimeout.Elapsed += new System.Timers.ElapsedEventHandler(tmrSendTimeout_Elapsed);
			
            ConnectionState = ConnectionStatus.NeverConnected;
        }

        #endregion

        #region Private methods/Event Handlers
        void tmrSendTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.ConnectionState = ConnectionStatus.SendFail_Timeout;
            DisconnectByHost();
        }
        void tmrReceiveTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            this.ConnectionState = ConnectionStatus.ReceiveFail_Timeout;
            DisconnectByHost();
        }
        void tmrConnectTimeout_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            ConnectionState = ConnectionStatus.ConnectFail_Timeout;
            DisconnectByHost();
        }
        private void DisconnectByHost()
        {
            this.ConnectionState = ConnectionStatus.DisconnectedByHost;
            tmrReceiveTimeout.Stop();
            if (AutoReconnect)
                Reconnect();
        }

        private void Reconnect()
        {
            if (this.ConnectionState == ConnectionStatus.Connected)
                return;

            this.ConnectionState = ConnectionStatus.AutoReconnecting;
            try
            {
                this._client.Client.BeginDisconnect(true, new AsyncCallback(cbDisconnectByHostComplete), this._client.Client);
            }
            catch { }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Try connecting to the remote host
        /// </summary>
        public void Connect()
        {
            if (this.ConnectionState == ConnectionStatus.Connected)
                return;

            this.ConnectionState = ConnectionStatus.Connecting;

            tmrConnectTimeout.Start();
            this._client.BeginConnect(this._IP, this._Port, new AsyncCallback(cbConnect), this._client.Client);
        }

        /// <summary>
        /// Try disconnecting from the remote host
        /// </summary>
        public void Disconnect()
        {
            if (this.ConnectionState != ConnectionStatus.Connected)
                return;

            this._client.Client.BeginDisconnect(true, new AsyncCallback(cbDisconnectComplete), this._client.Client);
        }
        /// <summary>
        /// Try sending a string to the remote host
        /// </summary>
        /// <param name="data">The data to send</param>
        public void Send(string data)
        {
            if (this.ConnectionState != ConnectionStatus.Connected)
            {
                this.ConnectionState = ConnectionStatus.SendFail_NotConnected;
                return;
            }

            var bytes = _encode.GetBytes(data);
            SocketError err = new SocketError();
            tmrSendTimeout.Start();
            this._client.Client.BeginSend(bytes,0,bytes.Length, SocketFlags.None,out err, new AsyncCallback(cbSendComplete), this._client.Client);
            if (err != SocketError.Success)
            {
                Action doDCHost = new Action(DisconnectByHost);
                doDCHost.Invoke();
            }
        }
        /// <summary>
        /// Try sending byte data to the remote host
        /// </summary>
        /// <param name="data">The data to send</param>
        public void Send(byte[] data)
        {
            if (this.ConnectionState != ConnectionStatus.Connected)
                throw new InvalidOperationException("Cannot send data, socket is not connected");

            SocketError err = new SocketError();
            this._client.Client.BeginSend(data, 0, data.Length, SocketFlags.None, out err, new AsyncCallback(cbSendComplete), this._client.Client);
            if (err != SocketError.Success)
            {
                Action doDCHost = new Action(DisconnectByHost);
                doDCHost.Invoke();
            }
        }
        public void Dispose()
        {
            this._client.Close();
            this._client.Client.Dispose();
        }
        #endregion

        #region Callbacks
        private void cbConnectComplete()
        {
            if (_client.Connected == true)
            {
                tmrConnectTimeout.Stop();
                ConnectionState = ConnectionStatus.Connected;
                this._client.Client.BeginReceive(this.dataBuffer, 0, this.dataBuffer.Length, SocketFlags.None, new AsyncCallback(cbDataReceived), this._client.Client);
            }
            else
            {
                ConnectionState = ConnectionStatus.Error;
            }
        }
        private void cbDisconnectByHostComplete(IAsyncResult result)
        {
            var r = result.AsyncState as Socket;
            if (r == null)
                throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a socket object");

            r.EndDisconnect(result);
            if (this.AutoReconnect)
            {
                Action doConnect = new Action(Connect);
                doConnect.Invoke();
                return;
            }
        }

        private void cbDisconnectComplete(IAsyncResult result)
        {
            var r = result.AsyncState as Socket;
            if (r == null)
                throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a socket object");

            r.EndDisconnect(result);
            this.ConnectionState = ConnectionStatus.DisconnectedByUser;
            
        }

        private void cbConnect(IAsyncResult result)
        {
            var sock = result.AsyncState as Socket;
            if (result == null)
                throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a socket object");

            if (!sock.Connected)
            {
                if (AutoReconnect)
                {
                    System.Threading.Thread.Sleep(ReconnectInterval);
                    Action reconnect = new Action(Connect);
                    reconnect.Invoke();
                    return;
                }
                else
                    return;
            }

            sock.EndConnect(result);

            var callBack = new Action(cbConnectComplete);
            callBack.Invoke();
        }
        private void cbSendComplete(IAsyncResult result)
        {
            var r = result.AsyncState as Socket;
            if (r == null)
                throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a socket object");

            SocketError err = new SocketError();
            r.EndSend(result, out err);
            if (err != SocketError.Success)
            {
                Action doDCHost = new Action(DisconnectByHost);
                doDCHost.Invoke();
            }
            else
            {
                lock (SyncLock)
                {
                    tmrSendTimeout.Stop();
                }
            }
        }
        private void cbChangeConnectionStateComplete(IAsyncResult result)
        {
            var r = result.AsyncState as EventDrivenTCPClient;
            if (r == null)
                throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as a EDTC object");

            r.ConnectionStatusChanged.EndInvoke(result);
        }
        private void cbDataReceived(IAsyncResult result)
        {
            var sock = result.AsyncState as Socket;

            if (sock == null)
                throw new InvalidOperationException("Invalid IASyncResult - Could not interpret as a socket");
            SocketError err = new SocketError();
            int bytes = sock.EndReceive(result, out err);
            if (bytes == 0 || err != SocketError.Success)
            {
                lock (SyncLock)
                {
                    tmrReceiveTimeout.Start();
                    return;
                }
            }
            else
            {
                lock (SyncLock)
                {
                    tmrReceiveTimeout.Stop();
                }
            }
            if (DataReceived != null)
                DataReceived.BeginInvoke(this, _encode.GetString(dataBuffer, 0, bytes), new AsyncCallback(cbDataRecievedCallbackComplete), this);
        }

        private void cbDataRecievedCallbackComplete(IAsyncResult result)
        {
            var r = result.AsyncState as EventDrivenTCPClient;
            if (r == null)
                throw new InvalidOperationException("Invalid IAsyncResult - Could not interpret as EDTC object");

            r.DataReceived.EndInvoke(result);
            SocketError err = new SocketError();
            this._client.Client.BeginReceive(this.dataBuffer, 0, this.dataBuffer.Length, SocketFlags.None,out err, new AsyncCallback(cbDataReceived), this._client.Client);
            if (err != SocketError.Success)
            {
                Action doDCHost = new Action(DisconnectByHost);
                doDCHost.Invoke();
            }
        }
        #endregion

        #region Properties and members
        private IPAddress _IP = IPAddress.None;
        private ConnectionStatus _ConStat;
        private TcpClient _client;
        private byte[] dataBuffer = new byte[5000];
        private bool _AutoReconnect = false;
        private int _Port = 0;
        private Encoding _encode = Encoding.Default;
        object _SyncLock = new object();
        /// <summary>
        /// Syncronizing object for asyncronous operations
        /// </summary>
        public object SyncLock
        {
            get
            {
                return _SyncLock;
            }
        }
        /// <summary>
        /// Encoding to use for sending and receiving
        /// </summary>
        public Encoding DataEncoding
        {
            get
            {
                return _encode;
            }
            set
            {
                _encode = value;
            }
        }
        /// <summary>
        /// Current state that the connection is in
        /// </summary>
        public ConnectionStatus ConnectionState
        {
            get
            {
                return _ConStat;
            }
            private set
            {
                bool raiseEvent = value != _ConStat;
                _ConStat = value;
                if (ConnectionStatusChanged != null && raiseEvent)
                    ConnectionStatusChanged.BeginInvoke(this, _ConStat, new AsyncCallback(cbChangeConnectionStateComplete), this);
            }
        }
        /// <summary>
        /// True to autoreconnect at the given reconnection interval after a remote host closes the connection
        /// </summary>
        public bool AutoReconnect
        {
            get
            {
                return _AutoReconnect;
            }
            set
            {
                _AutoReconnect = value;
            }
        }
        public int ReconnectInterval { get; set; }
        /// <summary>
        /// IP of the remote host
        /// </summary>
        public IPAddress IP
        {
            get
            {
                return _IP;
            }
        }
        /// <summary>
        /// Port to connect to on the remote host
        /// </summary>
        public int Port
        {
            get
            {
                return _Port;
            }
        }
        /// <summary>
        /// Time to wait after a receive operation is attempted before a timeout event occurs
        /// </summary>
        public int ReceiveTimeout
        {
            get
            {
                return (int)tmrReceiveTimeout.Interval;
            }
            set
            {
                tmrReceiveTimeout.Interval = (double)value;
            }
        }
        /// <summary>
        /// Time to wait after a send operation is attempted before a timeout event occurs
        /// </summary>
        public int SendTimeout
        {
            get
            {
                return (int)tmrSendTimeout.Interval;
            }
            set
            {
                tmrSendTimeout.Interval = (double)value;
            }
        }
        /// <summary>
        /// Time to wait after a connection is attempted before a timeout event occurs
        /// </summary>
        public int ConnectTimeout
        {
            get
            {
                return (int)tmrConnectTimeout.Interval;
            }
            set
            {
                tmrConnectTimeout.Interval = (double)value;
            }
        }
        #endregion       
    }

I don't really play too much with sockets, but I read over the source and it was a good read. :D
Thanks for posting, I'll archive it in my forms kit with credit to you. :)

do you really mean to call sock.EndRecieve if sock==null? Little confused here.

If sock is null, it would throw an InvalidOperationException. If there's no curly braces, it only executes the following statement.

Nope, it causes a "return" from the current method. It isn't quite a return, as no value is returned, and no variable will be assigned a return value. The exception will propogate through the call stack until it is caught in a try-catch block. In other words, the calling method will stop executing, and its calling method will stop executing and so on, until it is caught.

Hey,
If i use this class inside a dll how should i replace the if(invokerequired) and invoke() so the program will work perfect, because, if i remove the invoke i get a high delay between messages.

Hey,
the code looks very nice! Thanks!
I habe following problem, and i didnt know how to resolve it...
The EventDrivenTCPClient ist connected....
Then i dispose the objekt...
Directly i generate a new, and connect....
It will crash in method cbDataReceived with an objectdisposedexception...
It looks like the old object is still alive.....

Hello,

I've been using this code, and it seams to work fine.
But when I choose to disconnect, client.disconnect(), I've got a disconnectbyuser event, and then it tries to reconnect, what am I doing wrong?

Edited 2 Years Ago by Julio_1

Hi,
I have copied and used this code, it works very good for me.
Only thing is i need to wait in receive function for a specific END OF FRAME character in my client/server protocoll.
How can i implement this ?

This is exactly what I wanted in a TCP client. I pulled your class into a project and added:

EventDrivenTCPClient client = new EventDrivenTCPClient( IPAddress.Parse("192.168.1.101"), int.Parse("9010"));
        //Initialize the events
        client.DataReceived += new EventDrivenTCPClient.delDataReceived(client_DataReceived);
        client.ConnectionStatusChanged += new EventDrivenTCPClient.delConnectionStatusChanged(client_ConnectionStatusChanged);

to my existing code.
Then I tried to telenet to the IP address and port, without connection. I tried both the local address and the loopback address and was not able to telnet to either.

exactly what I wanted, Well I wanted the server. Trying to debug the problem I soon realized there was not a TCP listen statment. Sorry to bother you.

Hi guys, this source above was very useful to me, thanks a lot. Now I would like to have the source code above rewrited in C++ sing WinSock64 lib. Somebody has this skill or did try it?

Best regards.

The article starter has earned a lot of community kudos, and such articles offer a bounty for quality replies.