實現SuperSocket模板協議FixedHeaderReceiveFilter與msgpack結

實現SuperSocket模板協議FixedHeaderReceiveFilter與msgpack結合

在羣裏有個羣友在羣裏一直在問使用FixedHeaderReceiveFilter與msgpack結合的實現問題,搞了幾天都沒有搞定,因而就寫了這個文章來講明。但願能幫助到他。 SuperSocket的內置的經常使用協議實現模版文檔連接 msgpack的官網git

SuperSocket 簡稱爲SSc#


首先要定義一下協議數組

/// +-------+---+-------------------------------+
/// |request| l |                               |
/// | name  | e |    request body               |
/// |  (4)  | n |                               |
/// |       |(4)|                               |
/// +-------+---+-------------------------------+

如上說明,request name 佔4個字節,這個是用來尋找SS裏面的命令對象,根據是根據命令類名稱來查找的。 len是表示 request body序列化成byte後的長度。 request body 是表示咱們用msgpack序列化後的內容。緩存

*注意:*request name 和len佔用的字節是能夠本身定義的session

協議搞懂後,咱們就須要來編寫代碼了,須要編寫以下類:數據結構

  1. MsgPackReceiveFilter要繼承FixedHeaderReceiveFilter,做用是實現協議解析
  2. MsgPackReceiveFilterFactory要繼承IReceiveFilterFactory,做用是使得server能知道是用哪一個協議解析對象來作協議解析。
  3. MsgPackServer繼承AppServer,做用是加載協議解析工廠的。
  4. MsgPackSession做用參考一個Telnet示例
  5. MsgPackCommand做用參考一個Telnet示例,同時實現request body的反序列化
  6. 實現一個命令Test繼承MsgPackCommand
  7. MyData一個要傳輸的數據結構

下面來看下代碼的實現app

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.Common;
using SuperSocket.Facility.Protocol;
using SuperSocket.SocketBase.Protocol;

namespace FixedHeader
{
    /// +-------+---+-------------------------------+
    /// |request| l |                               |
    /// | name  | e |    request body               |
    /// |  (4)  | n |                               |
    /// |       |(4)|                               |
    /// +-------+---+-------------------------------+
    public class MsgPackReceiveFilter : FixedHeaderReceiveFilter<BinaryRequestInfo>
    {
        public MsgPackReceiveFilter() : base(8)
        {
        }

        protected override int GetBodyLengthFromHeader(byte[] header, int offset, int length)
        {
            var headerData = new byte[4];
            Array.Copy(header,offset+4,headerData,0,4);
            return BitConverter.ToInt32(headerData, 0);
        }

        protected override BinaryRequestInfo ResolveRequestInfo(ArraySegment<byte> header, byte[] bodyBuffer, int offset, int length)
        {
            return new BinaryRequestInfo(Encoding.UTF8.GetString(header.Array, header.Offset, 4), bodyBuffer.CloneRange(offset, length));
        }
    }
}

首先咱們看到構造函數base(8)裏面的輸入了一個8,這個8是協議頭的長度,也就是request name 加 len的長度。 而後再看實現了方法GetBodyLengthFromHeader,從名字上看,就能夠知道是根據協議頭的數據來獲取打包的數據的長度。 這個方法有三個參數socket

  1. *byte[] header * 緩存的數據,這個並非單純只包含協議頭的數據
  2. int offset 要取的數據的偏移量,也就是在header裏面從offset開始就是咱們從客戶的發送過來的數據。
  3. int length 就是我在base(8)這裏設置的長度也就是8.

在這裏咱們能夠取獲得協議頭的數據,就是在header從偏移量offset開始截取長度爲length的部分數組,就是咱們的協議頭了。可是咱們的協議頭是8位,要取打包數據的長度那麼就須要從偏移offset上再加4位,代碼就是ide

var headerData = new byte[4];
Array.Copy(header,offset+4,headerData,0,4);

而後再把取到的數據轉換成爲int類型也就是函數

BitConverter.ToInt32(headerData, 0);

ss就能夠根據這個長度來幫助咱們獲取到打包的數據。而後傳給方法ResolveRequestInfo。咱們須要實現這個方法。這個方法有四個參數:

  1. header 咱們的協議頭的數據
  2. bodyBuffer 緩存的數據,這個並非只單純包含打包數據的
  3. offset 打包數據在bodyBuffer裏面開始的位置
  4. int length 打包數據的長度

ResolveRequestInfo 返回的是 FixedHeaderReceiveFilter的一個泛型,這個對象是用於注入實現命令的。咱們這裏使用的是BinaryRequestInfo。

Encoding.UTF8.GetString(header.Array, header.Offset, 4)

這個代碼是把協議頭的前四位轉換成爲字符串,這字符串是用於查找要執行的命令的。

bodyBuffer.CloneRange(offset, length)

這個代碼是截取咱們須要的數據,這個數據也將是會傳給執行命令的。

到這裏咱們的MsgPackReceiveFilter協議解析已經完成了,而後再實現一個工廠來使得server可以加載到MsgPackReceiveFilter來解析咱們的協議


using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;

namespace FixedHeader
{
    public class MsgPackReceiveFilterFactory : IReceiveFilterFactory<BinaryRequestInfo>
    {
        public IReceiveFilter<BinaryRequestInfo> CreateFilter(IAppServer appServer, IAppSession appSession, IPEndPoint remoteEndPoint)
        {
            return new MsgPackReceiveFilter();
        }
    }
}

MsgPackReceiveFilterFactory的實現相對簡單,就是實現接口IReceiveFilterFactory的方法返回一個MsgPackReceiveFilter對象,這個就用多作解釋

而後要使得server可以加載到,還須要再實現一個server

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;

namespace FixedHeader
{
    public class MsgPackServer : AppServer<MsgPackSession, BinaryRequestInfo>
    {
        public MsgPackServer() : base(new MsgPackReceiveFilterFactory())
        {
            
        }
    }
}

只要實例化工廠MsgPackReceiveFilterFactory而後傳給構造函數就能夠了。

而後再實現一個MsgPackSession這個只要繼承AppSession就能夠了,不用作任何的實現。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Protocol;

namespace FixedHeader
{
    public class MsgPackSession : AppSession<MsgPackSession, BinaryRequestInfo>
    {
    }
}

而後再實現一個MsgPackCommand。這個主要是爲了把打包發送過來的數據統一反序列列化,這樣只要繼承MsgPackCommand的類,均可以直接獲得想要的對象。

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using MsgPack.Serialization;
using SuperSocket.SocketBase.Command;
using SuperSocket.SocketBase.Protocol;

namespace FixedHeader
{
    public abstract class MsgPackCommand<TMsgPack> : CommandBase<MsgPackSession, BinaryRequestInfo> where TMsgPack : class
    {
        public override void ExecuteCommand(MsgPackSession session, BinaryRequestInfo requestInfo)
        {
            var serializer = SerializationContext.Default.GetSerializer<TMsgPack>();
            using (var stream = new MemoryStream(requestInfo.Body))
            {
                var unpackedObject = serializer.Unpack(stream) as TMsgPack;
                ExecuteCommand(session, unpackedObject);
            }
            
        }

        public abstract void ExecuteCommand(MsgPackSession session, TMsgPack pack);
    }
}

最後再作一個測試的命令和一個數據結構,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SuperSocket.SocketBase.Command;
using SuperSocket.SocketBase.Protocol;

namespace FixedHeader
{
    public class Test : MsgPackCommand<MyData>
    {
        public override void ExecuteCommand(MsgPackSession session, MyData pack)
        {
            Console.WriteLine(pack.Name+":"+ pack.Other);
        }
    }
}
namespace FixedHeader
{
    public class MyData
    {
        public string Name { get; set; }

        public string Other { get; set; }
    }
}

到這裏就服務端就能夠了。接下來還須要實現一個客戶端來作簡單的測試,這個沒有什麼好說的,直接上代碼:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using MsgPack.Serialization;

namespace FixedHeaderClient
{
    class Program
    {
        static void Main(string[] args)
        {
            var socket = new Socket(AddressFamily.InterNetwork,SocketType.Stream, ProtocolType.Tcp);
            socket.Connect("127.0.0.1",2012);
            using (var stream = new MemoryStream())
            {
                var serializer = SerializationContext.Default.GetSerializer<MyData>();
                var myData = new MyData()
                {
                    Name = "Test",
                    Other = "abcd"
                };

                serializer.Pack(stream, myData);

                //var commandData = new byte[4];//協議命令只佔4位
                var commandData = Encoding.UTF8.GetBytes("Test");//協議命令只佔4位,若是佔的位數長過協議,那麼協議解析確定會出錯的

                var dataBody = stream.ToArray();

                var dataLen = BitConverter.GetBytes(dataBody.Length);//int類型佔4位,根據協議這裏也只能4位,不然會出錯

                var sendData = new byte[8+dataBody.Length];//命令加內容長度爲8

                // +-------+---+-------------------------------+
                // |request| l |                               |
                // | name  | e |    request body               |
                // |  (4)  | n |                               |
                // |       |(4)|                               |
                // +-------+---+-------------------------------+

                Array.ConstrainedCopy(commandData, 0, sendData, 0, 4);
                Array.ConstrainedCopy(dataLen, 0, sendData, 4,4);
                Array.ConstrainedCopy(dataBody, 0, sendData, 8, dataBody.Length);

                for (int i = 0; i < 1000; i++)
                {
                    socket.Send(sendData);
                }
                
            }

            Console.Read();
        }
    }
}

源碼託管

相關文章
相關標籤/搜索