java WebSocket開發入門WebSocket

前言

之前一個項目中九風開發app的用戶的消息部分,由于項目比較緊,而且之前沒有接觸過WebSocket開發,所以暫時先使用輪詢方式來開發消息模塊,最近準備升級消息模塊,準備使用tomcat的WebSocket來開發消息,寫此文章方便自己也方便大家。
如需馬上測試的scoket的請直接往下翻到代碼出
這篇文章中的代碼不能運行在spring mvc模式下,如需在mvc模式下運行,請參考這篇Sring MVC 模式下使用websocket

特別說明

此文章中的后臺代碼不能直接用于Spring MVC中web層、service層直接調用,下篇文章準備寫這個(還沒寫好,九風盡快), 文章中有需要改正的還請簡友指出。

消息推送

消息推送大家都不陌生,比如扣扣消息、某東某寶購物后的系統消息等等都是消息推送,在H5出來之前,消息推送基本上都是使用HTTP請求的,但HTTP請求只能在客戶端發起請求后服務端返回消息,而不能再客戶端未發起請求時服務端主動推送消息給客戶端,而對于HTTP的方式實現消息推送時,有以下幾種方式:

傳統HTTP請求響應客戶端服務器的交互圖

輪詢方式:客戶端定時向服務端發送ajax請求,服務器接收到請求后馬上返回消息并關閉連接。
優點:后端程序編寫比較容易。
缺點:TCP的建立和關閉操作浪費時間和帶寬,請求中有大半是無用,浪費帶寬和服務器資源。
實例:適于小型應用。

長輪詢:客戶端向服務器發送Ajax請求,服務器接到請求后hold住連接,直到有新消息才返回響應信息并關閉連接,客戶端處理完響應信息后再向服務器發送新的請求。
優點:在無消息的情況下不會頻繁的請求,耗費資源小。
缺點:服務器hold連接會消耗資源,返回數據順序無保證,難于管理維護。
實例:WebQQ、Hi網頁版、Facebook IM。

長連接:在頁面里嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設為對一個長連接的請求或是采用xhr請求,服務器端就能源源不斷地往客戶端輸入數據。
優點:消息即時到達,不發無用請求;管理起來也相對方便。
缺點:服務器維護一個長連接會增加開銷,當客戶端越來越多的時候,server壓力大!
實例:Gmail聊天

Flash Socket:在頁面中內嵌入一個使用了Socket類的 Flash 程序JavaScript通過調用此Flash程序提供的Socket接口與服務器端的Socket接口進行通信,JavaScript在收到服務器端傳送的信息后控制頁面的顯示。
優點:實現真正的即時通信,而不是偽即時。
缺點:客戶端必須安裝Flash插件,移動端支持不好,IOS系統中沒有flash的存在;非HTTP協議,無法自動穿越防火墻。
實例:網絡互動游戲。

webSocket:HTML5 WebSocket設計出來的目的就是取代輪詢和長連接,使客戶端瀏覽器具備像C/S框架下桌面系統的即時通訊能力,實現了瀏覽器和服務器全雙工通信,建立在TCP之上,雖然WebSocket和HTTP一樣通過TCP來傳輸數據,但WebSocket可以主動的向對方發送或接收數據,就像Socket一樣;并且WebSocket需要類似TCP的客戶端和服務端通過握手連接,連接成功后才能互相通信。
優點:雙向通信、事件驅動、異步、使用ws或wss協議的客戶端能夠真正實現意義上的推送功能。
缺點:少部分瀏覽器不支持。
示例:社交聊天(微信、QQ)、彈幕、多玩家玩游戲、協同編輯、股票基金實時報價、體育實況更新、視頻會議/聊天、基于位置的應用、在線教育、智能家居等高實時性的場景。

而websocket請求和服務器交互的如下圖所示:


WebSocket 請求響應客戶端服務器交互圖

對比前面的http的客戶端服務器的交互圖可以發現WebSocket方式減少了很多TCP打開和關閉連接的操作,WebSocket的資源利用率高。

輪詢和 WebSocket 實現方式的網絡負載對比圖

WebSocket規范

WebSocket一種在單個 TCP 連接上進行全雙工通訊的協議。WebSocket通信協議于2011年被IETF定為標準RFC 6455,并被RFC7936所補充規范。WebSocket API也被W3C定為標準。

WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。在 WebSocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就直接可以創建持久性的連接,并進行雙向數據傳輸。

WebSocket 協議本質上是一個基于 TCP 的協議。為了建立一個 WebSocket 連接,客戶端瀏覽器首先要向服務器發起一個 HTTP 請求,這個請求和通常的 HTTP 請求不同,包含了一些附加頭信息,附加信息如圖所示:

WebSocket請求與響應頭內容解析

瀏覽器支持:所有的最新瀏覽器支持最新WebSocket規范(RFC 6455) ,從維基百科上介紹瀏覽器對WebSocket的支持如下表所示:

瀏覽器 Chrome Edge Firfox IE Opera Safari
最低版本 16 支持 11.0 10 12.10 6.0

移動端支持:移動端基本都支持websocket了,其實和瀏覽器版支持的版本一樣,具體支持如下所示:

最低 android瀏覽器 Chrome 移動版 Firfox 移動版 Opera 移動版 Safari IOS版
最低版本 4.4 16 11.0 12.10 6.0

服務器支持:目前主流的web服務器都已經支持,具體版本如下表所示:

廠商 應用服務器 備注
IBM WebSphere WebSphere 8.0 以上版本支持,7.X 之前版本結合 MQTT 支持類似的 HTTP 長連接
甲骨文 WebLogic WebLogic 12c 支持,11g 及 10g 版本通過 HTTP Publish 支持類似的 HTTP 長連接
微軟 IIS IIS 7.0+支持
Apache Tomcat Tomcat 7.0.5+支持,7.0.2X 及 7.0.3X 通過自定義 API 支持
Jetty Jetty 7.0+支持

以下內容將使用tomcat服務器來實現Websocket

java WebSocket實現

Oracle 發布的 java 的 WebSocket 的規范是 JSR356規范 ,Tomcat從7.0.27開始支持WebSocket,從7.0.47開始支持JSR-356。

websocket簡單實現分為以下幾個步驟:添加websocket庫編寫后臺代碼編寫前端代碼

添加websocket庫

在maven中添加websocket庫的代碼如下所示:

<dependency>
   <groupId>javax.websocket</groupId>
   <artifactId>javax.websocket-api</artifactId>
   <version>1.1</version>
   <scope>provided</scope>
</dependency>

九風有次沒寫<scope>字段,前端后臺都會報錯,大家記得加上就行。
前端錯誤內容:
 WebSocket connection to 'ws://localhost:8080/{project-name}/websocket' failed: Error during WebSocket handshake: Unexpected response code: 404" 。

后臺錯誤內容:
 Did not find handler method for [/websocket]
 Matching patterns for request [/websocket] are [/**]
 URI Template variables for request [/websocket] are {}
 Mapping [/websocket] to HandlerExecutionChain with handler       [org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler@398f0b1f] and 1 interceptor
 Last-Modified value for [/{project-name}/websocket] is: -1

編寫后臺代碼

后臺實現websocket有兩種方式:使用繼承類、使用注解;注解方式比較方便,一下代碼中使用注解方式來進行演示。

聲明websocket地址類似Spring MVC中的@controller注解類似,websocket使用@ServerEndpoint來進行聲明接口:@ServerEndpoint(value="/websocket/{paraName}") ; 其中 “ { } ”用來表示帶參數的連接,如果需要獲取{}中的參數在參數列表中增加:@PathParam("paraName") Integer userId 。則連接地址形如:ws://localhost:8080/project-name/websocket/8,其中每個連接可以設置不同的paraName的值。

注解、成員數據介紹
1.@OnOpen
public void onOpen(Session session) throws IOException{ } -------有連接時的觸發函數。 我們可以在用戶連接時記錄用戶的連接帶的參數,只需在參數列表中增加參數:@PathParam("paraName") String paraName。

2.@OnClose
public void onClose(){ } ------連接關閉時的調用方法。

3.@OnMessage
public void onMessage(String message, Session session) { } -------收到消息時調用的函數,其中Session是每個websocket特有的數據成員,詳情見4.

4.Session ----每個Session代表了兩個web socket斷點的會話;當websocket握手成功后,websocket就會提供一個打開的Session,可以通過這個Session來對另一個端點發送數據;如果Session關閉后發送數據將會報錯。

5.Session.getBasicRemote().sendText("message") -------向該Session連接的用戶發送字符串數據。

6.@OnError
public void onError(Session session, Throwable error) { } --------發生意外錯誤時調用的函數。

后臺代碼:有以上基礎后就直接上代碼了.

import java.io.IOException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** 
 * @Class: Test
 * @Description: 簡單websocket demo
 * @author 九風萍舟
 */
@ServerEndpoint(value="/websocketTest/{userId}")
public class Test {
    private Logger logger = LoggerFactory.getLogger(Test.class);
    
    private static String userId;
    
    //連接時執行
    @OnOpen
    public void onOpen(@PathParam("userId") String userId,Session session) throws IOException{
        this.userId = userId;
        logger.debug("新連接:{}",userId);
    }
    
    //關閉時執行
    @OnClose
    public void onClose(){
        logger.debug("連接:{} 關閉",this.userId);
    }
    
    //收到消息時執行
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        logger.debug("收到用戶{}的消息{}",this.userId,message);
        session.getBasicRemote().sendText("收到 "+this.userId+" 的消息 "); //回復用戶
    }
    
    //連接錯誤時執行
    @OnError
    public void onError(Session session, Throwable error){
        logger.debug("用戶id為:{}的連接發送錯誤",this.userId);
        error.printStackTrace();
    }

}

ServerEndpoint報錯: 原因是不能自動檢測 ServerEndpoint 的包,解決方法:復制 import javax.websocket.server.ServerEndpoint; 到文件程序 import 區域即可。

編寫前端代碼

后臺代碼編寫了那么前端代碼就幾乎不用講解了,相信大家一眼就能看得懂。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title></title>
    </head>
    <body>
        websocket Demo---- user000 <br />
        <input id="text" type="text" /> 
        <button onclick="send()"> Send </button>   
        <button   onclick="closeWebSocket()"> Close </button>
        <div id="message">   </div>
        
    <script type="text/javascript">
     //判斷當前瀏覽器是否支持WebSocket
      if('WebSocket' in window){
          websocket = new WebSocket("ws://localhost:8080/Demo/websocketTest/user000");
          console.log("link success")
      }else{
          alert('Not support websocket')
      }
      
      //連接發生錯誤的回調方法
      websocket.onerror = function(){
          setMessageInnerHTML("error");
      };
       
      //連接成功建立的回調方法
      websocket.onopen = function(event){
          setMessageInnerHTML("open");
      }
       console.log("-----")
      //接收到消息的回調方法
      websocket.onmessage = function(event){
            setMessageInnerHTML(event.data);
      }
       
      //連接關閉的回調方法
      websocket.onclose = function(){
          setMessageInnerHTML("close");
      }
       
      //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket連接,防止連接還沒斷開就關閉窗口,server端會拋異常。
      window.onbeforeunload = function(){
          websocket.close();
      }
       
      //將消息顯示在網頁上
      function setMessageInnerHTML(innerHTML){
          document.getElementById('message').innerHTML += innerHTML + '<br/>';
      }
       
      //關閉連接
      function closeWebSocket(){
          websocket.close();
      }
       
      //發送消息
      function send(){
          var message = document.getElementById('text').value;
          websocket.send(message);
      }
    </script>
        
    </body>
</html>

測試運行

在Chrome上打開前端代碼后,馬上就建立了連接,大家可以使用F12查看下建立連接的請求與響應,可以對比前面關于協議建立的部分進行學習。
建立連接后,想后臺發送數據后,同時可以看到后臺返回的信息:

前端測試

在后臺可以看到連接的建立和收到的數據:

后臺測試

對于其他功能功能大家可以自己測測。

總結

websocket特別適合于需要實時數據傳送的場景,比輪詢方式效率高很多。

參考

WebSocket與消息推送
Java后端WebSocket的Tomcat實現
WebSocket 實戰
使用 HTML5 WebSocket 構建實時 Web 應用
混合移動應用的消息推送之 websocket
WebSocket 維基百科
WebSocket 接口文檔
RFC 6455 規范
JSR 356, Java API for WebSocket

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,991評論 19 139
  • 點擊查看原文 Web SDK 開發手冊 SDK 概述 網易云信 SDK 為 Web 應用提供一個完善的 IM 系統...
    layjoy閱讀 13,951評論 0 15
  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,373評論 11 349
  • 1 2008年,我的高中生涯畫上了句號。為了踏上大學這個所有學子們的...
    阿蔡Xing閱讀 384評論 0 0
  • 【意義定制】每一次的定制都是只屬于自己的專屬意義。而小意和你們的每次對話,也都是獨一無二的回憶!希望各位要經常找小...
    生活小訴閱讀 226評論 0 0