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-2

4.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連接返回給客戶端. 達到異步的效果.
C
ontroller中構造
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
待續
參考