Asynchronous TCP Client Example

Here’s some example code for an asynchronous TCP client.

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace RDavey.Net
{
    public class AsyncTcpClient
    {
        private IPAddress[] addresses;
        private int port;
        private WaitHandle addressesSet;
        private TcpClient tcpClient;
        private int failedConnectionCount;

        /// <summary>
        /// Construct a new client from a known IP Address
        /// </summary>
        /// <param name="address">The IP Address of the server</param>
        /// <param name="port">The port of the server</param>
        public AsyncTcpClient(IPAddress address, int port)
            : this(new[] { address }, port)
        {
        }

        /// <summary>
        /// Construct a new client where multiple IP Addresses for
        /// the same client are known.
        /// </summary>
        /// <param name="addresses">The array of known IP Addresses</param>
        /// <param name="port">The port of the server</param>
        public AsyncTcpClient(IPAddress[] addresses, int port)
            : this(port)
        {
            this.addresses = addresses;
        }

        /// <summary>
        /// Construct a new client where the address or host name of
        /// the server is known.
        /// </summary>
        /// <param name="hostNameOrAddress">The host name or address of the server</param>
        /// <param name="port">The port of the server</param>
        public AsyncTcpClient(string hostNameOrAddress, int port)
            : this(port)
        {
            addressesSet = new AutoResetEvent(false);
            Dns.BeginGetHostAddresses(hostNameOrAddress, GetHostAddressesCallback, null);
        }

        /// <summary>
        /// Private constuctor called by other constuctors
        /// for common operations.
        /// </summary>
        /// <param name="port"></param>
        private AsyncTcpClient(int port)
        {
            if (port < 0)
                throw new ArgumentException();
            this.port = port;
            this.tcpClient = new TcpClient();
            this.Encoding = Encoding.Default;
        }

        /// <summary>
        /// The endoding used to encode/decode string when sending and receiving.
        /// </summary>
        public Encoding Encoding { get; set; }

        /// <summary>
        /// Attempts to connect to one of the specified IP Addresses
        /// </summary>
        public void Connect()
        {
            if (addressesSet != null)
                //Wait for the addresses value to be set
                addressesSet.WaitOne();
            //Set the failed connection count to 0
            Interlocked.Exchange(ref failedConnectionCount, 0);
            //Start the async connect operation
            tcpClient.BeginConnect(addresses, port, ConnectCallback, null);
        }

        /// <summary>
        /// Writes a string to the network using the defualt encoding.
        /// </summary>
        /// <param name="data">The string to write</param>
        /// <returns>A WaitHandle that can be used to detect
        /// when the write operation has completed.</returns>
        public void Write(string data)
        {
            byte[] bytes = Encoding.GetBytes(data);
            Write(bytes);
        }

        /// <summary>
        /// Writes an array of bytes to the network.
        /// </summary>
        /// <param name="bytes">The array to write</param>
        /// <returns>A WaitHandle that can be used to detect
        /// when the write operation has completed.</returns>
        public void Write(byte[] bytes)
        {
            NetworkStream networkStream = tcpClient.GetStream();
            //Start async write operation
            networkStream.BeginWrite(bytes, 0, bytes.Length, WriteCallback, null);
        }

        /// <summary>
        /// Callback for Write operation
        /// </summary>
        /// <param name="result">The AsyncResult object</param>
        private void WriteCallback(IAsyncResult result)
        {
            NetworkStream networkStream = tcpClient.GetStream();
            networkStream.EndWrite(result);
        }

        /// <summary>
        /// Callback for Connect operation
        /// </summary>
        /// <param name="result">The AsyncResult object</param>
        private void ConnectCallback(IAsyncResult result)
        {
            try
            {
                tcpClient.EndConnect(result);
            }
            catch
            {
                //Increment the failed connection count in a thread safe way
                Interlocked.Increment(ref failedConnectionCount);
                if (failedConnectionCount >= addresses.Length)
                {
                    //We have failed to connect to all the IP Addresses
                    //connection has failed overall.
                    return;
                }
            }

            //We are connected successfully.
            NetworkStream networkStream = tcpClient.GetStream();
            byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
            //Now we are connected start asyn read operation.
            networkStream.BeginRead(buffer, 0, buffer.Length, ReadCallback, buffer);
        }

        /// <summary>
        /// Callback for Read operation
        /// </summary>
        /// <param name="result">The AsyncResult object</param>
        private void ReadCallback(IAsyncResult result)
        {
            int read;
            NetworkStream networkStream;
            try
            {
                networkStream = tcpClient.GetStream();
                read = networkStream.EndRead(result);
            }
            catch
            {
                //An error has occured when reading
                return;
            }

            if (read == 0)
            {
                //The connection has been closed.
                return;
            }

            byte[] buffer = result.AsyncState as byte[];
            string data = this.Encoding.GetString(buffer, 0, read);
            //Do something with the data object here.
            //Then start reading from the network again.
            networkStream.BeginRead(buffer, 0, buffer.Length, ReadCallback, buffer);
        }

        /// <summary>
        /// Callback for Get Host Addresses operation
        /// </summary>
        /// <param name="result">The AsyncResult object</param>
        private void GetHostAddressesCallback(IAsyncResult result)
        {
            addresses = Dns.EndGetHostAddresses(result);
            //Signal the addresses are now set
            ((AutoResetEvent)addressesSet).Set();
        }
    }
}
About these ads

Tags: , , ,

13 Responses to “Asynchronous TCP Client Example”

  1. singh Says:

    I looking for async TcpClient implementation with connection retry. I tried putting a retry timer and performing connect from there but thats seems to be failing with exception; A connect request was made on an already connected socket (Even though Connected property is set to false).

    Do you have any better idea?

  2. Arkadi Says:

    Thank you very much great code example!

  3. Sony Arouje Says:

    Thanks for the helpful post.

  4. Oliver Says:

    Couple of things:

    1. Where are your “Close” and cleanup methods?

    2. According to documentation:
    http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.getstream.aspx
    “You must close the NetworkStream when you are through sending and receiving data. Closing TcpClient does not release the NetworkStream.”
    It would probably be better to keep NetworkStream as a private class field.

    Great post though!

    • robjdavey Says:

      Thanks for your comments.
      Yes you are absolutely right this example does not contain methods for cleaning up or managing resources.
      This example is not intended to be taken straight from here and put into production code, it was purely intended to demonstrate to people how they can use the asynchronous versions of methods to read and write to clients. When I myself was looking to do some networking in C# I discovered there were no simple easy examples of this so I added it myself.
      If I were to use it for a real project it would also include a lot more error handling etc.
      Regarding the close it seems MSDN disagrees with itself on this one:
      http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient.close.aspx

      The Close method marks the instance as disposed and requests that the associated Socket close the TCP connection. Based on the LingerState property, the TCP connection may stay open for some time after the Close method is called when data remains to be sent. There is no notification provided when the underlying connection has completed closing.

      Calling this method will eventually result in the close of the associated Socket and will also close the associated NetworkStream that is used to send and receive data if one was created.

      But hey… what’s new there! :D

  5. Roman Says:

    Thank you very much for the helpful code!

  6. jhd Says:

    Hello i translate this code to vb.net.

    Classes for ASyncTcpServer and ASyncTcpClient are available at http://www.zem.fr/net-serveur-tcp-asynchrone-et-client-tcp-asynchrone/

  7. [.Net] Serveur TCP Asynchrone et Client Tcp Asynchrone | ZeM, geekeries en tout genre Says:

    [...] convertit ces classes en Vb.Net et qu’elles ont été développées à l’origine par Rob Davey. Source code   Imports System.Net.Sockets Imports System.Net Imports System.Text [...]

  8. Mark Says:

    Thanks a lot for this class. Just a question: with async connection would be possible to detect if a client is connected? or better if an async connection against a server has been completed succesfully?

    • robjdavey Says:

      If the EndConnect method throws an Exception, you’ve failed to connect.
      Otherwise you can usually assume you’re connected successfully.

      As for after the initial connection, the only way is to see if you receive a read callback of 0 length, or catch socket exceptions during read/write.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: