使用SuperSocket實現自定義協議C/S設計


一、簡介:

  21世紀是出於互聯網+的時代,許多傳統行業和硬件掛鈎的產業也逐步轉向了系統集成智能化,簡單來說就是需要軟硬件的結合。這時,軟硬件通訊便是這里面最主要的技術點,我們需要做到的是讓硬件能夠聽懂我們系統的指令,自定義協議便應運而生。

二、設計思路:

  1)引入SuperSocket所需要的各種項目文件

  2)新建兩個WinForm添加具體功能

  3)啟動項同樣設置兩個

  4)啟動服務器監聽

  5) 客戶登陸

  6)服務器廣播

三、代碼實現

  1)引入項目文件,包含在項目中,這里包含是 右鍵解決方案>添加>現有項目

  2)新建兩個WinForm,因為WinForm是一個有Program類的類庫

    2.1)在項目中會有控件類型說明

    2.2)修改Text也就是顯示給客戶的名字

    2.3)修改 設計>Name 這個是為了在代碼中設計功能用的名稱

  3)現在我們去寫服務器的啟動功能

    服務器啟動需要一個AppServer在體,我們定義這個載體為SocketRequestInfo,讓他繼承AppServer,從而調用SuperSocket的內部方法實現功能。然而定義SocketRequestInfo構造時可以傳入參數來實現自定義,兩個參數分別是:自定義的消息格式,以及需要一個session連接。而他的構造函數只需要將 數據,操作傳給工廠(DefaultReceiveFilterFactory)來代做。

    3.1)MainForm : Form

/// <summary>
        /// 啟動、停止
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnStartStop_Click(object sender, EventArgs e)
        {
            if (btnStartStop.Text.Equals("啟動"))
            {

                SocketServer = new SuperSocketServer();
                SocketServer.Setup(new RootConfig(), config);
                SocketServer.NewRequestReceived += SocketServer_NewRequestReceived;
                SocketServer.NewSessionConnected += SocketServer_NewSessionConnected;
                SocketServer.SessionClosed += SocketServer_SessionClosed;

                SocketServer.Start();
                btnStartStop.Text = "停止";

            }
            else
            {
                SocketServer.Stop();
                SocketServer.Dispose();
                btnStartStop.Text = "啟動";
            }
            lblCurrentState.Text = SocketServer.State.ToString();

        }

  類SuperSocketServer : AppServer<SocketProtocolSession, SocketRequestInfo>

    public class SuperSocketServer : AppServer<SocketProtocolSession, SocketRequestInfo>
    {
        public SuperSocketServer()
            : base(new DefaultReceiveFilterFactory<SocketRequestFilter, SocketRequestInfo>())//使用默認的接受過濾器工廠 
        {

        }
    }

  需要類SocketProtocolSession

    public class SocketProtocolSession : AppSession<SocketProtocolSession, SocketRequestInfo>
    {
        /// <summary>
        /// 當前用戶名
        /// </summary>
        public string UserName { get; set; }

    }

  以及類SocketRequestInfo

/// <summary>
    /// 請求消息
    /// </summary>
    public class SocketRequestInfo : IRequestInfo
    {

        /// <summary>
        /// Key[繼承自接口]
        /// </summary>
        public string Key { get; set; }

        /// <summary>
        /// 命令標識
        /// </summary>
        public byte Flag { get; set; }

        /// <summary>
        /// 命令字
        /// </summary>
        public byte Command { get; set; }


        /// <summary>
        /// 消息長度
        /// </summary>
        public int Length { get; set; }


        /// <summary>
        /// 消息內容
        /// </summary>
        public byte[] Data { get; set; }



    }

  和類SocketRequestFilter : FixedHeaderReceiveFilter<SocketRequestInfo>

public class SocketRequestFilter : FixedHeaderReceiveFilter<SocketRequestInfo>
    {
        /// <summary>
        /// 消息頭長度
        /// </summary>
        protected const int headerSize = 4;

        /// <summary>
        /// 構造函數
        /// </summary>
        public SocketRequestFilter()
            : base(headerSize)
        {

        }

        /// <summary>
        /// 獲取消息頭
        /// </summary>
        /// <param name="header">頭部</param>
        /// <param name="offset">偏移量</param>
        /// <param name="length">消息頭長度</param>
        /// <returns></returns>
        protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
        {
            return ((int)header[offset + 2] + (int)header[offset + 3] * 256);
        }

        /// <summary>
        /// 解析消息
        /// </summary>
        /// <param name="header">消息頭</param>
        /// <param name="bodyBuffer">消息體</param>
        /// <param name="offset">偏移量</param>
        /// <param name="length">消息體長度</param>
        /// <returns></returns>
        protected override SocketRequestInfo ResolveRequestInfo(ArraySegment<byte> header, byte[] bodyBuffer, int offset, int length)
        {
            SocketRequestInfo request = new SocketRequestInfo();
            request.Flag = bodyBuffer[offset - headerSize + 0];
            request.Key = bodyBuffer.CloneRange(offset - headerSize, 1).ToHexString();
            request.Command = bodyBuffer[offset - headerSize + 1];
            request.Length = ((int)bodyBuffer[offset - headerSize + 2] + 256 * (int)bodyBuffer[offset - headerSize + 3]);
            request.Data = bodyBuffer.CloneRange(offset, length);
            return request;
        }
    }

    來實現這個實例的構造。

    3.2)填充配置信息,來啟動服務

 //初始化服務配置
        IServerConfig config = new ServerConfig
        {
            Name = "SuperSocketServer",
            Ip = "Any",
            Port = 3000,
            MaxConnectionNumber = 1024,
            MaxRequestLength = 4096,
            ClearIdleSession = true,
            ClearIdleSessionInterval = 120,
            IdleSessionTimeOut = 120,
            KeepAliveInterval = 60
        };

  

    3.3)定義服務器的連接功功能

/// <summary>
        /// 新建連接
        /// </summary>
        /// <param name="session"></param>
        private void SocketServer_NewSessionConnected(SocketProtocolSession session)
        {
            txtMessage.Text += string.Format("{0}新建連接{1}", session.RemoteEndPoint.Address.ToString(), Environment.NewLine);
        }

  

    3.4)定義服務器的關閉功能

/// <summary>
        /// 斷開連接
        /// </summary>
        /// <param name="session"></param>
        /// <param name="value"></param>
        private void SocketServer_SessionClosed(SocketProtocolSession session, SuperSocket.SocketBase.CloseReason value)
        {
            txtMessage.Text += string.Format("{0}斷開連接{1}", session.RemoteEndPoint.Address.ToString(), Environment.NewLine);
        }

  

    3.5)定義服務器的接收信息功能

 /// <summary>
        /// 收到數據
        /// </summary>
        /// <param name="session"></param>
        /// <param name="requestInfo"></param>
        private void SocketServer_NewRequestReceived(SocketProtocolSession session, SocketRequestInfo requestInfo)
        {

            if (requestInfo.Flag == 0xAC)//0xAC類消息
            {
                if (requestInfo.Command == 0x01)//用戶登錄
                {
                    string userName = Encoding.UTF8.GetString(requestInfo.Data);
                    session.UserName = userName;
                    txtMessage.Text += string.Format("{0}登錄{1}", userName, Environment.NewLine);
                    session.Send(ack, 0, ack.Length);//回復
                }
                if (!string.IsNullOrEmpty(session.UserName))//登錄后才可以收消息
                {
                    if (requestInfo.Command == 0x02)//普通消息
                    {
                        string message = Encoding.UTF8.GetString(requestInfo.Data);
                        txtMessage.Text += string.Format("{0}消息:{1} {2}", session.UserName, message, Environment.NewLine);
                        session.Send(ack, 0, ack.Length);//回復
                    }
                    else if (requestInfo.Command == 0x03)
                    {

                    }
                }
            }
        }

  

    3.6)啟動服務

  4)客戶端連接

    4.1)同樣的想要玩轉客戶端也需要一個這樣的實例,而這個實例比較簡單,只需要繼承類 EasyClient就好了。

    4.2)在初始化這個實例的時候需要做的連接,關閉,錯誤返回,接收信息的操作如下代碼

private void client_Error(object sender, ErrorEventArgs e)
        {
            txtMessage.Text += string.Format("連接錯誤{0}", Environment.NewLine);
        }

        private void client_Closed(object sender, EventArgs e)
        {
            txtMessage.Text += string.Format("連接斷開{0}", Environment.NewLine);
            btnConnect.Text = "連接";
        }


        /// <summary>
        /// 發送注冊包
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void client_Connected(object sender, EventArgs e)
        {
            txtMessage.Text += string.Format("連接成功{0}", Environment.NewLine);
            btnConnect.Text = "斷開";
        }

        /// <summary>
        /// 收到消息
        /// </summary>
        private void OnMessageReceive(PackageInfo package)
        {
            if (package.Flag == 0xCA)
            {
                if (package.Command == 0xFE)//服務端默認應答
                {
                    txtMessage.Text += string.Format("服務端消息接受成功{0}", Environment.NewLine);
                }
                else if (package.Command == 0xFA)//服務器時間
                {
                    long ticks = BitConverter.ToInt64(package.Data, 0);
                    DateTime datetime = new DateTime(ticks);
                    txtMessage.Text += string.Format("服務器時間:{0:yyyy-MM-dd HH:mm:ss}{1}", datetime, Environment.NewLine);
                }
            }
        }

  以上部分實現了客戶端數據向Server發送時的不帶數據服務器接收,注入服務器所需要的業務邏輯,然后服務端發送數據給客戶端則是以lambda表達式來接收。

  5)客戶端登陸:

    5.1)登陸需要做的只是實例一個上邊的繼承了EasyClient的客戶端實例

    5.2)使用該實例send到客戶端,send的數據為字符數組

    5.3)哭護短接收信息以有可以直接對數據進行操作

        private void btnLogin_Click_1(object sender, EventArgs e)
        {
            if (client.IsConnected)
            {
                List<byte> buffer = new List<byte>();
                byte[] data = Encoding.UTF8.GetBytes(txtUserName.Text);

                buffer.Add(0xAC);
                buffer.Add(0x01);
                buffer.AddRange(BitConverter.GetBytes(data.Length).Take(2));
                buffer.AddRange(data);

                //數組小抽屜,也就是把數組放在抽屜里
                ArraySegment<byte> segment = new ArraySegment<byte>(buffer.ToArray());
                client.Send(segment);
            }
            else
            {
                MessageBox.Show("請先連接服務器", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

  

  6)客戶端發送消息

    發送消息如上登陸

/// <summary>
        /// 發送消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        
        private void btnSend_Click_1(object sender, EventArgs e)
        {
            if (client.IsConnected)
            {
                List<byte> buffer = new List<byte>();
                byte[] data = Encoding.UTF8.GetBytes(txtMsgContent.Text);

                buffer.Add(0xAC);
                buffer.Add(0x02);
                buffer.AddRange(BitConverter.GetBytes(data.Length).Take(2));
                buffer.AddRange(data);

                ArraySegment<byte> segment = new ArraySegment<byte>(buffer.ToArray());
                client.Send(segment);
            }
            else
            {
                MessageBox.Show("請先連接服務器", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

  

  7)服務端廣播信息

    廣播消息如上登陸一樣發送數組

/// <summary>
        /// 廣播[將當前系統時間發送到所有在線客戶端]
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnBroadcast_Click(object sender, EventArgs e)
        {
            var sessions = SocketServer.GetAllSessions();

            foreach (SocketProtocolSession session in sessions)
            {
                byte[] data = BitConverter.GetBytes(DateTime.Now.Ticks);

                List<byte> buffer = new List<byte>();
                buffer.Add(0xCA);
                buffer.Add(0xFA);
                buffer.AddRange(BitConverter.GetBytes(data.Length).Take(2));
                buffer.AddRange(data);
                session.Send(buffer.ToArray(), 0, buffer.Count);
            }
        }

  

  

 

三、總結

  1)使用superSocket就需要一個YourAppServer實例,這個實例必須繼承字人家SuperSocket的AppServer才能調用。想要繼承這個App Server就得能拿得到會話連接和會話數據並對數據進行一系列的操作。

   2)在客戶端和服務端中都有連接的代碼塊,在服務端這么做側重業務的實現,而客戶端只需要一個諒解功能就可以了。

   3)在發送時上訴實例使用了兩種方法,一種直接發送,第二種使用數組抽屜盒子,后者更優化是第二種可以解決一個問題就是出現斷網時候可以自己緩存消息,接通以后繼續傳輸而前者不行。

  5)增加項目時候要注意引入以后需要編譯

   4)源碼分享:http://pan.baidu.com/s/1kVqWdNp

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM