我需要将数据流式传输到TCP服务器,并且以前没有做过。数据应采用二进制帧格式。我用谷歌搜索并找到了本教程,我认为它描述了我需要做什么:
http://msdn.microsoft.com/en-us/library/system.net.sockets.tcpclient(v=vs.71).aspx
但是我不知道如何以所需的格式创建数据,以便以正确的方式发送数据。它应该采用以下格式:
Field|Offset| Type | size(octets)
id | 0 |unsign int| 1
name | 1 |Byte Array| 40
grade| 41 |sign float| 8
例:
Field| Data | InBytes |
id | 133 | 133 |
name | 247 | 247 0 |
grade| 0 | 0 |
我应该存储 int、字节数组、浮点数的数据类型是什么,我在哪里指定偏移量和大小,最后应该如何将其发送到服务器(在示例中,他们只发送字节数组)。
有关如何使用上面提供的链接中的代码发送此类数据的 C# 示例将不胜感激。
若要表示要序列化(和反序列化)的数据,可以使用结构并设置正确的元数据,以便 CLR 为您完成其余工作。就像其他人在这里说的那样,您将需要处理端点上的数据包接收。此外,您还必须考虑接收方预期的字符集,因为数据中有一个字符串字段。下面的代码是一个示例,说明如何实现结构以将托管数据转换为二进制格式,并带有注释。
// setting the layout to sequential will prevent the compiler/JIT
// to reorder the struct fields
// NOTE: Observe here that the Charset used is Ansi. You may need to
// change this depending on the format expected by the receiver.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
struct DataPacket
{
[MarshalAs(UnmanagedType.U4)]
public uint Id;
// As I understood from your question, the Name field
// has a prefixed size of 40 bytes. Attention here:
// the SizeConst actually means '40 characters', not bytes
// If you choose to use the Unicode charset, set SizeConst = 20
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 40)]
public String Name;
// This will be serialized in little endian format
// in your question this field is 8 bytes long, which
// in c# corresponds to the double type. If you really mean
// the float type (4 bytes), change here.
public double Grade;
// Calling this method will return a byte array with the contents
// of the struct ready to be sent via the tcp socket.
public byte[] Serialize()
{
// allocate a byte array for the struct data
var buffer = new byte[Marshal.SizeOf(typeof(DataPacket))];
// Allocate a GCHandle and get the array pointer
var gch = GCHandle.Alloc(buffer, GCHandleType.Pinned);
var pBuffer = gch.AddrOfPinnedObject();
// copy data from struct to array and unpin the gc pointer
Marshal.StructureToPtr(this, pBuffer, false);
gch.Free();
return buffer;
}
// this method will deserialize a byte array into the struct.
public void Deserialize(ref byte[] data)
{
var gch = GCHandle.Alloc(data, GCHandleType.Pinned);
this = (DataPacket)Marshal.PtrToStructure(gch.AddrOfPinnedObject(), typeof(DataPacket));
gch.Free();
}
}
用法:
DataPacket packet;
packet.Id = 1234;
packet.Name = "Marvin the paranoid robot";
packet.Grade = 9.2;
// serialize
var bytes = packet.Serialize();
// send via tcp
var tcp = new TcpClient(...);
tcp.GetStream().Write(bytes, 0, bytes.Length);
// deserializing;
DataPacket receivedPacket;
receivedPacket.Deserialize(bytes);
您已经有了数据包,现在您需要处理接收器上的数据包接收。那部分你不需要手工完成,你可以使用一些工具,就像@jgauffin说的那样。
我将采取的方法是在我的代码的其余部分和正在发送的数据的物理表示之间有两个接口的函数。 这将允许我封装从TCP连接发送的物理数据表示形式,并允许它在需要时进行更改。
因此,您可能想要一个TCP读取器类,它提供帧组合功能以及从帧的原始字节到数据结构的转换,以及一个TCP编写器类,该类采用数据结构并创建原始字节帧,然后将其写入TCP连接。
我将有一个消息缓冲区,它将是一个字节数组。 这是将从 TCP 连接发送的消息缓冲区。
接下来是一个数据结构,其中包含在系统其余部分使用的数据。
然后我会有一个函数,比如说FromTcpToDataStruct (dataStruct, messageArray)
从原始字节字符串转换为我正在使用的数据结构,然后FromDataStructToTcp (messageArray, dataStruct)
从数据结构转换为原始字符串。
您有几个需要考虑的问题。 首先是目标服务器上无符号 int 和浮点数的表示,即小端大端问题。 第二种是将一串字节从TCP连接转换为离散帧(从TCP连接读取字节并使用临时缓冲区组成完整的原始字节帧,以便读取提供完整的字节帧)。
你不能只发送这些数据。TCP是基于流的,这意味着您的信息可以通过一个操作(socket.Receive()
)或使用多个操作来接收。
解决此问题的最简单方法是使用包含标头(简单长度标头就足够)和正文(您指定的信息)的数据包格式。
我不知道您是否必须自己实现所有内容,或者是否可以使用网络库。我创建了一个库,可以为您处理所有套接字IO。您需要做的就是处理编码和解码。我为一个简单的二进制协议写了一个小例子(未完成)。
编码器/解码器类可以在这里找到:https://github.com/jgauffin/griffin.networking/tree/master/Source/Protocols/SimpleBinary/Griffin.Networking.Protocol.SimpleBinary/Handlers
格里芬网络介绍:http://blog.gauffin.org/2012/05/griffin-networking-a-somewhat-performant-networking-library-for-net/
我只需使用二进制编写器将数据转换为字节数组,然后像示例中一样发送它。
您可能需要检查字符串以确保其形式为 40 字节
MemoryStream MS = new MemoryStream();
BinaryWriter Writer = new BinaryWriter(MS);
Writer.Write(MyByte);
Writer.Write(ASCIIEncoding.UTF8.GetBytes(MyString));
Writer.Write(MyDouble);