好的,我在試圖輕鬆解析專有服務器的響應的同時,也有同樣的想法。以下是根據您的具體情況調整的簡化示例。
首先,您需要一些擴展功能,使其變得更加簡單。請注意,要做到這一點,您需要使用.NET 3.5或更高版本,或者請參閱答案here。
現在,這裏就是我有工作了我的擴展類:
public static class EndianExtensions {
/// <summary>
/// Convert the bytes to a structure in host-endian format (little-endian on PCs).
/// To use with big-endian data, reverse all of the data bytes and create a struct that is in the reverse order of the data.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="buffer">The buffer.</param>
/// <returns></returns>
public static T ToStructureHostEndian<T>(this byte[] buffer) where T : struct {
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
T stuff = (T) Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T));
handle.Free();
return stuff;
}
/// <summary>
/// Converts the struct to a byte array in the endianness of this machine.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="structure">The structure.</param>
/// <returns></returns>
public static byte[] ToBytesHostEndian<T>(this T structure) where T : struct {
int size = Marshal.SizeOf(structure);
var buffer = new byte[size];
GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
Marshal.StructureToPtr(structure, handle.AddrOfPinnedObject(), true);
handle.Free();
return buffer;
}
public static Dictionary<string, string> GetTypeNames<T>(this T structure) where T : struct {
var properties = typeof(T).GetFields();
var dict = new Dictionary<string, string>();
foreach (var fieldInfo in properties) {
string[] words = fieldInfo.Name.Split('_');
string friendlyName = words.Aggregate(string.Empty, (current, word) => current + string.Format("{0} ", word));
friendlyName = friendlyName.TrimEnd(' ');
dict.Add(fieldInfo.Name, friendlyName);
}
return dict;
}
}
(請注意,上述部分是從在CodeProject源,所有這些都是下CPOL license採樣)
另一個需要注意的重要事項是,如果使用CamelCaps並且在需要空格的地方突出顯示,則可以使用GetTypeNames
擴展名爲您的屬性獲取友好名稱。
以使這項工作(至少對於我的具體情況),最後一個關鍵部分是在反向來聲明結構。這是因爲我的服務器使用了大的字節順序。您可能想要在不改變排序順序的情況下嘗試,無論哪種方式適用於您。
所以實際使用此,這裏就是你要做的:
- 聲明你的結構。因爲我需要付諸大端發射之前,所有的煤礦都在反向:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Foo {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string User_Name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string Password;
};
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct Bar {
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string Password;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
public string User_Name;
};
現在上述假設發送的實際內容和接收數據緩衝區有不同的定義,以便在您的情況你只需要定義其中的一個結構。請注意,它們是按相反順序指定的;再次,這是因爲我需要以big-endian格式傳輸它。
現在一切都必須做的是創建結構派:
// buffer for storing our received bytes
var barBuf = new byte[64];
// struct that we're sending
var fuz = new Foo {
User_Name = "username",
Password = "password"
};
// get the byte equivalent of fuz
var fuzBytes = fuz.ToBytesHostEndian().Reverse().ToArray();
// simulates sock.send() and sock.receive()
// note that this does NOT simulate receiving big-endian data!!
fuzBytes.CopyTo(barBuf, 0);
// do the conversion from bytes to struct
barBuf = barBuf.Reverse().ToArray();
// change this to ToStructureHostEndian<Bar>() if receiving big endian
var baz = barBuf.ToStructureHostEndian<Foo>();
// get the property names, friendly and non-friendly
var bazDict = baz.GetTypeNames();
// change this to typeof(Bar) if receiving big endian
var bazProps = typeof(Foo).GetFields();
// loop through the properties array
foreach (var fieldInfo in bazProps) {
var propName = fieldInfo.Name;
// get the friendly name and value
var fieldName = bazDict[propName];
var value = fieldInfo.GetValue(baz);
// do what you want with the values
Console.WriteLine("{0,-15}:{1,10}", fieldName, value);
}
它是通過模擬sock.Send()
和sock.Receive()
命令要注意的是非常重要的,通過使用CopyTo()
,它不導致barBuf
中有一個大的排序數組。我相應地修改了代碼,但是如果您使用它來接收大端數據,只需更改代碼中指示的行。
我希望這會有所幫助。我花了很多時間弄清楚自己,因爲這些信息分散在多個來源。
你如何發送數據?你能張貼用於通過套接字發送/接收的代碼嗎?似乎你發佈的內容應該有效,錯誤可能是由錯誤的傳輸引起的。 –
什麼是運行時異常? –
我不會在sizeof調用或PtrToStructure調用中使用「obj」,而是引用結構本身。 obj引用可能爲null,因爲它的類型是「object」而不是「loginStruct」。另外,有沒有一個原因,你爲什麼不使用任何序列化器,並「做到這一點」?如果二進制格式是一個給定的,我仍然建議使用BinaryWriter/BinaryReader與編組(編碼/安全)speedbump的內存流。 –