WebSocketのSpringBootでの使用

10716 ワード

紹介する
WebSocketは、HTML 5が提供し始めた単一のTCP接続上でフルデュプレクス通信を行うプロトコルである.
WebSocketは、クライアントとサーバ間のデータ交換をより簡単にし、サービス側がクライアントにデータを積極的にプッシュできるようにします.WebSocket APIでは、ブラウザとサーバが握手を1回完了するだけで、両者の間に永続的な接続を直接作成し、双方向のデータ転送を行うことができます.
WebSocket APIでは、ブラウザとサーバが握手をするだけで、ブラウザとサーバの間に高速チャネルが形成されます.両者の間で直接データを互いに転送することができる.
現在、多くのサイトがプッシュテクノロジーを実現するために使用しているテクノロジーはAjaxポーリングです.ポーリングは、1秒ごとなどの特定の時間間隔で、ブラウザによってサーバに対してHTTP要求を発行し、サーバによって最新のデータをクライアントのブラウザに返す.このような従来のモードは、ブラウザがサーバに絶えず要求する必要があるという明らかな欠点をもたらしているが、HTTP要求には長いヘッダが含まれている可能性があり、その中で本当に有効なデータはほんの一部であり、多くの帯域幅などのリソースを浪費することは明らかである.
HTML 5で定義されたWebSocketプロトコルは、サーバリソースと帯域幅をより効率的に節約し、よりリアルタイムで通信することができます.
ブラウザはJavaScriptを介してWebSocket接続の確立をサーバに要求し,接続が確立されるとクライアント側とサーバ側はTCP接続により直接データを交換することができる.
Web Socket接続を取得するとsend()メソッドでサーバにデータを送信し、onmessageイベントでサーバから返されたデータを受信できます.
次のAPIは、WebSocketオブジェクトを作成するために使用されます.
WebSocketプロトコル
WebSocketは新しいプロトコルではなく、HTTPプロトコルを利用して接続を確立しています.WebSocket接続がどのように作成されているかを見てみましょう.
まず、WebSocket接続はブラウザによって開始される必要があります.要求プロトコルは標準的なHTTP要求であり、フォーマットは以下の通りです.
GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: client-random-string
Sec-WebSocket-Version: 13

この要求は通常のHTTP要求といくつかの違いがあります.
  • GET要求のアドレスは、/path/のようなものではなく、ws://で始まるアドレスである.
  • 要求ヘッダUpgrade: websocketおよびConnection: Upgradeは、この接続がWebSocket接続に変換されることを示す.
  • Sec-WebSocket-Keyは、この接続を識別するために使用され、データを暗号化するために使用されない.
  • Sec-WebSocket-Version WebSocketのプロトコルバージョンが指定されています.

  • その後、サーバがリクエストを受け入れると、次のような応答が返されます.
    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: server-random-string
    

    このレスポンスコード101は、今回接続されたHTTPプロトコルが変更されることを示し、変更されたプロトコルはUpgrade: websocketで指定されたWebSocketプロトコルである.
    バージョン番号とサブプロトコルは、双方が理解できるデータフォーマット、圧縮をサポートするかどうかなどを規定しています.WebSocketのAPIだけを使うなら、気にする必要はありません.
    WebSocket接続が正常に確立され、ブラウザとサーバがいつでも自発的にメッセージを送信できるようになりました.メッセージには2つあります.1つはテキストで、1つはバイナリデータです.通常、JSON形式のテキストを送信できます.これにより、ブラウザでの処理が容易になります.
    なぜWebSocket接続はフルデュプレクス通信を実現できるのか、HTTP接続はできないのか.実際にHTTPプロトコルはTCPプロトコル上に確立されており,TCPプロトコル自体はフルデュプレクス通信を実現しているが,HTTPプロトコルの要求応答メカニズムはフルデュプレクス通信を制限している.WebSocket接続が確立された後、実は簡単に規定しただけです.次は、HTTPプロトコルを使用しないで、直接データを送りましょう.
    安全なWebSocket接続メカニズムはHTTPSと似ています.まず、ブラウザがwss://xxxでWebSocket接続を作成すると、まずHTTPSを通じて安全な接続が作成され、その後、このHTTPS接続はWebSocket接続にアップグレードされ、下位通信は依然として安全なSSL/TLSプロトコルを歩んでいる.
    エクスプローラ
    WebSocket通信をサポートするには、ブラウザがこのプロトコルをサポートしなければならないことは明らかです.そうすれば、ws://xxxの要求を出すことができます.現在、WebSocketをサポートする主流ブラウザは以下の通りです.
  • Chrome
  • Firefox
  • IE >= 10
  • Sarafi >= 6
  • Android >= 4.4
  • iOS >= 8

  • SpringBootでのWebSocketの使用
    maven依存に加わる
    SpringBoot 2.0の後、このパッケージを直接案内すればいいです.
    
        org.springframework.boot
        spring-boot-starter-websocket
        1.5.10.RELEASE
    
    

    WebSocketConfig
    Websocketサポートをオンにし、tomcatなどの独立したservletコンテナを使用する場合は、サーバEndpointExporterに注入する必要はありません.
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.socket.server.standard.ServerEndpointExporter;
    
    /**
     *    websocket   
    * WebSocketEndpointServer * servlet , SpringBoot * ServerEndpointExporter, * @author zhangchuanqiang */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }

    WebSocketServer
    Websocketサーバ、wsプロトコルのControllerに相当
    import lombok.extern.java.Log;
    import org.springframework.stereotype.Component;
    
    import javax.websocket.*;
    import javax.websocket.server.PathParam;
    import javax.websocket.server.ServerEndpoint;
    import java.io.IOException;
    import java.util.concurrent.CopyOnWriteArraySet;
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * @Description: WebSocketServer
     * @Author: zcq
     * @Date: 2019-06-14 14:37
     */
    @Log
    @Component
    @ServerEndpoint("/index/{sid}")  //             webSocket  
    public class MySocket {
        /**
         *       
         */
        private static AtomicInteger online_num = new AtomicInteger(0);
        /**
         *      socket  ,        
         */
        private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
        /**
         *     
         */
        private Session session;
    
        /**
         *     id
         */
        private String sid = "";
    
        /**
         *            
         */
        @OnOpen
        public void onOpen(Session session, @PathParam("sid") String sid) {
            this.sid = sid;
            this.session = session;
            webSocketSet.add(this);
            //     +1
            addOnlineCount();
            log.info("     ,     :" + getOnline_num());
            sendMessageBasic("     ,     :" + getOnline_num());
        }
    
        /**
         *          
         */
        /**
         *          
         */
        @OnClose
        public void onClose() {
            webSocketSet.remove(this);
            subOnlineCount(); //    1
            log.info("      !       " + getOnline_num());
        }
    
        /**
         *              
         *
         * @param message
         * @param session
         * @throws IOException
         */
        @OnMessage
        public void onMessage(String message, Session session) throws IOException {
            log.info("        :" + message);
            for (MySocket item : webSocketSet) {
                item.sendMessageBasic(message);
            }
        }
    
        /**
         * @param session
         * @param error
         */
        @OnError
        public void onError(Session session, Throwable error) {
            log.info("    ");
            error.printStackTrace();
        }
    
        /**
         *          
         */
        public void sendMessageBasic(String message) {
            try {
                this.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        /**
         *          
         */
        public void sendMessageAsync(String message) {
            this.session.getAsyncRemote().sendText(message);
        }
    
        /**
         *        
         */
        public static void sendInfo(String message, @PathParam("sid") String sid) throws IOException {
            log.info("       " + sid + ",    :" + message);
            for (MySocket item : webSocketSet) {
                //            sid , null     
                if (sid == null || "all".equals(sid)) {
                    item.sendMessageBasic(message);
                } else if (item.sid.equals(sid)) {
                    item.sendMessageBasic(message);
                }
            }
        }
    
        public static MySocket getWebSocket(String sid) {
            if (webSocketSet == null || webSocketSet.size() <= 0) {
                return null;
            }
            for (MySocket item : webSocketSet) {
                if (sid.equals(item.sid)) {
                    return item;
                }
            }
            return null;
        }
    
        public AtomicInteger getOnline_num() {
            return MySocket.online_num;
        }
    
        public int subOnlineCount() {
            return MySocket.online_num.addAndGet(-1);
        }
    
        public int addOnlineCount() {
            return MySocket.online_num.addAndGet(1);
        }
    }
    

    メッセージをプッシュ
    /**
     * @Description:     
     * @Author: zcq
     * @Date: 2019-06-14 14:35
     */
    @GetMapping("/socket/{cid}")
    public ModelAndView socket(@PathVariable String cid) {
        ModelAndView mav = new ModelAndView("/index1");
        mav.addObject("cid", cid);
        return mav;
    }
    
    /**
     * @Description:       
     * @Author: zcq
     * @Date: 2019-06-14 14:35
     * @param cid   all  ,     
     */
    @ResponseBody
    @RequestMapping("/socket/push/{cid}")
    public Result pushToWeb(@PathVariable String cid, String message) {
        try {
            MySocket.sendInfo(message, cid);
        } catch (IOException e) {
            e.printStackTrace();
            return Result.error();
        }
        return Result.success(cid);
    }
    

    ページ開始socketリクエスト
    プロトコルはwsまたはwssであり、いくつかのbasePathのパスクラスをカプセル化することができ、replace(“http”,“ws”)でプロトコルを置き換えることができる.
    
    
        
        My WebSocket
    
    
    
    Welcome
    var websocket = null; // WebSocket if ('WebSocket' in window) { // Websocket ws wss , HTTPS, wss TLS Websocket。 // onlinenum = new WebSocket("ws://localhost:8086/websocket/20"); onlinenum = new WebSocket("http://localhost:8086/websocket/${cid}".replace("http", "ws").replace("https", "ws")); } else { alert('Not support websocket') } // onlinenum.onerror = function () { setMessageInnerHTML("error"); }; // onlinenum.onopen = function (event) { setMessageInnerHTML("open"); } // onlinenum.onmessage = function (event) { setMessageInnerHTML(event.data); } // onlinenum.onclose = function () { setMessageInnerHTML("close"); } // , , websocket , ,server 。 window.onbeforeunload = function () { onlinenum.close(); }; // function setMessageInnerHTML(innerHTML) { document.getElementById('message').innerHTML += innerHTML + '<br/>'; } // function closeWebSocket() { onlinenum.close(); } // function send() { const message = document.getElementById('text').value; onlinenum.send(message); }

    補足:
  • WARパッケージにする場合は、TOMCATに配備します.起動クラスを継承する必要がある:SpringBootServiceletInitializer、メソッドを追加する必要があります:
  • /** 
    *         WAR,    tomcat  
    */ 
    @Override 
        protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { 
        return builder.sources(AssetApplication.class); 
    } 
    
  • WARパッケージを作成した後、TOMCATでタイムズエラーを開始します:Multiple Endpoints may not be deployed to the same pathコメントWebSocketServerクラスの@Componentコメントを削除します.

  • 資料
  • WebSocket API
  • WebSocket廖雪峰node.js作成チャットルーム
  • SpringBoot2.0 WebSocketを統合し、バックグラウンドからフロントエンドに情報をプッシュする
  • を実現