一个bug肝一周...忍不住提了issue
导航
- 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端有以下几种不同编程语言的实现:
- JavaScript (whose documentation can be found here on this website)
- Java: https://github.com/mrniko/netty-socketio
- Java: https://github.com/trinopoty/socket.io-server-java
- Python: https://github.com/miguelgrinberg/python-socketio
- Golang: https://github.com/googollee/go-socket.io
Socket.IO
client端,大多数主流编程语言的也有实现
- JavaScript (which can be run either in the browser, in Node.js or in React Native)
- Installation steps
- API
- Source code
- Java: https://github.com/socketio/socket.io-client-java
- C++: https://github.com/socketio/socket.io-client-cpp
- Swift: https://github.com/socketio/socket.io-client-swift
- Dart: https://github.com/rikulo/socket.io-client-dart
- Python: https://github.com/miguelgrinberg/python-socketio
- .Net: https://github.com/doghappy/socket.io-client-csharp
- Rust: https://github.com/1c3t3a/rust-socketio
- Kotlin: https://github.com/icerockdev/moko-socket-io
为什么选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