olddoor: 通过本文可了解客户端和浏览器"实时通信"的解决方案
1 定时轮询(拉取)
1.1 定时轮询, 整个页面刷新(过时)
1.2 基于js, 定时轮询(普通轮询)
1.3 基于js, 长轮询等Comet类风格的请求.(客户端请求后服务器端有结果反馈, 没结果hold请求, 等有结果或超时再响应. 得到响应后客户端重新发起轮询请求. 此方式对比普通轮询,减少js请求服务器端的次数(减少创建连接的开销))
服务器端的早期实现方式可在servet中for循环等待有结果再response.
servet3.0开始 相关开发规范支持异步处理的特性. 对应服务器端可基于sevlet3的规范(服务器一侧接收请求的线程和处理请求的线程分开, 接收请求后容器线程处理其他请求, 原请求的连接不关闭, 待处理线程处理完毕后, 通过监听器等方式通过原未关闭的连接给与客户端响应.)
2 相互通信(推送, 如websocket为代表)
1 请求-响应的局限性
网络上的客户端-服务器通信在过去曾是一种请求-响应模型,要求客户端(比如 Web 浏览器)向服务器请求资源。服务器通过发送所请求的资源来响应客户端请求。如果资源不可用,或者客户端没有权限访问它,那么服务器会发送一条错误消息。在请求-响应架构中,未经请求, 服务器绝不能主动向客户端发送未经请求的消息。
2 浏览器和服务器实时通信的解决方案
浏览器需要和服务器保持实时通信效果实现的一些想法, 思路其实无非就是两种
1 客户端定时到服务器查询信息, 实现一种看起来是"服务器推送"的效果.
2 服务器和客户端实时通信, 双方能互相推送信息.
对应的技术实现上可能的方案有:
1 基于客户端Socket的技术(过时的解决方案, 代表方案Flash XMLSocket,Java Applet套接口,Activex包装的socket )
- 优点:原生socket的支持,和PC端和移动端的实现方式相似;
- 缺点:浏览器端需要装相应的插件;
2 传统的轮询方式: 利用js定时轮询服务器. 客户端每隔一段时间都会向服务器请求新数据. 要想取得数据,必须首先发送请求. 性能差.不推荐。
3 Comet技术 (可以理解为一种技术分类. 服务器在没有新数据的时候不再返回空响应,而是hold住连接.
而且把连接保持到有服务器方有更新的时候或超时(设置)才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求的这类实现技术. 称为Comet技术)
本质是基于HTTP长连接定时"拉数据" 达到一个浏览器和服务器实时通信, 貌似"服务器推"的效果.
Comet是一种技术思路, 代表的实现方案有 长轮询和 基于 Iframe 及流(streaming)方式.
4 HTML5 标准的WebSocket 和Server-sent-events(SSE)
5 自建或者使用第三方云推送(本质和上述3种已发生改变, 我方已变成推送接收方)
本文不涉及App或者小程序之类的推送.
3 轮询
不管服务端数据有无更新,客户端每隔定长时间请求拉取一次数据,可能有更新数据返回,也可能什么都没有。
这显然这不会是"实时通信"/"服务器推"效果可能的选择方案.
实现一般用AJAX 定时(可以使用JS的 setTimeout 函数)去服务器查询是否有新消息。这让用户感觉应用是实时的。实际上这会造成延时和性能问题,因为服务器每秒都要处理大量的连接请求,每次请求都会有 TCP 三次握手并附带 HTTP 的头信息。尽管现在很多应用仍在使用轮询,但这并不是理想的解决方案。
- 优点:服务端逻辑简单;
- 缺点:大多数请求是无效请求,在轮询很频繁的情况下对服务器的压力很大;
可能的实现代码, 利用XHR,通过setInterval定时发送请求,但会造成数据同步不及时及无效的请求,增加后端处理压力。
function ajax(data){
var xhr = new XMLHttpRequest();
xhr.open('get', '/cgi-bin/xxx', true);
xhr.onreadystatechange = function(){
if (xhr.readyState == 4) {
if (xhr.status == 200) {
......
}
}
}
xhr.send(data);
}
setTimeout(function(){ajax({"data":"hehe"});}, 2000);//每隔2秒请求一次
13
13
1
function ajax(data){
2
var xhr = new XMLHttpRequest();
3
xhr.open('get', '/cgi-bin/xxx', true);
4
xhr.onreadystatechange = function(){
5
if (xhr.readyState == 4) {
6
if (xhr.status == 200) {
7
8
}
9
}
10
}
11
xhr.send(data);
12
}
13
setTimeout(function(){ajax({"data":"hehe"});}, 2000);//每隔2秒请求一次
4 comet 技术模型
Comet是技术实现的一个分类而已. 也可理解为客户端所需要的响应信息不再需要主动地去索取,而是在服务器端以事件(Event)的形式推至客户端的技术类别.
具体实现方式为长轮询和iframe流.
4.1 长轮询
长轮询是在Ajax传统轮询基础上做的一些改进,服务器在没有新数据的时候不再返回空响应,而是hold住连接.
而且把连接保持到有服务器方有更新的时候或超时(设置)才返回响应信息并关闭连接,客户端处理完响应信息后再次向服务器发送新的请求。 (这种实现思路,这类方案被成为Comet技术)
即长轮询的效果是让HTTP的连接保持,服务器端会阻塞请求,直到服务器端有一个事件触发或者到达超时。客户端在收到响应后再次发出请求,重新建立连接。
----------延伸--------------
前面提到长轮询如果当时服务端没有需要的相关数据,此时请求会hold住,直到服务端把相关数据准备好,或者等待一定时间直到此次请求超时,这里大家是否有疑问,为什么不是一直等待到服务端数据准备好再返回,这样也不需要再次发起下一次的长轮询,节省资源?
主要原因是网络传输层主要走的是tcp协议,tcp协议是可靠面向连接的协议,通过三次握手建立连接。但是所建立的连接是虚拟的,可能存在某段时间网络不通,或者服务端程序非正常关闭,亦或服务端机器非正常关机,面对这些情况客户端根本不知道服务端此时已经不能互通,还在傻傻的等服务端发数据过来,而这一等一般都是很长时间。当然tcp协议栈在实现上有保活计时器来保证的,但是等到保活计时器发现连接已经断开需要很长时间,如果没有专门配置过相关的tcp参数,一般需要2个小时,而且这些参数是机器操作系统层面,所以,以此方式来保活不太靠谱,故长轮询的实现上一般是需要设置超时时间的。
-----------------------------
如图4-1, 从浏览器的角度来看,长轮询的办法保持了有效的请求,又避免了大量无效请求,并且即时性更好,这是一种可行的方案。
- 优点:任意浏览器都可用;实时性好,无消息的情况下不会进行频繁的请求;
- 缺点:连接创建销毁操作还是比较频繁,服务器维持着连接比较消耗资源;
在长轮询方式下,客户端是在 XMLHttpRequest 的 readystate 为 4(即数据传输结束)时调用回调函数,进行信息处理。当 readystate 为 4 时,数据传输结束,连接已经关闭。
4.1.1 短轮询和长轮询的区别
短轮询中服务器对请求立即响应,而长轮询中服务器等待新的数据到来才响应,因此实现了服务器向页面推送实时,并减少了页面的请求次数。
普通
Ajax
轮询与基于Ajax
的长轮询原理对比: 图4-24.1.2 长轮询的编码实现
可能的实现代码:
JS客户端
function longPoll(data, cbk){
var xhr = new XMLHttpRequest();
var url = '/cgi-bin/xxx';
xhr.onreadystatechange = function(){
if (xhr.readyState == 4) {//XMLHttpRequest 的状态中4: 请求已完成,且响应已就绪
if (xhr.status == 200) { //请求完毕后重新发起新的一次连接
cbk(xhr.responseText);
xhr.open('get', url, true);
xhr.send(otherData);
}
}
}
xhr.open('get', url, true);
xhr.send(data);
}
x
1
function longPoll(data, cbk){
2
var xhr = new XMLHttpRequest();
3
var url = '/cgi-bin/xxx';
4
xhr.onreadystatechange = function(){
5
if (xhr.readyState == 4) {//XMLHttpRequest 的状态中4: 请求已完成,且响应已就绪
6
if (xhr.status == 200) { //请求完毕后重新发起新的一次连接
7
cbk(xhr.responseText);
8
xhr.open('get', url, true);
9
xhr.send(otherData);
10
}
11
}
12
}
13
xhr.open('get', url, true);
14
xhr.send(data);
15
}
注意:
无论是轮询还是Comet技术, 思路都是客户端频繁间隔的对服务器端发送请求数据达到"服务器推"的效果, 会在服务端和客户端都需要维持一个比较长时间的连接状态,这一点在客户端不算什么太大的负担,但是服务端是要同时对多个客户端服务的,按照经典 Request-Response 交互模型,每一个请求都占用一个 Web 线程不释放的话,Web 容器的线程则会很快消耗殆尽,而这些线程大部分时间处于空闲等待的状态。
Comet对比轮询只不过是在请求服务器的频率上会大幅降低而已.
而服务器一方线程大部分时间处于空闲等待, 严重影响服务器性能(请求始终占用连接), 所以能够有异步处理的原因,希望 Web 线程不需要同步的、一对一的处理客户端请求,能做到一个 Web 线程处理多个客户端请求。
而服务器端能够异步处理请求的规范以及标准就是Servlet3.0规范引入的异步支持.
---------------延伸开始----------------------------
Servlet 3.0 作为 Java EE 6 规范体系中一员,随着 Java EE 6 规范一起发布。(Tomcat7提供了对Java EE6规范的支持。)
新特性部分列列举如下:
1 异步处理支持:有了该特性,Servlet 线程不再需要一直阻塞,直到业务处理完毕才能再输出响应,最后才结束该 Servlet 线程。在接收到请求之后,Servlet 线程可以将耗时的操作委派给另一个线程来完成,自己在不生成响应的情况下返回至容器。针对业务处理较耗时的情况,这将大大减少服务器资源的占用,并且提高并发处理速度。
2 新增的注解支持:该版本新增了若干注解,用于简化 Servlet、过滤器(Filter)和监听器(Listener)的声明,这使得 web.xml 部署描述文件从该版本开始不再是必选的了。
使用异步处理 Servlet 线程不再是一直处于阻塞状态以等待业务逻辑的处理,而是启动异步线程之后可以立即返回.
异步处理特性可以应用于 Servlet 和过滤器两种组件.
1)对于使用传统的部署描述文件 (web.xml) 配置 Servlet 和过滤器的情况Servlet 3.0 为 和 标签增加了 子标签,该标签的默认取值为 false,要启用异步处理支持,则将其设为 true 即可。以 Servlet 为例
<servlet>
<servlet-name>DemoServlet</servlet-name>
<servlet-class>footmark.servlet.Demo Servlet</servlet-class>
<async-supported>true</async-supported>
</servlet>
2) 使用注解方式: Servlet 3.0 提供的 @WebServlet 和 @WebFilter 进行 Servlet 或过滤器配置的情况,这两个注解都提供了 asyncSupported 属性,默认该属性的取值为 false,要启用异步处理支持,只需将该属性设置为 true 即可
@WebFilter 为例,其配置方式如下所示:
@WebFilter(urlPatterns = “/demo”,asyncSupported = true)
2、Servlet 3.0 还为异步处理提供了一个监听器,使用 AsyncListener 接口表示.
异步的拦截器:
1)、原生API的AsyncListener (使用异步servlet的时候需要注册AsyncListener)
2)、SpringMVC:实现AsyncHandlerInterceptor
---------------延伸结束----------------------------
服务器端
(1)服务端基于servlet(同步/异步)的实现
详见 Long Polling长轮询及例子详解 (名词解释, 例子服务端主要是基于servlet的实现)或者见(https://www.jianshu.com/p/d3f66b1eb748 和服务器端异步实现
需要做的是
保证
web.xml
中application的配置的版本是3.0<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
1
1
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
2
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
http://java.sun.com/xml/ns/javaee
4
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
5
version="3.0">
可以通过web.xml中的子元素<async-supported>true</async-supported>使得DispatcherServlet支持异步.此外的任何Filter参与异步语法处理必须配置为支持ASYNC分派器类型。这样可以确保Spring Framework提供的所有filter都能够异步分发.自从它们继承了OncePerRequestFilter之后.并且在runtime的时候会check filter是否需要被异步调用分发.
下面是web.xml的配置示例:
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<filter>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class>
<async-supported>true</async-supported>
</filter>
<filter-mapping>
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>
</filter-mapping>
</web-app>
1
21
1
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
2
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3
xsi:schemaLocation="
4
http://java.sun.com/xml/ns/javaee
5
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
6
version="3.0">
7
8
<filter>
9
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
10
<filter-class>org.springframework.~.OpenEntityManagerInViewFilter</filter-class>
11
<async-supported>true</async-supported>
12
</filter>
13
14
<filter-mapping>
15
<filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
16
<url-pattern>/*</url-pattern>
17
<dispatcher>REQUEST</dispatcher>
18
<dispatcher>ASYNC</dispatcher>
19
</filter-mapping>
20
21
</web-app>
如果使用Sevlet3,Java配置可以通过
WebApplicationInitializer
,你同样需要像在web.xml
中一样,设置”asyncSupported”标签为ASYNC.为了简化这个配置,考虑继承AbstractDispatcherServletInitializer
或者AbstractAnnotationConfigDispatcherServletInitializer
。它们会自动设置这些选项,使它很容易注册过滤器实例。在代码层面:
接受处理请求的servlet需要使用Servlet 3.0为异步处理提供了一个监听器,使用AsyncListener接口表示。此接口负责管理异步事件.
在Long Polling长轮询及例子详解例子中使用异步servlet处理请求, 就用到了AsyncListener
见代码片段
asyncContext.addListener(new AsyncListener() { //这里为异步处理提供了一个监听器,使用AsyncListener接口表示。此接口负责管理异步事件
@Override
public void onComplete(AsyncEvent event) throws IOException {
}
//超时处理,注意asyncContext.complete();,表示请求处理完成
@Override
public void onTimeout(AsyncEvent event) throws IOException {
AsyncContext asyncContext = event.getAsyncContext();
asyncContext.complete();
}
@Override
public void onError(AsyncEvent event) throws IOException {
}
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
}
});
23
23
1
asyncContext.addListener(new AsyncListener() { //这里为异步处理提供了一个监听器,使用AsyncListener接口表示。此接口负责管理异步事件
2
@Override
3
public void onComplete(AsyncEvent event) throws IOException {
4
5
}
6
7
//超时处理,注意asyncContext.complete();,表示请求处理完成
8
@Override
9
public void onTimeout(AsyncEvent event) throws IOException {
10
AsyncContext asyncContext = event.getAsyncContext();
11
asyncContext.complete();
12
}
13
14
@Override
15
public void onError(AsyncEvent event) throws IOException {
16
17
}
18
19
@Override
20
public void onStartAsync(AsyncEvent event) throws IOException {
21
22
}
23
});
(2)服务器端基于SpringMVC实现(DeferredResult 或 Callable)
官方文档中说DeferredResult和Callable(java.util.concurrent.Callable 涉及java多线程知识))都是为了异步生成返回值提供基本的支持。简单来说就是一个请求进来,如果你使用了DeferredResult或者Callable,在没有得到返回数据之前,DispatcherServlet和所有Filter就会退出Servlet容器线程,但响应保持打开状态,一旦返回数据有了,这个DispatcherServlet就会被再次调用并且处理,以异步产生的方式,向请求端返回值。
这么做的好处就是请求不会长时间占用服务连接池,提高服务器的吞吐量。
这其实也就是SpringMVC对于外部请求的异步处理(基本实现Servlet 3.0的异步处理规范).核心是为了提高减少http请求的连接的占用, 接受请求后快速释放,将业务逻辑交给其他线程处理. 业务逻辑处理完毕后重新拿到http请求连接, 由http连接返回给客户端. 达到异步的效果.
Controller中构造Callable并将其作为返回值.
使用Callable大致流程说明
客户端请求服务后;
- SpringMVC调用Controller,Controller返回一个Callback对象
- SpringMVC调用ruquest.startAsync并且将Callback提交到TaskExecutor使用一个隔离的线程去进行执行
- DispatcherServlet以及Filters等从应用服务器线程中结束,但Response仍旧是打开状态,也就是说暂时还不返回给客户端
- TaskExecutor调用Callback返回一个结果,SpringMVC将请求发送给应用服务器继续处理
- DispatcherServlet再次被调用并且继续处理Callback返回的对象,根据Callable返回的结果。SpringMVC继续进行视图渲染流程等, 最终将其返回给客户端
简易流程参考如图4-1-2
DeferredResult
DeferredResult的处理过程与Callback类似,不一样的地方在于它的结果不是DeferredResult直接返回的,而是由其它线程通过同步的方式设置到该对象中。
它的执行过程如下所示:
DeferredResult
的处理顺序与Callable十分相似,由应用程序多线程产生异步结果:
- Controller返回一个
DeferredResult
对象,并且把它保存在内在队列当中或者可以访问它的列表中。 - Spring MVC开始异步处理.
DispatcherServlet
与所有的Filter的Servlet容器线程退出,但Response仍然开放。- application通过多线程返回
DeferredResult
中sets值.并且Spring MVC分发request给Servlet容器. DispatcherServlet
再次被调用并且继续异步的处理产生的结果.
异步请求的异常处理 HTTP Streaming等略. 详见https://blog.csdn.net/u012410733/article/details/52124333 (推荐)
SpringMVC的配置
Spring MVC提供Java Config与MVC namespace作为选择用来配置处理异步request.WebMvcConfigurer可以通过configureAsyncSupport来进行配置,而xml可以通过子元素来进行配置.
如果你不想依赖Servlet容器(e.g. Tomcat是10)配置的值,允许你配置异步请求默认的timeout值。你可以配置AsyncTaskExecutor
用来包含Callable
实例作为controller方法的返回值.强烈建议配置这个属性,因为在默认情况下Spring MVC使用SimpleAsyncTaskExecutor
。Spring MVC中Java配置与namespace允许你注册CallableProcessingInterceptor
与DeferredResultProcessingInterceptor
实例.
如果你想覆盖DeferredResult
的默认过期时间,你可以选择使用合适的构造器.同样的,对于Callable
,你可以通过WebAsyncTask
来包装它并且使用相应的构造器来定制化过期时间.WebAsyncTask
的构造器同样允许你提供一个AsyncTaskExecutor
.
原文地址:spring-framework-reference-4.2.6.RELEASE
4.2 iframe流(永久帧)
iframe方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面.
function foreverFrame(url,callback){
var iframe = body.appendChild(document.createElement("iframe"));
iframe.style.display="none";
iframe.src=url+"?callback=parent.foreverFrame.callback";
this.callback = callback;
}
6
6
1
function foreverFrame(url,callback){
2
var iframe = body.appendChild(document.createElement("iframe"));
3
iframe.style.display="none";
4
iframe.src=url+"?callback=parent.foreverFrame.callback";
5
this.callback = callback;
6
}
只不过这里涉及父子iframe之间的通信,要注意跨域问题。关于iframe跨域问题,隔壁团队有个不错的实现方案。 见http://www.alloyteam.com/2013/11/the-second-version-universal-solution-iframe-cross-domain-communication/
然后服务器就发送一堆消息到iframe中
<script>
parent.foreverFrame.callback('hello world!');
</script>
<script>
parent.foreverFrame.callback('hello Mars!');
</script>
6
6
1
<script>
2
parent.foreverFrame.callback('hello world!');
3
</script>
4
<script>
5
parent.foreverFrame.callback('hello Mars!');
6
</script>
4.3 流(xhr流)
具体解决方案有XHR 流(xhr-multipart)、htmlfile
xhr流(XMLHttpRequest Streaming)也是通过标准的XMLHttpRequest对象获得的,但是需要在readyState为3的时候去访问数据,这样就不必等待连接关闭之后再操作数据。
参考代码
function xhrStreaming(url, callback) {
var xhr = new XMLHttpRequest();
xhr.open('post', url, true);
//保存上次返回的文档位置
var lastSize;
xhr.onreadystatechange = function() {
var newResponseText = "";
if (xhr.readyState > 2) {
newResponseText = xhr.responseText.slice(lastSize);
lastSize = xhr.responseText.length;
callback(newResponseText);
}
if (xhr.readyState == 4) {
xhrStreaming(url, callback);
}
}
xhr.send(null);
}
18
18
1
function xhrStreaming(url, callback) {
2
var xhr = new XMLHttpRequest();
3
xhr.open('post', url, true);
4
//保存上次返回的文档位置
5
var lastSize;
6
xhr.onreadystatechange = function() {
7
var newResponseText = "";
8
if (xhr.readyState > 2) {
9
newResponseText = xhr.responseText.slice(lastSize);
10
lastSize = xhr.responseText.length;
11
callback(newResponseText);
12
}
13
if (xhr.readyState == 4) {
14
xhrStreaming(url, callback);
15
}
16
}
17
xhr.send(null);
18
}
其实跟永久帧的方法也类似,只不过是把iframe获取内容的方式改成了ajax,然后在xhr内部处理增量逻辑、回调和重发。
这里需要注意的是链接时间需要有超时限制,否则内存性能会受到影响,另外单个请求返回数据需要限定长度,不能无限增大。
注意:
不管是长轮询还是流,请求都需要在服务器上存在一段较长时间,因此Comet被称为"基于HTTP长连接的服务器推技术"。这打破了每个请求一个线程的模型。这个模型显然对Comet不适用。所以服务端这边Java对此提出了非阻塞IO(non-blocking IO)解决方案, Java 通过它的NIO库提供非阻塞IO处理Comet。
Tomcat配置server.xml, 即启用异步版本的IO连接器
protocol="org.apache.coyote.http11.Http11NioProtocol"
1
1
1
protocol="org.apache.coyote.http11.Http11NioProtocol"
后台请求处理以servlet为例, 通过 servlet 实现 CometProcessor 接口。这个接口要求实现 event() 方法,在配置的 Http11NioProtocol 调用 event() 方法来处理请求,而不是 doGet 或 doPost。
服务器端代码实现略.
5 WebSocket
WebSocket是html5规范新引入的功能,是基于 TCP 的双向的、全双工的 socket 连接。(是独立的、创建在 TCP 上的协议。).
WebSocket用于解决浏览器与后台服务器双向通讯的问题,使用WebSocket技术,后台可以随时向前端推送消息,以保证前后台状态统一,在传统的无状态HTTP协议中,这是“无法做到”的。在WebSocke推出以前,服务端向客户端推送消息的方式都以曲线救国的轮询方式( Comet 或轮询)为主。
WebSocket不属于http无状态协议,协议名为”ws”,这意味着一个websocket连接地址会是这样的写法:ws://twaver.com:8080/webSocketServer。ws不是http,所以传统的web服务器不一定支持,需要服务器与浏览器同时支持, WebSocket才能正常运行,目前的支持还不普遍,需要特别的web服务器和现代的浏览器。
现在我们来看一下都有哪些浏览器支持 WebSocket:
Chrome >= 4
Safari >= 5
iOS >= 4.2
Firefox >= 4*
Opera >= 11*
检测浏览器是否支持 WebSocket 也非常简单、直接:
var supported = ("WebSocket" in window);
if (supported) alert("WebSockets are supported");
2
2
1
var supported = ("WebSocket" in window);
2
if (supported) alert("WebSockets are supported");
Websocket 通过 HTTP/1.1 协议的101状态码进行握手。为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。
利用HTTP完成握手有几个好处。首先,让WebSockets与现有HTTP基础设施兼容:WebSocket服务器可以运行在80和443 端口上,这通常是对客户端唯一开放的端口。其次,让我们可以重用并扩展HTTP的Upgrade流,为其添加自定义的WebSocket首部,以完成协商。
5.1 WebSocket API
浏览器提供的WebSocket API很简单,使用时无需关心连接管理和消息处理等底层细节,只需要发起连接,绑定相应的事件回调即可。
var connection = new WebSocket('ws://localhost:8080');
// When the connection is open, send some data to the server connection.onopen = function () { connection.send('Ping'); // Send the message 'Ping' to the server }; // Log errors connection.onerror = function (error) { console.log('WebSocket Error ' + error); }; // Log messages from the server connection.onmessage = function (e) { console.log('Server: ' + e.data); };
2
2
1
var connection = new WebSocket('ws://localhost:8080');
2
// When the connection is open, send some data to the server connection.onopen = function () { connection.send('Ping'); // Send the message 'Ping' to the server }; // Log errors connection.onerror = function (error) { console.log('WebSocket Error ' + error); }; // Log messages from the server connection.onmessage = function (e) { console.log('Server: ' + e.data); };
WebSocket资源URL采用了自定议模式,没有使用http是为了在非http协议场景下也能使用,wss表示使用加密信道通信(TCP + TLS),支持接收和发送文本和二进制数据。
请求头信息
Connection:Upgrade Sec-WebSocket-Key:eDCPPyPQZq7PiwRcx8SPog== Sec-WebSocket-Version:13 Upgrade:websocket
1
1
1
Connection:Upgrade Sec-WebSocket-Key:eDCPPyPQZq7PiwRcx8SPog== Sec-WebSocket-Version:13 Upgrade:websocket
响应头信息
HTTP/1.1 101 Switching Protocols
Upgrade:websocket
Connection:upgrade
Sec-WebSocket-Accept:QJsTRym36zHnArQ7FCmSdPhuK78=
// Connection:upgrade 升级被服务器同意
// Upgrade:websocket 指示客户端升级到websocket
// Sec-WebSocket-Accept:参考上面请求的Sec-WebSocket-Key的注释
8
8
1
HTTP/1.1 101 Switching Protocols
2
Upgrade:websocket
3
Connection:upgrade
4
Sec-WebSocket-Accept:QJsTRym36zHnArQ7FCmSdPhuK78=
5
6
// Connection:upgrade 升级被服务器同意
7
// Upgrade:websocket 指示客户端升级到websocket
8
// Sec-WebSocket-Accept:参考上面请求的Sec-WebSocket-Key的注释
最后,前述握手完成后,如果握手成功,该连接就可以用作双向通信信道交换WebSocket消息。到此,客户端与服务器之间不会再发生HTTP通信,一切由WebSocket 协议接管。
使用场景
适合于对数据的实时性要求比较强的场景,如通信、股票、Feed、直播、共享桌面,特别适合于客户端与服务频繁交互的情况下,如实时共享、多人协作等平台。
优点
- 真正的全双工通信
- 支持跨域设置(Access-Control-Allow-Origin)
缺点
- 采用新的协议,后端需要单独实现
- 客户端并不是所有浏览器都支持
- 代理服务器会有不支持websocket的情况
- 无超时处理
- 更耗电及占用资源
TIP
代理、很多现有的HTTP
中间设备可能不理解新的WebSocket
协议,而这可能导致各种问题,使用时需要注意,可以使借助TLS
,通过建立一条端到端的加密信道,可以让WebSocket
通信绕过所有中间代理。5.2 WebSocket在Java中
JavaEE 7的JSR-356:Java API for WebSocket,已经对WebSocket做了支持。不少Web容器,如Tomcat、Jetty等都支持WebSocket。Tomcat从7.0.27开始支持WebSocket,从7.0.47开始支持JSR-356。
待续
6 SSE
待续
参考