03.07 WebSocket是什么原理?为什么可以实现持久连接?

用户68736146


WebSocket是一种不同于HTTP的协议,重要的是它完美弥补了http协议在某些场合下的重大不足。

接下来,我来简单介绍下websocket与http的区别。

http/https请求是目前最广泛使用的网络通信协议,但是它们有一个非常大局限性,那就是请求只能由agent发起,server只能被动的等待请求,而且一次请求就是一个response和request对应。虽然在HTTP 1.1中进行了改进,增加了keep-alive,出现了长连接这样的概念,但是仍然是一个request对应一个response,这在request中携带大量header信息,而response中没什么有用信息的时候,无疑是对通信资源的浪费。

也许你觉得这么说有点抽象,那么我们举个例子来说明,我们在浏览器上用QQ聊天,如果浏览器作为agent使用的是http协议与server端通信,那么它需要定时去访问server(轮询),问它,喜欢的女神有没有回复我的信息啊。可是女神可能去洗澡了,手机没带进浴室,于是browser这个agent就不停的发一个大脑袋的request去server,每次拿回来的却都是干瘪瘪的response。这时候,如果你一边看电影一边等女神的回复,那么结果可能就是电影很卡,女神也没有消息。

这个时候WebSocket协议就出现了。如果使用的是websocket协议,在登陆了网页版QQ之后,这个浏览器就会作为agent向server发起请求,建立一个连接,在这个连接建立期间,是可以进行双工通信的,就是说agent可以主动把消息发送给server,server也能在收到女神回复后,第一时间把消息传递到你的屏幕上,减少了无意义的轮询消耗,同时也保证了等女神回复期间电影不卡,不会无聊。甚至也可以简单的理解,只需要经过一次HTTP请求的连接建立,就可以进行源源不断的信息传送了。

说到实现长连接,说白了,websocket的设计者在最初就是没想过像http协议那样谨慎小气,建立了连接说完一句话就跑。否则任何基于TCP的应用协议,都是可以进行长连接通信的。

也许正是当初http的如此设计,才给了WebSocket如今的用武之地。

以上是我的浅见,欢迎各位在下方评论区与我沟通交流。

我是苏苏思量,来自BAT的Java开发工程师,每日分享科技类见闻,欢迎关注我,与我共同进步。


一个存在感小透明


解释WebSocket为什么可以实现持久连接,还是先介绍一些什么是WebSocket,以及它产生的原因是什么。

是什么WebSocket?

WebSocket是一个协议。

协议就是王八的屁股——规定,你可以不遵守,但是别人都遵守你不遵守,你就跟别人玩不到一块去。

WebSocket协议在2008年诞生,2011年成为了国际标准,现在绝大部分浏览器都已经支持了。

产生的原因

其实原因是为了弥补HTTP协议的不足,因为HTTP协议只能由客户端发起请求,并且一个Request要对应一个Response(长链接也是如此)。

举个例子:

  • 我之前做过一个小项目,只有一个页面,展示的是各个分公司当天的业绩,就是挣了多少钱。后台服务是Java,数据库是Mysql,有一张汇总表,内容大概是北京-100万,上海-80万这样的。


  • 流程很简单,HTML页面发起请求到Java,Java访问数据库查询数据,再返回给HTML展示。但是Mysql中的汇总表的数据,是不定期更新的,可能10分钟,可能20分钟。

  • 最简单的做法:HTML中用JS设置一个定时轮询(Polling),每隔几秒去发起一次请求,获取最新的数据,如果数据没有变化,页面也保持变化;缺点很明显,前端发起的很多请求都是无效的(因为数据没有变化)。

WebSocket的通信原理

而WebSocket,是要在客户端和服务器之间,建立一个通道,建立一个【真的长链接】。

WebSocket是要借助于HTTP,完成一部分工作。我在找到一个WebSocket在线测试的网站,打开之后查看请求和响应(具体网站连接也看下面图片中的信息)。

可以看出来协议里面多了两行:

Upgrade: websocket

Connection: Upgrade

这个就是关键内容了,通过请求告诉服务器:看清楚咯,请求要用WebSocket协议。


服务器会回答:好的,那我就切换到WebSocket协议啦。

到了这时候,HTTP完成它所有工作,客户端和服务器已经建立好了一个通道,下面就按照WebSocket协议进行了,服务端也就可以主动推送信息给客户端(双向),并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接,故此WebSocket也就实现了持久连接。


希望我的回答,能够帮助到你!我将持续分享Java开发、架构设计、程序员职业发展等方面的见解,希望能得到你的关注。


会点代码的大叔


对于Web项目开发,往往需要前端和后端进行数据通信交互,在以前如果前端要和后端通信往往是通过Ajax这种异步方式,但这样就存在一些弊端,比如说实时性要求高的项目(如在线聊天室)就不好使用Ajax这种方式,而应该使用“服务器推”技术。

在WebSocket出来之前,实现“服务器推”基本上都是野路子,不够优雅,而WebSocket一经推出,就可以便捷实现长连接了。

WebSocket是一种新的协议

WebSocket是一种借鉴了HTTP协议的新协议,随着HTML5一起推出了WebSocket API(WebSocket不属于HTML5,HTML5中所谓的“WebSocket”其实是指“WebSocket API”)。WebSocket和HTTP没有必然联系,不能单纯的把WebSocket理解成HTTP协议的升级,不过WebSocket为了兼容现有的浏览器握手规范,借鉴了HTTP协议规范!不过WebSocket协议解决了HTTP协议的被动性,可以实现长连接。

WebSocket为什么可以持久化连接?

WebSocket协议实现了浏览器与服务器的全双工通信,它通过已建立的TCP连接来传输数据,WebSocket协议的特点是:

  • WebSocket协议名为“ws”,它有对应的安全连接协议名为“wss”;

  • 先像TCP一样建立连接,WebSocket基于TCP协议;

  • 客户端和服务端握手过程中,客户端会发送一个包含 Upgrade:请求头的HTTP请求,告诉服务器建立一个WebSocket连接;

  • 服务端收到请求后,会将协议转换为WebSocket协议,且在协议转换过程中该连接没有中断;

  • 当第一个HTTP Request请求建立TCP连接之后,以后的数据交换就不需要再次发送HTTP Request了,因此这个连接也就变成了长连接;

  • 不同URI可复用同一个WebSocket连接。

以上就是我的观点,对于这个问题大家是怎么看待的呢?欢迎在下方评论区交流 ~ 我是科技领域创作者,十年互联网从业经验,欢迎关注我了解更多科技知识!

网络圈


先说为什么WebSocket可以实现持久连接。这个前面已经有人回答了:WebSocket是基于TCP的,TCP本身就支持长连接。

那么问题就变成了,为什么早期的HTTP不支持长连接?以及HTTP1.1开始默认支持长连接了,为什么还要WebSocket?


为什么早期的HTTP不支持长连接?

个人认为有几个方面的原因:

  • 网络环境:早期网络,带宽不高,速度也不咋的,长连接浪费资源

  • 交互方式:早期的网页以浏览为主,交互性不强,可能页面展示出来后,阅读需要花较长的时间,保持长连接意义不大。Web2.0开始,交互性越来越强,页面展示后,可能还会有各种交互。
  • 底层IO实现:epoll是2003年,在linux的2.6版本的内核中加入的。在此之前的select和poll都有一个问题,都是通过遍历文件描述符来获取已经就绪的socket的,随着连接数的增加,性能会越来越低。


HTTP1.1开始默认支持长连接了,为什么还要WebSocket?

要回答这个问题,需要先来看HTTP的设计目的是什么。可以参考Roy Thomas Fielding博士的博士论文《Architectural Styles and the Design of Network-based Software Architectures》,也就是传说中的REST,这是HTTP的设计指导原则。

Roy博士认为一个良好设计的 Web 应用应该这样运转:一个由网页组成的网络(一个虚拟状态机),用户通过选择链接在应用中前进(状态迁移),导致下一个页面(应用的下一个状态的表述)被转移给用户,并且呈现给他们,以便他们来使用。

可以看出普通Web应用是个标准的CS架构,且通过Client来操作Server的「状态变迁」!Server只对Client的请求做出响应,而不会主动的响应Client。

也就是说HTTP是「单向的」!但实际场景中还是有Server向Client推送消息的情况,所以就有了类似ajax轮询,Comet这样的方式。但实际还是需要Client主动发送请求,Server响应,只是响应的时机不同。

WebSocket就是为了解决这个问题的。怎么解决的呢?定义了一套新的协议!只不过以HTTP做了个嫁衣。


WebSocket原理

最后来说说WebSocket是如何实现的!

协议传输方式其实都是大同小异,可以回想一下编写Socket的代码逻辑:

  • 建立连接

  • Client组装请求协议消息发送
  • Server接收协议消息,进行解析处理
  • 然后再组装响应协议消息,发送回去

Server端直接向Client推送消息的流程也是类似的。区别就是Client与Server是「一对多」的关系。也就是说Server端有多个连接。

那要Server端能够推送消息给指定Client端,无非就是Server端需要维护这个连接池。你可以把这个连接池想象成一个key,value键值对。key是ClientId,key是Connection。只需要根据ClientId找到对应的Connection,实际发送方式和Client是一样的。


WebSocket有一个比较特别的地方就是,它是通过HTTP来建立连接的:

  • Client首先通过HTTP发送一个GET请求来告诉Server,我们能通过WebSocket来进行通信吗?

  • Server答复101,表示好的
  • 然后Client和Server就开始使用WebSocket开始通信了

这个流程是不是似曾相识:

  • 你在fb上撩到个「外国妹子」,你问道:Can you speak Chinese?

  • 对方回答:Yes

  • 然后你们就开始用中文撩了~「大妹子,你哪人啊?」「东北的」「这么巧,俺也是」......


架构思维


首先需要明白:基于TCP的应用层协议,只要设计者愿意,都是可以实现持久连接的。

你问的方式,大概是在和HTTP做比较。

HTTP

http协议是请求应答式的文本协议,协议设计就是Client-Server模式,出发点是服务端为客户端提供资源。http服务端只能监听和响应来自客户端的请求,http客户端只能发起请求接受响应,这个是HTTP协议本身的设计,双向通信不在设计的考虑之内。

关于Http协议,额外说点:

HTTP1.0/0.9

不支持keep-alive,要完成一次HTTP请求,需要建立一个新的TCP连接,然后发送http请求,待接收响应后关闭连接。

HTTP1.1

默认使用keep-alive,一次HTTP请求完成后不会关闭TCP连接,会继续为下一个HTTP请求服务(可以类比数据库连接池和线程池的设计),减小建立和关闭TCP连接的开销(三次握手四次挥手)。当然闲置超时后也会关闭。并非楼下所说的“把多个HTTP请求合并为一个”。

HTTP协议的设计无法实现对TCP通道的分用和复用。因为HTTP协议没有请求的唯一标记(仅仅是URL是不行的,原因大家想)用来从同一TCP通道分离不同的HTTP消息,所以一个完整的HTTP请求在发送请求到响应回来之间是独占一个TCP通道的!是不是觉得HTTP对TCP的利用率太低了?而关于pipeline模式,不管在服务端还是客户端排队,HTTP响应依然要通过进入服务端队列的顺序返回,这样才能和客户端HTTP请求队列用顺序做对应!所以pipeline模式某个请求被服务端因为某些原因阻塞了的情况下,后续请求都会阻塞,会引起很大的问题,实际上很少用。

浏览器或者一般HTTP客户端组件为某一个服务器端点(域名+端口)保留4-6条活跃TCP连接。你可以F12观察浏览器,看看同时是几个请求阻塞了就知道你的浏览器设置的多少。比较大的门户网站,比如京东,首页请求非常多,但是大量都需要排队等TCP空闲。限制客户端的连接数量的出发点主要是性能,否则会占用服务器太多Socket资源(考虑socket预留的读写缓冲区,windows的内核对象或者linux的文件句柄)或者变相地造成DoS攻击。

Tips:HTTP客户端组件一般会提供诸如ConnectionLimit的选项让你控制最大TCP连接数。如果你是桌面客户端,或者请求远程服务,不宜设置过大。如果你是内部服务之间调用,可以根据需求合理设置以增加并发性能。

HTTP2.0

针对以上的问题(主要是性能)做了很多改进,这个也会提高很多人在后端不同服务器之间做通信时选择HTTP(我在HTTP2.0出来之前就是自己设计RPC方案)。详细的HTTP2.0的东西,这里不展开了,详细参考官方文档。

HTTP相关知识推荐《HTTP权威指南》以及相关的RFC文档,尽量少去看博客上面支离破碎的小知识,体系化的认知结构对你帮助更大。

WebSocket

WebSocket的出现,就是为了解决http协议不支持双向通信的缺口。所以WebSocket的握手协议就是使用的HTTP消息来Upgrade。

现代的Web场景,服务端推送的需求非常大,这个发展过程中使用的Ajax轮询,Comet等都只是临时解决方案,从设计上看,只为满足需求,一点都不优雅。

Html5规范将WebSocket纳入后,得到了现代几乎所有浏览器的支持,当然IE(10+才支持)仍然是一个巨坑,在乎用户覆盖面的产品依然要通过浏览器是否支持ws来做出降级处理(轮询、长连接)。

websocket协议实现独占一条tcp通道,它负责从tcp流确定消息边界,解析出每个独立的消息包。可进行全双工的双向通信。题主所谓的WebSocket可以实现持久连接,只是的一个服务端WebSocket会话和对应的客户端WebSocket会话在使用一个固定的保持连接的TCP通信而已。一般需要将服务端WebSocket会话和某位用户关联起来(客户单连接后,可以再单独发送凭证验证),实现给某个用户推送消息,只需根据关联找到对应的WebSocket会话调用发送API即可。

应用

使用单独实现websocket协议的服务\\客户端组件,可以更加轻松地实现自定义协议:在websocket的二进制或者文本消息体内或者直接使用websocket的自协议定义机制封装自己定义的协议。

推荐大家如果有些需要自建IM服务器,推送服务器的场合尝试先用WebSocket来实现。负载高(协议头消耗小),协议简洁,几乎所有客户端(减少了大量的工作)都有对应的开源项目可用,同时还是唯一可以在浏览器上用的双向通信协议(flash和silverlight等插件方式除外)。

如果你要用websocket实现请求应答式的子协议,要点是你要设计唯一的请求标志,响应也将请求标志带回来,然后你就可以从客户端的请求队列中查找响应对应的请求将响应交给上层处理!

特别注意:

关于webcket持久连接,本质上是下层tcp连接的保持,核心问题同样是如何保活。需要考虑Nat失效(基站最突出,一般有效期只有3分钟)或者其它网络原因导致大量半连接存在。解决方案就是合理的心跳时间,一般我设置为2分50秒的样子。

其它

不论是否从事网络编程,都应该花时间学习下TCP/IP协议簇方面的知识,着重理解分层原理,各层的功能和为上层提供了哪些功能。就像这个问题,如果不对TCP有所了解,回答的内容就没多大意义了。阅读一个你比较熟悉的语言的的一种协议(比如http)实现项目的源码,帮助应该很大。

和网络IO密切相关的就是线程,要设计高可用的TCP服务器,必须要熟悉多线程。网络IO和多线程是我认为最重要的两个核心知识点。

关于协议的设计,你可以多学习其他优秀的基于TCP实现的应用层协议,简单的就有Redis的通信协议,里面有阻塞式的消费者队列,那个就需要一条单独的tcp通道。协议设计是很有意思的一件事情,就是mysql和mongodb的通信协议我也不会放过,去看看,会给自己设计协议带来不少的参考价值。

如果时间允许,有标准的协议最好看看RFC文档,现在Chrome的翻译已经很好了,如果英文不太好,问题也不大。

关于TCP/IP相关的书籍

《计算机网络:自顶向下方法》和谢希仁的《计算机网络》都是不错的入门书籍。

《TCP/IP详解》是经典,虽然出版已久,内容是没过时的。

网络应用脱离不了操作系统,所以可以再看看操作系统关于网络IO这一块的设计。

实际开发更多和Socket以及多线程打交道,Windows下面可以看看《Windows核心编程》。

其它的就是开源项目:Nginx,netty等大量优秀的项目都在等你。

还是要感谢大家对我写的东西有那么一点感兴趣,能对大家有所帮助就更好了。


迁徙de麻雀


TCP长连接,只要连接不释放,Server端可以主动向Client推数据。


光明右使8787


http请求响应模式是一夜情,websocket请求响应模式是小三,tcp请求响应模式是夫妻关系。

一句话就可以说清楚的问题,饶了这么大的一个圈子。因为websocket的本质还是基于tcp的,tcp是老爹,既然tcp可以全双工,那么websocket当然可以实现。http不支持是因为协议本身规定是只能响应一次,第二次服务器不认识客户端了 就像一夜情。如何解决这个问题?原理就是让服务器保留客户端的身份信息,你不需要再一次告诉服务器,我是谁,我是你的情人,可以再上车。


分享到:


相關文章: