UNIX网络编程——Socket粘包问题

1、两个简单概念长链接与短链接:
一、长链接
算法

    Client方与Server方先创建通信链接,链接创建后不断开, 而后再进行报文发送和接收。编程

二、短链接 网络

    Client方与Server每进行一次报文收发交易时才进行通信链接,交易完毕后当即断开链接。此种方式经常使用于一点对多点 通信,好比多个Client链接一个Server。tcp

 

二 、何时须要考虑粘包问题?函数

       若是利用tcp每次发送数据,就与对方创建链接,而后双方发送完一段数据后,就关闭链接,这样就不会出现粘包问题(由于只有一种包结构,相似于http协议)。关闭链接主要要双方都发送close链接(参考tcp关闭协议)。如:A须要发送一段字符串给B,那么A与B创建链接,而后发送双方都默认好的协议字符如"hello give me sth abour yourself",而后B收到报文后,就将缓冲区数据接收,而后关闭链接,这样粘包问题不用考虑到,由于你们都知道是发送一段字符。
      
性能

       若是发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包。
优化


       若是双方创建链接,须要在链接后一段时间内发送不一样结构数据,如链接后,有好几种结构
 1)"hello give me sth abour yourself"
 2)"Don't give me sth abour yourself"
      那这样的话,若是发送方连续发送这个两个包出去,接收方一次接收可能会是"hello give me sth abour yourselfDon't give me sth abour yourself" 这样接收方就傻了,究竟是要干吗?不知道,由于协议没有规定这么诡异的字符串,因此要处理把它分包,怎么分也须要双方组织一个比较好的包结构,因此通常可能会在头加一个数据长度之类的包,以确保接收。
 
spa

3、 粘包出现缘由:设计

       在TCP传输中会出现粘包,UDP不会出现粘包,由于它有消息边界。
进程

  • 发送端须要等发送缓冲区满才发送出去,形成粘包;
  • 接收方不及时接收缓冲区的包,形成多个包接收。


4、解决办法:

       为了不粘包现象,可采起如下三种措施:

  • 对于发送方引发的粘包现象,用户可经过编程设置来避免,TCP提供了强制数据当即传送的操做指令push,TCP软件收到该操做指令后,就当即将本段数据发送出去,而没必要等待发送缓冲区满;
  • 对于接收方引发的粘包,则可经过优化程序设计、精简接收进程工做量、提升接收进程优先级等措施,使其及时接收数据,从而尽可能避免出现粘包现象;
  • 由接收方控制,将一包数据按结构字段,人为控制分屡次接收,而后合并,经过这种手段来避免粘包。

       以上提到的三种措施,都有其不足之处:

       第一种编程设置方法虽然能够避免发送方引发的粘包,但它关闭了优化算法,下降了网络发送效率,影响应用程序的性能,通常不建议使用。

       第二种方法只能减小出现粘包的可能性,但并不能彻底避免粘包,当发送频率较高时,或因为网络突发可能使某个时间段数据包到达接收方较快,接收方仍是有可能来不及接收,从而致使粘包。

       第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。



       一个包没有固定长度,以太网限制在46-1500字节,1500就是以太网的MTU,超过这个量,TCP会为IP数据报设置偏移量进行分片传输,如今通常可容许应用层设置8k(NTFS系)的缓冲区,8k的数据由底层分片,而应用看来只是一次发送。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),可能不会再触发,使用事件方式进行接收时,密切注意这点。这些特性就是体现了流和数据包的区别。

相关文章
相关标签/搜索