Home > Networking > Dotnet Async Socket Server – 4.Echo server

Dotnet Async Socket Server – 4.Echo server

Introduction

In this part, we will make the basic echo messaging logics on the prior bare bone connection components.

Contents of this series

 

Overview

I’ll extend the existing the SocketListener, the Connection and the SocketClient classes to support sending and receiving messages. Also, I will add a helper class for the SocketAsyncEventArgs to simplify my codes.

Let’s view below diagram that describes the duplex messaging process. Be aware we will not implement the casting function in this part (Step A,B and C).

The Connection class

When this connection object is instantiated by the socket listener, the listener class may pass a raw SocketAsyncEventArgs instance with buffer setting. Be aware we will replace this simple assigning code to the pooling and buffering code from the AsyncArgsPool and the BufferManager classes in the future part of this series.

    public class SocketListener
    {
        ...


        private void ProcessAccept(SocketAsyncEventArgs e)
        {
            ...

            if (e.SocketError == SocketError.Success)
            {
                //Connection Create with passing a raw SocketAsyncEventArgs with buffer setting
                SocketAsyncEventArgs messageArgs = new SocketAsyncEventArgs();
                byte[] buff = new byte[this.bufferSize];
                messageArgs.SetBuffer(buff, 0, buff.Length);
                Connection connection = new Connection(
                    e.AcceptSocket, this.OnDisconnected,this.OnReceived, this.OnSent, messageArgs, this.bufferSize);

                ...
            }

            this.StartAccept();
        }
    }

Let’s see the connection class that has two SocketAsyncEventArgs. In this part, we will use only the messageArgs object to service listening request from clients and to responding those requests. When this connection class is instantiated, the StartRecieve() method is also started ( 1st step of the above diagram). The above StartRecieve()  method will be re- started after execution of the ProcessSent callback handler for response ( 6th step of the above diagram).

...

namespace SuWare.Net.Sockets
{
    public class Connection
    {
        ...
        private readonly int offset;
        private readonly int bufferSize;

        ...
        private Action<SocketAsyncEventArgs> onReceived;
        private Action<SocketAsyncEventArgs> onSent;

        public Connection(
            Socket socket,Action<EndPoint> onDisconnected,Action<SocketAsyncEventArgs> onReceived, 
            Action<SocketAsyncEventArgs> onSent, SocketAsyncEventArgs messageArgs, int bufferSize)
        {
            ...

            this.offset = messageArgs.Offset;
            this.bufferSize = bufferSize;

            ...

            this.onReceived = onReceived;
            this.onSent = onSent;
            ...

            this.StartReceive();
        }

        private void StartReceive()
        {
            if (this.socket.Connected)
            {
                if (!socket.ReceiveAsync(this.messageArgs))
                {
                    this.ProcessMessage(this.messageArgs);
                }
            }
        }

        ...
    }
}

Let’s see the other added methods to the prior version. In the latest version, we didn’t implement the ProcessMessage(…) method. See the implemented the “//implementing messaging operation” section which controls the toggle messaging function.

...

namespace SuWare.Net.Sockets
{
    public class Connection
    {
        ...

        private void Message_Completed(object sender, SocketAsyncEventArgs e)
        {
            this.ProcessMessage(e);
        }

        private void ProcessMessage(SocketAsyncEventArgs e)
        {
            if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
            {
                //implementing messaging operation
                switch (e.LastOperation)
                {
                    case SocketAsyncOperation.Receive:
                        this.ProcessReceive(e);
                        break;
                    case SocketAsyncOperation.Send:
                        this.ProcessSent(e);
                        break;
                    default:
                        throw new ArgumentException("The last operation completed on the socket was not a receive or send");
                }
            }
            else
            {
                this.CloseClientSocket();
            }
        }

        private void ProcessReceive(SocketAsyncEventArgs e)
        {
            //In this step, user can set sending buffer to the messageargs instance setting onReceived delegate.
            if (this.onReceived != null)
            {
                this.onReceived(e);
            }

            //response to the client socket
            lock (this)
            {
                if (!this.socket.SendAsync(e))
                {
                    this.ProcessSent(e);
                }
            }
        }

        private void ProcessSent(SocketAsyncEventArgs e)
        {
            if (this.onSent != null)
            {
                this.onSent(e);
            }

            //Reset the buffer of the messageargs object to pre-fixed buffer setting
            //In later, this.offset will be the cursor of the one large buffer of the Buffer Manager
            e.SetBuffer(this.offset, this.bufferSize);
            this.StartReceive();

        }

        ...
    }
}

The SocketListener class

Adding two action delegators for messaging is the main additional task.

...

namespace SuWare.Net.Sockets
{
    public class SocketListener
    {
        ...

        public Action<SocketAsyncEventArgs> OnReceived;
        public Action<SocketAsyncEventArgs> OnSent;

	...
    }
}

The SocketClient class

I added two more SocketAsyncEventArgs and two action delegates for messaging. The initialing process was added in the constructor.

...

namespace SuWare.Net.Sockets
{
    public class SocketClient
    {
        ...
        private SocketAsyncEventArgs sendArgs;
        private SocketAsyncEventArgs receiveArgs;

        ...
        public Action<SocketAsyncEventArgs> OnSent;
        public Action<SocketAsyncEventArgs> OnReceive;

        public SocketClient(int bufferSize)
        {
            ...

            this.sendArgs = new SocketAsyncEventArgs();
            this.sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(Send_Completed);
            this.sendArgs.UserToken = this.socket;

            this.receiveArgs = new SocketAsyncEventArgs();
            this.receiveArgs.Completed += new EventHandler<SocketAsyncEventArgs>(Receive_Completed);
            this.receiveArgs.SetBuffer(new byte[bufferSize], 0, bufferSize);
            this.receiveArgs.UserToken = this.socket;
        }

        ...
    }
}

We will use the added send and receive functions of below code snippets.

...

namespace SuWare.Net.Sockets
{
    public class SocketClient
    {
        ...

        public void Send(string message)
        {
            this.Send(Encoding.UTF8.GetBytes(message));
        }

        public void Send(byte[] buffer)
        {
            ...

            lock (this)
            {
                this.sendArgs.SetBuffer(buffer, 0, buffer.Length);
                if (!this.socket.SendAsync(this.sendArgs))
                {
                    this.Send_Completed(this, this.sendArgs);
                }
            }
        }


        private void ConnectCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success)
            {
                this.StartReceive(this.receiveArgs);

                ...
            }
        }

        private void Send_Completed(object sender, SocketAsyncEventArgs e)
        {
            if ( this.OnSent != null)
            {
                this.OnSent(e);
            }
        }

        private void Receive_Completed(object sender, SocketAsyncEventArgs e)
        {
            ...

            if (this.OnReceive != null)
            {
                this.OnReceive(e);
            }

            this.StartReceive(e);
        }

        private void StartReceive(SocketAsyncEventArgs e)
        {
            ...

            lock (this)
            {
                if (!this.socket.ReceiveAsync(e))
                {
                    this.Receive_Completed(this,e);
                }
            }
        }

        ...
    }
}

The Echo Server

Let’s add the action delegators to assign the messaging handlers of the socket listener.

...

namespace SuWare.Sample2.EchoServer
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Press enter key to start server");
            Console.ReadLine();

            SocketListener server = new SocketListener(IPAddress.Parse("127.0.0.1"), 4096, 10, 4096);
            ...
            server.OnReceived = new Action<SocketAsyncEventArgs>(OnReceived);
            server.OnSent = new Action<SocketAsyncEventArgs>(OnSent);
            ...
        }

        ...

        static void OnReceived(SocketAsyncEventArgs e)
        {
            string rcvMsg = e.GetString();//extention helper method
            LogPrint("Message Received", rcvMsg);

            //Setting up new buffer to the messageArgs before sending is very important.
            e.CopyToArgs("Echo:" + rcvMsg);//extention helper method
        }

        static void OnSent(SocketAsyncEventArgs e)
        {
            LogPrint("Message Sent", e.GetString());
        }

        ...
    }
}

The AsyncArgsExtension helper class

To get a clean code, it is good to use the following extension helper class for the SocketAsyncEventArgs. Check the Buffer.BlockCopy(…) in the below code, that provides better performance than the Array.Copy(…).

...

namespace SuWare.Net.Sockets
{
    public static class AsyncArgsExtension
    {

        public static string GetString(this SocketAsyncEventArgs args)
        {
            return GetString(args, Encoding.UTF8);
        }

        public static string GetString(this SocketAsyncEventArgs args, Encoding encode)
        {
            return encode.GetString(args.Buffer, args.Offset, args.BytesTransferred);
        }

        public static byte[] CopyFromArgs(this SocketAsyncEventArgs args)
        {
            byte[] buff = new byte[args.BytesTransferred];
            Buffer.BlockCopy(args.Buffer, args.Offset, buff, 0, buff.Length);
            return buff;
        }

        public static void CopyToArgs(this SocketAsyncEventArgs args, string message)
        {
            CopyToArgs(args, message, Encoding.UTF8);
        }

        public static void CopyToArgs(this SocketAsyncEventArgs args, string message, Encoding encode)
        {
            CopyToArgs(args, encode.GetBytes(message));
        }

        public static void CopyToArgs(this SocketAsyncEventArgs args, byte[] srcBuffer)
        {
            Buffer.BlockCopy(srcBuffer, 0, args.Buffer, args.Offset, srcBuffer.Length);
            args.SetBuffer(args.Offset, srcBuffer.Length);
        }
    }
}

The EchoClient class

Reading the user input in this console app is the main added things.

...

namespace SuWare.Sample2.EchoClient
{
    class Program
    {
        static void Main(string[] args)
        {
            ...
            client.OnSent = new Action<SocketAsyncEventArgs>(OnSent);
            client.OnReceive = new Action<SocketAsyncEventArgs>(OnReceive);
            ...

            Console.WriteLine("Write a message that you want to send and press enter key");
            string message = Console.ReadLine();
            client.Send(message);

            ...
        }

        static void Connected(Socket socket)
        {
            LogPrint("Connected", socket.RemoteEndPoint.ToString());
        }

        static void Disconnected(EndPoint endpoint)
        {
            LogPrint("Disconnected", endpoint.ToString());
        }

        static void OnSent(SocketAsyncEventArgs e)
        {
            LogPrint("Sent", e.BytesTransferred.ToString());
        }

        static void OnReceive(SocketAsyncEventArgs e)
        {
            LogPrint("Received", e.GetString());
        }

        static void LogPrint(string lable, string message)
        {
            Console.WriteLine(string.Format("[{0}] {1}", lable, message));
        }
    }
}

Result

This is the final result of this echo socket solution.

You can download the source of this part at here.

Advertisements
  1. November 5, 2016 at 8:26 pm

    I could still recall the main one occasion I went along with her to get presents.
    Consequently folks prefer to visijt this shop again and again. The purpose iss – doeds
    Zippos provbide coupons and discount csmpaign requirements?
    http://tomq.al/peoplewouldnt39889

  1. April 20, 2010 at 4:52 am
  2. April 20, 2010 at 4:52 am
  3. April 20, 2010 at 4:53 am

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

%d bloggers like this: