我正在尝试将缓冲区从服务器发送到我自己制作的客户端。它适用于TCP上的套接字。
我有一个需要发送的结构:
[StructLayout(LayoutKind.Sequential, Pack = 1)]
struct loginStruct
{
public string userName;
public string password;
public loginStruct(string userName, string password)
{
this.userName = userName;
this.password = password;
}
}
我让这些函数从字节数组转换为结构体,从结构体转换为字节数组:
public static byte[] StructToByteArray(object obj)
{
int len = Marshal.SizeOf(obj);
byte[] arr = new byte[len];
IntPtr ptr = Marshal.AllocHGlobal(len);
Marshal.StructureToPtr(obj, ptr, false);
Marshal.Copy(ptr, arr, 0, len);
Marshal.FreeHGlobal(ptr);
return arr;
}
public static void ByteArrayToStruct(byte[] buffer, ref object obj)
{
int len = Marshal.SizeOf(obj);
IntPtr i = Marshal.AllocHGlobal(len);
Marshal.Copy(buffer, 0, i, len);
obj = Marshal.PtrToStructure(i, obj.GetType());
Marshal.FreeHGlobal(i);
}
在客户端中,我收到缓冲区,但是当客户端尝试使用ByteArrayToStruct函数时,我遇到了运行时错误。
在尝试轻松解析来自专有服务器的响应时,我有同样的想法。 下面是一个根据您的具体情况进行调整的简化示例。
首先,您需要一些扩展才能使这变得更容易。 请注意,为此,您需要使用 .NET 3.5 或更高版本,或者在此处查看答案。
现在,这是我为扩展类所做的工作:
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许可下)
另一个需要注意的重要事项是,如果使用 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;
};
现在,上述假设发送和接收数据缓冲区的实际内容定义不同,因此在您的情况下,您只需定义其中一个结构。 请注意,它们是按相反的顺序指定的;同样,这是因为我需要以大端格式传输它。
现在要做的就是创建要发送的结构:
// 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);
}
需要注意的是,通过使用CopyTo()
模拟sock.Send()
和sock.Receive()
命令,它不会在barBuf
中产生大端数组。 我已经相应地修改了代码,但是如果您确实使用它来接收大端序数据,只需更改代码中指示的行即可。
我希望这有所帮助。 我花了很多时间来弄清楚自己,因为这些信息在多个来源中分散开来。