该文章写的有些乱,欢迎来喷 ! 另外文章后续不断更新中,请到原文地址查看更新。http://xiaorui.cc/?p=3198
只要做过服务开发的人都会遇到粘包的问题,跟着标题也知道我在忙啥,对的,我在用python写一个简单粗暴的web服务器,中间就遇到了一个粘包的问题,我会在后面说明下这造成这问题的原因。
理解TCP流的概念及粘包的现象
首先我们要明确一点,HTTP是基于TCP的上层应用协议,这是废话 ! tcp是数据流,这里有两个要点:
– 数据没有边界
你可以理解为水在一个水管里的流动,我们不知道哪段数据是一个我们需要的完整数据
– 收发有缓冲区
比如:当水从一端流到了另一端,我们在收数据的时候,不可能每来一滴水就处理一次,这个缓冲区就相当于有了一个水桶,再接了一定的水之后内核再给数据交到用户空间,这样可以大大提升性能。
那这里就有一个问题:由于这个特性,内核无法知道哪部分数据是你想要的,所以应用层拿到数据可能是完整数据,也可能是不完整或者一部分完整、一部分不完整(粘包),那么怎样能得到想要的数据呢?
数据协议可以理解为一种约定,按照这个约定来处理收到的数据进而得到想要的数据,这个过程就称为拆包,那问题又来了?
那么http会出现粘包的问题么?
粘包是因为双方没有协商好协议导致的,如果有一个协议支撑整个数据的流通,那么不应该出现粘包的问题。我上次写过一篇golang tcp server粘包的问题,后来也是用了一个简单的协议解决粘包的问题。
http的请求和响应是以message为单位的。解析http当然和其他的协议一样,需要考虑不符合协议的情况,400 Bad Request响应就是因为发给server的数据包不合理。
http/1.1之前,每次请求响应都使用新的tcp连接,所以不需要考虑数据边界问题。
http/1.1支持keep-alive,多个message可以使用同一个tcp连接,所以要有确定边界的机制。
按照此类请求头的内容来解析就可以了,解析失败就返回400关连接,让客户端自己重试。
或者无视客户端的Connection: keep-alive头,一律返回Connection: close,就不用操心这个问题了。
那么http协议是如何定义边界,换个说法是怎么解决http粘包的问题。
因为web的http可以理解为一个公共的数据协议,在实现一个web服务器的时候,就必需按照这个协议来进行数据处理,这样保证在应用层获取到数据是完整的数据,http是典型的包头+包体的这个一个约定格式.
– 通过\r\n\r\n来区分包头和包体
– 包头通过\r\n来区分不同的信息
看个示例:
POST / HTTP/1.1\r\n Host: www.xiaorui.cc/search\r\n Content-Length: 19\r\n Connection: keep-alive\r\n \r\n\r\n kw=fengyun&range=10
对于大数据传输是怎么解决的。
通常如果 Content-Length 比实际长度短,会造成内容被截断;如果比实体内容长,会造成浏览器pending。持久链接必须要加Content-length,而且要精确算好大小,如果对于无法计算大小的respone,可以用Transfer-Encoding: chunked模式。 他适用于那种边取计算边发送的场景。
Transfer-Encoding chunked的格式:
字节数(16进制来表示) 具体内容
最后以字节大小为0的块为结束,不用包含任何数据。当浏览器看到是0字节,就不再组装数据包.
HTTP/1.1 200 OK\r\n Transfer-Encoding: chunked\r\n \r\n b\r\n 01234567890\r\n 5\r\n 12345\r\n 0\r\n \r\n
另外我们要注意 multipart/form-data的Content-Type , multipart一般是用来上传文件的,大部分的http client已经实现了对该模式的支持。
实现也相对简单,首先我们估算一下文件的大小,然后传递一个boundary数据组分隔符,服务端会根据Content-Length和boundary来获取数据。
Content-Length: 1999 Content-Type: multipart/form-data; boundary=xxxxxxx
文章部分内容抄袭链接:
https://www.zhihu.com/question/24598268
END