TCP是一个“流”协议,所谓流,就是没有界限的一长串二进制数据。TCP做为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际状况进行数据包的划分,因此在业务上认为是一个完整的包,可能会被TCP拆分红多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。git
假设应用层协议是httpgithub
我从浏览器中访问了一个网站,网站服务器给我发了200k的数据。创建链接的时候,通告的MSS是50k,因此为了防止ip层分片,tcp每次只会发送50k的数据,一共发了4个tcp数据包。若是我又访问了另外一个网站,这个网站给我发了100k的数据,此次tcp会发出2个包,问题是,客户端收到6个包,怎么知道前4个包是一个页面,后两个是一个页面。既然是tcp将这些包分开了,那tcp会将这些包重组吗,它送给应用层的是什么?算法
这是我本身想的一个场景,正式一点讲的话,这个现象叫拆包。json
咱们再考虑一个问题。浏览器
tcp中有一个negal算法,用途是这样的:通讯两端有不少小的数据包要发送,虽然传送的数据不多,可是流程一点没少,也须要tcp的各类确认,校验。这样小的数据包若是不少,会形成网络资源很大的浪费,negal算法作了这样一件事,当来了一个很小的数据包,我不急于发送这个包,而是等来了更多的包,将这些小包组合成大包以后一并发送,不就提升了网络传输的效率的嘛。这个想法收到了很好的效果,可是咱们想一下,若是是分属于两个不一样页面的包,被合并在了一块儿,那客户那边如何区分它们呢?服务器
这就是粘包问题。网络
从粘包问题咱们更能够看出为何tcp被称为流协议,由于它就跟水流同样,是没有边界的,没有消息的边界保护机制,因此tcp只有流的概念,没有包的概念。并发
咱们还须要有两个概念tcp
长链接: Client方与Server方先创建通信链接,链接创建后不断开, 而后再进行报文发送和接收。
短链接:Client方与Server每进行一次报文收发交易时才进行通信链接,交易完毕后当即断开链接。此种方式经常使用于一点对多点 通信,好比多个Client链接一个Server.
下面咱们揭晓答案:性能
我想象的关于粘包的场景是不对的,http链接是短链接,请求以后,收到回答,立马断开链接,不会出现粘包。
拆包现象是有可能存在的
既然拆包现象可能存在,若是遇到了,那么该如何处理呢?这里提供两种方法
经过包头+包长+包体的协议形式,当服务器端获取到指定的包长时才说明获取完整。
指定包的结束标识,这样当咱们获取到指定的标识时,说明包获取完整。
咱们从上面的分析看到,虽然像http这样的短链接协议不会出现粘包的现象,可是一旦创建了长链接,粘包仍是有可能会发生的。
网上的处理方法有不少,这里不列举了,但你们看这些处理方法,都会发现,这些方法并很差,都会作一些牺牲。好比禁用negal算法,就是以网络性能做为牺牲。
对于第一种状况,服务器的处理流程能够是这样的:当客户端与服务器的链接创建成功之后,服务器不断读取客户端发送过来的数据,当客户端与服务器链接断开之后,服务器知道已经读完了一条消息,而后进行解码和后续处理。对于第二种状况,若是按照上面相同的处理逻辑来处理,那就有问题了,咱们来看看第二种状况下客户端发送的两条消息递交到服务端有可能出现的状况:
服务器一共读到两个数据包,第一个包包含客户端发出的第一条消息的完整信息,第二个包包含客户端发出的第二条消息,那这种状况比较好处理,服务器只须要简单的从网络缓冲区去读就行了,第一次读到第一条消息的完整信息,消费完再从网络缓冲区将第二条完整消息读出来消费。
服务器一共就读到一个数据包,这个数据包包含客户端发出的两条消息的完整性,这个时候基于以前逻辑实现的服务器就懵了。由于服务器不知道第一条消息从哪结束以及第二条消息从哪开始,这是发生了TCP粘包。
服务器一共收到了两个数据包,
第一个数据包只包含了第一条消息的一部分,第一条消息的后半部分和第二条消息都在第二个数据包中;
或者第一个数据包包含了第一条消息的完整信息和第二条消息的一部分信息,第二个数据包包含了第二条消息的剩下部分,这种状况实际上是发送了TCP拆包。
由于发生了一条消息被拆分在了两个包里面发送了,一样上面的服务器逻辑对于这种状况是很差处理的。
咱们知道TCP是以流动的方式传输数据的,传输的最小单位为一个报文段(Segment)。TCP Header中有个Options标识位。常见的标识位为MSS(Maximum Segment Size)指的是,链接层每次传输的数据有个最大限制MTU(Maximum Transmission Unit),通常是1500bit,超过这个量要分红多个报文段,MSS则是这个最大限制减去TCP的header,光是要传输的数据的大小,通常为1460bit。换算成字节,也就是180多字节。
TCP为提升性能,发送端会将须要发送的数据发送到缓冲区,等待缓冲区满了之后,再将缓冲中的数据发送到接收方。同理,接收方也有缓冲区这样的机制来接受数据。
发生TCP粘包、拆包主要是如下缘由:
一、应用程序写入数据大于套接字缓冲区大小,会发生拆包。
二、应用程序写入数据小于套接字缓冲区大小,网卡将应用屡次写入的数据发送到网络上,这将会发送粘包。
三、进行MSS(最大报文长度)大小的TCP分段,当TCP报文长度-TCP header长度>MSS 的时候会发生拆包。
四、接收方法不及时读取套接字缓冲区数据,这将发生粘包。
......
既然知道TCP是无界的数据流,且协议自己没法避免粘(拆)包的发生。那咱们只能再应用层数据协议上加以控制。一般再制定传输数据时,可使用以下方法:
一、使用带消息头的协议。消息头存储消息开始标识及消息长度信息,服务器获取消息头的时候解析出消息长度,而后向后读取该长度的内容。
二、设置定长消息。服务器每次读取既定长度的内容做为一条完整消息。
三、设置消息边界。服务器从网络流中按消息编辑分离出消息内容。
a)先基于第3种方法,假设区分数据边界的标识为换行符"\n"(【注意】:请求数据自己内部不能包含换行符),数据格式为JSON。以下是一个符合该规则的请求包。
{"type":"message","content":"hello"}\n
(\n表明一个请求的结束)
b)基于第1种方法,能够制定,首部固定10的字节长度用来保存整个数据包长度,位数不够补0的数据协议。
0000000036{"type":"message","content":"hello"}
c)基于第1种方法。能够制定,首部4字节网络字节序unsigned int,标记整个包的长度
****{"type":"message","content":"hello all"}
其中首部4字节*表明一个网阔字节序的unsigned int数据,为不可见字符,紧接着是JSON的数据格式的包体数据。