java WebSocket開發(fā)入門WebSocket

前言

之前一個(gè)項(xiàng)目中九風(fēng)開發(fā)app的用戶的消息部分妒蛇,由于項(xiàng)目比較緊机断,而且之前沒有接觸過WebSocket開發(fā),所以暫時(shí)先使用輪詢方式來開發(fā)消息模塊绣夺,最近準(zhǔn)備升級消息模塊吏奸,準(zhǔn)備使用tomcat的WebSocket來開發(fā)消息,寫此文章方便自己也方便大家陶耍。
如需馬上測試的scoket的請直接往下翻到代碼出奋蔚。
這篇文章中的代碼不能運(yùn)行在spring mvc模式下,如需在mvc模式下運(yùn)行烈钞,請參考這篇Sring MVC 模式下使用websocket泊碑。

特別說明

此文章中的后臺(tái)代碼不能直接用于Spring MVC中web層、service層直接調(diào)用毯欣,下篇文章準(zhǔn)備寫這個(gè)(還沒寫好馒过,九風(fēng)盡快), 文章中有需要改正的還請簡友指出酗钞。

消息推送

消息推送大家都不陌生腹忽,比如扣扣消息、某東某寶購物后的系統(tǒng)消息等等都是消息推送砚作,在H5出來之前窘奏,消息推送基本上都是使用HTTP請求的,但HTTP請求只能在客戶端發(fā)起請求后服務(wù)端返回消息葫录,而不能再客戶端未發(fā)起請求時(shí)服務(wù)端主動(dòng)推送消息給客戶端蔼夜,而對于HTTP的方式實(shí)現(xiàn)消息推送時(shí),有以下幾種方式:

傳統(tǒng)HTTP請求響應(yīng)客戶端服務(wù)器的交互圖

輪詢方式:客戶端定時(shí)向服務(wù)端發(fā)送ajax請求压昼,服務(wù)器接收到請求后馬上返回消息并關(guān)閉連接求冷。
優(yōu)點(diǎn):后端程序編寫比較容易。
缺點(diǎn):TCP的建立和關(guān)閉操作浪費(fèi)時(shí)間和帶寬窍霞,請求中有大半是無用匠题,浪費(fèi)帶寬和服務(wù)器資源。
實(shí)例:適于小型應(yīng)用但金。

長輪詢:客戶端向服務(wù)器發(fā)送Ajax請求韭山,服務(wù)器接到請求后hold住連接,直到有新消息才返回響應(yīng)信息并關(guān)閉連接,客戶端處理完響應(yīng)信息后再向服務(wù)器發(fā)送新的請求钱磅。
優(yōu)點(diǎn):在無消息的情況下不會(huì)頻繁的請求梦裂,耗費(fèi)資源小。
缺點(diǎn):服務(wù)器hold連接會(huì)消耗資源盖淡,返回?cái)?shù)據(jù)順序無保證年柠,難于管理維護(hù)。
實(shí)例:WebQQ褪迟、Hi網(wǎng)頁版冗恨、Facebook IM。

長連接:在頁面里嵌入一個(gè)隱蔵iframe味赃,將這個(gè)隱蔵iframe的src屬性設(shè)為對一個(gè)長連接的請求或是采用xhr請求掀抹,服務(wù)器端就能源源不斷地往客戶端輸入數(shù)據(jù)。
優(yōu)點(diǎn):消息即時(shí)到達(dá)心俗,不發(fā)無用請求傲武;管理起來也相對方便。
缺點(diǎn):服務(wù)器維護(hù)一個(gè)長連接會(huì)增加開銷城榛,當(dāng)客戶端越來越多的時(shí)候谱轨,server壓力大!
實(shí)例:Gmail聊天

Flash Socket:在頁面中內(nèi)嵌入一個(gè)使用了Socket類的 Flash 程序JavaScript通過調(diào)用此Flash程序提供的Socket接口與服務(wù)器端的Socket接口進(jìn)行通信吠谢,JavaScript在收到服務(wù)器端傳送的信息后控制頁面的顯示土童。
優(yōu)點(diǎn):實(shí)現(xiàn)真正的即時(shí)通信,而不是偽即時(shí)工坊。
缺點(diǎn):客戶端必須安裝Flash插件献汗,移動(dòng)端支持不好,IOS系統(tǒng)中沒有flash的存在王污;非HTTP協(xié)議罢吃,無法自動(dòng)穿越防火墻。
實(shí)例:網(wǎng)絡(luò)互動(dòng)游戲昭齐。

webSocket:HTML5 WebSocket設(shè)計(jì)出來的目的就是取代輪詢和長連接尿招,使客戶端瀏覽器具備像C/S框架下桌面系統(tǒng)的即時(shí)通訊能力,實(shí)現(xiàn)了瀏覽器和服務(wù)器全雙工通信阱驾,建立在TCP之上就谜,雖然WebSocket和HTTP一樣通過TCP來傳輸數(shù)據(jù),但WebSocket可以主動(dòng)的向?qū)Ψ桨l(fā)送或接收數(shù)據(jù)里覆,就像Socket一樣丧荐;并且WebSocket需要類似TCP的客戶端和服務(wù)端通過握手連接,連接成功后才能互相通信喧枷。
優(yōu)點(diǎn):雙向通信虹统、事件驅(qū)動(dòng)弓坞、異步、使用ws或wss協(xié)議的客戶端能夠真正實(shí)現(xiàn)意義上的推送功能车荔。
缺點(diǎn):少部分瀏覽器不支持渡冻。
示例:社交聊天(微信、QQ)忧便、彈幕族吻、多玩家玩游戲、協(xié)同編輯茬腿、股票基金實(shí)時(shí)報(bào)價(jià)、體育實(shí)況更新宜雀、視頻會(huì)議/聊天切平、基于位置的應(yīng)用、在線教育辐董、智能家居等高實(shí)時(shí)性的場景悴品。

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


WebSocket 請求響應(yīng)客戶端服務(wù)器交互圖

對比前面的http的客戶端服務(wù)器的交互圖可以發(fā)現(xiàn)WebSocket方式減少了很多TCP打開和關(guān)閉連接的操作,WebSocket的資源利用率高简烘。

輪詢和 WebSocket 實(shí)現(xiàn)方式的網(wǎng)絡(luò)負(fù)載對比圖

WebSocket規(guī)范

WebSocket一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議苔严。WebSocket通信協(xié)議于2011年被IETF定為標(biāo)準(zhǔn)RFC 6455,并被RFC7936所補(bǔ)充規(guī)范孤澎。WebSocket API也被W3C定為標(biāo)準(zhǔn)届氢。

WebSocket 使得客戶端和服務(wù)器之間的數(shù)據(jù)交換變得更加簡單,允許服務(wù)端主動(dòng)向客戶端推送數(shù)據(jù)覆旭。在 WebSocket API 中退子,瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接型将,并進(jìn)行雙向數(shù)據(jù)傳輸寂祥。

WebSocket 協(xié)議本質(zhì)上是一個(gè)基于 TCP 的協(xié)議。為了建立一個(gè) WebSocket 連接七兜,客戶端瀏覽器首先要向服務(wù)器發(fā)起一個(gè) HTTP 請求丸凭,這個(gè)請求和通常的 HTTP 請求不同,包含了一些附加頭信息腕铸,附加信息如圖所示:

WebSocket請求與響應(yīng)頭內(nèi)容解析

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

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

移動(dòng)端支持:移動(dòng)端基本都支持websocket了,其實(shí)和瀏覽器版支持的版本一樣狠裹,具體支持如下所示:

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

服務(wù)器支持:目前主流的web服務(wù)器都已經(jīng)支持向拆,具體版本如下表所示:

廠商 應(yīng)用服務(wù)器 備注
IBM WebSphere WebSphere 8.0 以上版本支持,7.X 之前版本結(jié)合 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+支持

以下內(nèi)容將使用tomcat服務(wù)器來實(shí)現(xiàn)Websocket

java WebSocket實(shí)現(xiàn)

Oracle 發(fā)布的 java 的 WebSocket 的規(guī)范是 JSR356規(guī)范 ,Tomcat從7.0.27開始支持WebSocket刹缝,從7.0.47開始支持JSR-356。

websocket簡單實(shí)現(xiàn)分為以下幾個(gè)步驟:添加websocket庫颈将、編寫后臺(tái)代碼梢夯、編寫前端代碼

添加websocket庫

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

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

九風(fēng)有次沒寫<scope>字段晴圾,前端后臺(tái)都會(huì)報(bào)錯(cuò)颂砸,大家記得加上就行。
前端錯(cuò)誤內(nèi)容:
 WebSocket connection to 'ws://localhost:8080/{project-name}/websocket' failed: Error during WebSocket handshake: Unexpected response code: 404" 死姚。

后臺(tái)錯(cuò)誤內(nèi)容:
 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

編寫后臺(tái)代碼

后臺(tái)實(shí)現(xiàn)websocket有兩種方式:使用繼承類人乓、使用注解;注解方式比較方便都毒,一下代碼中使用注解方式來進(jìn)行演示色罚。

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

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

2.@OnClose
public void onClose(){ } ------連接關(guān)閉時(shí)的調(diào)用方法。

3.@OnMessage
public void onMessage(String message, Session session) { } -------收到消息時(shí)調(diào)用的函數(shù)禀晓,其中Session是每個(gè)websocket特有的數(shù)據(jù)成員柄粹,詳情見4.

4.Session ----每個(gè)Session代表了兩個(gè)web socket斷點(diǎn)的會(huì)話;當(dāng)websocket握手成功后匆绣,websocket就會(huì)提供一個(gè)打開的Session驻右,可以通過這個(gè)Session來對另一個(gè)端點(diǎn)發(fā)送數(shù)據(jù);如果Session關(guān)閉后發(fā)送數(shù)據(jù)將會(huì)報(bào)錯(cuò)崎淳。

5.Session.getBasicRemote().sendText("message") -------向該Session連接的用戶發(fā)送字符串?dāng)?shù)據(jù)堪夭。

6.@OnError
public void onError(Session session, Throwable error) { } --------發(fā)生意外錯(cuò)誤時(shí)調(diào)用的函數(shù)。

后臺(tái)代碼:有以上基礎(chǔ)后就直接上代碼了.

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 九風(fēng)萍舟
 */
@ServerEndpoint(value="/websocketTest/{userId}")
public class Test {
    private Logger logger = LoggerFactory.getLogger(Test.class);
    
    private static String userId;
    
    //連接時(shí)執(zhí)行
    @OnOpen
    public void onOpen(@PathParam("userId") String userId,Session session) throws IOException{
        this.userId = userId;
        logger.debug("新連接:{}",userId);
    }
    
    //關(guān)閉時(shí)執(zhí)行
    @OnClose
    public void onClose(){
        logger.debug("連接:{} 關(guān)閉",this.userId);
    }
    
    //收到消息時(shí)執(zhí)行
    @OnMessage
    public void onMessage(String message, Session session) throws IOException {
        logger.debug("收到用戶{}的消息{}",this.userId,message);
        session.getBasicRemote().sendText("收到 "+this.userId+" 的消息 "); //回復(fù)用戶
    }
    
    //連接錯(cuò)誤時(shí)執(zhí)行
    @OnError
    public void onError(Session session, Throwable error){
        logger.debug("用戶id為:{}的連接發(fā)送錯(cuò)誤",this.userId);
        error.printStackTrace();
    }

}

ServerEndpoint報(bào)錯(cuò): 原因是不能自動(dòng)檢測 ServerEndpoint 的包拣凹,解決方法:復(fù)制 import javax.websocket.server.ServerEndpoint; 到文件程序 import 區(qū)域即可森爽。

編寫前端代碼

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

<!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">
     //判斷當(dāng)前瀏覽器是否支持WebSocket
      if('WebSocket' in window){
          websocket = new WebSocket("ws://localhost:8080/Demo/websocketTest/user000");
          console.log("link success")
      }else{
          alert('Not support websocket')
      }
      
      //連接發(fā)生錯(cuò)誤的回調(diào)方法
      websocket.onerror = function(){
          setMessageInnerHTML("error");
      };
       
      //連接成功建立的回調(diào)方法
      websocket.onopen = function(event){
          setMessageInnerHTML("open");
      }
       console.log("-----")
      //接收到消息的回調(diào)方法
      websocket.onmessage = function(event){
            setMessageInnerHTML(event.data);
      }
       
      //連接關(guān)閉的回調(diào)方法
      websocket.onclose = function(){
          setMessageInnerHTML("close");
      }
       
      //監(jiān)聽窗口關(guān)閉事件爬迟,當(dāng)窗口關(guān)閉時(shí),主動(dòng)去關(guān)閉websocket連接菊匿,防止連接還沒斷開就關(guān)閉窗口付呕,server端會(huì)拋異常计福。
      window.onbeforeunload = function(){
          websocket.close();
      }
       
      //將消息顯示在網(wǎng)頁上
      function setMessageInnerHTML(innerHTML){
          document.getElementById('message').innerHTML += innerHTML + '<br/>';
      }
       
      //關(guān)閉連接
      function closeWebSocket(){
          websocket.close();
      }
       
      //發(fā)送消息
      function send(){
          var message = document.getElementById('text').value;
          websocket.send(message);
      }
    </script>
        
    </body>
</html>

測試運(yùn)行

在Chrome上打開前端代碼后,馬上就建立了連接徽职,大家可以使用F12查看下建立連接的請求與響應(yīng)象颖,可以對比前面關(guān)于協(xié)議建立的部分進(jìn)行學(xué)習(xí)。
建立連接后姆钉,想后臺(tái)發(fā)送數(shù)據(jù)后说订,同時(shí)可以看到后臺(tái)返回的信息:

前端測試

在后臺(tái)可以看到連接的建立和收到的數(shù)據(jù):

后臺(tái)測試

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

總結(jié)

websocket特別適合于需要實(shí)時(shí)數(shù)據(jù)傳送的場景潮瓶,比輪詢方式效率高很多陶冷。

參考

WebSocket與消息推送
Java后端WebSocket的Tomcat實(shí)現(xiàn)
WebSocket 實(shí)戰(zhàn)
使用 HTML5 WebSocket 構(gòu)建實(shí)時(shí) Web 應(yīng)用
混合移動(dòng)應(yīng)用的消息推送之 websocket
WebSocket 維基百科
WebSocket 接口文檔
RFC 6455 規(guī)范
JSR 356, Java API for WebSocket

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市毯辅,隨后出現(xiàn)的幾起案子埂伦,更是在濱河造成了極大的恐慌,老刑警劉巖悉罕,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赤屋,死亡現(xiàn)場離奇詭異立镶,居然都是意外死亡壁袄,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門媚媒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗜逻,“玉大人,你說我怎么就攤上這事缭召≌磺辏” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵嵌巷,是天一觀的道長萄凤。 經(jīng)常有香客問我,道長搪哪,這世上最難降的妖魔是什么靡努? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮晓折,結(jié)果婚禮上惑朦,老公的妹妹穿的比我還像新娘。我一直安慰自己漓概,他們只是感情好漾月,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著胃珍,像睡著了一般梁肿。 火紅的嫁衣襯著肌膚如雪蜓陌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天栈雳,我揣著相機(jī)與錄音护奈,去河邊找鬼。 笑死哥纫,一個(gè)胖子當(dāng)著我的面吹牛霉旗,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蛀骇,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼厌秒,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了擅憔?” 一聲冷哼從身側(cè)響起鸵闪,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎暑诸,沒想到半個(gè)月后蚌讼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡个榕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年篡石,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片西采。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡凰萨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出械馆,到底是詐尸還是另有隱情胖眷,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布霹崎,位于F島的核電站珊搀,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏尾菇。R本人自食惡果不足惜境析,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望错沽。 院中可真熱鬧簿晓,春花似錦、人聲如沸千埃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽放可。三九已至谒臼,卻和暖如春朝刊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蜈缤。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工拾氓, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人底哥。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓咙鞍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親趾徽。 傳聞我的和親對象是個(gè)殘疾皇子续滋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)孵奶,斷路器疲酌,智...
    卡卡羅2017閱讀 134,659評論 18 139
  • 點(diǎn)擊查看原文 Web SDK 開發(fā)手冊 SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個(gè)完善的 IM 系統(tǒng)...
    layjoy閱讀 13,767評論 0 15
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司了袁,掛了不少朗恳,但最終還是拿到小米、百度载绿、阿里粥诫、京東、新浪卢鹦、CVTE臀脏、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,253評論 11 349
  • 1 2008年劝堪,我的高中生涯畫上了句號冀自。為了踏上大學(xué)這個(gè)所有學(xué)子們的...
    阿蔡Xing閱讀 372評論 0 0
  • 【意義定制】每一次的定制都是只屬于自己的專屬意義。而小意和你們的每次對話秒啦,也都是獨(dú)一無二的回憶熬粗!希望各位要經(jīng)常找小...
    生活小訴閱讀 210評論 0 0