Java Web高級編程(四)

来源:http://www.cnblogs.com/winterfells/archive/2017/12/11/8025413.html
-Advertisement-
Play Games

WebSocket 一、WebSocket的產生 用戶希望Web頁面可以進行交互,用於解決這個問題的技術是JavaScript,現在Web上有許多的可用的JavaScript框架,在使用極少的JavaScript的情況下就可以創建出豐富的單頁面Web——Ajax技術(非同步JavaScript和XML ...


WebSocket

一、WebSocket的產生

用戶希望Web頁面可以進行交互,用於解決這個問題的技術是JavaScript,現在Web上有許多的可用的JavaScript框架,在使用極少的JavaScript的情況下就可以創建出豐富的單頁面Web——Ajax技術(非同步JavaScript和XML)。

在採用了Ajax之後,瀏覽器中的Web應用程式可以與伺服器端的組件進行通信,而不需要改變瀏覽器頁面或者刷新。這個通信過程不需要用戶知道,並且它可以用於向伺服器發送新數據或者從伺服器獲得新數據。

但是,瀏覽器只可以從伺服器專區新的數據,但是瀏覽器並不知道數據什麼時候使用,只有伺服器知道什麼時候有新數據發送到瀏覽器,而瀏覽器並不知道。

解決方法1,頻繁輪詢

頻繁輪詢伺服器獲取新數據,以一個固定的頻率,通常是每秒一次,瀏覽器將發送Ajax請求到伺服器查詢新數據。如果瀏覽器有新的數據發送到伺服器,數據將被添加到輪詢請求中一同發送給瀏覽器(但是大量請求會被浪費)。

解決方法2,長輪詢

伺服器只有在發送數據時才會響應瀏覽器(如果瀏覽器在伺服器響應之前有新數據要發送,瀏覽器就必須要創建一個新的並行請求,或者終止當前的請求;TCP和HTTP規定了連接超時的情況;HTTP存在著強制的連接限制)。

解決方法3,分塊編碼

伺服器可以在不聲明內容長度的情況下響應請求。在響應中,每個塊的開頭一次是:一個用於表示塊長度的數字、一系列表示塊擴展的可選字元和一個CRLF(回車換行)序列。接著是塊包含的數據和另一個CRLF。瀏覽器將創建一個連接到“下游端點”的長生命連接,並且伺服器將使用該連接以塊的方式向瀏覽器發送更新。

解決方法4,Applet和Adobe Flash

創建連接到伺服器的普通TCP套接字連接,當瀏覽器有了新的數據要發送到伺服器,它將由瀏覽器插件暴露出的JavaScript DOM函數調用Java或Flash方法,然後該方法吧數據轉發到伺服器上。

解決方法5,WebSocket

WebSocket連接首先將使用非正常的HTTP請求以特定的模式訪問一個URL,WebSocket是持久的全雙工通信協議。在握手完成之後,文本和二進位消息將可以同時在兩個方向上進行發送,而不需要關閉和重新連接。

WebSocket的優點:

  1. 連接埠在80(ws)和433(wss),所以不會被防火牆阻塞。
  2. 使用HTTP握手,可以自然地集成到網路瀏覽器和HTTP伺服器上。
  3. 使用ping和pong保持WebSocket一直處於活躍狀態。
  4. 當消息啟動和它的內容到達時,伺服器和客戶端都可以知道。
  5. WebSocket在關閉連接時會發送特殊的關閉消息。
  6. 可以支持跨區域連接。

二、WebSocket API

WebSocket並不只是在瀏覽器和伺服器的通信,兩個以任何框架編寫、支持WebSocket的應用程式都可以創建WebSocket連接進行通信。

WebSocket的Java API包含在javax.websocket中,並指定了一組類和介面包含所有的常見功能。

客戶端API

客戶端API基於ContainerProvider類和WebSocketContainer、RemoteEndpoint和Session介面構建。

WebSocketContainer提供了對所有WebSocket客戶端特性的訪問,而ContainerProvider類聽了靜態的getWebSocketContainer方法用來獲取底層WebSocket客戶端的實現。

WebSocketContainer提供了4個重載的connectToServer方法,它們都將接受一個URI,用於連接遠程終端和初始化握手。

  1. 標註了@ClientEndpoint的任意類型的POJO
  2. 標註了@ClientEndpoint的任意類型的POJO的Class<?>
  3. Endpoint類的實例或者一個Class<? extends EndPoint>。

當握手完成是,connectToServer方法將返回一個Session。

其中WebSocket的Endpoint有3個方法,onOpen、onClose和onError,它們將在這些時間發生時進行調用。

而@ClientEndpoint類標註了@onOpen、@onClose和@onError的方法。

  1. @OnOpen方法可以有:一個可選的Session參數,一個可選的EndpointConfig參數。
  2. @OnClose方法可以有:一個可選的Session參數,一個可選的CloseReason參數。
  3. @OnError方法可以有:一個可選的Session參數,一個可選的Throwable參數。
  4. @OnMessage方法可以有:一個可選的Session參數,其它參數的組合。

這是一個WebSocket創建多人游戲的伺服器終端代碼:

public class TicTacToeServer
{
    private static Map<Long, Game> games = new Hashtable<>();
    private static ObjectMapper mapper = new ObjectMapper();

    @OnOpen
    public void onOpen(Session session, @PathParam("gameId") long gameId,
                       @PathParam("username") String username)
    {
        try
        {
            TicTacToeGame ticTacToeGame = TicTacToeGame.getActiveGame(gameId);
            if(ticTacToeGame != null)
            {
                session.close(new CloseReason(
                        CloseReason.CloseCodes.UNEXPECTED_CONDITION,
                        "This game has already started."
                ));
            }

            List<String> actions = session.getRequestParameterMap().get("action");
            if(actions != null && actions.size() == 1)
            {
                String action = actions.get(0);
                if("start".equalsIgnoreCase(action))
                {
                    Game game = new Game();
                    game.gameId = gameId;
                    game.player1 = session;
                    TicTacToeServer.games.put(gameId, game);
                }
                else if("join".equalsIgnoreCase(action))
                {
                    Game game = TicTacToeServer.games.get(gameId);
                    game.player2 = session;
                    game.ticTacToeGame = TicTacToeGame.startGame(gameId, username);
                    this.sendJsonMessage(game.player1, game,
                            new GameStartedMessage(game.ticTacToeGame));
                    this.sendJsonMessage(game.player2, game,
                            new GameStartedMessage(game.ticTacToeGame));
                }
            }
        }
        catch(IOException e)
        {
            e.printStackTrace();
            try
            {
                session.close(new CloseReason(
                        CloseReason.CloseCodes.UNEXPECTED_CONDITION, e.toString()
                ));
            }
            catch(IOException ignore) { }
        }
    }

    @OnMessage
    public void onMessage(Session session, String message,
                          @PathParam("gameId") long gameId)
    {
        Game game = TicTacToeServer.games.get(gameId);
        boolean isPlayer1 = session == game.player1;

        try
        {
            Move move = TicTacToeServer.mapper.readValue(message, Move.class);
            game.ticTacToeGame.move(
                    isPlayer1 ? TicTacToeGame.Player.PLAYER1 :
                            TicTacToeGame.Player.PLAYER2,
                    move.getRow(),
                    move.getColumn()
            );
            this.sendJsonMessage((isPlayer1 ? game.player2 : game.player1), game,
                    new OpponentMadeMoveMessage(move));
            if(game.ticTacToeGame.isOver())
            {
                if(game.ticTacToeGame.isDraw())
                {
                    this.sendJsonMessage(game.player1, game,
                            new GameIsDrawMessage());
                    this.sendJsonMessage(game.player2, game,
                            new GameIsDrawMessage());
                }
                else
                {
                    boolean wasPlayer1 = game.ticTacToeGame.getWinner() ==
                            TicTacToeGame.Player.PLAYER1;
                    this.sendJsonMessage(game.player1, game,
                            new GameOverMessage(wasPlayer1));
                    this.sendJsonMessage(game.player2, game,
                            new GameOverMessage(!wasPlayer1));
                }
                game.player1.close();
                game.player2.close();
            }
        }
        catch(IOException e)
        {
            this.handleException(e, game);
        }
    }

    @OnClose
    public void onClose(Session session, @PathParam("gameId") long gameId)
    {
        Game game = TicTacToeServer.games.get(gameId);
        if(game == null)
            return;
        boolean isPlayer1 = session == game.player1;
        if(game.ticTacToeGame == null)
        {
            TicTacToeGame.removeQueuedGame(game.gameId);
        }
        else if(!game.ticTacToeGame.isOver())
        {
            game.ticTacToeGame.forfeit(isPlayer1 ? TicTacToeGame.Player.PLAYER1 :
                    TicTacToeGame.Player.PLAYER2);
            Session opponent = (isPlayer1 ? game.player2 : game.player1);
            this.sendJsonMessage(opponent, game, new GameForfeitedMessage());
            try
            {
                opponent.close();
            }
            catch(IOException e)
            {
                e.printStackTrace();
            }
        }
    }

伺服器API

伺服器API依賴於完整的客戶端API,它只添加了少數的類和介面,ServerContainer集成了WebSocketContainer,在Servlet環境中調用ServletContext.getAttribute("javax.websocket.server.ServerCOntainer")可以獲得ServerContainer實例,在獨立運行的應用程式中,需要按照特定的WebSocket實現的指令獲取ServerContainer實例。

不過,其實可以使用@ServerEndPoint標註伺服器終端類即可,WebSocket實現可以掃描類的註解,並自動選擇和註冊伺服器終端,容器在每次收到WebSocket連接時創建對應終端的實例,在連接關閉之後在銷毀實例。

在使用@ServerEndPoint,至少需要制定必須的value特性目標是該終端可以做出像的應用程式相對應的URL:

@ServerEndpoint("/ticTacToe/{gameId}/{username}")

如果應用程式部署到的地址為:http://www.example.org/app,那麼該伺服器終端會響應地址:ws://www.example.org/app/ticTacToe/1/andre等,然後伺服器終端中所有的@OnOpen、@OnClose、@OnError和@OnMessage方法都可以只用@PathParam(“{gameId}/{username}”)標註出一個可選的額外參數,並且其內容為改參數的值(1/andre)。

伺服器終端中的時間處理方法將和客戶端中的時間處理方法一樣工作,區別隻存在於握手階段,之後並沒有伺服器和客戶端的差別。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.Es6引入了Class 類這個概念,作為對象的模板,通過class 關鍵字,可以定義類。 2.類和模塊的內部,預設就是嚴格模式,所以不需要使用use strict 指定運行模式。 3.constructor 方法就是類的預設方法,通過new 命令生成對象實例時,自動調動該 方法,一個類必須有co ...
  • 原網頁http://blog.csdn.net/leihope_/article/details/70158902 要在一個頁面中設置一個半透明的白色div。這個貌似不是難題,只需要給這個div設置如下的屬性即可: 但是要相容到ie8。這個就有點蛋疼了。因為ie8不支持rgba()函數。下麵我們總結 ...
  • `Vue.nextTick(callback)`,當數據發生變化,更新後執行回調。 `Vue.$nextTick(callback)`,當dom發生變化,更新後執行的回調。 參考原文:http://www.flowerboys.cn/VueJs/2017/0614/99.html ...
  • 開發一款游戲,裡面有各種鴨子,這些鴨子有共同點:會游泳、會叫; 1.設計超類Duck,裡面有swim()方法和quack()方法,所有鴨子繼承此超類,那麼繼承的對象便都有了游泳和叫的技能; 2.需求變更:增加三種叫的方法,不同的鴨子叫聲不同,有“吱吱叫”、“呱呱叫”,還有不會叫;那麼可以覆寫每個子類 ...
  • 定義一個字元數組並初始化,然後輸出其中字元串 定義一個字元串變數並初始化,輸出 指向字元串的字元指針 將字元串str1複製為字元串str2 ...
  • 1.ESP8266_12E(NodeMCU1.0)(AI Thinker)板Arduino IDE環境安裝(1)方法1(自動安裝,windows,mac,linux平臺都可)http://arduino.esp8266.com/stable/package_esp8266com_index.json... ...
  • 對應sql 其中的BINARY是 精確大小寫 而’icontains’中的’i’表示 忽略大小寫 ...
  • 這幾個include完……之後的函數引用應該都沒問題了 程式運行過程中為了讓顯示有結果的窗體停留,而不是直接返回一個值之後結束運行,可用以下兩種方法—— **雙十二前夜搞了第一個C++小程式,也發了第一篇記錄博客,謹作紀念。待更** ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...