如何使用套接字流正确分离数据包? C#

时间:2016-05-10 19:52:11

标签: c# sockets tcp

我正在构建一个服务器/客户端应用程序,并且我正在查看用于分离数据包的选项。我已经读过,最合适的一个是创建一个包含有效载荷有多大的信息的标题,然后读取直到它结束。

这是如何以编程方式工作的?

使用" \ n"分隔它们。新队。一个恰当的例子会很好。

我以这种方式异步接收数据:

private void AsyncReceive(IAsyncResult result)
    {
        int bytesTransfered;

        try
        {
            bytesTransfered = _handle.EndReceive(result);

            if(bytesTransfered <= 0)
            {
                throw new Exception("No bytes transfered");
            }
        }
        catch(NullReferenceException)
        {
            return;
        }
        catch(ObjectDisposedException)
        {
            return;
        }
        catch(Exception)
        {
            return;
        }


        byte[] received = new byte[bytesTransfered];

        try
        {
            Array.Copy(_readBuffer, received, received.Length);
        }
        catch(Exception)
        {
            Disconnect();
            return;
        }


        // How should I process the received data now?


        try
        {
            _handle.BeginReceive(_readBuffer, 0, _readBuffer.Length, SocketFlags.None, AsyncReceive, null);
        }
        catch(ObjectDisposedException)
        {
            return;
        }
        catch(Exception)
        {

        }
    }

2 个答案:

答案 0 :(得分:2)

首先,您需要区分不同类型的消息。您可以使用单字节,这将允许最多255种不同的消息类型。为此创建一个枚举,并使用一个属性来标记您的消息(见下文):

enum MessageType : byte {
    FirstMessage,
    SecondMessage
}

class MessageAttribute : Attribute {
    public MessageAttribute(MessageType type) {
        Type = type;
    }

    public MessageType Type { get; private set; }
}

其次,您的消息需要紧凑的序列化程序。一个很好的选择是protobuf - 它非常紧凑(不会序列化属性名称,只有值等等),同时仍然易于使用。

[Message(MessageType.FirstMessage)]
[ProtoContract]
class MyFirstMessage {
    [ProtoMember(1)]
    public string Value { get; set; }
    [ProtoMember(2)]
    public int AnotherValue { get; set; }
}

[Message(MessageType.SecondMessage)]
[ProtoContract]
class MySecondMessage {
    [ProtoMember(1)]
    public decimal Stuff { get; set; }
}

第三,你需要知道消息的长度,正如来电者所说。使用2或4个字节(分别为Int16和Int32类型的大小)。

所以我们的格式是:1字节 - 消息类型。 2-5字节 - 消息大小,5-5 +大小字节 - protobuf序列化消息。然后按照以下定义分三个步骤阅读您的信息流:

class MessageReader {
    static readonly Dictionary<byte, Type> _typesMap = new Dictionary<byte, Type>(); 
    static MessageReader() {
        // initialize your map
        // this is executed only once per lifetime of your app
        foreach (var type in Assembly.GetExecutingAssembly().GetTypes().Where(c => c.GetCustomAttribute<MessageAttribute>() != null)) {
            var message = type.GetCustomAttribute<MessageAttribute>();
            _typesMap.Add((byte)message.Type, type);
        }
    }

    public async Task<object> Read(Stream stream) {
        // this is your network or any other stream you have
        // read first byte - that is message type
        var firstBuf = new byte[1];
        if (await stream.ReadAsync(firstBuf, 0, 1) != 1) {
            // failed to read - end of stream
            return null;
        }

        var type = firstBuf[0];
        if (!_typesMap.ContainsKey(type)) {
            // unknown message, handle somehow
            return null;
        }
        // read next 4 bytes - length of a message
        var lengthBuf = new byte[4];
        if (await stream.ReadAsync(lengthBuf, 0, 4) != 4) {
            // read less than expected - EOF
            return null;
        }
        var length = BitConverter.ToInt32(lengthBuf, 0);
        // check if length is not too big here! or use 2 bytes for length if your messages allow that
        if (length > 1*1024*1024) {
            // for example - adjust to your needs
            return null;
        }
        var messageBuf = new byte[length];
        if (await stream.ReadAsync(messageBuf, 0, length) != length) {
            // didn't read full message - EOF
            return null;
        }
        try {
            return ProtoBuf.Serializer.NonGeneric.Deserialize(_typesMap[type], new MemoryStream(messageBuf));
        }
        catch {
            // handle invalid message somehow
            return null;
        }
    }
}

从流中读取一条消息后 - 继续以相同的方式阅读下一条消息。读取调用将阻塞,直到新数据到达。如果有任何违反协议的行为 - 丢弃连接。

答案 1 :(得分:0)

您是否考虑过使用TCPClient和TCPListener,然后使用NetworkStream?套接字的级别很低,在大多数情况下可能不需要。

看这篇文章: How reading messages from server?(TCP)

此外,除非您记录并重新抛出异常,否则不要捕获无法恢复的异常。当异常被静默吞噬时,这将导致很难调试行为。