在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 }