最近在学习netty相关的知识,看到netty可以实现 websoket,因此记录一下在netty中实现websocket的步骤,主要实现传递文本消息和传递二进制消息,传递二进制消息由于需要传递额外信息,因此使用自定义消息协议。
需求:
1、使用 netty 实现 websocket 服务器
2、实现 文本信息 的传递
3、实现 二进制 信息的传递,如果是图片则传递到后台后在前台直接显示,非图片提示。(此处的图片和非图片是前端传递到后台的二进制数据然后后端在原封不动的直接返回到前台)
4、只需要考虑 websocket 协议,不用处理http请求
实现细节:
1、netty中对websocket增强的处理器
WebSocketServerProtocolHandler
>> 此处理器可以处理了 webSocket 协议的握手请求处理,以及 Close、Ping、Pong控制帧的处理。对于文本和二进制的数据帧需要我们自己处理。
>> 如果我们需要拦截 webSocket 协议握手完成后的处理,可以实现ChannelInboundHandler#userEventTriggered方法,并判断是否是 HandshakeComplete 事件。
>> 参数:websocketPath 表示 webSocket 的路径
>> 参数:maxFrameSize 表示最大的帧,如果上传大文件时需要将此值调大
2、文本消息的处理
客户端: 直接发送一个字符串即可
服务端: 服务端给客户端响应文本数据,需要返回 TextWebSocketFrame 对象,否则客户端接收不到。
3、二进制消息的处理
客户端:向后台传递一个 blob 对象即可,如果我们需要传递额外的信息,那么可以在 blob 对象中进行添加,此例中自定义前4个字节表示数据的类型。
服务端:处理 BinaryWebSocketFrame 帧,并获取前4个字节,判断是否是图片,然后返回 BinaryWebSocketFrame对象给前台。
4、针对二进制消息的自定义协议如下:(此处实现比较简单)
前四个字节表示文件类型,后面的字节表示具体的数据。
在java中一个int是4个字节,在js中使用Int32表示
此协议主要是判断前端是否传递的是 图片,如果是图片就直接传递到后台,然后后台在返回二进制数据到前台直接显示这个图片。非图片不用处理。
5、js中处理二进制数据
见 webSocket.html 文件中的处理。
实现步骤:
1、主要的依赖
- <dependency>
- <groupId>io.netty</groupId>
- <artifactId>netty-all</artifactId>
- <version>4.1.31.Final</version>
- </dependency>
2、webSocket服务端编写
- @Slf4j
- public class WebSocketServer {
- public static void main(String[] args) throws InterruptedException {
- EventLoopGroup bossGroup = new NioEventLoopGroup();
- EventLoopGroup workGroup = new NioEventLoopGroup();
- try {
- ServerBootstrap bootstrap = new ServerBootstrap();
- bootstrap.group(bossGroup, workGroup)
- .option(ChannelOption.SO_BACKLOG, 128)
- .childOption(ChannelOption.TCP_NODELAY, true)
- .childOption(ChannelOption.SO_KEEPALIVE, true)
- .handler(new LoggingHandler(LogLevel.TRACE))
- .channel(NioServerSocketChannel.class)
- .childHandler(new ChannelInitializer<SocketChannel>() {
- @Override
- protected void initChannel(SocketChannel ch) throws Exception {
- ch.pipeline()
- .addLast(new LoggingHandler(LogLevel.TRACE))
- // HttpRequestDecoder和HttpResponseEncoder的一个组合,针对http协议进行编解码
- .addLast(new HttpServerCodec())
- // 分块向客户端写数据,防止发送大文件时导致内存溢出, channel.write(new ChunkedFile(new File("bigFile.mkv")))
- .addLast(new ChunkedWriteHandler())
- // 将HttpMessage和HttpContents聚合到一个完成的 FullHttpRequest或FullHttpResponse中,具体是FullHttpRequest对象还是FullHttpResponse对象取决于是请求还是响应
- // 需要放到HttpServerCodec这个处理器后面
- .addLast(new HttpObjectAggregator(10240))
- // webSocket 数据压缩扩展,当添加这个的时候WebSocketServerProtocolHandler的第三个参数需要设置成true
- .addLast(new WebSocketServerCompressionHandler())
- // 服务器端向外暴露的 web socket 端点,当客户端传递比较大的对象时,maxFrameSize参数的值需要调大
- .addLast(new WebSocketServerProtocolHandler("/chat", null, true, 10485760))
- // 自定义处理器 - 处理 web socket 文本消息
- .addLast(new TextWebSocketHandler())
- // 自定义处理器 - 处理 web socket 二进制消息
- .addLast(new BinaryWebSocketFrameHandler());
- }
- });
- ChannelFuture channelFuture = bootstrap.bind(9898).sync();
- log.info("webSocket server listen on port : [{}]", 9898);
- channelFuture.channel().closeFuture().sync();
- } finally {
- bossGroup.shutdownGracefully();
- workGroup.shutdownGracefully();
- }
- }
- }
注意:
1、看一下上方依次引入了哪些处理器
2、对于 webSocket 的握手、Close、Ping、Pong等的处理,由 WebSocketServerProtocolHandler 已经处理了,我们自己只需要处理 Text和Binary等数据帧的处理。
3、对于传递比较大的文件,需要修改 maxFrameSize 参数。