前言:WebSocket是一种通信协议,提供了在单个TCP连接上进行全双工、实时通信的能力。它允许客户端和服务器之间进行双向数据交换,而无需频繁建立和关闭连接。WebSocket适用于需要实时更新的应用,如聊天应用、游戏和金融交易平台。它通过建立一个持久的连接,减少了数据传输的延迟。
一、导入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
二、编写配置类
开启WebSocket的支持,并把该类注入到spring容器中
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
三、Websocket实现类
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class WebSocketService {
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
private String userId;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
private static final CopyOnWriteArraySet<WebSocketService> webSockets =new CopyOnWriteArraySet<>();
// 用来存在线连接用户信息
private static final ConcurrentHashMap<String,Session> sessionPool = new ConcurrentHashMap<>();
/**
* 链接成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam(value="userId")String userId) {
this.session = session;
this.userId = userId;
webSockets.add(this);
sessionPool.put(userId, session);
log.info("【websocket消息】有新的连接,总数为:"+webSockets.size());
}
/**
* 链接关闭调用的方法
*/
@OnClose
public void onClose() {
webSockets.remove(this);
sessionPool.remove(this.userId);
log.info("【websocket消息】连接断开,总数为:"+webSockets.size());
}
/**
* 收到客户端消息后调用的方法
*
* @param message
*/
@OnMessage
public void onMessage(String message) {
log.info("【websocket消息】收到客户端消息:"+message);
}
/** 发送错误时的处理
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误,原因:"+error.getMessage());
error.printStackTrace();
}
// 此为广播消息
public void sendAllMessage(String message) {
log.info("【websocket消息】广播消息:"+message);
for(WebSocketService webSocket : webSockets) {
try {
if(webSocket.session.isOpen()) {
webSocket.session.getAsyncRemote().sendText(message);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 此为单点消息
public void sendOneMessage(String userId, String message) {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 此为单点消息(多人)
public void sendMoreMessage(String[] userIds, String message) {
for(String userId:userIds) {
Session session = sessionPool.get(userId);
if (session != null&&session.isOpen()) {
try {
log.info("【websocket消息】 单点消息:"+message);
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
四、问题
经过以上配置,基本可以实现使用Websocket的消息实时收发,但将程序服务部署到服务器上后,出现Websocket连接404问题,查找资料发现是nginx反向代理的原因。
1) 默认 proxy_http_version 1.0,而’websocket’是建立在’1.1’的基础
2) nginx默认’不会’为客户端’转发’Upgrade、Connection标头
于是在nginx.conf文件下添加以下设置:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
增加Upgrade协议头和Connection协议头,使http连接升级到websocket连接,问题解决。
五、补充
在Websocket连接后一分钟没操作的话会断开连接。可在前端页面加个心跳机制
// 建立连接
v.webSocket.onopen = function(event) {
console.log(v.dateFormat(new Date(),"yyyy-MM-dd hh:mm:ss:S") + " onopen=" + event);
v.timer = window.setInterval(function(){ //每隔5秒钟发送一次心跳,避免websocket连接因超时而自动断开
console.log('每隔5秒钟发送一次心跳');
var ping = {"type":"ping"};
v.webSocket.send(JSON.stringify(ping));
},5000);
};