Socket开发之通讯协议及处理

在Socket应用开发中,还有一个话题是讨论的比较多的,那就是数据接收后如何处理的问题。这也是一个令刚接触Socket开发的人很头疼的问题。

因为Socket的TCP通讯中有一个“粘包”的现象,既:大多数时候发送端多次发送的小数据包会被连在一起被接收端同时接收到,多个小包被组成一个大包被接收。有时候一个大数据包又会被拆成多个小数据包发送。这样就存在一个将数据包拆分和重新组合的问题。那么如何去处理这个问题呢?这就是我今天要讲的通讯协议。

所谓的协议就是通讯双方协商并制定好要传送的数据的结构与格式。并按制定好的格式去组合与分析数据。从而使数据得以被准确的理解和处理。

那么我们如何去制定通讯协议呢?很简单,就是指定数据中各个字节所代表的意义。比如说:第一位代表封包头,第二位代表封类型,第三、四位代表封包的数据长度。然后后面是实际的数据内容。

如下面这个例子:

01

01

06 00

01 0f ef 87 56 34

协议类别

协议代码

数据长度

实际数据

前面三部分称之为封包头,它的长度是固定的,第四部分是封包数据,它的长度是不固定的,由第三部分标识其长度。因为我们的协议将用在TCP中,所以我没有加入校验位。原因是TCP可以保证数据的完整性。校验位是没有必要存在的。

接下来我们要为这个数据封包声明一个类来封装它:


 1    public class Message
 2    {
 3        private byte _class;
 4        private byte _flag;
 5        private int _size;
 6        private byte[] _content;
 7
 8        public byte[] Content
 9        {
10            get { return _content; }
11            set { _content = value; }
12        }
13
14        public int Size
15        {
16            get { return _size; }
17            set { _size = value; }
18        }
19
20        public byte Flag
21        {
22            get { return _flag; }
23            set { _flag = value; }
24        }
25
26        public byte Class
27        {
28            get { return _class; }
29            set { _class = value; }
30        }
31
32        public Message()
33        {
34
35        }
36
37        public Message(byte @class, byte flag, byte[] content)
38        {
39            _class = @class;
40            _flag = flag;
41            _size = content.Length;
42            _content = content;
43        }
44
45        public byte[] ToBytes()
46        {
47            byte[] _byte;
48            using (MemoryStream mem = new MemoryStream())
49            {
50                BinaryWriter writer = new BinaryWriter(mem);
51                writer.Write(_class);
52                writer.Write(_flag);
53                writer.Write(_size);
54                if (_size > 0)
55                {
56                    writer.Write(_content);
57                }
58                _byte = mem.ToArray();
59                writer.Close();
60            }
61            return _byte;
62        }
63
64        public static Message FromBytes(byte[] Buffer)
65        {
66            Message message = new Message();
67            using (MemoryStream mem = new MemoryStream(Buffer))
68            {
69                BinaryReader reader = new BinaryReader(mem);
70                message._class = reader.ReadByte();
71                message._flag = reader.ReadByte();
72                message._size = reader.ReadInt32();
73                if (message._size > 0)
74                {
75                    message._content = reader.ReadBytes(message._size);
76                }
77                reader.Close();
78            }
79            return message;
80        }
81
82    }

我们可以用Tobytes()和FromBytes()将封包转换成二进制数组和从二进制数组转换回来。

事情看起来已经解决了,但……真的是这样子吗?不然,我们知道,TCP数据是以流的形式被传送的,我们并不知道一个数据包是否被传送完毕,也不知道我们接收回来的数据包中是否有多个数据包,如果直接使用FromBytes()来转换的话,很可能会因为数据不完整而出现异常,也有可能会因为数据中含有多个数据包而导致数据丢失(因为你并不知道这些数据中含有多少个数据包)。那我们该怎么办?这也不难,我们先把接收回来的数据写入一个流中。然后分析其中是否有完整的数据包,如果有,将其从流中取出,并将这部分数据从流中清除。直到流中没有完整的数据为止,以后接收回来的数据就将其写入流的结尾处,并从头继续分析。直到结束。

让我们来看看这部分的代码:


  1    public class MessageStream
  2    {
  3        private byte[] _buffer;
  4        private int _position;
  5        private int _length;
  6        private int _capacity;
  7
  8        public MessageStream()
  9        {
 10            _buffer = new byte[0];
 11            _position = 0;
 12            _length = 0;
 13            _capacity = 0;
 14        }
 15
 16        private byte ReadByte()
 17        {
 18            if (this._position >= this._length)
 19            {
 20                return 0;
 21            }
 22            return this._buffer[this._position++];
 23        }
 24
 25        private int ReadInt()
 26        {
 27            int num = this._position += 4;
 28            if (num > this._length)
 29            {
 30                this._position = this._length;
 31                return -1;
 32            }
 33            return (((this._buffer[num - 4] | (this._buffer[num - 3] << 8)) | (this._buffer[num - 2] << 0x10)) | (this._buffer[num - 1] << 0x18));
 34        }
 35
 36        private byte[] ReadBytes(int count)
 37        {
 38            int num = this._length - this._position;
 39            if (num > count)
 40            {
 41                num = count;
 42            }
 43            if (num <= 0)
 44            {
 45                return null;
 46            }
 47            byte[] buffer = new byte[num];
 48            if (num <= 8)
 49            {
 50                int num2 = num;
 51                while (--num2 >= 0)
 52                {
 53                    buffer[num2] = this._buffer[this._position + num2];
 54                }
 55            }
 56            else
 57            {
 58                Buffer.BlockCopy(this._buffer, this._position, buffer, 0, num);
 59            }
 60            this._position += num;
 61            return buffer;
 62        }
 63
 64        public bool Read(out Message message)
 65        {
 66            message = null;
 67            _position = 0;
 68            if (_length > 6)
 69            {
 70                message = new Message();
 71                message.Class = ReadByte();
 72                message.Flag = ReadByte();
 73                message.Size = ReadInt();
 74                if (message.Size <= 0 || message.Size <= _length - _position)
 75                {
 76                    if (message.Size > 0)
 77                    {
 78                        message.Content = ReadBytes(message.Size);
 79                    }
 80                    Remove(message.Size + 6);
 81                    return true;
 82                }
 83                else
 84                {
 85                    message = null;
 86                    return false;
 87                }
 88            }
 89            else
 90            {
 91                return false;
 92            }
 93        }
 94
 95        private void EnsureCapacity(int value)
 96        {
 97            if (value <= this._capacity)
 98                return;
 99            int num1 = value;
100            if (num1 < 0x100)
101                num1 = 0x100;
102            if (num1 < (this._capacity * 2))
103                num1 = this._capacity * 2;
104            byte[] buffer1 = new byte[num1];
105            if (this._length > 0)
106                Buffer.BlockCopy(this._buffer, 0, buffer1, 0, this._length);
107            this._buffer = buffer1;
108            this._capacity = num1;
109        }
110
111        public void Write(byte[] buffer, int offset, int count)
112        {
113            if (buffer.Length - offset < count)
114            {
115                count = buffer.Length - offset;
116            }
117            EnsureCapacity(buffer.Length + count);
118            Array.Clear(_buffer, _length, _capacity - _length);
119            Buffer.BlockCopy(buffer, offset, _buffer, _length, count);
120            _length += count;
121        }
122
123        private void Remove(int count)
124        {
125            if (_length >= count)
126            {
127                Buffer.BlockCopy(_buffer, count, _buffer, 0, _length - count);
128                _length -= count;
129                Array.Clear(_buffer, _length, _capacity - _length);
130            }
131            else
132            {
133                _length = 0;
134                Array.Clear(_buffer, 0, _capacity);
135            }
136        }
137    }

这个类的使用非常简单,你只要用Write(byte[] buffer, int offset, int count)将接收到的数据写入数据流中,并用bool Read(out Message message)将数据中的第一个数据包取出,如果函数返回True,就说明取回一个封包成功,如果返回False,则说明流中已经没有完整的封包,你需要继续接收后面的数据以组成一个完整的封包。

这们我们的数据分析就会变得非常简单。我们可以在ReceiveCallBack回调函数中将接收到的数据写入到流中并通知线程池中的工作者线程分析数据流并处理数据。我在前面的关于Socket异步操作的文章中的Analyzer函数就是用这两个类来分析处理数据的。这样的好处理就是,Socket工作线程只需要负责数据的接收,并将其写入流,其它的事情由其它的线程这处理,就不会因为处理的时间过长而导致接收操作被阻塞。从而影响Socket的性能。

本文所述方法只是协议处理的多种方法中的其中一种,而且可能并不是很优秀的方法,如果谁有更好的方法,还希望您能和我多多交流。好了,今天就到这里了,关于Socket的文章到这里可能就告一段落了,我现在在研究VS2008里面的新东西,如果有什么必得的话,我会继续写出来的。谢谢大家的支持。

转载于:https://www.cnblogs.com/songtzu/archive/2013/05/09/3068560.html

tcp协议与粘包现象【转http://www.cnblogs.com/wzd24/archive/2007/12/24/1011932.html】相关推荐

  1. 为什么tcp不采用停等协议_为什么 TCP 协议有粘包问题

    来自公众号:真没什么逻辑 链接:https://draveness.me/whys-the-design-tcp-message-frame/ 为什么这么设计(Why's THE Design)是一系 ...

  2. TCP协议的粘包问题

    TCP粘包 首先TCP作为面向字节流的传输方式,创建一个tcp的socket,同时在内核中创建一个发送缓冲区和一个接受缓冲区. 当调用write时,向会将数据写入到发送缓冲区中 如果发送的字节数太长, ...

  3. Socket TCP协议解决粘包、半包问题的三种解决方案

    什么是粘包.半包问题:         粘包:例如服务端依次将两条消息发送给客户端,我们暂且简单的将这两条消息举例为"Hello"."Unity",而客户端一次 ...

  4. TCP协议的粘包问题(数据无边界性)及解决方法

    其他相关文章:http://c.biancheng.net/view/2350.html https://blog.csdn.net/seamanj/article/details/40063093 ...

  5. TCP协议的粘包问题(数据的无边界性)

    上节我们讲到了socket缓冲区和数据的传递过程,可以看到数据的接收和发送是无关的,read()/recv() 函数不管数据发送了多少次,都会尽可能多的接收数据.也就是说,read()/recv() ...

  6. Python之路(第三十一篇) 网络编程:简单的tcp套接字通信、粘包现象

    一.简单的tcp套接字通信 套接字通信的一般流程 服务端 server = socket() #创建服务器套接字server.bind() #把地址绑定到套接字,网络地址加端口server.liste ...

  7. netty权威指南学习笔记三——TCP粘包/拆包之粘包现象

    TCP是个流协议,流没有一定界限.TCP底层不了解业务,他会根据TCP缓冲区的实际情况进行包划分,在业务上,一个业务完整的包,可能会被TCP底层拆分为多个包进行发送,也可能多个小包组合成一个大的数据包 ...

  8. socket Php 粘包,python3 tcp的粘包现象和解决办法解析

    这篇文章主要介绍了python3 tcp的粘包现象和解决办法解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 服务器端 import socket ...

  9. 解决TCP网络传输“粘包”问题

    当前在网络传输应用中,广泛采用的是TCP/IP通信协议及其标准的socket应用开发编程接口(API).TCP/IP传输层有两个并列的协议:TCP和UDP.其中TCP(transport contro ...

最新文章

  1. python3 _笨方法学Python_日记_DAY3
  2. 流 java_java8——使用流
  3. 外贸网络推广分享让网站文章标题优化效果更好地小技巧!
  4. 大学计算机高级应用学什么,大学计算机高级应用 . 上卷
  5. 地理模块化施工的优点_模块化建筑适合学校的6个原因
  6. 产品经理十大悲催错误
  7. 特斯拉同意支付150万美元和解电池电压降低的索赔
  8. 小程序获取openid保存缓存吗_小程序获取openid踩坑
  9. jersey2.22.2异常java.lang.NoSuchMethodError: org.glassfish.jersey.CommonProperties.getValue
  10. Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel 1
  11. linux(Centos7)安装elasticsearch6.2.2
  12. java web 邮件_Java Web(十二) JavaMail发送邮件
  13. xp系统怎样安装传真服务器,XP系统传真服务怎么安装?配置传真教程
  14. opencv与pcl去找质心
  15. Java并发编程synchronized详解
  16. 快速检测npm registry镜像网址下载的速度
  17. iframe 边框去除,使用大全
  18. 计算机命令大全游戏,电脑小知识cmd命令大全【图文教程】
  19. 今天我教大家用js制作一个简单的计算器
  20. 机械原理第二章 连杆机构总结

热门文章

  1. 图形渲染技术分享:《GTA V 》图形分析摘要
  2. oracle11g自动内存管理
  3. Spring中@Resource与@Autowired、@Qualifier的用法与区别
  4. 关于mingw编译Qt时无法编译opengl es2(ANGLE)版本的问题
  5. 处理训练集和测试集分布同的方法(对抗训练)
  6. ASP.NET Web Pages:Chart 帮助器
  7. [BZOJ4825][HNOI2017]单旋(线段树+Splay)
  8. nginx配置设置,使部分页面访问跳转到404页面
  9. CentOs6系统安装及前期为安装Cdh4准备
  10. Rose2003的安装和破解