TCP Message Framework

Extensions


MessageSerializer.cs

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Collections.Generic;
using MessageFramework.Interfaces;

namespace MessageFramework.Extensions
{
    /// <summary>
    /// THIS CLASS IS USED FOR EXTENDING OBJECTS TO SERIALIZE AND DESERIALIZE THEM
    /// </summary>
    public static class MessageSerializer
    {
        /// <summary>
        /// SERIALIZES AN OBJECT INTO XML AS A BYTE ARRAY
        /// </summary>
        /// <typeparam name="t"></typeparam>
        /// <param name="msg"></param>
        /// <returns>RETURNS A BYTE ARRAY OF THE OBJECT AS XML</returns>
        public static byte[] Serialize<t>(this t msg)
        {
            using (MemoryStream tmpStream = new MemoryStream())
            {
                XmlSerializer tmpSerializer = new XmlSerializer(msg.GetType());

                tmpSerializer.Serialize(tmpStream, msg);
                tmpStream.Flush();

                return tmpStream.ToArray();
            }
        }

        /// <summary>
        /// DESERIALIZES A BYTE ARRAY INTO AN OBJECT OF TYPE T
        /// </summary>
        /// <typeparam name="t">TYPE OF OBJECT WHICH IS BEING DESERIALIZED</typeparam>
        /// <param name="msg">ACTUAL BYTE ARRAY DATA TO DESERIALIZE</param>
        /// <returns>RETURNS AN OBJECT OF TYPE T</returns>
        public static t DeSerialize<t>(this byte[] msg)
        {
            return DeSerialize<t>(msg, typeof(t));
        }

        /// <summary>
        /// DESERIALIZES A BYTE ARRAY INTO AN OBJECT OF TYPE objType
        /// </summary>
        /// <typeparam name="t">TYPE OF OBJECT TO RETURN (THIS CAN BE A GENERIC TYPE)</typeparam>
        /// <param name="msg">ACTUAL BYTE ARRAY DATA TO DESERIALIZE</param>
        /// <param name="objType">TYPE OF OBJECT TO DESERIALIZE INTO (THIS CAN DIFFER FROM TYPE T)</param>
        /// <returns>RETURNS AN OBJECT OF TYPE T</returns>
        public static t DeSerialize<t>(this byte[] msg, Type objType)
        {
            using (MemoryStream tmpStream = new MemoryStream(msg))
            {
                XmlSerializer tmpSerializer = new XmlSerializer(objType);
                return (t)tmpSerializer.Deserialize(tmpStream);
            }
        }
    }
}

Interfaces (To be fair it’s actually an Abstract class)


iMessage.cs

using System;
using System.IO;
using System.Linq;
using System.Text;
using MessageFramework.Messages;
using System.Collections.Generic;
using MessageFramework.Extensions;

namespace MessageFramework.Interfaces
{
    /// <summary>
    /// THIS CLASS IS USED AS A BASE FOR ANY MESSAGE THAT WISHES TO BE SENT THROUGH THIS FRAMEWORK
    /// </summary>
    public abstract class iMessage
    {
        /// <summary>
        /// STORES INFORMATION ABOUT WHAT TYPE OF MESSAGE THIS IS
        /// </summary>
        public string TypeName
        {
            get
            {
                return this.GetType().ToString();
            }
            set
            {
            }
        }

        /// <summary>
        /// CALLED ON THE SERVERSIDE TO EXECUTE ANYTHING THAT NEEDS TO OCCUR ON THE SERVER
        /// </summary>
        /// <param name="connections">LIST OF ALL ACTIVE CONNECTIONS IN THE SERVERS LIST</param>
        /// <param name="thisConnection">CONNECTION FROM WHICH THE MESSAGE CAME FROM</param>
        public abstract void ServerSide(List<Connection> connections, Connection thisConnection);

        /// <summary>
        /// CALLED ON THE CLIENTSIDE TO EXECUTE ANYTHING THAT NEEDS TO OCCUR ON THE REMOTE CLIENT SIDE
        /// </summary>
        public abstract void ClientSide(Connection thisConnection);
        
        /// <summary>
        /// OVERRIDABLE SEND FUNCTION WHICH PACKAGES THE MESSAGE AND SENDS IT ACROSS THE CONNECTIONS NETWORK STREAM
        /// </summary>
        /// <param name="connection">CONNECTION FOR WHICH THE MESSAGE WILL BE SENT OUT ON</param>
        internal virtual void Send(Connection connection)
        {
            //CREATE A WRAPPER CLASS AROUND THE MESSAGE AND SEND THE WRAPPER THROUGH THE STREAM
            Message_Wrapper tmpWrapper = new Message_Wrapper() { Payload = this.Serialize(), InternalType = this.TypeName };
            connection.SafeWrite(tmpWrapper.Serialize());
        }
    }
}

Messages


Message_Wrapper.cs

using System;
using System.IO;
using System.Linq;
using System.Text;
using System.IO.Compression;
using System.Collections.Generic;
using MessageFramework.Interfaces;
using MessageFramework.Extensions;

namespace MessageFramework.Messages
{
    /// <summary>
    /// THIS IS AN OUTERWRAPPER USED TO CONTAIN MESSAGES AND COMPRESS THEIR DATA PRIOR TO SHIPPING OUT OF THE FRAMEWORK
    /// </summary>
    public class Message_Wrapper : iMessage
    {
        public string InternalType { get; set; }

        private byte[] mPayload;
        public byte[] Payload
        {
            get
            {
                //CREATE A MEMORY STREAM TO USE AND READ THE COMPRESSED PAYLOAD FROM
                using (MemoryStream tmpMem = new MemoryStream(mPayload))
                {
                    //CREATE A DECOMPRESS STREAM TO READ THE COMPRESSED DATA WITH
                    using (GZipStream gStream = new GZipStream(tmpMem, CompressionMode.Decompress))
                    {
                        List<byte> finalBuffer = new List<byte>();
                        byte[] tmpBuffer = new byte[1024];
                        int readLen = 0;

                        //WHILE THERE IS STILL DATA IN THE COMPRESSED STREAM KEEP READING AND APPEND IT TO THE FINAL OUTPUT
                        while ((readLen = gStream.Read(tmpBuffer, 0, tmpBuffer.Length)) != 0)
                        {
                            finalBuffer.AddRange(tmpBuffer.Take(readLen));
                        }

                        //RETURN THE FINAL UNCOMPRESSED DATA
                        return finalBuffer.ToArray();
                    }
                }
            }
            set
            {
                //CREATE A MEMORY STREAM TO HOLD THE COMPRESSED DATA WITH
                using (MemoryStream tmpMem = new MemoryStream())
                {
                    //CREATE A COMPRESSION STREAM TO WRITE THE DATA WITH
                    using (GZipStream gStream = new GZipStream(tmpMem, CompressionMode.Compress))
                    {
                        //WRITE THE PAYLOAD AND FLUSH THE STREAM
                        gStream.Write(value, 0, value.Length);
                        gStream.Flush();
                    }

                    //STORE THE COMPRESSED DATA IN THE PAYLOAD VARIABLE
                    mPayload = tmpMem.ToArray();
                }
            }
        }

        public override void ServerSide(List<Connection> connections, Connection thisConnection)
        {
            //THIS IS NOT USED IN A MESSAGE WRAPPER MESSAGE
            throw new NotImplementedException();
        }
        public override void ClientSide(Connection thisConnection)
        {
            //THIS IS NOT USED IN A MESSAGE WRAPPER MESSAGE
            throw new NotImplementedException();
        }

        /// <summary>
        /// SENDS THE WRAPPER MESSAGE OUT THE CONNECTION
        /// </summary>
        /// <param name="connection">CONNECTION TO USE FOR SENDING THE WRAPPED MESSAGE</param>
        internal override void Send(Connection connection)
        {
            //SIMPLY WRITE THE SERIALIZED VERSION OF THIS CLASS OUT TO THE NETWORK STREAM
            connection.SafeWrite(this.Serialize());
        }
    }
}

Message_Hello_World.cs

using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;
using MessageFramework.Interfaces;

namespace MessageFramework.Messages
{
    /// <summary>
    /// THIS IS A SAMPLE CLASS OF A MESSAGE THAT CAN BE SENT THROUGH THIS FRAMEWORK
    /// </summary>
    public class Message_Hello_World : iMessage
    {
        public string PayLoad = "Hello, World!";

        public override void ServerSide(List<Connection> connections, Connection thisConnection)
        {
            Console.WriteLine(PayLoad);
        }

        public override void ClientSide(Connection thisConnection)
        {
            throw new NotImplementedException();
        }
    }
}

Core Classes


Client.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using System.IO;
using MessageFramework.Interfaces;
using MessageFramework.Extensions;

namespace MessageFramework
{
    /// <summary>
    /// THIS CLASS IS USED TO CREATE A CONNECTION TO THE SERVER
    /// </summary>
    public class Client
    {
        #region "Properties"
        private Connection mConnection;
        public bool Connected
        {
            get
            {
                return mConnection.Client.Connected;
            }
        }
        #endregion

        #region "Constructors"
        public Client()
        {
            //INITIALIZE THE CONNECTOR AND THE MESSAGE EVENT HANDLER
            mConnection = new Connection();
            mConnection.MessageReceived += new Connection.MessageReceivedDelegate(mConnection_MessageReceived);
            mConnection.Disconnected += new Connection.DisconnectedDelegate(mConnection_Disconnected);
        }
        #endregion

        #region "Event Handlers"
        void mConnection_MessageReceived(iMessage Message, Connection CurrentConnection)
        {
            //CALL THE CLIENT SIDE CODE FOR THE MESSAGE
            Message.ClientSide(CurrentConnection);
        }
        void mConnection_Disconnected(Connection CurrentConnection)
        {
            //HANDLES THE SERVER DISCONNECTING FROM THE CLIENT
            //TODO: ADD EVENT HANDLER TO PASS THIS BACK TO THE PARENT
        }
        #endregion

        #region "Methods"
        public bool Connect(string host, int port)
        {
            try
            {
                //CREATE A CONNECTION AND START THE MESSAGE LOOP SO WE WILL RECEIVE ANYTHING THAT COMES BACK IN THE NETWORK STREAM
                mConnection.Client.Connect(host, port);
                mConnection.StartIncomingMessageLoop();

                //RETURN IF THE CONNECT WAS SUCCESSFULL
                return mConnection.Client.Connected;
            }
            catch
            {
                //IF SOMETHING FAILED THEN WE DID NOT CONNECT
                return false;
            }
        }
        public void Send(iMessage msg)
        {
            //THIS CALLS THE ACTUAL SEND METHOD ON THE MESSAGE SINCE IT IS HIDDEN OUTSIDE OF THIS CLASS
            msg.Send(mConnection);
        }
        public void Disconnect()
        {
            //IF THE CLIENT BELIEVES ITS CONNECTED THEN CLOSE THE CONNECTION
            if (Connected)
                try
                {
                    mConnection.Client.Close();
                }
                catch
                {
                    //IF THERE IS NOT ACTUALLY A CONNECTION IT WILL THROW AN ERROR SO JUST CATCH IT HERE
                }
        }
        #endregion
    }
}

Connection.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Net.Sockets;
using MessageFramework.Messages;
using MessageFramework.Interfaces;
using MessageFramework.Extensions;

namespace MessageFramework
{
    /// <summary>
    /// THIS CLASS IS USED TO HOUSE INFORMATION REGARDING A CONNECTION
    /// </summary>
    public class Connection
    {
        #region "Properties"
        private TcpClient mClient;
        public TcpClient Client
        {
            get
            {
                return mClient;
            }
        }

        /// <summary>
        /// THIS STORES THE REMOTE HOST INFORMATION FOR AFTER A DISONNECT OCCURS YOU CAN SEE WHO DISCONNECTED
        /// </summary>
        private string mRemoteHost = null;
        public string RemoteHost
        {
            get
            {
                return mRemoteHost;
            }
        }
        #endregion

        #region "Private Properties"
        /// <summary>
        /// KEEPS TRACK SO THAT ONLY A SINGLE THREAD CAN WRITE TO THE STREAM AT A TIME
        /// </summary>
        private Mutex threadBlock = new Mutex();

        /// <summary>
        /// HOLDS THE NETWORK STREAM SO WE DO NOT HAVE TO GET IT OVER AND OVER AGAIN TO WRITE TO
        /// </summary>
        private NetworkStream NetStream
        {
            get
            {
                return Client.GetStream();
            }
        }
        #endregion

        #region "Constants"
        /// <summary>
        /// USED TO INCASE THE MESSAGE WITH A HEADER AND A FOOTER SO WE CAN PARSE THE STREAM IF IT GETS MORE THEN ONE MESSAGE
        /// </summary>
        private byte[] Header = System.Text.ASCIIEncoding.ASCII.GetBytes("<--");
        private byte[] Footer = System.Text.ASCIIEncoding.ASCII.GetBytes("-->");
        #endregion

        #region "Events"
        /// <summary>
        /// RAISED WHEN A MESSAGE COMES IN FROM THE NETWORK STREAM
        /// </summary>
        /// <param name="Message">INCOMING MESSAGE</param>
        /// <param name="CurrentConnection">CONNECTION FROM WHICH THE MESSAGE CAME</param>
        public delegate void MessageReceivedDelegate(iMessage Message, Connection CurrentConnection);
        public event MessageReceivedDelegate MessageReceived;

        /// <summary>
        /// RAISED WHEN A CLIENT DISCONNECTS
        /// </summary>
        /// <param name="CurrentConnection">CONNECTION WHICH WAS SEVERED</param>
        public delegate void DisconnectedDelegate(Connection CurrentConnection);
        public event DisconnectedDelegate Disconnected;
        #endregion

        #region "Constructors"
        /// <summary>
        /// BASE CONSTRUCTOR USED IF NO PARAMETERS ARE PASSED IN
        /// </summary>
        public Connection()
        {
            mClient = new TcpClient();
        }

        /// <summary>
        /// USED ON THE SERVERSIDE SINCE THE CONNECTION ALREADY EXISTS WHEN THIS CLASS IS CREATED
        /// </summary>
        /// <param name="client"></param>
        public Connection(TcpClient client)
        {
            mClient = client;
            mRemoteHost = client.Client.RemoteEndPoint.ToString();
        }
        #endregion

        #region "Methods"
        /// <summary>
        /// CREATES A MESSAGE LISTENING THREAD
        /// </summary>
        public void StartIncomingMessageLoop()
        {
            Thread msgThread = new Thread(msgLoop);
            msgThread.Start(this);

            mRemoteHost = Client.Client.RemoteEndPoint.ToString();
        }

        /// <summary>
        /// ACTUAL LOOP THAT WILL CONTINUE SO LONG AS THE CLIENT IS CONNECTED AND THERE IS DATA TO READ
        /// </summary>
        /// <param name="connection">INCOMING CONNECTION FROM WHICH TO START READING FROM</param>
        private void msgLoop(object connection)
        {
            Connection connectedClient = (Connection)connection;

            int readLen = -1;
            byte[] netBuffer = new byte[1024];
            List<byte> msgBuffer = new List<byte>();

            //ENTER A CONSTANT LOOP SO LONG AS THERE IS DATA READ OF THE CLIENT APPEARS TO BE CONNECTED
            do
            {
                try
                {
                    //READ DATA FROM THE NETWORK STREAM INTO A TEMP BUFFER
                    readLen = NetStream.Read(netBuffer, 0, netBuffer.Length);

                    //STORE WHAT WAS READ INTO THE ACTUAL MESSAGE BUFFER
                    msgBuffer.AddRange(netBuffer.Take(readLen));

                    //IF THERE IS DATA IN THE MESSAGE BUFFER THEN ATTEMPT TO SEE IF THERE IS A MESSAGE WAITING
                    if (msgBuffer.Count != 0)
                    {
                        int msgStart = -1;

                        //LOOP THROUGH AND LOOK FOR A FOTTER IN THE MESSAGE BUFFER
                        while (msgBuffer.Count != 0 && (msgStart = msgBuffer.IndexOf(Footer[0], msgStart + 1)) != -1)
                        {
                            bool footerFound = true;

                            //MAKE SURE THERE ARE ENOUGH BYTES TO READ AN ENTIRE FOOTER BEFORE CONTINUING
                            if (msgStart + Footer.Length <= msgBuffer.Count)
                            {
                                //READ FROM THE START TO LOOK AND SEE IF THE COMPLETE FOOTER APPEARS
                                for (int i = 0; i < Footer.Length; i++)
                                {
                                    //IF WE FIND A MISMATCH THEN BREAK AND STORE FINDING THE FOOTER AS FALSE
                                    if (msgBuffer[msgStart + i] != Footer[i])
                                    {
                                        footerFound = false;
                                        break;
                                    }
                                }

                                //IF THE FOOTER WAS FOUND THEN WE HAVE A COMPLETE MESSAGE WE NEED TO PARSE
                                if (footerFound)
                                {
                                    //READ THE MESSAGE OUT OF THE MESSAGE BUFFER
                                    byte[] message = msgBuffer.Skip(Header.Length).Take(msgStart - Header.Length).ToArray();

                                    try
                                    {
                                        //UNWRAP THE OUTTER MESSAGE
                                        Message_Wrapper tmpMessage = message.ToArray().DeSerialize<Message_Wrapper>();

                                        //UNWRAP THE INNER MESSAGE
                                        iMessage innerMessage = tmpMessage.Payload.DeSerialize<iMessage>(Type.GetType(tmpMessage.InternalType));

                                        //IF THERE IS A LISTENER FOR RECEIVING MESSAGES THEN RAISE THE EVENT
                                        if (MessageReceived != null)
                                            MessageReceived(innerMessage, connectedClient);

                                        //REMOVE THE MESSAGE AND THE FOOTER FROM THE MESSAGE BUFFER AND CONTINUE ON
                                        msgBuffer.RemoveRange(0, msgStart + Footer.Length);
                                    }
                                    catch
                                    {
                                        //IF THERE WAS AN ERROR BECAUSE THE MESSAGE WAS CORRUPT THEN SIMPLY CLEAR ALL THE BUFFERS AND CONTINUE READING
                                        while (NetStream.DataAvailable)
                                            NetStream.ReadByte();

                                        msgBuffer.Clear();
                                    }

                                    //IF WE FOUND A MESSAGE MOVE THE STARTING POINT BACK TO 0 SO WE START LOOKING FROM THE BEGINING AGAIN
                                    msgStart = 0;
                                }
                            }
                        }


                    }
                }
                catch (System.IO.IOException)
                {
                    //CATCHES THE IO EXCEPTION IN THE EVENT THE CONNECTION WAS CLOSED
                }
            } while (connectedClient.Client.Connected && readLen != 0);

            //IF THERE IS A DISCONNECT LISTENER THEN RAISE THE EVENT
            if (Disconnected != null)
                Disconnected(connectedClient);
        }

        /// <summary>
        /// WRITES TO A STREAM AND MAKES SURE THAT ONLY ONE WRITE OCCURS AT A TIME TO STOP MESSAGES FROM CORRUPTING
        /// </summary>
        /// <param name="msgData">DATA TO BE SENT THROUGH THE STREAM</param>
        public void SafeWrite(byte[] msgData)
        {
            //WAIT FOR THE THREAD LOCKING TO BE AVAILABLE
            threadBlock.WaitOne();

            //WRITE THE HEADER MESSAGE THEN THE FOOTER TO THE STREAM
            NetStream.Write(Header, 0, Header.Length);
            NetStream.Write(msgData, 0, msgData.Length);
            NetStream.Write(Footer, 0, Footer.Length);

            //RELEASE THE STREAM FROM BLOCKING
            threadBlock.ReleaseMutex();
        }
        #endregion

        #region "Operator Overrides"
        public static bool operator ==(Connection a, Connection b)
        {
            if (a.Client != b.Client || a.NetStream != b.NetStream)
                return false;
            else
                return true;
        }
        public static bool operator !=(Connection a, Connection b)
        {
            return !(a == b);
        }
        public override bool Equals(object obj)
        {
            return this == (Connection)obj;
        }
        public override int GetHashCode()
        {
            return Client.GetHashCode();
        }
        #endregion
    }
}

Server.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
using MessageFramework.Interfaces;
using MessageFramework.Extensions;

namespace MessageFramework
{
    /// <summary>
    /// THIS CLASS IS USED TO CREATE A SERVER LISTENING FOR CONNECTIONS
    /// </summary>
    public class Server
    {
        #region "Properties"
        private bool mListening = false;
        public bool Listening
        {
            get
            {
                return mListening;
            }
        }
        #endregion

        #region "Private Properties"
        private TcpListener mListener;
        private List<Connection> mConnections = new List<Connection>();
        #endregion

        #region "Event Handlers"
        private void tmpConnection_MessageReceived(iMessage Message, Connection CurrentConnection)
        {
            //RAISE THE SERVERSIDE CODE OF THE INCOMING MESSAGE
            Message.ServerSide(mConnections, CurrentConnection);
        }

        void tmpConnection_Disconnected(Connection CurrentConnection)
        {
            //REMOVE THE CONNECTION FROM THE LIST OF ACTIVE CONNECTIONS SO WE DO NOT TRY TO WRITE TO IT AGAIN
            mConnections.Remove(CurrentConnection);
        }
        #endregion

        #region "Methods"
        /// <summary>
        /// CONNECTION LOOP FOR LISTENING FOR INCOMING CONNECTIONS AND ACCEPTING THEM
        /// </summary>
        /// <param name="port">PORT TO START LISTENING ON</param>
        private void connectLoop(object port)
        {
            mListener = new TcpListener(System.Net.IPAddress.Any, (int)port);

            mListener.Start();

            //LOOP WHILE LISTENING AND WAITING FOR ACTIVE CONNECTIONS
            while (Listening)
            {
                //WHEN A CONNECTION ATTEMPT OCCURS CONNECT IT AND CREATE A CONNECTION CLASS TO STORE ALL THE INFORMATION
                Connection tmpConnection = new Connection(mListener.AcceptTcpClient());

                //SETUP THE EVENT HANDLERS FOR RECEIVING MESSAGES AND EVENTS
                tmpConnection.MessageReceived += new Connection.MessageReceivedDelegate(tmpConnection_MessageReceived);
                tmpConnection.Disconnected += new Connection.DisconnectedDelegate(tmpConnection_Disconnected);

                //START THE MESSAGE LOOP TO READ THE STREAM
                tmpConnection.StartIncomingMessageLoop();

                //ADD IT TO THE LIST OF CONNECTED CONNECTIONS
                mConnections.Add(tmpConnection);
            }
        }

        /// <summary>
        /// USED TO START THE LISTENING THREAD
        /// </summary>
        /// <param name="port">PORT IN WHICH TO LISTEN ON</param>
        public void Listen(int port)
        {
            Thread listener = new Thread(connectLoop);

            mListening = true;
            listener.Start((object)port);
        }

        /// <summary>
        /// USED TO STOP THE LISTENING THREAD FROM LISTENING ANYMORE
        /// </summary>
        public void StopListening()
        {
            mListening = false;
            mListener.Stop();
        }
        #endregion
    }
}

Server App


Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace TestServer
{
    class Program
    {
        static void Main(string[] args)
        {
            //CREATE A SERVER AND START LISTENING FOR CONNECTIONS
            MessageFramework.Server tmpConnector = new MessageFramework.Server();
            tmpConnector.Listen(8080);

            //JUST SIT THERE AND DO NOTHING UNTIL THE USER HITS ENTER
            Console.ReadLine();
        }
    }
}

Client App


frmMain.cs

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using MessageFramework;
using MessageFramework.Messages;

namespace TestApp
{
    public partial class frmMain : Form
    {
        //CREATE A CLIENT VARIAVLE
        Client mClient;

        public frmMain()
        {
            InitializeComponent();
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            //UPDATE THE GUI TO NOT ALLOW THE USER TO CONNECT AGAIN
            btnConnect.Enabled = false;
            btnDisconnect.Enabled = true;
            btnSend.Enabled = true;

            //CONNECT TO THE LOCAL HOST ON PORT 8080
            mClient = new Client();
            mClient.Connect("127.0.0.1", 8080);
        }

        private void btnDisconnect_Click(object sender, EventArgs e)
        {
            //UPDATE THE GUI TO ALLOW THE USER TO CONNECT AGAIN
            btnConnect.Enabled = true;
            btnDisconnect.Enabled = false;
            btnSend.Enabled = false;

            //CAUSE THE CLIENT TO DISCONNECT
            mClient.Disconnect();
        }

        private void btnSend_Click(object sender, EventArgs e)
        {
            //SEND 1 HELLO_WORLD MESSAGE
            mClient.Send(new Message_Hello_World());
        }
    }
}

Leave a Reply

You must be logged in to post a comment.

  • RSS
  • Twitter
  • Facebook
  • LinkedIn
  • DeviantArt