1. 程式人生 > >使用Websocket實現訊息推送(心跳)

使用Websocket實現訊息推送(心跳)

0x00 心跳

本來以為寫完了,結果最近和一個同事在討論心跳的事情,這裡再做一個補充。先說我的結論:

  • WebSocket協議已經設計了心跳,這個功能可以到達檢測連結是否可用
  • 心跳是用來檢測連結是否可用的,不一定支援攜帶資料,可要看具體實現
  • 如果非要心跳中帶上覆雜資料,那這個可作為應用層的一個功能自己去實現。

心跳邏輯

0x01 WebSocket協議的控制幀

上一篇的最後簡單提到了心跳,下面是對websocket協議控制幀的描述:

5.5.  Control Frames

   Control frames are identified by opcodes where the most significant
   bit of the opcode is 1.  Currently defined opcodes for control frames
   include 0x8 (Close), 0x9 (Ping), and 0xA (Pong).  Opcodes 0xB-0xF are
   reserved for further control frames yet to be defined.

   Control frames are used to communicate state about the WebSocket.
   Control frames can be interjected in the middle of a fragmented
   message.

   All control frames MUST have a payload length of 125 bytes or less
   and MUST NOT be fragmented.
  • Ping的協議頭是0x9,Pong的協議頭是0xA
  • 控制幀最大載荷為125bytes且不能拆分

0x02 WebSocket協議的心跳

下面再來看看對心跳的規定:

5.5.2.  Ping

   The Ping frame contains an opcode of 0x9.

   A Ping frame MAY include "Application data".
   // 注:Ping幀中可能會攜帶資料

   Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
   response, unless it already received a Close frame.  It SHOULD
   respond with Pong frame as soon as is practical.  Pong frames are
   discussed in Section 5.5.3.
   // 注:在收到Ping幀後,端點必須傳送Pong幀響應,除非已經收到了Close幀。在實際中應儘可能快的響應。

   An endpoint MAY send a Ping frame any time after the connection is
   established and before the connection is closed.

   NOTE: A Ping frame may serve either as a keepalive or as a means to
   verify that the remote endpoint is still responsive.

5.5.3.  Pong

   The Pong frame contains an opcode of 0xA.

   Section 5.5.2 details requirements that apply to both Ping and Pong
   frames.

   A Pong frame sent in response to a Ping frame must have identical
   "Application data" as found in the message body of the Ping frame
   being replied to.
   // 注:在響應Ping幀的的Pong幀中,必須攜和被響應的Ping幀中相同的資料。

   If an endpoint receives a Ping frame and has not yet sent Pong
   frame(s) in response to previous Ping frame(s), the endpoint MAY
   elect to send a Pong frame for only the most recently processed Ping
   frame.

從上面的描述我們可以得到如下結論:

  • 心跳包中可能會攜帶資料
  • 當收到Ping幀的時候需要立即返回一個Pong幀
  • 在連線建立之後,隨時都可以傳送Ping幀
  • 心跳是用來測試連結是否存在和對方是否線上
  • 在響應Ping幀的的Pong幀中,必須攜和被響應的Ping幀中相同的資料

0x03 測試

和之前一樣,自己本地搭建的伺服器,用的庫是 org.java_websocket
在其原始碼中我們可以找到這樣一段:

package org.java_websocket;

public abstract class WebSocketAdapter implements WebSocketListener
{
... public void onWebsocketPing(WebSocket conn, Framedata f) { FramedataImpl1 resp = new FramedataImpl1(f); resp.setOptcode(Opcode.PONG); conn.sendFrame(resp); } public void onWebsocketPong(WebSocket conn, Framedata f) { } ... }

客戶端也可以使用這個庫,相同的邏輯,程式碼也是這一份。

然後我們再換一個庫,com.squareup.okhttp3:okhttp-ws:3.4.2,他的實現如下:


package okhttp3.internal.ws;

public abstract class RealWebSocket implements WebSocket {
  ...
  public RealWebSocket(boolean isClient, BufferedSource source, BufferedSink sink, Random random,
      final Executor replyExecutor, final WebSocketListener listener, final String url) {
    this.listener = listener;

    writer = new WebSocketWriter(isClient, sink, random);
    reader = new WebSocketReader(isClient, source, new FrameCallback() {
      @Override public void onMessage(ResponseBody message) throws IOException {
        listener.onMessage(message);
      }

      @Override public void onPing(final Buffer buffer) {
        replyExecutor.execute(new NamedRunnable("OkHttp %s WebSocket Pong Reply", url) {
          @Override protected void execute() {
            try {
              writer.writePong(buffer);
            } catch (IOException ignored) {
            }
          }
        });
      }

      @Override public void onPong(Buffer buffer) {
        listener.onPong(buffer);
      }

      @Override public void onClose(final int code, final String reason) {
        readerSentClose = true;
        replyExecutor.execute(new NamedRunnable("OkHttp %s WebSocket Close Reply", url) {
          @Override protected void execute() {
            peerClose(code, reason);
          }
        });
      }
    });
  }
...
}

在處理Ping幀的時候,也是將協議欄位改為Pong然後返回。

在實際的測試中,可能會遇到一些異常,比如在我們自己的生產環境:當客戶端傳送帶了簡單資料的Ping幀後,伺服器立馬返回Pong幀,但是它會將攜帶的資料丟棄。這個就是服務端的問題了。

參考: