C#网络编程数据传输中封装数据帧头的方法


  在C/S端编程的时候,经常要在C端和S端之间传数据时自定义一下报文的帧头,如果是在C/C++,封装帧头是一件很简单的事情,直接把unsigned char *强转为struct就行,但是在C#中,并没有提供直接从struct到byte[]的转换,这个时候就需要用到Marshal等非托管的方法了。

自定义帧


我们可以在C#中写出如下代码:

 1 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1,Size = 12)]  2 [Serializable()]  3  public struct DatagramHeaderFrame  4 {  5             // MessageType类型:
 6             public MessageType MsgType;  7 
 8             //一个四个字节的特征码
 9             public uint FeatureCode; 10 
11             //用于标识报文的长度,用于校验
12             public int MessageLength; 13   }

  首先我们说明一下,StructLayout是一个用于管理struct的布局特性,

CharSet指示在默认情况下是否应将类中的字符串数据字段作为 LPWSTR 或 LPSTR 进行封送处理;

Pack控制类或结构的数据字段在内存中的对齐方式。

Size指示类或结构的绝对大小。

  LayoutKind是布局的类型,这个枚举有三个值:

Auto运行库自动为非托管内存中的对象的成员选择适当的布局。 使用此枚举成员定义的对象不能在托管代码的外部公开。 尝试这样做将引发异常。

Explicit在未管理内存中的每一个对象成员的精确位置是被显式控制的,服从于 StructLayoutAttribute. Pack 字段的设置。每个成员必须使用 FieldOffsetAttribute 指示该字段在类型中的位置。在MSDN文档中为我们展示了下面的一个例子:

1 [StructLayout(LayoutKind.Explicit)] 2 public struct Rect 3 { 4    [FieldOffset(0)] public int left; 5    [FieldOffset(4)] public int top; 6    [FieldOffset(8)] public int right; 7    [FieldOffset(12)] public int bottom; 8 }   

Sequential对象的成员按照它们在被导出到非托管内存时出现的顺序依次布局。 这些成员根据在 StructLayoutAttribute. Pack 中指定的封装进行布局,并且可以是不连续的。

  Serialzable是一个用于指示对象是否能序列化的特性,简单的来说序列化的用处就是,比如我客户端给你传一定的数据,这个数据不是标准的类型的时候,如果我们不使用序列化的时候,我们就要把数据的每个部分都转成二进制然后存储,就很麻烦,而且容易出错,所以C#就提供了这样一个机制给程序员简单使用并且转成二进制(或者其他格式)来使用(使用formatter),而且当一个对象没有被标明为可序列化的时候,我们使用formatter的时候会报错,具体怎么使用请查看MSDN文档即可。比如文档有这样一段代码:

using System; using System.IO; using System.Runtime.Serialization; using System.Runtime.Serialization.Formatters.Soap; //using System.Runtime.Serialization.Formatters.Binary;

public class Test { public static void Main() { //Creates a new TestSimpleObject object.
      TestSimpleObject obj = new TestSimpleObject(); Console.WriteLine("Before serialization the object contains: "); obj.Print(); //Opens a file and serializes the object into it in binary format.
      Stream stream = File.Open("data.xml", FileMode.Create); SoapFormatter formatter = new SoapFormatter(); //BinaryFormatter formatter = new BinaryFormatter();
 formatter.Serialize(stream, obj); stream.Close(); //Empties obj.
      obj = null; //Opens file "data.xml" and deserializes the object from it.
      stream = File.Open("data.xml", FileMode.Open); formatter = new SoapFormatter(); //formatter = new BinaryFormatter();
 obj = (TestSimpleObject)formatter.Deserialize(stream); stream.Close(); Console.WriteLine(""); Console.WriteLine("After deserialization the object contains: "); obj.Print(); } } // A test object that needs to be serialized.
[Serializable()] public class TestSimpleObject { public int member1; public string member2; public string member3; public double member4; // A field that is not serialized.
    [NonSerialized()] public string member5; public TestSimpleObject() { member1 = 11; member2 = "hello"; member3 = "hello"; member4 = 3.14159265; member5 = "hello world!"; } public void Print() { Console.WriteLine("member1 = '{0}'", member1); Console.WriteLine("member2 = '{0}'", member2); Console.WriteLine("member3 = '{0}'", member3); Console.WriteLine("member4 = '{0}'", member4); Console.WriteLine("member5 = '{0}'", member5); } }

 

封装和解析


  要解析一个struct并且把他变成bytes,需要用到非托管的方法:

        public static byte[] StructToBytes(object structObj) { int size = Marshal.SizeOf(structObj); IntPtr buffer = Marshal.AllocHGlobal(size); try { Marshal.StructureToPtr(structObj, buffer, false); byte[] bytes = new byte[size]; Marshal.Copy(buffer, bytes, 0, size); return bytes; } finally { Marshal.FreeHGlobal(buffer); } }

要把bytes变成sturct,反过来即可:

 1         public static object BytesToStruct(byte[] bytes, Type strcutType)  2  {  3             int size = Marshal.SizeOf(strcutType);  4             IntPtr buffer = Marshal.AllocHGlobal(size);  5             try
 6  {  7                 Marshal.Copy(bytes, 0, buffer, size);  8                 return Marshal.PtrToStructure(buffer, strcutType);  9  } 10             finally
11  { 12  Marshal.FreeHGlobal(buffer); 13  } 14         }

下面演示一下在socket上传输报文+帧头:

 1         public static byte[] PackingMessageToBytes  2             (MessageType messageType, uint featureCode, int messageLength, byte[] msgBytes)  3  {  4             DatagramHeaderFrame frame = new DatagramHeaderFrame();  5             frame.MsgType = messageType;  6             frame.FeatureCode = featureCode;  7             frame.MessageLength = messageLength;  8 
 9             byte[] header = StructToBytes(frame); 10 
11             byte[] datagram = new byte[header.Length + msgBytes.Length]; 12             header.CopyTo(datagram, 0); 13  msgBytes.CopyTo(datagram, FrameSize); 14 
15             return datagram; 16  } 17 
18         /// <summary>
19         /// 封装消息和报文 20         /// </summary>
21         /// <param name="headerFrame">报文帧头</param>
22         /// <param name="message">报文</param>
23         /// <param name="encoding">编码器</param>
24         /// <returns></returns>
25         public static byte[] PackingMessageToBytes 26             (DatagramHeaderFrame headerFrame, byte[] msgBytes) 27  { 28             byte[] header = StructToBytes(headerFrame); 29 
30             byte[] datagram = new byte[header.Length + msgBytes.Length]; 31             header.CopyTo(datagram, 0); 32  msgBytes.CopyTo(datagram, FrameSize); 33 
34             return datagram; 

 

接收端代码节选:

1 DatagramHeaderFrame headerFrame = new DatagramHeaderFrame(); 2 headerFrame.MsgType = messageType; 3 headerFrame.MessageLength = bytes.Length; 4 byte[] datagram = PackingMessageToBytes(headerFrame, bytes); 5 
6 GetStream().BeginWrite(datagram, 0, datagram.Length, HandleDatagramWritten, this);

 

发送端代码节选:

 1 DatagramHeaderFrame headerFrame = new DatagramHeaderFrame();  2 byte[] datagramBytes = new byte[0];  3 
 4 byte[] datagramBuffer = (byte[])ar.AsyncState;  5 byte[] recievedBytes = new byte[numberOfRecievedBytes];  6 
 7 Buffer.BlockCopy(datagramBuffer, 0, recievedBytes, 0, numberOfRecievedBytes);  8                 PrasePacking(recievedBytes, numberOfRecievedBytes, ref headerFrame, ref datagramBytes);  9 
10 GetStream().BeginRead(datagramBuffer, 0, datagramBuffer.Length, HandleDatagramReceived, datagramBuffer);

 

C++端解析和封装的代码(用Qt写的)

 1 QByteArray TcpHeaderFrameHelper::bindHeaderAndDatagram(const TcpHeaderFrame &header,const QByteArray &realDataBytes)  2 {  3  QByteArray byteArray, temp;  4     temp.resize(4);  5     
 6  unsignedToQByteArray((unsigned)header.messageType, temp);  7     byteArray += temp;  8     
 9  unsignedToQByteArray((unsigned)header.featureCode, temp); 10     byteArray += temp; 11     
12  unsignedToQByteArray((unsigned)header.messageLength, temp); 13     byteArray += temp; 14     
15     byteArray +=realDataBytes; 16     return byteArray; 17 } 18 
19 void TcpHeaderFrameHelper::praseHeaderAndDatagram(const QByteArray &dataBytes,TcpHeaderFrame &headerFrame,QByteArray &realDataBytes) 20 { 21     realDataBytes.resize(dataBytes.size() - TcpHeaderFrameHelper::headerSize); 22     headerFrame.messageType = qByteArrayToInt(dataBytes.left(4)); 23     headerFrame.featureCode = qByteArrayToInt(dataBytes.mid(4,4)); 24     headerFrame.messageLength = qByteArrayToInt(dataBytes.mid(8,4)); 25     
26     realDataBytes = dataBytes.mid(12, dataBytes.size()); 27 } 28 
29 unsigned TcpHeaderFrameHelper::qByteArrayToInt(QByteArray bytes) 30 { 31     int result = 0; 32     result |= ((bytes[0]) & 0x000000ff); 33     result |= ((bytes[1] << 8) & 0x0000ff00); 34     result |= ((bytes[2] << 16) & 0x00ff0000); 35     result |= ((bytes[3] << 24) & 0xff000000); 36     
37     return result; 38 } 39 
40 void TcpHeaderFrameHelper::unsignedToQByteArray(unsigned num, QByteArray &bytes) 41 { 42     bytes.resize(4); 43     bytes[0] = (char)( 0x000000ff & num); 44     bytes[1] = (char)((0x0000ff00 & (num)) >> 8); 45     bytes[2] = (char)((0x00ff0000 & (num)) >> 16); 46     bytes[3] = (char)((0xff000000 & (num)) >> 24); 47 }

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM