前言
博主在工作中碰到一个需求,现有安卓app1(uniapp编写)和安卓app2(帆软10.0编写),想要实现这样的效果:帆软推送一个消息给uniapp,后者监听到消息来了,然后执行一定的处理逻辑。
分析
首先分析技术栈,帆软和uniapp都是客户端,无法直接通信。
那么就可以考虑在中间架一个服务端,参考预警系统使用到的websocket,帆软和uniapp都连接到这台websokect服务器,当前者向服务器推送消息时,后者就可以感知到并执行消息处理。流程图如下:

问题
然而,理想很丰满,现实很骨感。我们不幸的得知帆软无法与websocket服务器建立连接,只能发送http请求。
那么,可不可以让帆软通过http协议调用websocket服务器,然后websocket服务器主动给uniapp推送消息呢?答案是可行的。
因为websocket与http协议最大的区别就是它可以实现双端通信,客户端可以推送消息给服务端,服务端也可以推送消息给客户端。
流程图修正为:

代码
首先我们需要搭建一个websocket服务器,比较简单的方式就是在SpringBoot后端直接搭建一个。你只需要参考如下流程:
- 在
pom.xml
引入starter
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
- 创建websocket配置类
typescript
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
/**
* websocket相关配置
*/
@Configuration
public class WebSocketConfig {
/**
* 自动注册使用了@ServerEndpoint注解声明的Websocket endpoint
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
/**
* 通信文本消息和二进制缓存区大小
* 避免对接 第三方 报文过大时,Websocket 1009 错误
*/
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
// 在此处设置bufferSize
container.setMaxTextMessageBufferSize(10240000);
container.setMaxBinaryMessageBufferSize(10240000);
container.setMaxSessionIdleTimeout(15 * 60000L);
return container;
}
}
- 创建websocket服务器
typescript
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.extern.slf4j.Slf4j;
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.ConcurrentHashMap;
@Component
@Slf4j
@ServerEndpoint("/api/pushMessage/{userId}")
public class WebSocketServer {
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
*/
private static int onlineCount = 0;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。
*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收userId
*/
private String userId = "";
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userId") String userId) {
this.session = session;
this.userId = userId;
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
//加入set中
webSocketMap.put(userId, this);
} else {
//加入set中
webSocketMap.put(userId, this);
//在线数加1
addOnlineCount();
}
log.info("用户连接:" + userId + ",当前在线人数为:" + getOnlineCount());
sendMessage("连接成功");
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if (webSocketMap.containsKey(userId)) {
webSocketMap.remove(userId);
//从set中删除
subOnlineCount();
}
log.info("用户退出:" + userId + ",当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消
* 息后调用的方法
* @param message 客户端发送过来的消息
**/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用户消息:" + userId + ",报文:" + message);
}
/**
* 错误处理
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:" + this.userId + ",原因:" + error.getMessage());
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发送自定义消息
**/
public static void sendInfo(String message, String userId) {
log.info("发送消息到:" + userId + ",报文:" + message);
if (StringUtils.isNotBlank(userId) && webSocketMap.containsKey(userId)) {
webSocketMap.get(userId).sendMessage(message);
} else {
log.error("用户" + userId + ",不在线!");
}
}
/**
* 发送自定义消息给所有用户
* @return 是否发送成功
*/
public static Boolean sendInfoToAll(String message) {
log.info("发送消息给所有用户,消息内容:{}", message);
if (webSocketMap.isEmpty()) {
return false;
}
for (String userId : webSocketMap.keySet()) {
webSocketMap.get(userId).sendMessage(message);
log.info("发送消息{}给用户{}成功", message, userId);
}
return true;
}
/**
* 获得此时的在线人数
*/
public static synchronized int getOnlineCount() {
return onlineCount;
}
/**
* 在线人数加1
*/
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
/**
* 在线人数减1
*/
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
- 重启项目,此时websocket服务器就已经启动了
uniapp
根据前文可知,安卓app2是一个uniapp项目,参考相关文档:
zh.uniapp.dcloud.io/api/request...
我们可以编写出如下实例代码,用于简单地建立websocket连接和接收消息:
javascript
const host = "127.0.0.1:8080";
const websocketUrl = "ws://" + host + "/api/pushMessage/xiaoming";
uni.connectSocket({
url: websocketUrl,
complete: ()=> {
console.log('连接到websocket服务器成功:' + websocketUrl)
uni.onSocketOpen(function (res) {
console.log("消息来了:" + JSON.stringify(res))
});
uni.onSocketClose(function (res) {
console.log('WebSocket 已关闭!');
});
uni.onSocketMessage(function (res) {
console.log('收到服务器内容:' + res.data);
});
setTimeout(() => {
uni.sendSocketMessage({
data: 'uniapp发送的消息'
})
}, 1000)
}
});
帆软
因帆软无法直接和websocket服务器建立连接,所有我们使用http协议调用服务器,让服务器自己去推送消息,后端控制器示例如下:
less
@PostMapping("test")
@ResponseBody
public Map<String, Object> test(@RequestBody Map<String, Object> params) {
Boolean result = WebSocketServer.sendInfoToAll((String) params.get("test"));
Map<String, Object> map = new HashMap<>();
if (result) {
map.put("status", "ok");
} else {
map.put("status", "error");
map.put("errMsg", "安卓客户端没有连接到websocket");
}
return map;
}
心跳机制
根据前文所述,uniapp需要与websocket长期建立连接,如果在一定时间内没有消息的收发,那么服务就会下线。这显然不是我们想看到的。
我们可以参考心跳机制,每隔5秒,让uniapp向websocket推送一个心跳包,这样就可以保持长连接了,代码示例如下:
xml
<script>
export default {
data() {
return {
socketTask: null,
heartbeatInterval: null,
reconnectInterval: null
};
},
onLaunch: function () {
console.log('App Launch')
this.connectWebSocket()-;
},
onUnload() {
if (this.socketTask) {
this.socketTask.close();
}
this.stopHeartbeat();
this.stopReconnect();
},
methods: {
connectWebSocket() {
const websocketUrl = 'ws://127.0.0.1:8082/api/pushMessage/xiaoming'
console.log('开始连接到websocket服务器:' + websocketUrl)
this.socketTask = uni.connectSocket({
url: websocketUrl,
complete: ()=> {
console.log('连接到websocket服务器成功:' + websocketUrl)
uni.onSocketOpen(function (res) {
console.log("消息来了:" + JSON.stringify(res))
});
uni.onSocketClose(function (res) {
console.log('WebSocket 已关闭!自动重连...');
this.stopHeartbeat();
this.startReconnect();
});
uni.onSocketMessage(function (res) {
console.log('收到服务器内容:' + res.data);
});
uni.onSocketError(function (err) {
console.error('WebSocket 发生错误:', JSON.stringify(err));
this.stopHeartbeat();
this.startReconnect();
})
// 开启心跳机制
this.startHeartbeat();
setTimeout(() => {
uni.sendSocketMessage({
data: 'uniapp发送的消息'
})
}, 1000)
},
fail: (err) => {
console.log('连接到websocket服务器失败:' + websocketUrl)
// 尝试重连
this.startReconnect();
}
});
},
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.socketTask) {
this.socketTask.send({
data: 'ping', // 心跳包数据
success: () => {
console.log('心跳包发送成功');
},
fail: (err) => {
console.error('心跳包发送失败:', err);
}
});
}
}, 5000); // 每 5 秒发送一次心跳包
},
stopHeartbeat() {
if (this.heartbeatInterval) {
clearInterval(this.heartbeatInterval);
this.heartbeatInterval = null;
}
},
startReconnect() {
if (!this.reconnectInterval) {
this.reconnectInterval = setInterval(() => {
console.log('尝试重新连接 WebSocket...');
this.connectWebSocket();
}, 5000); // 每 5 秒尝试重新连接一次
}
},
stopReconnect() {
if (this.reconnectInterval) {
clearInterval(this.reconnectInterval);
this.reconnectInterval = null;
}
}
}
}
</script>
总结
通过上述步骤,我们就解决了两个客户端通信的问题。当然,除了websocket,你也可以使用java.net.ServerSocket
、NettySocket
、RabbitMQ
等等技术,但是无论如何两个客户端中间都是得有一个服务端才能完成通信的。