TCP通信之数据流粘包和拆包解决方法

产生数据粘包和拆包的原因这里不再赘述。本文简单介绍三种解决方法。

1. 定长协议

这种方式发送方发送的消息都具有固定的长度,比如每个消息长度都是1024个字节。

如果发送的消息内容没有1024个字节长度,那么消息尾部补0填充直到长度达到1024个字节,发送的消息内容不能超过1024个字节。

接收方收到消息后,每1024个字节截取为一段,认为是一个完整的消息。

缺点:发送的消息不能大于固定长度;消息尾部补充的0不易清除;浪费带宽(如果消息只有24个字节,那么每次需要发1024个字节数据,浪费1000个字节)

 

 

 

2.特殊字符分隔协议

这种方法使用一个特殊的字符,比如'\r'。

发送方发送消息的尾部加上这个字符用来和下一个消息区分开。

接收方每收到消息时,首先将这一震消息添加到缓存队列中,然后取出缓存中全部消息,判断消息中是否包含特殊字符,如果没有说明还没有收到一个完整的消息,继续等待,如果消息中包含特殊字符,则截取出特殊字符前面的所有内容作为一个完整的消息。

缺点:消息体中不能出现特殊字符

 

编码过程:

public class DelimiterBasedFrameEncoder : IEncoder
    {
        const char DELIMITER = '\r';

        public byte[] Encoder(byte[] msg)
        {
            byte[] tail = BitConverter.GetBytes(DELIMITER);

            List<byte> sendMsg = new List<byte>();
            sendMsg.AddRange(msg);
            sendMsg.AddRange(tail);

            return sendMsg.ToArray();
        }
    }

 

解码过程:

public class DelimiterBasedFrameDecoder : IDecoder
    {
        const char DELIMITER = '\r';

        public byte[] Decoder(List<byte> msg)
        {
            byte[] tail = BitConverter.GetBytes(DELIMITER);            
            
            int index = FindIndexDelimiter(msg.ToArray(), tail);

            if (index == -1)
            {
                return null;
            }           

            byte[] result = new byte[index];
            Array.Copy(msg.ToArray(), result, index);

            msg.RemoveRange(0, index + tail.Length );           

            return result;
        }
        

        int FindIndexDelimiter(byte[] maxSource, byte[] smallSource)
        {
            int index = maxSource.ToList().IndexOf(smallSource.First());

            for (int i = 1; i < smallSource.Length; i++)
            {
                int id= maxSource.ToList().IndexOf(smallSource[i]);
                if (id - index != i)
                {
                    return -1;
                }
            }

            return index;
        }
    }

 

 

3.变长协议

将消息分为两个部分,前面一部分叫做消息头,长度4个字节,后面一部分叫做消息体,长度为发送消息实际长度。

发送方发送消息时,首先获得消息的长度数值L,然后将L转换为4个字节的数组,将这个数组加到消息体前,形成一个新的字节数组发送。

接收方收到消息首先添加到缓存队列中,然后取出缓存中全部消息,判断消息的字节数组长度是否大4,如果小于则不做处理,等待后续的消息;如果大于4则取出前4个字节,将其转换为整型数字叫做L,然后再判断整个消息长度减去4(消息头)是否大于L,

如果不大于则不做处理,继续等待后续消息;如果大于L,则从第5个字节开始取出L个字节作为一个完整的消息,然后再将缓存中前4+L个字节删除。

假如发送队列中有3个数据待发送,到达接收缓存队列中可能是下图显示的样子。

 

 

发送方构造发送内容:

 

 

接收方解析数据:

 

 

 

编码过程:

public class LengthFieldBasedFrameEncoder : IEncoder
    {
        public byte[] Encoder(byte[] msg)
        {
            byte[] header = BitConverter.GetBytes(msg.Length);

            List<byte> sendMsg = new List<byte>();
            sendMsg.AddRange(header);
            sendMsg.AddRange(msg);

            return sendMsg.ToArray();
        }
    }

解码过程:

public class LengthFieldBasedFrameDecoder : IDecoder
    {
        public byte[] Decoder(List<byte> msgCache)
        {
            byte[] result = null;
            int cacheLength = msgCache.Count;
            if (cacheLength < 4)
            {
                return result;
            }

            int bodyLength = BitConverter.ToInt32(msgCache.ToArray(), 0);
            cacheLength = msgCache.Count;
            if (cacheLength - 4 < bodyLength)
            {
                return result;
            }

            result = new byte[bodyLength];
            
            result = msgCache.Skip(4).Take(bodyLength).ToArray();
            msgCache.RemoveRange(0, 4 + bodyLength);

            return result;
        }
    }

 

posted @ 2021-12-21 15:56  追风少年2021  阅读(461)  评论(0)    收藏  举报