WebSocketプロジェクトの実戦共有

7430 ワード

私たちのフロントエンドチームは車源オークションという業務の需要を受け取って半年を超えました.この需要の中でHTML 5 WebSocket通信プロトコルを使って車源オークションを実現した後、リストページと詳細ページの価格情報をリアルタイムで更新することができます.現在、この業務の需要と技術の実現はオンラインで3ヶ月以上安定しています.もう一度共有します.
WebSocketとは?
WebSocketはHTML 5の新しいプロトコルであり、ブラウザとサーバのフルデュプレクス通信(full-duplex)を実現し、サーバリソースと帯域幅をより節約し、リアルタイム通信を実現していることを知っているはずです.WebSocketはHTML 5が提供し始めた単一TCP接続上で全二重通信を行うプロトコルで、そのAPIではブラウザがJavaScriptを通じてサーバーにWebSocket接続の確立を要求し、握手の動作をして接続を確立した後、クライアントはsend()メソッドによってサーバにデータを送信し、onmessageイベントによってサーバから返されたデータを受信することができる.  定義を通して,WebsocketはHTTPのような非永続的なプロトコルに対して永続化されたプロトコルであることが分かる.WebSocket協定は2008年に誕生し、2011年に国際標準となった.すべてのブラウザがサポートされています.WebSocketの最大の特徴は、サーバがクライアントに積極的に情報を送信することができ、クライアントもサーバに積極的に情報を送信することができ、真の双方向平等な対話である.
Websocketは実は新しいプロトコルで、HTTPプロトコルとはほとんど関係なく、既存のブラウザと互換性があるため、握手段階でHTTPを使用しました.
WebSocketの特徴:
  • はTCPプロトコル上に確立されており、サーバ側の実装は比較的容易である.
  • はHTTPプロトコルと良好な互換性を持っている.デフォルトポートも80と443であり、握手段階ではHTTPプロトコルを採用しているため、握手時にマスクしにくく、各種HTTPプロキシサーバを通過することができる.
  • データフォーマットは比較的軽量で、性能オーバーヘッドが小さく、通信が効率的である.
  • は、テキストを送信してもよいし、バイナリデータを送信してもよい.同源の制限はなく、クライアントは任意のサーバと通信できます.
  • プロトコル識別子はws(暗号化されている場合はwss)であり、サーバURLはURLである.

  • WebSocketはHTTPのいくつかの難題を解決した.まず,パッシブ性は,サーバがプロトコルアップグレードを完了すると(HTTP->Websocket),サービス側が自発的にクライアントに情報をプッシュすることができる.
    HTTPプロトコルには、通信がクライアントによってのみ開始されるという欠点がある.WebSocket接続は本質的にTCP接続である
    WebSocket接続を確立するために、クライアントブラウザはまずサーバにHTTP要求を開始しなければならない.この要求は通常のHTTP要求とは異なり、いくつかの付加ヘッダ情報が含まれており、付加ヘッダ情報「Upgrade:WebSocket」は、申請プロトコルが昇格したHTTP要求であることを示している.サーバ側はこれらの付加的なヘッダ情報を解析して応答情報を生成してクライアントに返し、クライアント側とサーバ側のWebSocket接続が確立され、双方はこの接続チャネルを通じて自由に情報を伝達することができ、この接続はクライアント側またはサーバ側のいずれかがアクティブに接続を閉じるまで持続する.
    なぜWebSocketが必要なの?
    私たちはすでにHTTPプロトコルを持っていますが、なぜ別のプロトコルが必要なのでしょうか.HTTPプロトコルには、通信はクライアントからしか開始できず、サーバがクライアントに情報を自発的にプッシュすることができないという欠点がある.このような一方向要求の特徴は,サーバが連続的に状態が変化すると,クライアントが知るのは非常に面倒であることに決まっている.WebSocketの登場は、オークション後に価格をリアルタイムで表示したり、微博コメント後にリアルタイムで表示したりするなど、現在ますます多くのWebアプリケーションのリアルタイム性に対する需要が増えているためです.それが現れる前に、一般的にポーリングメカニズムとストリーム技術を通じてリアルタイムのWebアプリケーションを実現します:ポーリング:クライアントは一定の時間間隔でサービス側に要求を出して、頻繁に要求する方式でクライアントとサーバー側の同期を維持します;明らかに、このリアルタイムスキームは多くの無意味なネットワーク伝送をもたらし、非常に非効率である.したがって、タイミングポーリングを改善し、サーバ側にデータ更新がない場合、接続はデータまたはステータスが変化したり、時間が切れたりするまで一定の期間維持され、このメカニズムにより無効なクライアントとサーバ間のインタラクションを低減する必要があります.≪Streams|Streams|emdw≫:クライアントのページで非表示のウィンドウを使用して、サービス側に長い接続を要求します.サーバ側は、この要求に応答し、クライアント側とサーバ側の接続が期限切れにならないように接続状態を更新し続けます.このようなメカニズムにより、サーバ側の情報をクライアントに絶えずプッシュすることができる.このメカニズムは、ユーザー体験に問題があり、異なる[ブラウザ]に対して異なるスキームを設計してユーザー体験を改善する必要があります.また、このメカニズムは、比較的大きな同時実行の場合、サーバ側のリソースにとって大きな試練です.
    上記の方法は実際には真のリアルタイム技術ではなく,シミュレーションリアルタイムを実現するためのテクニックを用いただけである.クライアント側とサーバ側が対話するたびにHTTPのリクエストと応答のプロセスが行われ、毎回のHTTPリクエストと応答には完全なHTTPヘッダ情報が含まれており、これにより伝送毎のデータ量が増加する.しかし、これらの方法が最も苦痛なのは開発者であり、クライアントとサーバ側の実現が複雑であるため、比較的真実なリアルタイム効果をシミュレートするために、開発者はクライアントとサーバ間の双方向通信をシミュレートするために2つのHTTP接続を構築する必要があり、1つの接続はクライアントからサーバ側へのデータ伝送を処理するために使用される.1つの接続は、サーバ側からクライアントへのデータ伝送を処理するために使用され、これはプログラミング実装の複雑さを必然的に増加させ、サーバ側の負荷を増加させ、アプリケーションシステムの拡張性を制約する.上記の弊害に基づいて、Webのリアルタイムアプリケーションを実現する技術が登場し、WebSocketはブラウザが提供するAPIを通じて、C/Sアーキテクチャの下のデスクトップシステムのようなリアルタイム通信能力を実現した.その原理はJavaScript呼び出しブラウザのAPIを使用してWebSocket要求をサーバに送信し、握手を経てサーバとTCP通信を確立することであり、本質的にTCP接続であるため、データ伝送の安定性が強く、データ伝送量が比較的小さい. WebSocket接続を確立するために、クライアントブラウザはまずサーバにHTTP要求を開始しなければならない.この要求は通常のHTTP要求とは異なり、付加ヘッダ情報が含まれている.付加ヘッダ情報「Upgrade:WebSocket」は、アップグレードを申請するHTTP要求であることを示している.サーバ側はこれらの付加的なヘッダ情報を解析して応答情報を生成してクライアントに返し、クライアント側とサーバ側のWebSocket接続が確立され、この接続はクライアント側またはサーバ側のいずれかがアクティブに接続を閉じるまで持続する.
    WebSocketのインスタンスとその一般的なプロパティとAPI
    まず簡単な例を見てください.
    function WebSocketDemo() {
        // WebSocket         ,     WebSocket   
        var ws = new WebSocket("ws://localhost:9000"); 
        //      onopen  ,              
        //            ,    addEventListener  
        ws.onopen = function() {
             //      send()            。
             ws.send("    "); //    send()       
         };
        //      onmessage  ,                 
        ws.onmessage = function (event) { 
              var rec_msg = event.data; //     
        };
        //      onclose  ,              
        ws.onclose = function() { 
             alert("     ..."); //    websocket
        };
        //      onerror  ,            。
        ws. onerror = function() { 
             alert("   .."); 
    }
    

    読み取り専用プロパティreadyStateは接続ステータスを表し、0-接続が確立されていないことを示します.1-接続が確立され、通信可能であることを示します.2-接続が閉じられていることを示します.3-接続が閉じているか、接続が開かないことを示します.
    WebSocketリアルプロジェクト実戦共有
    入門レベルのWebSocketの前後の例については、菜鳥チュートリアルや他のネット上の文章を見てください.ここでは、実際のプロジェクトのコアコード実装に重点を置いています.バックエンドはJava実装に基づいており、フロントエンドはvueテクノロジースタックに基づいています.ここでは、フロントエンドについて説明し、実際の問題をどのように解決するかを共有します.以下はvueテクノロジースタックがwebsocketを利用して車源オークションを実現するオンラインの真実なコードであり、心拍再接続メカニズムを実現しています.
    export default {
        name: 'auctionList',
        data() {
          return {
            ws: null, // websocket  
            lockReconnect: false, //   websocket    
            reconnectTimer: null, //          
            heartCheck: { //       ,      
              vue: this,
              timeout: 20000, // 20 
              timeoutObj: null,
              serverTimeoutObj: null,
              reset: function () {
                clearTimeout(this.timeoutObj);
                clearTimeout(this.serverTimeoutObj);
                return this;
              },
              start: function () {
                const self = this.vue;
                this.timeoutObj = setTimeout(() => {
                  //         ,     ,        ,
                  // onmessage              ,     、     
                  self.ws.send('HeartBeat2');
                  //             ,         
                  this.serverTimeoutObj = setTimeout(() => { 
                    self.ws.close();//   onclose   reconnect,    ws.close()   .      reconnect    onclose      
                  }, this.timeout);
                }, this.timeout);
              },
            },
          };
        },
        created() {
          this.initWebSocket();
        },
        beforeDestroy() {
          //    close   reconnect  ,    createWebSocket
          this.lockReconnect = true;
          //      ,          
          this.heartCheck.reset();
          this.ws.close(); //   websocket
          clearTimeout(this.reconnectTimer); //            
        },
        methods: {
          initWebSocket() {
            api.getWsUrl().then((res) => {
              const resUrl = res.data.data.wsUrl;
              const wsUrl = `${resUrl}?x-access-token=${accessToken}`;
              this.createWS(wsUrl);
            });
          },
          createWS(url) { 
            try {
              this.ws = new WebSocket(url);
              this.ws.onclose = () => {
                this.reconnect(url);
              };
              this.ws.onerror = () =>  {
                this.reconnect(url);
              };
              this.ws.onopen = () =>  {
                this.heartCheck.reset().start();
              };
              this.ws.onmessage = (event) => {
                //        ,      
                const redata = JSON.parse(event.data);
                //                  
                if (redata.carId) {
                  this.auctionInfo = redata;
                }
                //                  
                this.heartCheck.reset().start();
              };
            } catch (e) {
              this.reconnect(url);
            }
          },
          reconnect(url) { //     
            if (this.lockReconnect) {
              return;
            }
            this.lockReconnect = true;
            //          ,          
            this.reconnectTimer = setTimeout(() => {
              this.createWebSocket(url);
              this.lockReconnect = false;
            }, 2000);
          },
        },
      };