TJ/T808 終端通訊協議設計與實現(碼農本色)

smark發表於2016-12-21

        由於公司專案涉及到相關技術,對於平常寫WEB的技術人員來說對這人來說比較默生;為了讓下面的技術人員更好地對這個協議的實施,所以單獨針對這個協議進行了分析和設計,以更於後期更好指導相關開發工作。由於自己對網路這一塊比較熟悉,之前也有過想法實現TJ/T808相關協議,只是一直沒這個動力去做;恰好碰到這次機會順更也動動手寫下程式碼。

TJ/T808協議瞭解

        其實看了一下這個協議,在設計上感覺有些不太合理,不過已經是國標的東西也沒有什麼可異議的;總體來說這個協議還是比較簡單,以下是這個協議的基礎部分:

為了方便所以截個圖就算了,圖上已經描述的協議的組成部門和一些主要細節;後面的基本就是一些具體訊息體的技術,有需要的朋友可以看協議的詳細文件。

設計

        協議整體分為兩大部分,訊息頭和訊息體;在訊息中還有一個相對處理工作比較的多資訊,就是訊息屬性。所以在設計上主要分為以下幾大部分:協議封裝和解釋,訊息結構定義,訊息體結構定義和訊息體屬性結構定義;部體結構設計如下:

為了達到更好的通用性,在設計上通過協議封裝和解釋介面和最終網路通訊環節隔離;這樣在整合和開發上都具備比較高的靈活性。

IProtocolBuffer協議

        首先我們需要一個規範來定義網路資料封裝和解釋,並且可以和網路處理層進行一個良好的隔離;這個協議介面的主要功能包括:組包,拆包,相關基礎型別的讀取和寫入。       

    public interface IProtocolBuffer
    {
        void Write(byte[] data);
        void Write(byte data);
        byte Read();
        byte[] Read(int length);
        bool Import(byte value);
        int Import(byte[] data, int offset, int count);
        void ReadSubBuffer(IProtocolBuffer buffer, int count);
        void WriteSubBuffer(IProtocolBuffer buffer);
        void Reset();
        int Length { get; }
        void SetLength(int length);
        int Postion { get; set; }
        byte[] Array { get; }
        void Write(ushort value);
        void Write(uint value);
        void WriteBCD(string value);
        ushort ReadUInt16();
        uint ReadUInt();
        string ReadBCD(int length);
    }

        在實現上需要注意一些細節,由於協議規定是大端處理,而C#是小端的,所以在處理一些資料上需要進行一些反轉處理,以下是針對shot,int,long等基礎型別處理程式碼: 

        public static short SwapInt16(short v)
        {
            return (short)(((v & 0xff) << 8) | ((v >> 8) & 0xff));
        }
        public static ushort SwapUInt16(ushort v)
        {
            return (ushort)(((v & 0xff) << 8) | ((v >> 8) & 0xff));
        }
        public static int SwapInt32(int v)
        {
            return (int)(((SwapInt16((short)v) & 0xffff) << 0x10) |
                          (SwapInt16((short)(v >> 0x10)) & 0xffff));
        }
        public static uint SwapUInt32(uint v)
        {
            return (uint)(((SwapUInt16((ushort)v) & 0xffff) << 0x10) |
                           (SwapUInt16((ushort)(v >> 0x10)) & 0xffff));
        }
        public static long SwapInt64(long v)
        {
            return (long)(((SwapInt32((int)v) & 0xffffffffL) << 0x20) |
                           (SwapInt32((int)(v >> 0x20)) & 0xffffffffL));
        }
        public static ulong SwapUInt64(ulong v)
        {
            return (ulong)(((SwapUInt32((uint)v) & 0xffffffffL) << 0x20) |
                            (SwapUInt32((uint)(v >> 0x20)) & 0xffffffffL));
        }

        在這個協議上還有一個需要注意的地方,由於協議採用單位元組作為開始和結束標識,對於相關字元需要進行一個轉議處理;以下是主要部分的程式碼封裝:        

        private ProtocolBuffer OnWrite(byte value)
        {
            mArray[mPostion] = value;
            mPostion++;
            mLength++;
            return this;
        }

        public bool Import(byte value)
        {
            if (value == PROTOBUF_TAG)
            {
                OnWrite(value);
                if (!mProtocolStart)
                {
                    mProtocolStart = true;
                }
                else
                {
                    mPostion = 0;
                    return true;
                }
            }
            else
            {
                if (mProtocolStart)
                {
                    OnWrite(value);
                }
            }
            return false;
        }

        public int Import(byte[] data, int offset, int count)
        {
            int result = 0;
            for (int i = offset; i < count; i++)
            {
                result++;
                byte value = data[i];
                if (Import(value))
                    return result;
            }
            return -1;
        }

        public byte Read()
        {
            byte result = mArray[mPostion];
            mPostion++;
            return result;
        }

        public byte[] Read(int length)
        {
            byte[] result = new byte[length];
            for (int i = 0; i < length; i++)
            {
                byte value = Read();
                if (value == REPLACE_TAG)
                {
                    value = Read();
                    if (value == 0x01)
                    {
                        result[i] = REPLACE_TAG;
                    }
                    else if (value == 0x02)
                    {
                        result[i] = PROTOBUF_TAG;
                    }
                    else
                    {
                        //result[i] = value;
                    }
                }
                else
                {
                    result[i] = value;
                }
            }
            return result;
        }

        public void Write(byte data)
        {
            if (data == PROTOBUF_TAG)
            {
                OnWrite(REPLACE_TAG).OnWrite(0x02);
            }
            else if (data == REPLACE_TAG)
            {
                OnWrite(REPLACE_TAG).OnWrite(0x01);
            }
            else
            {
                OnWrite(data);
            }
        }

訊息結構定義

        一看到需求進行程式碼編寫的實現程式碼的習慣並不好,最好在設計的時候通過介面結構來描述具體編寫程式碼總體框架的可行性,這樣可以在設計階段能更好的把控存在問題。根據協議的要求訊息的結構定義出介面,交根據實際規劃細化介面的組成部分:

    public interface IMessage
    {
        ushort ID { get; set; }
        MessageBodyAttributes Property { get; set; }
        string SIM { get; set; }
        ushort BussinessNO { get; set; }
        PacketInfo Packet { get; set; }
        void Save(IProtocolBuffer buffer);
        void Load(IProtocolBuffer buffer);
        IMessageBody Body { get; set; }
        byte CRC { get; set; }
    }

        由於有兩大部分相對比較複雜所以針對訊息的訊息體屬性和訊息體單獨抽象出來,這樣主要降低在協議封裝和解釋過程在主訊息介面處理的複雜度。

         介面制定了Save和Load方法用一描述訊息包的封裝和解釋,通過這個規範設計訊息的封裝和解釋完全和具體的資料來源隔離;根據具體訊息封裝和解釋的具體實現如下:        

        public void Load(IProtocolBuffer buffer)
        {
            byte crc = 0;
            for (int i = 1; i < buffer.Length - 1; i++)
                crc ^= buffer.Array[i];
            //read start
            buffer.Read();
            //read id
            ID = buffer.ReadUInt16();
            //read property
            Property.Load(buffer);
            //read sim
            SIM = buffer.ReadBCD(6);
            //read no
            BussinessNO = buffer.ReadUInt16();
            //read packet
            if (Property.IsPacket)
            {
                Packet = new PacketInfo();
                Packet.Load(buffer);
            }
            //read body
            if (Property.BodyLength > 0)
            {
                ProtocolBuffer bodybuffer = new ProtocolBuffer();
                IMessageBody body = MessageBodyFactory.Default.GetBody(ID);
                if (body != null)
                    body.Load(bodybuffer);
            }
            //read crc
            this.CRC = buffer.Read();
            if (this.CRC != crc)
                throw new Exception("message check CRC error!");
            //read end
            buffer.Read();
        }

        public void Save(IProtocolBuffer buffer)
        {
            ProtocolBuffer bodybuffer = null;
            if (Packet != null)
                Property.IsPacket = true;
            if (Body != null)
            {
                bodybuffer = new ProtocolBuffer();
                Body.Save(bodybuffer);
                if (bodybuffer.Length > MessageBodyAttributes.BODY_LENGTH)
                    throw new Exception("message body to long!");
                Property.BodyLength = (ushort)bodybuffer.Length;
            }
            //write start
            buffer.Write(ProtocolBuffer.PROTOBUF_TAG);
            //write id
            buffer.Write(ID);
            //write body property
            Property.Save(buffer);
            //write sim
            buffer.WriteBCD(SIM);
            //write no
            buffer.Write(BussinessNO);
            //write packet
            if (Packet != null)
                Packet.Save(buffer);
            //write body
            if (bodybuffer != null)
                buffer.WriteSubBuffer(bodybuffer);
            //write crc
            byte crc = 0;
            for (int i = 1; i < buffer.Length; i++)
                crc ^= buffer.Array[i];
            buffer.Write(crc);
            //write end
            buffer.Write(ProtocolBuffer.PROTOBUF_TAG);
        }

 訊息體屬性描述

          由於訊息體屬性描述是通過解位來處理,所以對於WEB開發的技術人員來這些基礎知識相對來說還是比較薄弱了一點。其實大體上就是通過移位,&,|的一些操作來獲取相關位的資訊,如果對於二進位制真的不熟悉其實可以用系統帶的計算器開啟程式設計師模式就可以了(這方面的知識對於程式設計師來說還是有必要補充一下)。        

        //保留位15
        public bool CustomHigh { get; set; }
        //保留位14
        public bool CustomLow { get; set; }
        //分包位13
        public bool IsPacket { get; set; }
        //加密位12
        public bool EncryptHigh { get; set; }
        //加密位11
        public bool EncryptMiddle { get; set; }
        //加密位10
        public bool EncryptLow { get; set; }
        //訊息長度9-0
        public ushort BodyLength { get; set; }
        public void Save(IProtocolBuffer buffer)
        {
            ushort value = (ushort)(BodyLength & BODY_LENGTH);
            if (CustomHigh)
                value |= CUSTOM_HEIGHT;
            if (CustomLow)
                value |= CUSTOM_LOW;
            if (IsPacket)
                value |= IS_PACKET;
            if (EncryptHigh)
                value |= ENCRYPT_HEIGHT;
            if (EncryptMiddle)
                value |= ENCRYPT_MIDDLE;
            if (EncryptLow)
                value |= ENCRYPT_LOW;
            buffer.Write(value);
        }
        public void Load(IProtocolBuffer buffer)
        {
            ushort value = buffer.ReadUInt16();
            CustomHigh = (CUSTOM_HEIGHT & value) > 0;
            CustomLow = (CUSTOM_LOW & value) > 0;
            IsPacket = (IS_PACKET & value) > 0;
            EncryptHigh = (ENCRYPT_HEIGHT & value) > 0;
            EncryptMiddle = (ENCRYPT_MIDDLE & value) > 0;
            EncryptLow = (ENCRYPT_LOW & value) > 0;
            BodyLength = (ushort)(BODY_LENGTH & value);
        }

訊息體描述

        在訊息設計上通過介面和具體網路處理隔離,在訊息體設計也應該採用同樣的原則;這樣訊息體的實現和擴充套件就不會對上層訊息程式碼有任何的影響。       

    public interface IMessageBody
    {
        void Save(IProtocolBuffer buffer);
        void Load(IProtocolBuffer buffer);
    }

        只需要很簡單的程式碼即能完成這個工作,所以我們在設計不要為了一些的方便而不去制定抽象行為;其實在抽象的過程就是一個很好的設計方式。有這個規範那在實現基礎訊息就會方便多了,也不用提心對上層的影響;以下是一個終端設通用響應的實現        

    class ClientResponse : IMessageBody
    {
        public ushort BussinessNO { get; set; }

        public ushort ResultID { get; set; }

        public ResultType Result { get; set; }

        public void Load(IProtocolBuffer buffer)
        {
            BussinessNO = buffer.ReadUInt16();
            ResultID = buffer.ReadUInt16();
            Result = (ResultType)buffer.Read();
        }

        public void Save(IProtocolBuffer buffer)
        {
            buffer.Write(BussinessNO);
            buffer.Write(ResultID);
            buffer.Write((byte)Result);
        }
    }

總結

        以上是針對TJ/T808協議實現的一種方式緊供參考!其實在設計上我們還是有些基礎準則可以遵守的,在設計根據職責劃分抽像規則,把複雜的結構拆成簡單獨立的個體進行組合應用;介面的抽像定義也是非常重要,其實很多時候溝通過時發現有很多程式設計師對介面的定性是除了多寫程式碼沒有什麼作用!其實介面是一個邏輯規劃的抽像,通過抽像可以上你在設計階段的時候更深入的瞭解功能切割和模組快,通過介面可以更快速有效的稽核自己設計的合理性。

相關文章