刨根问底 HTTP 和 WebSocket 协议(上)
那天和boss聊天,不经意间提到了Meteor,然后聊到了WebSocket,然后就有了以下对话,不得不说,看问题的方式不同,看到的东西也会大不相同。
A:Meteor是一个很新的开发框架,我觉得它设计得十分巧妙。
B:怎么个巧妙之处?
A:它的前后端全部使用JS,做到了真正的前后端统一;前端浏览器里存有一份后台开放出来的数据库的拷贝,快;使用WebSocket协议来做数据传输协议,来同步前后端的数据库,实现了真正的实时同步。
B:哦?WebSocket是什么东西?真实时?那底层是不是还是轮训?和HTTP的长连接有什么不同?
A:(开始心虚)它是一个新的基于TCP的应用层协议,只需要一次连接,以后的数据不需要重新建立连接,可以直接发送,它是基于TCP的,属于和HTTP相同的地位(呃,开始胡诌了),底层不是轮训,和长连接的区别……这个就不清楚了。
B:它的传输过程大致是什么样子的呢?
A:首先握手连接(又是胡诌),好像可以基于HTTP建立连接(之前用过Socket.io,即兴胡诌),建立了连接之后就可以传输数据了,还包括断掉之后重连等机制。
B:看起来和HTTP长连接做的事情差不多嘛,好像就是一种基于HTTP和Socket的协议啊。
A:呃……(我还是回去看看书吧)
有时候看事情确实太流于表面,了解到了每个事物的大致轮廓,但不求甚解,和朋友聊天说出来也鲜有人会刨根问底,导致了很多基础知识并不牢靠,于是回来大致把HTTP和WebSocket协议的RFC文档(RFC2616 和 RFC6455),刚好对HTTP的传输过程一直有点模糊,这里把两个协议的异同总结一下。
协议基础
仔细去看这两个协议,其实都非常简单,但任何一个事情想做到完美都会慢慢地变得异常复杂,各种细节。这里只会简单地描述两个协议的结构,并不会深入到很深的细节之处,对于理解http已经足够了。
HTTP
HTTP的地址格式如下:
http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
协议和host不分大小写
HTTP消息
一个HTTP消息可能是request或者response消息,两种类型的消息都是由开始行(start-line),零个或多个header域,一个表示header域结束的空行(也就是,一个以CRLF为前缀的空行),一个可能为空的消息主体(message-body)。一个合格的HTTP客户端不应该在消息头或者尾添加多余的CRLF,服务端也会忽略这些字符。
header的值不包括任何前导或后续的LWS(线性空白),线性空白可能会出现在域值(filed-value)的第一个非空白字符之前或最后一个非空白字符之后。前导或后续的LWS可能会被移除而不会改变域值的语意。任何出现在filed-content之间的LWS可能会被一个SP(空格)代替。header域的顺序不重要,但建议把常用的header放在前边(协议里这么说的)。
Request 消息
RFC2616中这样定义HTTP Request 消息:
Request = Request-Line*(( general-header | request-header(跟本次请求相关的一些header)| entity-header ) CRLF)(跟本次请求相关的一些header)CRLF[ message-body ]
一个HTTP的request消息以一个请求行开始,从第二行开始是header,接下来是一个空行,表示header结束,最后是消息体。
请求行的定义如下:
//请求行的定义
Request-Line = Method SP Request-URL SP HTTP-Version CRLF
//方法的定义
Method = "OPTIONS" | "GET" | "HEAD" |"POST" |"PUT" |"DELETE" |"TRACE" |"CONNECT" | extension-method
//资源地址的定义
Request-URI ="*" | absoluteURI | abs_path | authotity(CONNECT)
Request消息中使用的header可以是general-header或者request-header,request-header(后边会讲解)。其中有一个比较特殊的就是Host,Host会与reuqest Uri一起来作为Request消息的接收者判断请求资源的条件,方法如下:
如果Request-URI是绝对地址(absoluteURI),这时请求里的主机存在于Request-URI里。任何出现在请求里Host头域值应当被忽略。
假如Request-URI不是绝对地址(absoluteURI),并且请求包括一个Host头域,则主机由该Host头域值决定。
假如由规则1或规则2定义的主机是一个无效的主机,则应当以一个400(错误请求)错误消息返回。
Response 消息
响应消息跟请求消息几乎一模一样,定义如下:
Response = Status-Line *(( general-header | response-header | entity-header ) CRLF) CRLF[ message-body ]
可以看到,除了header不使用request-header之外,只有第一行不同,响应消息的第一行是状态行,其中就包含大名鼎鼎的返回码。
Status-Line的内容首先是协议的版本号,然后跟着返回码,最后是解释的内容,它们之间各有一个空格分隔,行的末尾以一个回车换行符作为结束。定义如下:
Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
返回码
返回码是一个3位数,第一位定义的返回码的类别,总共有5个类别,它们是:
- 1xx: Informational - Request received, continuing process- 2xx: Success - The action was successfully received,understood, and accepted- 3xx: Redirection - Further action must be taken in order tocomplete the request- 4xx: Client Error - The request contains bad syntax or cannotbe fulfilled- 5xx: Server Error - The server failed to fulfill an apparentlyvalid request
RFC2616中接着又给出了一系列返回码的扩展,这些都是我们平时会用到的,但是那些只是示例,HTTP1.1不强制通信各方遵守这些扩展的返回码,通信各方在返回码的实现上只需要遵守以上边定义的这5种类别的定义,意思就是,返回码的第一位要严格按照文档中所述的来,其他的随便定义。
任何人接收到一个不认识的返回码xyz,都可以把它当做x00来对待。对于不认识的返回码的响应消息,不可以缓存。
Header
RFC2616中定义了4种header类型,在通信各方都认可的情况下,请求头可以被扩展的(可信的扩展只能等到协议的版本更新),如果接收者收到了一个不认识的请求头,这个头将会被当做实体头。4种头类型如下:
1.通用头(General Header Fields):可用于request,也可用于response的头,但不可作为实体头,只能作为消息的头。
general-header = Cache-Control ; Section 14.9| Connection ; Section 14.10| Date ; Section 14.18| Pragma ; Section 14.32| Trailer ; Section 14.40| Transfer-Encoding ; Section 14.41| Upgrade ; Section 14.42| Via ; Section 14.45| Warning ; Section 14.46
2.请求头(Request Header Fields):被请求发起端用来改变请求行为的头。
request-header = Accept ; Section 14.1| Accept-Charset ; Section 14.2| Accept-Encoding ; Section 14.3| Accept-Language ; Section 14.4| Authorization ; Section 14.8| Expect ; Section 14.20| From ; Section 14.22| Host ; Section 14.23| If-Match ; Section 14.24| If-Modified-Since ; Section 14.25| If-None-Match ; Section 14.26| If-Range ; Section 14.27| If-Unmodified-Since ; Section 14.28| Max-Forwards ; Section 14.31| Proxy-Authorization ; Section 14.34| Range ; Section 14.35| Referer ; Section 14.36| TE ; Section 14.39| User-Agent ; Section 14.43
3.响应头(Response Header Fields):被服务器用来对资源进行进一步的说明。
response-header = Accept-Ranges ; Section 14.5| Age ; Section 14.6| ETag ; Section 14.19| Location ; Section 14.30| Proxy-Authenticate ; Section 14.33| Retry-After ; Section 14.37| Server ; Section 14.38| Vary ; Section 14.44| WWW-Authenticate ; Section 14.47
4.实体头(Entity Header Fields):如果消息带有消息体,实体头用来作为元信息;如果没有消息体,就是为了描述请求的资源的信息。
entity-header = Allow ; Section 14.7| Content-Encoding ; Section 14.11| Content-Language ; Section 14.12| Content-Length ; Section 14.13| Content-Location ; Section 14.14| Content-MD5 ; Section 14.15| Content-Range ; Section 14.16| Content-Type ; Section 14.17| Expires ; Section 14.21| Last-Modified ; Section 14.29| extension-header
消息体(Message Body)和实体主体(Entity Body)
如果有Transfer-Encoding头,那么消息体解码完了就是实体主体,如果没有Transfer-Encoding头,消息体就是实体主体。
message-body = entity-body| <entity-body encoded as per Transfer-Encoding>
在request消息中,消息头中含有Content-Length或者Transfer-Encoding,标识会有一个消息体跟在后边。如果请求的方法不应该含有消息体(如OPTION),那么request消息一定不能含有消息体,即使客户端发送过去,服务器也不会读取消息体。
在response消息中,是否存在消息体由请求方法和返回码来共同决定。像1xx,204,304不会带有消息体。
消息体的长度
消息体长度的确定有一下几个规则,它们顺序执行:
所有不应该返回内容的Response消息都不应该带有任何的消息体,消息会在第一个空行就被认为是终止了。
如果消息头含有Transfer-Encoding,且它的值不是identity,那么消息体的长度会使用chunked方式解码来确定,直到连接终止。
如果消息头中有Content-Length,那么它就代表了entity-length和transfer-length。如果同时含有Transfer-Encoding,则entity-length和transfer-length可能不会相等,那么Content-Length会被忽略。
如果消息的媒体类型是multipart/byteranges,并且transfer-length也没有指定,那么传输长度由这个媒体自己定义。通常是收发双发定义好了格式, HTTP1.1客户端请求里如果出现Range头域并且带有多个字节范围(byte-range)指示符,这就意味着客户端能解析multipart/byteranges响应。
如果是Response消息,也可以由服务器来断开连接,作为消息体结束。
从消息体中得到实体主体,它的类型由两个header来定义,Content-Type和Content-Encoding(通常用来做压缩)。如果有实体主体,则必须有Content-Type,如果没有,接收方就需要猜测,猜不出来就是用application/octet-stream。
HTTP 连接
HTTP1.1的连接默认使用持续连接(persistent connection),持续连接指的是,有时是客户端会需要在短时间内向服务端请求大量的相关的资源,如果不是持续连接,那么每个资源都要建立一个新的连接,HTTP底层使用的是TCP,那么每次都要使用三次握手建立TCP连接,将造成极大的资源浪费。
持续连接可以带来很多的好处:
使用更少的TCP连接,对通信各方的压力更小。
可以使用管道(pipeline)来传输信息,这样请求方不需要等待结果就可以发送下一条信息,对于单个的TCP的使用更充分。
流量更小
顺序请求的延时更小。
不需要重新建立TCP连接就可以传送error,关闭连接等信息。
HTTP1.1的服务器使用TCP的流量控制来控制HTTP的流量,HTTP1.1的客户端在收到服务器连接中发过来的error信息,就要马上关闭此链接。关于HTTP连接还有很多细节,之后再详述。
WebSocket
只从RFC发布的时间看来,WebSocket要晚近很多,HTTP 1.1是1999年,WebSocket则是12年之后了。WebSocket协议的开篇就说,本协议的目的是为了解决基于浏览器的程序需要拉取资源时必须发起多个HTTP请求和长时间的轮训的问题……而创建的。
待续
本来是打算在一篇文章里把HTTP和WebSocket两个协议的大致细节理出来,然后进行对比。可是写着写着就发现篇幅可能会比较长,读起来就不那么友好了,那么刚好就再写第二篇吧。第二篇里会将WebSocket的大致情况描述一下,然后和HTTP适用的场景进行对比。
刨根问底 HTTP 和 WebSocket 协议(上)相关推荐
- 使用Nginx为TCP/WebSocket协议做反向代理和几个易踩的坑
通常,我们使用Nginx为后端WEB服务做反向代理或负载均衡,但如果我们的后端服务,并不是HTTP/HTTPS协议,而是TCP协议或WebSocket协议呢 最近遇到一个需求,我们的HTTPS以及MQ ...
- 八问WebSocket协议:为你快速解答WebSocket热门疑问
一.引言 WebSocket是一种比较新的协议,它是伴随着html5规范而生的,虽然还比较年轻,但大多主流浏览器都已经支持.它使用方面.应用广泛,已经渗透到前后端开发的各种场景中. 对http一问一答 ...
- WebSocket 协议
1.1 背景知识 由于历史原因,在创建一个具有双向通信机制的 web 应用程序时,需要利用到 HTTP 轮询的方式.围绕轮询产生了 "短轮询" 和 "长轮询". ...
- WebSocket协议分析
点击上方↑↑↑蓝字[协议分析与还原]关注我们 " 解析websocket数据格式." 好久不见,一晃一年又过去了,祝大家新年好运. 今天,给大家分析一个常见的协议--WebSock ...
- 用node实现websocket协议
协议 WebSocket是一种基于TCP之上的客户端与服务器全双工通讯的协议,它在HTML5中被定义,也是新一代webapp的基础规范之一. 它突破了早先的AJAX的限制,关键在于实时性,服务器可以主 ...
- 基于WebSocket协议的iOS端即时聊天
好好好久没有在cnblogs上写博客,不过在这里写的最早的一篇博客的时间戳,真是时间久远啊,那时候还没毕业.不在cnblogs的期间,在github pages.简书上写过博客,github page ...
- websocket python爬虫_python实现基于websocket协议的网络爬虫
WebSocket是一种在单个TCP连接上进行全双工通信的协议,简单来说就是建立一个TCP长连接之后,你可以服务器随时可以给客户端发送消息,客户端随时可以给服务器发送消息,而以前只能是客户端给服务器发 ...
- 【译】 WebSocket 协议第十二章——使用其他规范中的WebSocket协议
概述 本文为 WebSocket 协议的第十二章,本文翻译的主要内容为如何使用其他规范中的 WebSocket 协议. 使用其他规范中的WebSocket协议(协议正文) WebSocket协议旨在由 ...
- websocket中发生数据丢失_tcp协议;websocket协议;同源策略和跨域
tcp协议 为什么连接的时候是三次握手,关闭的时候却是四次握手? 答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文.其中ACK报文是用来应答的,SYN报 ...
最新文章
- 【SQL Server学习笔记】变更数据捕获(Change Data Capture)
- 发现藏匿在加密流量中的威胁
- 【搜索树】高级打字机(luogu 1383)
- jeecgboot前端开发_Jeecg-Boot 技术文档
- 开源社区Review代码步骤
- Zabbix的应用(6)----常见错误
- 人工智能(2)---从0开始搭建产品经理的AI知识框架:计算机视觉
- 构造器模式(Builder模式)
- BitTorrent 简介
- java钝化_javaEE之--------------session的活化与钝化
- java微信公众平台开发教程分享
- 【其他笔记】 双屏显示分辨率低下、屏幕闪烁、暗沉等问题。
- 学习python需要很多数学知识吗_Python数据分析需要学习哪方面的数学知识
- winform设置透明图片
- 百度之星资格赛1003 度度熊与邪恶大魔王(dp)--2017
- 3dsmax 2020下载安装
- springboot/springcloud整合mybatis(mysql)
- 完美更新安装TensorFlow-gpu
- js 自由变换图片尺寸
- 编译原理三大圣经——龙书、虎书、鲸书
热门文章
- Shell脚本——业务上线前如何去扫描指定网段的所有IP地址呢?
- 全志T507核心板4路AHD摄像头实现方案-飞凌嵌入式
- 2022-2028全球与中国微生物强化采油市场现状及未来发展趋势
- 读后感----《我奋斗了18年才和你坐在一起喝咖啡》
- 服务器ssd硬盘接笔记本,让你的老本满血复活,笔记本固态硬盘升级指南
- 解决:flex布局之 flex-wrap:wrap 自动换行属性,导致上下两行div中间有空行
- 取供应商类型所在的快码
- python如何写生日快乐说说_一句祝自己生日快乐的说说
- R语言时间序列平稳性几种单位根检验(ADF,KPSS,PP)及比较分析
- Jenkins 企业微信消息通知