基于C#的通信协议封包(附代码)

接上一篇《基于.NET技术的监控系统应用分析》中所描述的数据通信协议设计,我们来看一下在C#中是怎么对自定义协议进行封包的?我们知道基于流的数据协议的特点:发送和接收到的数据都是连续的流。每次网络I/O操作的流长度不确定,也就是无法知道每次接收到的数据是一个完整的数据包。同样,主机发送一个数据包也会根据网络的实际情况执行若干次。所以我们对这类消息的编解码过程需要进行一个统一的封装。

重新回顾一下每个消息的结构:消息头 + 消息体。每次先发送出去的是消息头,然后是消息体。消息头里描述了这个数据包的类型,长度,序列号等信息。消息头的长度是固定的,消息体的长度是根据每个消息类型会有所的区别。

 

消息头的定义:

字段

长度(字节)

类型

说明

Length

4

Int

消息的总长度(字节)

Command ID

4

Int

命令ID

NodeID

4

Int

结点ID

TimeID

4

Int

时间戳

SequenceID

4

Int

递增序列号

 

对应的封装代码:


  1 using System;
  2 using MonitorLib.Utility;
  3  
  4 namespace MonitorLib.Protocol
  5 {
  6     /// <summary>
  7     /// 消息头
  8     /// </summary>
  9     [Serializable]
 10     public class Head  
 11     {  
 12         private byte[] initValue = new byte[Head.HeaderLength];
 13  
 14         public Head(Command CommandID) 
 15         { 
 16             Converter.IntToBytes((uint)CommandID).CopyTo(initValue, 4);
 17         }
 18  
 19         public Head(byte[] bs) 
 20         {
 21             uint length = Head.HeaderLength ;
 22
 23             for (int i = 0;i < length;i++)
 24             {
 25                 initValue[i]=bs[i];
 26             } 
 27         } 
 28
 29
 30         public Head(byte[] bs,int baseIndex) 
 31         {
 32             uint length = Head.HeaderLength ;
 33
 34             for (int i = 0; i < length; i++)
 35             {
 36                 initValue[i]=bs[baseIndex+i];
 37             } 
 38         } 
 39  
 40         /// <summary>
 41         /// 消息的整个长度
 42         /// </summary>
 43         public uint Length  
 44         {
 45             get
 46             {
 47                 return (Converter.BytesToUInt(initValue,0)); 
 48             }
 49             set
 50             {
 51                 byte[] byt = Converter.IntToBytes(value);
 52                 for (int i = 0;i < 4;i++)
 53                 {
 54                     initValue[i]= byt[i];
 55                 }
 56             }
 57         }
 58  
 59         /// <summary>
 60         /// 命令类型
 61         /// </summary>
 62         public uint CommandID
 63         {
 64             get
 65             {
 66                 return (Converter.BytesToUInt(initValue, 4));
 67             }
 68             set
 69             {
 70                 byte[] t=Converter.IntToBytes(value);
 71                 for (int i = 0; i < 4; i++)
 72                 {
 73                     initValue[i + 4] = t[i];
 74                 }
 75             }
 76         }
 77  
 78         /// <summary>
 79         /// 源结点号
 80         /// </summary>
 81         public uint NodeID
 82         {
 83             get
 84             {     
 85                 return (Converter.BytesToUInt(initValue, 8));
 86             }
 87             set
 88             {
 89                 byte[] t = Converter.IntToBytes(value);
 90                 for (int i = 0; i < 4; i++)
 91                 {
 92                     initValue[i + 8] = t[i];
 93                 }
 94             }
 95         }
 96
 97         /// <summary>
 98         /// 时间戳
 99         /// </summary>
100         public uint TimeID
101         {
102             get
103             {     
104                 return (Converter.BytesToUInt(initValue,12));
105             }
106             set
107             {
108                 byte[] t = Converter.IntToBytes(value);
109                 for (int i = 0; i < 4; i++)
110                 {
111                     initValue[i + 12] = t[i];
112                 }
113             }
114         }
115
116         /// <summary>
117         /// 序列号
118         /// </summary>
119         public uint SequenceID
120         {
121             get
122             {     
123                 return (Converter.BytesToUInt(initValue,16));
124             }
125             set
126             {
127                 byte[] t = Converter.IntToBytes(value);
128                 for (int i = 0;i < 4;i++)
129                 {
130                     initValue[i + 16] = t[i];
131                 }
132             }
133         }
134      
135  
136         /// <summary>
137         /// 输出字节流
138         /// </summary>
139         /// <returns></returns>
140         public byte[] ToBytes()
141         {
142             return initValue;
143         }
144  
145         /// <summary>
146         /// 从字节流中转换
147         /// </summary>
148         /// <param name="bs"></param>
149         public void FromBytes(byte[] bs)
150         {
151             for (int i = 0; i < Head.HeaderLength; i++)
152             {
153                 initValue[i] = bs[i];
154             }     
155         }
156  
157         /// <summary>
158         /// 消息头的长度
159         /// </summary>
160         public static uint HeaderLength
161         {
162             get
163             {
164                 return (4 + 4 + 12);
165             }
166         }
167     }
168 }
169

 


using System;
using MonitorLib.Utility;

namespace MonitorLib.Protocol
{
    /// <summary>
    /// Sequence 的摘要说明。
    /// </summary>
    [Serializable]
    public class Sequence
    {
        private uint node;
        private uint time;
        private uint sequence ;
        public Sequence()
        {
            
        }

        public uint Node
        {
            get{ return this.node; }
            set{ this.node = value ;}
        }

        public uint Time
        {
            get{ return this.time ; }
            set{ this.time = value; }

        }

        public uint Value
        {
            get{ return sequence;}
            set{this.sequence = value;}
        }

        public ulong ToUInt64()
        {
            string temp = String.Format("{0}{1}{2}",Node, Time, Value);
            return Convert.ToUInt64(temp);
        }
    }

    public class Seed
    {
        private uint sequence = uint.MinValue;

        public uint GetSequence()
        {
            lock (this)
            {
                return this.sequence >= 90000 ? uint.MinValue : this.sequence++;
            }
        }

        public uint GetTimeStamp()
        {
            lock (this)
            {
                return Convert.ToUInt32( DateTime.Now.ToString("MMddHHmmss") );
            }
        }

    }
}

 

上面只是一个消息头,要成为一个完整的消息,一般还必须包含消息体(当然你也可以根据需要仅发送一个消息头的数据,作为特殊用途,例如自定义的心跳包)。举个例子:客户机与服务器连接上后,它通常会发送一个绑定(Bind) 消息给服务器端。例如:验证确认客户端的合法性。那么此时的Bind消息的格式是:

 

字段

长度(字节)

类型

说明

HEAD

 

 

上面的消息头部

loginName

16

string

用户名(固定16位,不足用空格填充)

LoginPassword

16

string

密码(固定16位,不足用空格填充)

 

对应的封装代码:


AbstractBase

 

Command&Utility

 


using System;
using System.Text;
using MonitorLib.Utility;

namespace MonitorLib.Protocol
{
    /// <summary>
    /// Bind消息
    /// </summary>
    [Serializable]
    public class Bind : AbstractBase
    {
        private string loginName;
        private string loginPassword;

        /// <summary>
        /// 初始Bind命令的消息头
        /// </summary>
        /// <param name="Sequence">序列号</param>
        public Bind(Sequence seq)
        {
            header = new Head(Command.MOT_BIND);
            header.NodeID = seq.Node;
            header.TimeID = seq.Time ;
            header.SequenceID = seq.Value ;
            header.Length = Head.HeaderLength + 16 + 16;
        }
         
        public Bind(byte[] receive)
        {
            initValue = new byte[receive.Length];
            receive.CopyTo(initValue,0); 
        }

        /// <summary>
        /// 登录名
        /// </summary>
        public string LoginName 
        {
            get
            {
                return Encoding.ASCII.GetString(initValue,20,16);
            }
            set
            {
                loginName = value;
            }
        }

        /// <summary>
        /// 密码
        /// </summary>
        public string LoginPassword
        {
            get
            {
                return Encoding.ASCII.GetString(initValue,36,16);
            }
            set
            {
                loginPassword = value;
            }
        }

        /// <summary>
        /// 把消息结构转换成字节数组
        /// </summary>
        /// <returns>结果字节数组</returns>
        public override byte[] ToBytes()
        {
            byte[] retValue = new byte[this.header.Length];
            uint index = 0;

            //填充消息头
            header.ToBytes().CopyTo(retValue,index);

            index += Head.HeaderLength;
            Encoding.ASCII.GetBytes(loginName).CopyTo(retValue,index);

            //移位16位, 填充密码
            index += 16;
            Encoding.ASCII.GetBytes(loginPassword).CopyTo(retValue,index);
            return retValue;
        }
    }


    /// <summary>
    /// Bind应答结构
    /// </summary>
    [Serializable]
    public class Bind_Resp : AbstractBase
    {
        private uint result;

        /// <summary>
        /// 构造函数,把接收的字节数组复制到initValue
        /// </summary>
        /// <param name="recBytes">从网络上接收到的字节数组</param>
        public Bind_Resp(byte[] receive)
        {
            initValue = new byte[receive.Length];
            receive.CopyTo(initValue,0); 
        }

        public Bind_Resp(Sequence seq)
        {
            header = new Head(Command.MOT_BIND_RESP);
            header.NodeID = seq.Node;
            header.TimeID = seq.Time ;
            header.SequenceID = seq.Value ;
            header.Length = Head.HeaderLength + 4;
        }

        /// <summary>
        /// bind 执行命令是否成功,0-成功。其它:错误码。
        /// </summary>
        public uint Result
        {
            get
            {
                return Convert.ToUInt32(initValue[20].ToString());
            }
            set
            {
                result = value;
            }
        }

        public override byte[] ToBytes()
        {
            byte[] retValue =  new byte[header.Length];
            header.ToBytes().CopyTo(retValue,0);
            BitConverter.GetBytes(result).CopyTo(retValue,20);
            return retValue;
        }
    }

}

除了这种协议封装方法外,还有一种直接利用 .NET 的字节流操作类来编解码,例如 ICMP 协议的封包代码:

 


 1     public class ICMPHDR 
 2     {  
 3         private byte mType; 
 4         public byte Type 
 5         {  
 6             get{ return mType; } 
 7             set{ mType = value; } 
 8         }
 9
10         private byte mCode = 0; 
11         public byte Code 
12         {  
13             get{ return mCode; } 
14             set{ mCode = value; } 
15         }
16
17         private ushort mChecksum = 0; 
18         public ushort Checksum 
19         {  
20             get{ return mChecksum; } 
21             set{ mChecksum = value; } 
22         }
23
24         private ushort mID; 
25         public ushort ID 
26         {  
27             get{ return mID; } 
28             set{ mID = value; } 
29         }
30         
31         private ushort mSeq;
32         public ushort Seq 
33         {  
34             get{ return mSeq; } 
35             set{ mSeq = value; } 
36         }
37  
38         private ulong mtmSend;
39         public ulong tmSend 
40         {  
41             get{ return mtmSend; } 
42             set{ mtmSend = value; } 
43         } 
44
45         private int mnTaskId; 
46         public int nTaskId 
47         {  
48             get{ return mnTaskId; } 
49             set{ mnTaskId = value; } 
50         }
51
52         public void Encode(BinaryWriter writer) 
53         {  
54             writer.Write(Type); 
55             writer.Write(Code); 
56             writer.Write((UInt16)Checksum); 
57             writer.Write((UInt16)ID); 
58             writer.Write((UInt16)Seq); 
59             writer.Write((UInt32)tmSend); 
60             writer.Write(nTaskId); 
61          } 
62
63         public void Decode(BinaryReader reader) 
64         {  
65             Type = reader.ReadByte(); 
66             Code = reader.ReadByte(); 
67             Checksum = reader.ReadUInt16(); 
68             ID = reader.ReadUInt16(); 
69             Seq = reader.ReadUInt16(); 
70             tmSend = reader.ReadUInt32(); 
71             nTaskId = reader.ReadInt32(); 
72         } 
73
74         public uint Sum() 
75         {  
76             uint sum = 0; 
77             sum += (ushort)(Type + (Code << 8)); 
78             sum += (ushort)ID; 
79             sum += (ushort)Seq; 
80             sum += (ushort)tmSend; 
81             sum += (ushort)(tmSend >> 16); 
82             sum += (ushort)nTaskId; 
83             sum += (ushort)(nTaskId >> 16); 
84             return sum; 
85         } 
86     } 

 

 以上介绍了用C#是如何对自定义的通信协议封装的过程。 如有不同的处理方法的朋友,欢迎评论,一起探讨一下。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值