教育行業(yè)A股IPO第一股(股票代碼 003032)

全國咨詢/投訴熱線:400-618-4000

WebSocket分析及實踐

更新時間:2018年12月07日13時14分 來源:傳智播客 瀏覽次數(shù):

# WebSocket分析及實踐

## WebSocket產(chǎn)生背景:實時Web應(yīng)用的窘境

Web 應(yīng)用的信息交互過程通常是客戶端通過瀏覽器發(fā)出一個請求,服務(wù)器端接收和審核完請求后進行處理并返回結(jié)果給客戶端,然后客戶端瀏覽器將信息呈現(xiàn)出來,這種機制對于信息變化不是特別頻繁的應(yīng)用尚能相安無事,但是對于那些實時要求比較高的應(yīng)用來說,比如說實時報表統(tǒng)計、在線游戲、在線證券、設(shè)備監(jiān)控、新聞在線播報、RSS 訂閱推送等等,當(dāng)客戶端瀏覽器準(zhǔn)備呈現(xiàn)這些信息的時候,這些信息在服務(wù)器端可能已經(jīng)過時了。所以保持客戶端和服務(wù)器端的信息同步是實時 Web 應(yīng)用的關(guān)鍵要素,對 Web 開發(fā)人員來說也是一個難題。

? 在 WebSocket 出來之前,開發(fā)人員想實現(xiàn)這些實時的 Web 應(yīng)用,不得不采用一些折衷的方案,其中最常用的就是輪詢 (Polling) 和 Comet 技術(shù),而 Comet 技術(shù)實際上是輪詢技術(shù)的改進,又可細分為兩種實現(xiàn)方式,一種是長輪詢機制,一種稱為流技術(shù)。下面簡單介紹一下這幾種技術(shù):

- **輪詢**

這是最早的一種實現(xiàn)實時 Web 應(yīng)用的方案??蛻舳艘砸欢ǖ臅r間間隔向服務(wù)端發(fā)出請求,以頻繁請求的方式來保持客戶端和服務(wù)器端的同步。這種同步方案的最大問題是:當(dāng)客戶端以固定頻率向服務(wù)器發(fā)起請求的時候,服務(wù)器端的數(shù)據(jù)可能并沒有更新,這樣會帶來很多無謂的網(wǎng)絡(luò)傳輸,所以這是一種非常低效的實時方案。

- **長輪詢:**

長輪詢是對定時輪詢的改進和提高,目地是為了降低無效的網(wǎng)絡(luò)傳輸。當(dāng)服務(wù)器端沒有數(shù)據(jù)更新的時候,連接會保持一段時間周期直到數(shù)據(jù)或狀態(tài)改變或者時間過期,通過這種機制來減少無效的客戶端和服務(wù)器間的交互。當(dāng)然,如果服務(wù)端的數(shù)據(jù)變更非常頻繁的話,這種機制和定時輪詢比較起來沒有本質(zhì)上的性能的提高。

- **流:**

流技術(shù)方案通常就是在客戶端的頁面使用一個隱藏的窗口向服務(wù)端發(fā)出一個長連接的請求。服務(wù)器端接到這個請求后作出回應(yīng)并不斷更新連接狀態(tài)以保證客戶端和服務(wù)器端的連接不過期。通過這種機制可以將服務(wù)器端的信息源源不斷地推向客戶端。這種機制在用戶體驗上有一點問題,需要針對不同的瀏覽器設(shè)計不同的方案來改進用戶體驗,同時這種機制在并發(fā)比較大的情況下,對服務(wù)器端的資源是一個極大的考驗。

綜合這幾種方案,您會發(fā)現(xiàn)這些目前我們所使用的所謂的實時技術(shù)并不是真正的實時技術(shù),它們只是在用 Ajax 方式來模擬實時的效果,在每次客戶端和服務(wù)器端交互的時候都是一次 HTTP 的請求和應(yīng)答的過程,而每一次的 HTTP 請求和應(yīng)答都帶有完整的 HTTP 頭信息,這就增加了每次傳輸?shù)臄?shù)據(jù)量,而且這些方案中客戶端和服務(wù)器端的編程實現(xiàn)都比較復(fù)雜,在實際的應(yīng)用中,為了模擬比較真實的實時效果,開發(fā)人員往往需要構(gòu)造兩個 HTTP 連接來模擬客戶端和服務(wù)器之間的雙向通訊,一個連接用來處理客戶端到服務(wù)器端的數(shù)據(jù)傳輸,一個連接用來處理服務(wù)器端到客戶端的數(shù)據(jù)傳輸,這不可避免地增加了編程實現(xiàn)的復(fù)雜度,也增加了服務(wù)器端的負載,制約了應(yīng)用系統(tǒng)的擴展性。

##什么是WebSocket?

WebSocket是HTML5的新特性之一,其設(shè)計出來的目的就是要取代輪詢和 Comet 技術(shù),使客戶端瀏覽器具備像 C/S 架構(gòu)下桌面系統(tǒng)的實時通訊能力。

那WebSocket究竟是什么?首先我們需要清楚,WebSocket本質(zhì)上就是一種計算機網(wǎng)絡(luò)應(yīng)用層的協(xié)議(HTTP就是一種網(wǎng)絡(luò)應(yīng)用層協(xié)議),用來彌補HTTP協(xié)議在持久通信能力上的不足。我們知道HTTP協(xié)議本身是無狀態(tài)協(xié)議,每一個新的HTTP請求,只能通過客戶端主動發(fā)起,通過建立連接-->傳輸數(shù)據(jù)-->斷開連接的方式來傳輸數(shù)據(jù),傳送完連接就斷開了,也就是此次HTTP請求已經(jīng)完全結(jié)束了(雖然HTTP1.1增加了keep-alive請求頭可以通過一條通道請求多次,但本質(zhì)上還是一樣的)。并且服務(wù)器是不能主動給客戶端發(fā)送數(shù)據(jù)的(因為之前的請求得到響應(yīng)后連接就斷開了,之后服務(wù)器根本不知道誰請求過),客戶端也不會知道之前請求的任何信息,所以HTTP協(xié)議本身是沒有持久通信能力的,正因為這樣,也就出現(xiàn)了上述實時Web應(yīng)用的窘境。

WebSocket協(xié)議實現(xiàn)了瀏覽器與服務(wù)器的全雙工通信(指在通信的任意時刻,線路上存在A到B和B到A的雙向信號傳輸,簡單說就如同打電話一樣,瀏覽器和服務(wù)器任何一方隨時都能夠主動給對方說話)。并且在HTML5標(biāo)準(zhǔn)中增加了有關(guān)WebSocket協(xié)議的相關(guān)API,所以只要實現(xiàn)了HTML5標(biāo)準(zhǔn)的客戶端,就可以與支持WebSocket協(xié)議的服務(wù)器進行全雙工的持久通信了。

與HTTP協(xié)議一樣,WebSocket協(xié)議也需要通過已建立的TCP連接來傳輸數(shù)據(jù)。具體實現(xiàn)上是通過HTTP協(xié)議建立通道,然后在此基礎(chǔ)上用真正的WebSocket協(xié)議進行通信,所以WebSocket協(xié)議和Http協(xié)議是有一定的交叉關(guān)系的。Websocket是應(yīng)用層第七層上的一個應(yīng)用層協(xié)議,它必須依賴 HTTP 協(xié)議進行一次握手 ,握手成功后,數(shù)據(jù)就直接從TCP通道傳輸,與HTTP無關(guān)了。

WebSocket分析及實踐

## 為什么需要WebSocket?

瀏覽器通過 JavaScript 向服務(wù)器發(fā)出建立 WebSocket 連接的請求,連接建立以后,客戶端和服務(wù)器端就可以通過 TCP 連接直接交換數(shù)據(jù)。因為 WebSocket 連接本質(zhì)上就是一個 TCP 連接,所以在數(shù)據(jù)傳輸?shù)姆€(wěn)定性和數(shù)據(jù)傳輸量的大小方面,和輪詢以及 Comet 技術(shù)比較,具有很大的性能優(yōu)勢。Websocket.org 網(wǎng)站對傳統(tǒng)的輪詢方式和 WebSocket 調(diào)用方式作了一個詳細的測試和比較,將一個簡單的 Web 應(yīng)用分別用輪詢方式和 WebSocket 方式來實現(xiàn),在這里引用一下他們的測試結(jié)果圖:

WebSocket分析及實踐

通過這張圖可以清楚的看出,在流量和負載增大的情況下,WebSocket 方案相比傳統(tǒng)的 Ajax 輪詢方案有極大的性能優(yōu)勢。這也是為什么我們認為 WebSocket 是未來實時 Web 應(yīng)用的首選方案的原因。

## WebSocket使用場景

什么時候使用WebSocket,你需要考慮如下兩個因素

- 你的應(yīng)用是否提供多個用戶之間的相互交流?

- 你的應(yīng)用是展示服務(wù)器端經(jīng)常變動的數(shù)據(jù)嗎?

如果上述兩個問題你的回答是肯定的,那么請考慮使用WebSocket。如果你還不確定,那有一些經(jīng)典場景,你可以參考并激發(fā)一下自己的靈感。

- **實時統(tǒng)計(圖表)**

你需要你的統(tǒng)計數(shù)據(jù)(或者圖表)實時更新,類似于淘寶雙11大屏那樣的效果

- **系統(tǒng)即時提醒**

- **實時地圖位置**

- **彈幕**

- **社交訂閱**

社交類的應(yīng)用的一個裨益之處就是能夠即時的知道你的朋友正在做什么。雖然聽起來有點可怕,但是我們都喜歡這樣做。你不會想要在數(shù)分鐘之后才知道微信朋友圈朋友發(fā)布的更新動態(tài)。你是在線的,所以你的訂閱的更新應(yīng)該是實時的。

- **股票基金報價**

金融界瞬息萬變——幾乎是每毫秒都在變化。我們?nèi)祟惖拇竽X不能持續(xù)以那樣的速度處理那么多的數(shù)據(jù),所以我們寫了一些算法來幫我們處理這些事情。雖然你不一定是在處理高頻的交易,但是,過時的信息也只能導(dǎo)致?lián)p失。當(dāng)你有一個顯示盤來跟蹤你感興趣的公司時,你肯定想要隨時知道他們的價值,而不是10秒前的數(shù)據(jù)。使用WebSocket可以流式更新這些數(shù)據(jù)變化而不需要等待。

- **體育實況更新**

如果你在你的網(wǎng)站應(yīng)用中包含了體育新聞,WebSocket能夠助力你的用戶獲得實時的更新。

- **基于位置的應(yīng)用**

越來越多的開發(fā)者借用移動設(shè)備的GPS功能來實現(xiàn)他們基于位置的應(yīng)用。如果你收集到了用戶的位置數(shù)據(jù)(比如記錄運動軌跡)。如果你想實時的更新網(wǎng)絡(luò)數(shù)據(jù)儀表盤(可以說是一個監(jiān)視運動員的教練),HTTP協(xié)議顯得有些笨拙。借用WebSocket TCP鏈接可以讓數(shù)據(jù)飛起來。

- **在線教育**

上學(xué)花費越來越貴了,但互聯(lián)網(wǎng)變得更快和更便宜。在線教育是一種不錯的學(xué)習(xí)方式,尤其是你可以和老師以及其他同學(xué)一起交流。此時,用WebSocket來實現(xiàn)是個不錯的選擇,可以多媒體聊天、文字聊天以及其它優(yōu)勢如與別人合作一起在公共數(shù)字黑板上畫畫等。

## 如何使用WebSocket?

使用WebSocket需要客戶端和服務(wù)器端,客戶端通常就是瀏覽器(基于瀏覽器進行的JavaScript調(diào)用),服務(wù)器端通常就是支持WebSocket的中間件。

- **客戶端瀏覽器支持(以下是主流瀏覽器對HTML5 WebSocket的支持情況)**

- **支持 WebSocket 的服務(wù)器**

服務(wù)器端的實現(xiàn)不受平臺和開發(fā)語言的限制,只需要遵從 WebSocket 規(guī)范即可,目前已經(jīng)出現(xiàn)了一些比較成熟的 WebSocket 服務(wù)器端實現(xiàn),比如Kaazing WebSocket Gateway(一個 Java 實現(xiàn)的 WebSocket Server)、mod_pywebsocket(一個 Python 實現(xiàn)的 WebSocket Server)、Netty(一個 Java 實現(xiàn)的網(wǎng)絡(luò)框架其中包括了對 WebSocket 的支持)、Node.js(一個 Server 端的 JavaScript 框架提供了對 WebSocket 的支持),當(dāng)然也可以使用Tomcat(需要為Tomcat7.0.47以上,且Tomcat7.x和Tomcat8.x的使用方式還不一樣)。

- **WebSocket JavaScript API接口**

針對 Web 開發(fā)人員的 WebSocket JavaScript 客戶端接口是非常簡單的,以下是 WebSocket JavaScript 接口的定義:

其中 URL 屬性代表 WebSocket 服務(wù)器的網(wǎng)絡(luò)地址,協(xié)議通常是”ws”,send 方法就是發(fā)送數(shù)據(jù)到服務(wù)器端,close 方法就是關(guān)閉連接。除了這些方法,還有一些很重要的事件:onopen,onmessage,onerror 以及 onclose。

##WebSocket實戰(zhàn)—系統(tǒng)即時提醒

在上一節(jié)中我們說到使用WebSocket需要客戶端和服務(wù)器端,客戶端通常就是瀏覽器(基于瀏覽器進行的JavaScript調(diào)用),服務(wù)器端通常就是支持WebSocket的中間件。本節(jié)中我們將進行一個WebSocket實戰(zhàn)—系統(tǒng)即時提醒(使用Chrome + Tomcat7.0.70,在此基礎(chǔ)上進行前后端代碼開發(fā)),后端模擬業(yè)務(wù)變化,向前端實時推送提醒消息,前端頁面進行消息提醒的實時展示。

- WebSocket客戶端代碼(基于Jsp)

```jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>

```

- WebSocket服務(wù)器端代碼(基于Java)

```java

package com.itheima.ssm.controller;

import java.io.IOException;

import java.util.concurrent.CopyOnWriteArraySet;

import javax.websocket.*;

import javax.websocket.server.ServerEndpoint;

/**

* @ServerEndpoint 注解是一個類層次的注解,它的功能主要是將目前的類定義成一個websocket服務(wù)器端,

* 注解的值將被用于監(jiān)聽用戶連接的終端訪問URL地址,客戶端可以通過這個URL來連接到WebSocket服務(wù)器端

*/

@ServerEndpoint("/websocket")

public class WebSocketTest {

//靜態(tài)變量,用來記錄當(dāng)前在線連接數(shù)。應(yīng)該把它設(shè)計成線程安全的。

private static int onlineCount = 0;

//concurrent包的線程安全Set,用來存放每個客戶端對應(yīng)的MyWebSocket對象。若要實現(xiàn)服務(wù)端與單一客戶端通信的話,可以使用Map來存放,其中Key可以為用戶標(biāo)識

public static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();

//與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)

private Session session;

/**

* 連接建立成功調(diào)用的方法

* @param session 可選的參數(shù)。session為與某個客戶端的連接會話,需要通過它來給客戶端發(fā)送數(shù)據(jù)

*/

@OnOpen

public void onOpen(Session session){

this.session = session;

webSocketSet.add(this); //加入set中

addOnlineCount(); //在線客戶端數(shù)加1

System.out.println("有新連接加入!當(dāng)前在線客戶端數(shù)為" + getOnlineCount());

}

/**

* 連接關(guān)閉調(diào)用的方法

*/

@OnClose

public void onClose(){

webSocketSet.remove(this); //從set中刪除

subOnlineCount(); //在線客戶端數(shù)減1

System.out.println("有一連接關(guān)閉!當(dāng)前在線客戶端數(shù)為" + getOnlineCount());

}

/**

* 收到客戶端消息后調(diào)用的方法

* @param message 客戶端發(fā)送過來的消息

* @param session 可選的參數(shù)

*/

@OnMessage

public void onMessage(String message, Session session) {

System.out.println("來自客戶端的消息:" + message);

}

/**

* 發(fā)生錯誤時調(diào)用

* @param session

* @param error

*/

@OnError

public void onError(Session session, Throwable error){

System.out.println("發(fā)生錯誤");

error.printStackTrace();

}

/**

* 這個方法與上面幾個方法不一樣。沒有用注解,是根據(jù)自己需要添加的方法。

* @param message

* @throws IOException

*/

public void sendMessage(String message) throws IOException{

this.session.getBasicRemote().sendText(message);

}

public static synchronized int getOnlineCount() {

return onlineCount;

}

public static synchronized void addOnlineCount() {

WebSocketTest.onlineCount++;

}

public static synchronized void subOnlineCount() {

WebSocketTest.onlineCount--;

}

}

```

- 服務(wù)器端測試代碼,模擬服務(wù)器主動向前端發(fā)送消息

```java

/**

* 觸發(fā)后模擬服務(wù)器主動向前端發(fā)送系統(tǒng)即時提醒

*/

@RequestMapping("sendMsg")

public void sendMsg() {

SimpleDateFormat sf = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");

// 模擬向已連接的WebSocket客戶端發(fā)送系統(tǒng)提醒

for(WebSocketTest item: WebSocketTest.webSocketSet){

try {

item.sendMessage("系統(tǒng)提醒:當(dāng)前時間," + sf.format(new Date()) + ",請盡快完成任務(wù)!");

} catch (IOException e) {

e.printStackTrace();

continue;

}

}

}

```

- 服務(wù)器啟動后,通過http://localhost:8080/WebSocket/alert.action跳轉(zhuǎn)到了前端Jsp頁面,頁面創(chuàng)建了WebSocket連接,然后通過調(diào)用http://localhost:8080/WebSocket/sendMsg.action模擬服務(wù)器主動向前端發(fā)送系統(tǒng)提醒信息,前端進行即時的展示,效果如下:

WebSocket分析及實踐

## 注意

通過上面的講述,WebSocket 的優(yōu)勢已經(jīng)很明顯了,但是作為一個正在演變中的 Web 規(guī)范,我們也要看到目前用 Websocket 構(gòu)建應(yīng)用程序的一些風(fēng)險。首先,WebSocket 規(guī)范目前還處于草案階段,也就是它的規(guī)范和 API 還是有變動的可能,另外的一個風(fēng)險就是微軟的 IE 作為占市場份額最大的瀏覽器,和其他的主流瀏覽器相比,對 HTML5 的支持是比較差的,這是我們在構(gòu)建企業(yè)級的 Web 應(yīng)用的時候必須要考慮的一個問題。

0 分享到:
和我們在線交談!