1、前言
websocket在java中有多种实现方式,一直没有做一个整理,今天整理下三种最常用的实现方式以及一些注意点
2、javax 实现方式
之前已经单独记录了这种方式
【SpringBoot系列】springboot websocket全套模板,省去搭建的烦恼,还有福利拿哦-CSDN博客
这种好处在于接口简单,只需要几个注解即可
支持路径参数:@ServerEndpoint("/tracking/{groupName}")
参数获取:通过map访问key进行获取
Map<String, List<String>> params = session.getRequestParameterMap();
List<String> strings = params.get("groupName");
连接方式:ws:localhost:8080/tracking/123?group=1
@ServerEndpoint("/url") 该注解用于注释服务端的类,被该注解注释的类,将会被标注为webSocket的服务类,参数value为访问的路径
@OnOpen 被该注解注释的方法,将在客户端与服务端建立连接时执行
@OnMessage 被该注解注释的方法,将在服务端收到消息时执行
@OnClose 被该注解注释的方法,将在链接关闭时执行
@OnError 被该注解注释的方法,将在链接发生错误时执行
特点:ws 容器为tomcat,受限于tomcat,性能有一定问题,在常规开发中对性能没那么高要求的情况下,没有问题
3、spring-boot-starter-websocket
3.1 实现方式
依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
实现具体路径handler
package com.example.wstest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.Map;
/**
* @author 种鑫
* @date 2024/6/17 13:41
*/
@Component
public class WsHandler extends AbstractWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
URI uri = session.getUri();
// UrlQuery of = UrlQuery.of(uri.toString(), Charset.defaultCharset());
UriComponents builder = UriComponentsBuilder.fromUri(uri).build();
String path = builder.getPath();
MultiValueMap<String, String> queryParams = builder.getQueryParams();
Map<String, Object> attributes = session.getAttributes();
System.out.println("新连接");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("text msg");
}
@Override
protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
super.handleBinaryMessage(session, message);
System.out.println("Binary msg");
}
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
super.handlePongMessage(session, message);
System.out.println("连接错误");
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
super.handleTransportError(session, exception);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
super.afterConnectionClosed(session, status);
System.out.println("连接关闭");
}
}
配置handler生效
@Configuration
@EnableWebSocket
public class WsConfig implements WebSocketConfigurer {
@Resource
WsHandler wsHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(wsHandler,"/spce/{id}").setAllowedOrigins("*");
}
}
3.2 使用方式
获取参数需要自己构建
访问方式:ws:localhost:8080/spce/111?abc=123
3.3 原理解析
依赖Springboot web,底层同样是使用tomcat支撑Websocket
org.springframework.web.socket.config.annotation.AbstractWebSocketHandlerRegistration#getMappings
protected final M getMappings() {
M mappings = createMappings();
if (this.sockJsServiceRegistration != null) {
SockJsService sockJsService = this.sockJsServiceRegistration.getSockJsService();
this.handlerMap.forEach((wsHandler, paths) -> {
for (String path : paths) {
String pathPattern = (path.endsWith("/") ? path + "**" : path + "/**");
addSockJsServiceMapping(mappings, sockJsService, wsHandler, pathPattern);
}
});
}
else {
HandshakeHandler handshakeHandler = getOrCreateHandshakeHandler();
HandshakeInterceptor[] interceptors = getInterceptors();
this.handlerMap.forEach((wsHandler, paths) -> {
for (String path : paths) {
addWebSocketHandlerMapping(mappings, wsHandler, handshakeHandler, interceptors, path);
}
});
}
return mappings;
}
可以看到对于不是/结尾的路径前后都加了匹配
4、Netty Websocket
依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.69.Final</version>
</dependency>
构建服务器
@Slf4j
@Configuration
public class WsServer implements CommandLineRunner {
private static final Integer PORT = 8888;
@Override
public void run(String... args) throws Exception {
new WebSocketConfig().start();
}
public void start() {
// 创建EventLoopGroup
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup(Runtime.getRuntime().availableProcessors() * 2);
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new HttpServerCodec());
// 最大数据长度
pipeline.addLast(new HttpObjectAggregator(65536));
// 添加接收websocket请求的url匹配路径
pipeline.addLast(new WebSocketServerProtocolHandler("/websocket"));
// 10秒内收不到消息强制断开连接
// pipeline.addLast(new ReadTimeoutHandler(10, TimeUnit.SECONDS));
pipeline.addLast(new WebSocketHandler());
}
});
ChannelFuture future = serverBootstrap.bind(PORT).sync();
log.info("websocket server started, port={}", PORT);
// 处理 channel 的关闭,sync 方法作用是同步等待 channel 关闭
// 阻塞
future.channel().closeFuture().sync();
} catch (Exception e) {
log.error("websocket server exception", e);
throw new RuntimeException(e);
} finally {
log.info("websocket server close");
// 关闭EventLoopGroup
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
5、总结
对于常规使用,压力不大的情况下使用javax方式,很方便也很简单
对于一些游戏服务器来说建议使用Netty这种方式,可以掌控,同时可以轻松切换socket服务器
注:2,3 两种方式在注入时候会有问题,建议使用static变量,手动注入类