Java网络编程(五)socket的半包,粘包与分包的问题

[size=small]首先看两个概念: [color=blue]短链接:[/color] 链接->传输数据->关闭链接 HTTP是无状态的,浏览器和服务器每进行一次HTTP操做,就创建一次链接,但任务结束就中断链接。 也能够这样说:短链接是指SOCKET链接后发送后接收完数据后立刻断开链接。 [color=blue]长链接:[/color] 链接->传输数据->保持链接 -> 传输数据-> 。。。 ->关闭链接。 长链接指创建SOCKET链接后不论是否使用都保持链接,但安全性较差。 [color=red][b]之因此出现粘包和半包现象,是由于TCP当中,只有流的概念,没有包的概念.[/b][/color] [color=blue][b]半包[/b][/color] 指接受方没有接受到一个完整的包,只接受了部分,这种状况主要是因为TCP为提升传输效率,将一个包分配的足够大,致使接受方并不能一次接受完。[color=red](在长链接和短链接中都会出现)。[/color] [color=blue][b]粘包与分包[/b][/color] 指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。出现粘包现象的缘由是多方面的,它既可能由发送方形成,也可能由接收方形成。发送方引发的粘包是由TCP协议自己形成的,TCP为提升传输效率,发送方每每要收集到足够多的数据后才发送一包数据。若连续几回发送的数据都不多,一般TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。接收方引发的粘包是因为接收方用户进程不及时接收数据,从而致使粘包现象。这是由于接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据还没有被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据以后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。分包是指在出现粘包的时候咱们的接收方要进行分包处理。[color=red](在长链接中都会出现)[/color] [b]何时须要考虑半包的状况?[/b] 从备注中咱们了解到Socket内部默认的收发缓冲区大小大概是8K,可是咱们在实际中每每须要考虑效率问题,从新配置了这个值,来达到系统的最佳状态。 一个实际中的例子:用mina做为服务器端,使用的缓存大小为10k,这里使用的是短链接,全部不用考虑粘包的问题。 问题描述:在并发量比较大的状况下,就会出现一次接受并不能完整的获取全部的数据。 处理方式: 1.经过包头+包长+包体的协议形式,当服务器端获取到指定的包长时才说明获取完整。 2.指定包的结束标识,这样当咱们获取到指定的标识时,说明包获取完整。 [b]何时须要考虑粘包的状况?[/b] 1.当时短链接的状况下,不用考虑粘包的状况 2.若是发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包 3.若是双方创建链接,须要在链接后一段时间内发送不一样结构数据 处理方式: 接收方建立一预处理线程,对接收到的数据包进行预处理,将粘连的包分开 [color=red]注:粘包状况有两种,一种是粘在一块儿的包都是完整的数据包,另外一种状况是粘在一块儿的包有不完整的包[/color] [color=blue] 备注: [/color] 一个包没有固定长度,以太网限制在46-1500字节,1500就是以太网的MTU,超过这个量,TCP会为IP数据报设置偏移量进行分片传输,如今通常可容许应用层设置8k(NTFS系)的缓冲区,8k的数据由底层分片,而应用看来只是一次发送。windows的缓冲区经验值是4k,Socket自己分为两种,流(TCP)和数据报(UDP),你的问题针对这两种不一样使用而结论不同。甚至还和你是用阻塞、仍是非阻塞Socket来编程有关。 一、通讯长度,这个是你本身决定的,没有系统强迫你要发多大的包,实际应该根据需求和网络情况来决定。对于TCP,这个长度能够大点,但要知道,Socket内部默认的收发缓冲区大小大概是8K,你能够用SetSockOpt来改变。但对于UDP,就不要太大,通常在1024至10K。注意一点,你不管发多大的包,IP层和链路层都会把你的包进行分片发送,通常局域网就是1500左右,广域网就只有几十字节。分片后的包将通过不一样的路由到达接收方,对于UDP而言,要是其中一个分片丢失,那么接收方的IP层将把整个发送包丢弃,这就造成丢包。显然,要是一个UDP发包佷大,它被分片后,链路层丢失分片的概率就佷大,你这个UDP包,就佷容易丢失,可是过小又影响效率。最好能够配置这个值,以根据不一样的环境来调整到最佳状态。 send()函数返回了实际发送的长度,在网络不断的状况下,它毫不会返回(发送失败的)错误,最多就是返回0。对于TCP你能够字节写一个循环发送。当send函数返回SOCKET_ERROR时,才标志着有错误。但对于UDP,你不要写循环发送,不然将给你的接收带来极大的麻烦。因此UDP须要用SetSockOpt来改变Socket内部Buffer的大小,以能容纳你的发包。明确一点,TCP做为流,发包是不会整包到达的,而是源源不断的到,那接收方就必须组包。而UDP做为消息或数据报,它必定是整包到达接收方。 二、关于接收,通常的发包都有包边界,首要的就是你这个包的长度要让接收方知道,因而就有个包头信息,对于TCP,接收方先收这个包头信息,而后再收包数据。一次收齐整个包也能够,可要对结果是否收齐进行验证。这也就完成了组包过程。UDP,那你只能整包接收了。要是你提供的接收Buffer太小,TCP将返回实际接收的长度,余下的还能够收,而UDP不一样的是,余下的数据被丢弃并返回WSAEMSGSIZE错误。注意TCP,要是你提供的Buffer佷大,那么可能收到的就是多个发包,你必须分离它们,还有就是当Buffer过小,而一次收不完Socket内部的数据,那么Socket接收事件(OnReceive),可能不会再触发,使用事件方式进行接收时,密切注意这点。这些特性就是体现了流和数据包的区别。 [/size] 参照:[url]http://176170847.iteye.com/blog/819446(Socket粘包问题)[/url]