前言:
业务需要实时通讯,所以就调研了一下。整体感觉 websocket 使用门槛低、配置简单、稳定性相对较高。
一、核心依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.7.0</version>
</dependency>
<!-- 日志注解用的是 lombok 的 @Slf4j,可以按照自己的习惯替换掉-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
二、配置类
websocket 的配置类就是一个简单的配置声明,没有任何特殊的地方。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter endpointExporter() {
return new ServerEndpointExporter();
}
}
三、服务
这块是最重要的地方,关键地方都写了注释;其中几个有注解的方法主要是给前端调用的。
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@Component
@Slf4j
@Service
@ServerEndpoint("/websocket/{clientId}")
public class WebSocketServer {
//静态变量,用来记录当前在线连接数
private static int onlineCount = 0;
//用来存放每个客户端对应的的连接信息
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//连接的 clientId
private String clientId = "";
/**
* 前端连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("clientId") String clientId) {
this.session = session;
this.clientId = clientId;
webSocketSet.add(this);
addOnlineCount();
try {
String msg = "clientId:" + clientId + "连接成功,当前在线客户端数:" + getOnlineCount();
sendMessage(msg);
log.info(msg);
} catch (IOException e) {
log.error("client:{},连接故障,原因:{}", clientId, e.getMessage());
}
}
/**
* 前端连接关闭调用的方法
* 可以在里面写一些关闭连接需要处理的逻辑
*/
@OnClose
public void onClose() {
webSocketSet.remove(this);
subOnlineCount();
log.info("释放连接,clientId:{};当前连接数:{}", clientId, getOnlineCount());
}
/**
* 该方法用于接受客户端发送的消息
* 当前逻辑是:相同 clientId 的连接可以接受消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自clientId:{} 的消息:{}", clientId, message);
for (WebSocketServer item : webSocketSet) {
try {
if (item.clientId.equals(clientId)) { //广播发送的话就去掉if
item.sendMessage(message);
}
} catch (IOException e) {
log.error("client:{} 发送消息故障,原因:{}", item.clientId, e.getMessage());
}
}
}
/**
* 用于记录发生的错误
*/
@OnError
public void onError(Session session, Throwable e) {
log.error("出现故障:{}", e.getMessage());
}
/**
* 服务器主动推送消息
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 定向 clientId 发送消息
*/
public void sendInfo(String clientId, String message) throws IOException {
log.info("推送消息到clientId:{},msg:{}", clientId, message);
for (WebSocketServer item : webSocketSet) {
if (item.clientId.equals(clientId)) {
item.sendMessage(message);
}
}
}
/**
* 群发消息,广播形式
*/
public void sendInfoAll(String message) throws IOException {
for (WebSocketServer item : webSocketSet) {
item.sendMessage(message);
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
}
四、测试
测试需要一个前端,这简直就是难为搞后端的同学,所以我在网上找了一个别人写好的前端。
打开网址:http://www.websocket-test.com
启动本地websocket的工程,输入本地websocket的地址(ws://localhost:port/{路由}/{clientId}),点击连接(先断开之前的连接)
注意:
1.因为当前 onMessage 方法是按照 clientId 相同才能接受消息,所以当发消息的时候只能是相同的 clientId 收到消息(可以再打开两个页面分别输入与上述不同和相同的clientId做测试)。如果需要广播就把判断条件去掉。
2.如果想通过后端推送消息到前端,可以调用 WebSocketServer 的 sendInfo 和 sendInfoAll 方法。