一個bug肝一週...忍不住提了issue

2022-06-28 15:02:53

導航

  • Socket.IO是什麼
  • Socket.IO的應用場景
  • 為什麼選socket.io-client-java
  • 實戰案例
  • 參考

本文首發於智客工坊-《socket.io使用者端向webserver傳送訊息實踐》,感謝您的閱讀,預計閱讀時長2min。

Socket.IO是什麼

Socket.IO是一個庫,它支援使用者端和伺服器之間的低延遲雙向基於事件的通訊。

它構建在WebSocket協定之上,並提供額外的保證,如回退到HTTP長輪詢或自動重新連線。



Socket.IO的應用場景

Socket.IO目前應用比較多的場景就是網頁的IM實時聊天。

Notes: 在C#中,也有個類庫signalr實現簡單的網頁實時聊天。

Socket.IO server端有以下幾種不同程式語言的實現:

Socket.IO client端,大多數主流程式語言的也有實現

為什麼選socket.io-client-java

本文主要是針對socket.io-client-java的一次實踐。

我們團隊已經使用Node.js搭建了webserver,並實現了web使用者端和webserver的訊息互通(即雙方都是基於JavaScript的實現)。

但是,有個特殊業務場景,需要在我們後端業務介面中根據業務狀態變更向指定的IM對談投遞實時訊息。

這種需求的實現方案:

  • 方案1,後端業務介面將訊息投遞到kafka topic,再由webserver消費指定topic,實現訊息的實時推播
  • 方案2,後端直接連線webserver,然後投遞訊息

綜合考慮之後,我們選擇了方案2。

因此,技術選型上就只有華山一條路——socket.io-client-java

在實現的過程中,也確實是踩了一些坑,所以記錄一下,順便和大家分享一下。

實戰案例

現在我們開始socket.io-client-java
之旅吧!

引入socket.io-client-java

Notice: socket.io使用者端和伺服器端的版本要匹配,否則會連不上或者沒有反應。

根據socket.io-client-java官方檔案給出的版本匹配表格:

Client version Socket.IO server
0.9.x 1.x
1.x 2.x
2.x 3.x / 4.x

因為我們webserver端的socket.io 版本 "socket.io": "^2.4.1"

所以,使用者端我只能選擇1.x,這裡選擇1.0.0。

        <dependency>
            <groupId>io.socket</groupId>
            <artifactId>socket.io-client</artifactId>
            <version>1.0.0</version>
        </dependency>

maven更新之後,即可使用。



程式碼實現

package com.zhike.blogmanager.Msg;

import io.socket.client.IO;
import io.socket.client.Socket;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j;
import org.apache.poi.ddf.EscherColorRef;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Component;

import java.net.URI;
import java.util.Date;

/**
 * Created with IntelliJ IDEA.
 * User: lenovo
 * Date: 2022/6/25
 * Time: 21:25
 * Description: No Description
 */
@Component
@RequiredArgsConstructor
public class PushMessageManager {

    private Socket socket;

    /**
     * 訊息推播到webserver
     * */
    public void pushToWebServer() {
        //保證只會範例化一次socket
        if(socket==null)
        {
            connentSocket();
            System.out.println(socket);
        }

        //構造JSONObject物件
        JSONObject data=bulidMsg();
        System.out.println("【使用者端推播訊息】"+data);
        //event 要和webserver一致才能接受到訊息
        socket.emit("2",data);

        if(!socket.connected())
        {
            socket.connect();
        }
    }

    private void connentSocket(){
        try
        {
            //String url ="http://172.xx.xx.xx:3000";
            //String url ="http://172.xx.xx.xx:3001";
            String url = "http://172.xx.xx.xx:3002";//web伺服器地址以實際為準
            IO.Options options = new IO.Options();
            options.forceNew = true;
            // 失敗重連時間間隔
            options.reconnectionDelay = 1000;
            // 連線超時時間
            options.timeout = 5000;

            socket = IO.socket(URI.create(url), options);
        }catch (Exception ex)
        {
            System.out.println("連線伺服器失敗,error:"+ex);
        }
    }

    /**
     * 訊息體構造
     * 定義須和webserver保持一致,webserver才能解析
     * */
    private  JSONObject bulidMsg()
    {
        JSONObject data = new JSONObject();
        try {
            data.put("type", "1");
            data.put("from", "[FromUSerId]");
            data.put("to", "[ToUserId]");
            data.put("msgContent", "Hello World!");
            data.put("msgTime", new Date());
        } catch (JSONException e) {
            throw new AssertionError(e);
        }
        return  data;
    }
}

程式碼中有詳細註釋,不再贅述。

斷線重連的坑

這裡還有個巨坑,不知道是否是socket.io-client-java的bug。

如果webserver部署了多個應用並被nginx負載,如下:

server {
    listen 3000;
    server_name localhost;
    
    access_log  /data/logs/nginx/webserver/access.log  main;

    location / {
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
      proxy_set_header Host $host;

      proxy_pass http://webserver-nodes;

      # enable WebSockets
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "upgrade";
    }
  }

  upstream webserver-nodes {
    # enable sticky session based on IP
    # ip_hash;

    server 172.xx.xx.xx:3001;
    server 172.xx.xx.xx:3002;
  }

當我設定url="http://172.xx.xx.xx:3000"的時候,就會出現socket會在兩臺webserver之間disconnect,reconnect的情況。

當時同事反饋,使用JavaScript client連線是正常的。

這個研究了兩三天了,嘗試了很多方式依然沒有解決。

所以,最終我們只能指定連線其中一臺webserver。

最後

最後給提了一個issue #715



參考