日期:2014-05-20  浏览次数:20622 次

处理粘包和半包问题的socket分包Java实现
只知道原理,代码实现还不知道怎么实现?请高手指点,谢谢!高分馈赠!

------解决方案--------------------
一般在socket处理大数据量传输的时候会产生粘包和半包问题,有的时候tcp为了提高效率会缓冲N个包后再一起发出去,这个与缓存和网络有关系

在java中对于这样的优化,lz可以用非阻塞的流操作,非阻塞socket和流操作都是nio包中

个人建议,呵呵,没有去认真的考虑过这个问题


------解决方案--------------------
可能解决 粘包的问题。

Java code

Socket socket = new Socket();
socket.setTcpNoDelay(true);

------解决方案--------------------
譬如
粘包 为x.5个包
半包 为0.5个包

由于网络原因 一次可能会来 0.5/1 /2/ 2.5/ 。。。。个包
当接收到时 要先看看那这个包中有多少个完整的包。把完整的包都处理了 也就是说把x都处理了。剩下的0.5留在接收区中,等待下次接收。
这回接收到的就是0.5+1.5/0.5+1.3/0.5+0.5..... 把完整的包都处理了,有残缺的扔掉 0.8的。

一般情况 接收到正确的后都要给发送端一个应答。不给应答的算超时,发送端将重发。

有头没尾的不能扔
没头有尾的可以扔
有头有尾但缺东西可以扔
有头有尾不缺东西不能扔
------解决方案--------------------
之所以出现粘包和半包现象,是因为TCP当中,只有流的概念,没有包的概念.

楼主可以使用UDP协议.这样可以就可以区分每个包了.但是要确保包的丢失处理.为了提到效率,可以考虑写一个滑动窗口进行收发包.

若采用TCP协议进行传输,就要将每个包区分开来.可以有三种方式.因为TCP是面向流的.流只有打开和关闭,你要用一个流传输多个包,那就要向办法区分出每个包.

一:: 可以每次发送同样大小的包,过大的包不予发送,过小的包,后面部分用固定的字符'\0'进行填充.

二:: 将流按字符处理,抽出一个字符做转义字符(通常Java用'\'来做转义字符,比如"\n"表示换行).假如就设'\'为转义字符,发送方如果流当中出现'\',就在后面在追加一个'\',如果包结束,则用'\'做包的结束符.这样,在接收方,若读取一个单独的'\'或者流结束,就标示前面的内容构成一个包,如果连续读取两个'\',就将两个'\'用一个'\'进行替换.这样,就可以保证原来包中的信息不变,同时也能区分出每个包了.

三:: 在发送方发送一个包的时候,先将这个包的长度发送给对方(一般是4个字节表示包长),然后再将包的内容发送过去.接收方先接收4个字节,看看包的长度,然后按照长度来接收包,这样就不会出错了.

以上三种方法,是网络传输中经常用到的方法.后两种很常见.最后一种,在TCP长连接传输中应用最多.
综合以上的说法,就是要在TCP协议以上再封装一层协议,用来做分包的信息交换.
------解决方案--------------------
当然,如果TCP不是非要长连接,或者,信息包不是批量传输的情况下.可以一次TCP连接只传输一个包.这种情况下一般,一次TCP完成一次交互,即发送方发送信息包,接收方接收信息包同时发送一个接收方的响应包给发送方,表明接收方收到信息包,还是收到了错误包,或者接收方系统异常没有处理这个包之类的信息.
其实HTTP的交互过程,就是非常类似这样的.

当然这种情况,必须要求发送方和接收方互为服务器客户端,否则信息就是单向的了.

------解决方案--------------------
我一般处理是:

一个BUFFER,用于保存当前连接的读缓存
有数据时,Buffer = Buffer + DataIn,不停的接收
收完成后,开始解析Buffer,
根据包的协议,不停的解析Buffer,并形成一个个包进行处理,处理后,Buffer = Buffer - Data,并继续解包
完成。
------解决方案--------------------
喜欢使用Buffer,把数据先压入,而后按照协议去解析,这样就绕开了半包等问题
------解决方案--------------------
一般协议设计,每个包不是以特定标志为结尾,就是把长度声明在开头。
------解决方案--------------------
尊敬楼主!能将自己的想法说的如此清晰详细已备后来者学习,值得尊敬!学习中...
------解决方案--------------------
JDK里面.BufferedReader是用来处理字符流的.在网络通信当中,一般不用这个类.
而用这个类来处理的.一般是我讲到的第二种处理方式.只不过,是用换行符作为包的分隔符(我讲的第二方式比较通用,特例的情况下如果传输的是ACSII字符流,可以指定换行符为包的分隔符).接收端使用BufferedReader的readLine()方法.发送端在发送字符串之后,再追发一个换行符'\n',用于进行包的分隔.
楼主可以试一下,这个效率会很高,而且,不会有粘包,半包的现象.

由于网络传输数据的不确定性(也就是说有可能传输的是图像,文件什么的),所以,一般直接使用InputStream这个类来操作.它的read方法都是阻塞读的.参数为byte数组的情况下,如果流没有结束,该方法直到byte数组填满才会返回.楼主可以试一下,直接用InputStream这个类来处理.就不会出现使用BufferedReader读出半包的情况了.当然也不用再去递归补读了.

直接使用InputStream一般效率还是很高的,如果还要提高效率,那就要自己编写一个缓冲区了.使用多线程(线程数量3个就可以)并发处理.性能会显著提高的.




------解决方案--------------------
socket在通信处理中数据包一分为二或二合为一的状况很多,而且不好模拟测试. 处理方法一般有二:

1.定长法. 每次发送数据都定长. 多余的数据作为下一个包的起始部分保留. 收不够长度就存储起来, 知道够数据为止
2.起始符法. 每个数据都有特定的起始符号内的特定数据, 如果起始符范围外有数据,可以确认是程序逻辑有问题或有人搞破坏了.

上面的方法开工前, 都要制定你的应用协议. 原理清楚了, 协议搞完整了, 程序就好写了.
------解决方案--------------------
29楼说的 UDP是独立路由的,这一点,我赞同. 
但是,"TCP一旦建立连接后路由路径就定下来了",这句话,我不赞同.