WebSocket实现长连接
前言
什么是WebSocket?
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
WebSocket 与 HTTP的区别
相同点:
-
都是 TCP 协议;
-
都使用 Request/Response 模型进行连接的建立;
-
websocket 是基于 http 的,他们的兼容性都很好;
-
在连接的建立过程中对错误的处理方式相同;
-
都可以在网络中传输数据。
不同点:
- websocket 是持久连接,http 是短连接;
- websocket 的协议是以 ws/wss 开头,http 对应的是 http/https;
- websocket 是有状态的,http 是无状态的;
- websocket 连接之后服务器和客户端可以双向发送数据,http 只能是客户端发起一次请求之后,服务器才能返回数据;
- websocket 是可以跨域的;
- websocket 连接建立之后,数据的传输使用帧来传递,不再需要Request消息。
应用场景
- 即时通讯
- 消息推送弹幕
- 媒体聊天
- 协同编辑
- 基于位置的应用
- 体育实况更新
- 股票基金报价实时更新
Java集成WebSocket服务
1. 导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2. 编写WebSocket处理器
连接Websocket前首先会进入到WebsocketHandler进行业务逻辑处理,服务端的参数在拦截器中获取之后通过attributes传递给WebSocketHandler
/**
* @Author zzw
* @Create 2022/1/20 - 21:57
* @Description 连接Websocket前首先会进入到WebsocketHandler进行业务逻辑处理,服务端的参数在拦截器中获取之后通过 attributes传递给WebSocketHandler
*/
@Slf4j
@Component
public class MyWebSocketHandler implements WebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) {
//连接成功之后,应该在这个方法中将session保存起来,通常都用
//一个Map来保存,key是用户ID(或其他唯一标识),这样的话想
//给哪个用户发送消息,那么就直接拿到那个用户的session,然后
//调用session.sendMessage方法发送消息就可以了
log.info("连接成功");
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
log.info("客户端过来一条消息,内容是:" + message.getPayload().toString());
// TextMessage textMessage = new TextMessage(JSONObject.toJSONString("客户端你好,我已经收到你的消息"));
// session.sendMessage(textMessage);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
log.info("错误处理");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
log.info("连接关闭");
}
@Override
public boolean supportsPartialMessages() {
return false;
}
}
3. 编写WebSocket配置类
注册WebSocket实现类,绑定接口,同时将实现类和拦截器绑定,拦截WebSocket服务将在这里执行
/**
* @Author zzw
* @Create 2022/1/20 - 21:57
* @Description WebSocket 配置类实现(注册WebSocket实现类,绑定接口,同时将实现类和拦截器绑定)
*/
@Configuration
@Slf4j
public class WebSocketConfig implements WebSocketConfigurer {
private final MyWebSocketHandler wsHandler;
/**
* WebSecoket握手拦截器(执行相关业务逻辑)
*/
private HandshakeInterceptor handshakeInterceptor = new HandshakeInterceptor() {
/**
* 在获取请求或响应之前进行拦截,获取一些请求或响应的数据
* @param request
* @param response
* @param wsHandler
* @param attributes 如果该方法通过,可以在监听器或controller层拿到这里设置的数据
* @return 返回false则拦截,返回true则通过
* @throws Exception
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
// 进行业务逻辑处理
return true; // 返回true
}
/**
* 在通过请求或响应之后被调用
* @param request
* @param response
* @param wsHandler
* @param exception
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
}
};
public WebSocketConfig(MyWebSocketHandler wsHandler) {
this.wsHandler = wsHandler;
}
/**
* websocket拦截器(springsecurity需要配置,因为springsecurity拦截不到websocket)
* @ServerEndpoint("/websocket") 拦截的是websocket连接的服务
* @param registry
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(wsHandler, "/websocket") // 拦截路径
.setAllowedOrigins("*")
.addInterceptors(handshakeInterceptor); // 拦截之后进去的拦截器
}
/**
* 配置ServerEndpointExporter,配置后会自动注册所有“@ServerEndpoint”注解声明的Websocket Endpoint
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
4. 编写WebSocket服务类
进行消息的接收与发送
/**
* 核心配置
*
* @author Zzw
* @ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* * 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
* @date 2021/6/9 16:03
*/
@Slf4j
@Component
@ServerEndpoint("/websocket")
public class WebSocketServer{
/**
* 当前在线人数
*/
private static AtomicInteger onlineCount = new AtomicInteger(0);
/**
* 存放每个客户端对应的 WebSocketServer 对象
*/
private static CopyOnWriteArrayList<WebSocketServer> webSocketServers = new CopyOnWriteArrayList<>();
private Session session;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session) {
this.session = session;
// 加入集合中
webSocketServers.add(this);
// 在线人数+1
int count = onlineCount.incrementAndGet();
log.info("有新的窗口开始监听:, 当前在线人数为:{}", count);
}
/**
* 收到客服端消息后调用的方法
*
* @param message 客服端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口:{}, 消息内容:{}", message,session.getMessageHandlers());
if (!StringUtils.isNullOrEmpty(message)) {
// 进行业务逻辑处理
}
// 群发消息
// for (WebSocketServer webSocketServer : getWebSocketServers()) {
// webSocketServer.sendMessage(message);
// }
}
/**
* 发生错误时调用的方法
*/
@OnError
public void onError(Session session, @NotNull Throwable throwable) {
log.error("发生错误");
throwable.printStackTrace();
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
// 从集合中移除
webSocketServers.remove(this);
// 在线人数-1
int count = onlineCount.decrementAndGet();
log.info("释放的sid为:{}");
log.info("有一连接关闭!当前在线人数为:{}", count);
}
/**
* 实现服务器主动推送消息
*
* @param message 消息内容
*/
public void sendMessage(String message) {
try {
this.session.getBasicRemote().sendText(message);
log.info("message:{}",message);
} catch (IOException e) {
log.error("websocket IO Exception");
e.printStackTrace();
}
}
/**
* 发送消息到指定窗口 null则群发
*/
public void sendInfo(String message) {
for (WebSocketServer webSocketServer : getWebSocketServers()) {
webSocketServer.sendMessage(message);
}
return;
}
public static CopyOnWriteArrayList<WebSocketServer> getWebSocketServers() {
return webSocketServers;
}
}
5. 启动类上添加相关依赖
启动类上添加 @EnableWebSocket 依赖,表示启用webSocket服务
6. 进行WebSocket测试
WebSocket测试工具:http://www.jsons.cn/websocket/
@ServerEndpoint(“/websocket”)
ws://127.0.0.1:端口/ServerEndpoint配置的路径
集成完毕
非常感谢以下博主:
https://blog.csdn.net/weixin_43299180/article/details/117027846
https://blog.csdn.net/weixin_47428270/article/details/126639625