基於C#的通訊協議封包(附程式碼)

iDotNetSpace發表於2009-07-06

接上一篇《基於.NET技術的監控系統應用分析》中所描述的資料通訊協議設計,我們來看一下在C#中是怎麼對自定義協議進行封包的?我們知道基於流的資料協議的特點:傳送和接收到的資料都是連續的流。每次網路I/O操作的流長度不確定,也就是無法知道每次接收到的資料是一個完整的資料包。同樣,主機傳送一個資料包也會根據網路的實際情況執行若干次。所以我們對這類訊息的編解碼過程需要進行一個統一的封裝。

重新回顧一下每個訊息的結構:訊息頭 + 訊息體。每次先傳送出去的是訊息頭,然後是訊息體。訊息頭裡描述了這個資料包的型別,長度,序列號等資訊。訊息頭的長度是固定的,訊息體的長度是根據每個訊息型別會有所的區別。

 

訊息頭的定義:

欄位

長度(位元組)

型別

說明

Length

4

Int

訊息的總長度(位元組)

Command ID

4

Int

命令ID

NodeID

4

Int

結點ID

TimeID

4

Int

時間戳

SequenceID

4

Int

遞增序列號

 

對應的封裝程式碼:

基於C#的通訊協議封包(附程式碼)Head

 

上面只是一個訊息頭,要成為一個完整的訊息,一般還必須包含訊息體(當然你也可以根據需要僅傳送一個訊息頭的資料,作為特殊用途,例如自定義的心跳包)。舉個例子:客戶機與伺服器連線上後,它通常會傳送一個繫結(Bind) 訊息給伺服器端。例如:驗證確認客戶端的合法性。那麼此時的Bind訊息的格式是:

 

欄位

長度(位元組)

型別

說明

HEAD

 

 

上面的訊息頭部

loginName

16

string

使用者名稱(固定16位,不足用空格填充)

LoginPassword

16

string

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

 

對應的封裝程式碼:


基於C#的通訊協議封包(附程式碼)
using System;
using MonitorLib.Utility;

namespace MonitorLib.Protocol
基於C#的通訊協議封包(附程式碼)
{
基於C#的通訊協議封包(附程式碼)    
/// 
    
/// AbstractBase 的摘要說明。
    
/// 

    
    [Serializable]
    
public abstract class AbstractBase
基於C#的通訊協議封包(附程式碼)    

        
protected byte[] initValue;
        
public Head header;

        
public AbstractBase()
基於C#的通訊協議封包(附程式碼)        
{
             
        }


        
public virtual byte[] ToBytes()
基於C#的通訊協議封包(附程式碼)        
{
            
return null;
        }

    }

}

 

基於C#的通訊協議封包(附程式碼)
using System;
using System.Text;
using MonitorLib.Utility;

namespace MonitorLib.Protocol
基於C#的通訊協議封包(附程式碼)
{
基於C#的通訊協議封包(附程式碼)    
/// 
    
/// Bind訊息
    
/// 

    [Serializable]
    
public class Bind : AbstractBase
基於C#的通訊協議封包(附程式碼)    
{
        
private string loginName;
        
private string loginPassword;

基於C#的通訊協議封包(附程式碼)        
/// 
        
/// 初始Bind命令的訊息頭
        
/// 
        
/// 序列號

        public Bind(Sequence seq)
基於C#的通訊協議封包(附程式碼)        
{
            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)
基於C#的通訊協議封包(附程式碼)        
{
            initValue 
= new byte[receive.Length];
            receive.CopyTo(initValue,
0); 
        }


基於C#的通訊協議封包(附程式碼)        
/// 
        
/// 登入名
        
/// 

        public string LoginName 
基於C#的通訊協議封包(附程式碼)        
{
            
get
基於C#的通訊協議封包(附程式碼)            
{
                
return Encoding.ASCII.GetString(initValue,20,16);
            }

            
set
基於C#的通訊協議封包(附程式碼)            
{
                loginName 
= value;
            }

        }


基於C#的通訊協議封包(附程式碼)        
/// 
        
/// 密碼
        
/// 

        public string LoginPassword
基於C#的通訊協議封包(附程式碼)        
{
            
get
基於C#的通訊協議封包(附程式碼)            
{
                
return Encoding.ASCII.GetString(initValue,36,16);
            }

            
set
基於C#的通訊協議封包(附程式碼)            
{
                loginPassword 
= value;
            }

        }


基於C#的通訊協議封包(附程式碼)        
/// 
        
/// 把訊息結構轉換成位元組陣列
        
/// 
        
/// 結果位元組陣列

        public override byte[] ToBytes()
基於C#的通訊協議封包(附程式碼)        
{
            
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;
        }

    }



基於C#的通訊協議封包(附程式碼)    
/// 
    
/// Bind應答結構
    
/// 

    [Serializable]
    
public class Bind_Resp : AbstractBase
基於C#的通訊協議封包(附程式碼)    
{
        
private uint result;

基於C#的通訊協議封包(附程式碼)        
/// 
        
/// 建構函式,把接收的位元組陣列複製到initValue
        
/// 
        
/// 從網路上接收到的位元組陣列

        public Bind_Resp(byte[] receive)
基於C#的通訊協議封包(附程式碼)        
{
            initValue 
= new byte[receive.Length];
            receive.CopyTo(initValue,
0); 
        }


        
public Bind_Resp(Sequence seq)
基於C#的通訊協議封包(附程式碼)        
{
            header 
= new Head(Command.MOT_BIND_RESP);
            header.NodeID 
= seq.Node;
            header.TimeID 
= seq.Time ;
            header.SequenceID 
= seq.Value ;
            header.Length 
= Head.HeaderLength + 4;
        }


基於C#的通訊協議封包(附程式碼)        
/// 
        
/// bind 執行命令是否成功,0-成功。其它:錯誤碼。
        
/// 

        public uint Result
基於C#的通訊協議封包(附程式碼)        
{
            
get
基於C#的通訊協議封包(附程式碼)            
{
                
return Convert.ToUInt32(initValue[20].ToString());
            }

            
set
基於C#的通訊協議封包(附程式碼)            
{
                result 
= value;
            }

        }


        
public override byte[] ToBytes()
基於C#的通訊協議封包(附程式碼)        
{
            
byte[] retValue =  new byte[header.Length];
            header.ToBytes().CopyTo(retValue,
0);
            BitConverter.GetBytes(result).CopyTo(retValue,
20);
            
return retValue;
        }

    }


}

除了這種協議封裝方法外,還有一種直接利用 .NET 的位元組流操作類來編解碼,例如 ICMP 協議的封包程式碼:

 

基於C#的通訊協議封包(附程式碼)
 1    public class ICMPHDR 
 2基於C#的通訊協議封包(附程式碼)    {  
 3        private byte mType; 
 4        public byte Type 
 5基於C#的通訊協議封包(附程式碼)        {  
 6基於C#的通訊協議封包(附程式碼)            getreturn mType; } 
 7基於C#的通訊協議封包(附程式碼)            set{ mType = value; } 
 8        }

 9
10        private byte mCode = 0
11        public byte Code 
12基於C#的通訊協議封包(附程式碼)        {  
13基於C#的通訊協議封包(附程式碼)            getreturn mCode; } 
14基於C#的通訊協議封包(附程式碼)            set{ mCode = value; } 
15        }

16
17        private ushort mChecksum = 0
18        public ushort Checksum 
19基於C#的通訊協議封包(附程式碼)        {  
20基於C#的通訊協議封包(附程式碼)            getreturn mChecksum; } 
21基於C#的通訊協議封包(附程式碼)            set{ mChecksum = value; } 
22        }

23
24        private ushort mID; 
25        public ushort ID 
26基於C#的通訊協議封包(附程式碼)        {  
27基於C#的通訊協議封包(附程式碼)            getreturn mID; } 
28基於C#的通訊協議封包(附程式碼)            set{ mID = value; } 
29        }

30        
31        private ushort mSeq;
32        public ushort Seq 
33基於C#的通訊協議封包(附程式碼)        {  
34基於C#的通訊協議封包(附程式碼)            getreturn mSeq; } 
35基於C#的通訊協議封包(附程式碼)            set{ mSeq = value; } 
36        }

37 
38        private ulong mtmSend;
39        public ulong tmSend 
40基於C#的通訊協議封包(附程式碼)        {  
41基於C#的通訊協議封包(附程式碼)            getreturn mtmSend; } 
42基於C#的通訊協議封包(附程式碼)            set{ mtmSend = value; } 
43        }
 
44
45        private int mnTaskId; 
46        public int nTaskId 
47基於C#的通訊協議封包(附程式碼)        {  
48基於C#的通訊協議封包(附程式碼)            getreturn mnTaskId; } 
49基於C#的通訊協議封包(附程式碼)            set{ mnTaskId = value; } 
50        }

51
52        public void Encode(BinaryWriter writer) 
53基於C#的通訊協議封包(附程式碼)        {  
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基於C#的通訊協議封包(附程式碼)        {  
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基於C#的通訊協議封包(附程式碼)        {  
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#是如何對自定義的通訊協議封裝的過程。 如有不同的處理方法的朋友,歡迎評論,一起探討一下。

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-608424/,如需轉載,請註明出處,否則將追究法律責任。

相關文章