如何以二进制帧格式发送数据(TCP)

时间:2012-08-15 13:17:32

标签: c# tcp binary-data

我需要将数据流传输到TCP服务器并且之前已经完成了。数据应采用二进制帧格式。我用Google搜索并找到了我认为我需要做的教程:

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#示例将不胜感激。

4 个答案:

答案 0 :(得分:2)

我将采用的方法是为我的其余代码和正在发送的数据的物理表示之间的接口提供两个函数。这将允许我封装从TCP连接发出的物理数据表示,并允许它在需要时进行更改。

所以你可能想要的是拥有一个TCP读取器类,它提供帧组合功能,从帧的原始字节转换为数据结构和TCP编写器类,它采用数据结构并创建一个原始字节帧然后将其写入TCP连接。

我会有一个消息缓冲区,它是一个字节数组。这是将通过TCP连接发送的消息缓冲区。

接下来是一个数据结构,其中包含在系统其余部分中使用的数据。

然后我会有一个函数,比如FromTcpToDataStruct (dataStruct, messageArray)从原始字节字符串转换为我正在使用的数据结构,然后FromDataStructToTcp (messageArray, dataStruct)从数据结构转换为原始字符串。

您需要考虑一些问题。首先是目标服务器上的unsign int和float的表示,Little Endian Big Endian problem。第二种是将一串字节从TCP连接转换为离散帧(从TCP连接读取字节,并使用临时缓冲区组成一个完整的原始字节帧,以便读取提供完整的字节帧)。

答案 1 :(得分:2)

您不能只发送该数据。 TCP是基于流的,这意味着您可以使用一个操作(socket.Receive())或使用多个操作来接收您的信息。

最简单的方法是让数据包格式包含一个标题(一个简单的长度标题就足够了)和一个正文(你指定的信息)。

我不知道您是否必须自己实施所有内容,或者是否可以使用网络库。我已经创建了一个库,它可以为您处理所有套接字IO。您需要做的就是处理编码和解码。我为一个简单的二进制协议写了一个小例子(未完成)。

可在此处找到编码器/解码器类:https://github.com/jgauffin/griffin.networking/tree/master/Source/Protocols/SimpleBinary/Griffin.Networking.Protocol.SimpleBinary/Handlers

Griffin.Networking简介:http://blog.gauffin.org/2012/05/griffin-networking-a-somewhat-performant-networking-library-for-net/

答案 2 :(得分:2)

要表示要序列化(和反序列化)的数据,您可以使用结构并设置正确的元数据,因此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说。

答案 3 :(得分:1)

我只是使用二进制编写器将数据转换为字节数组,然后像示例一样发送它。

您可能需要检查字符串以确保它形成40个字节

        MemoryStream MS = new MemoryStream();
        BinaryWriter Writer = new BinaryWriter(MS);
        Writer.Write(MyByte);
        Writer.Write(ASCIIEncoding.UTF8.GetBytes(MyString));
        Writer.Write(MyDouble);