Skip to content

Netty Websocket 案例

MyServer

java
public class MyServer {

    public static void main(String[] args) {

        EventLoopGroup bossGroup = new MultiThreadIoEventLoopGroup(
                NioIoHandler.newFactory()
        );

        EventLoopGroup workerGroup = new MultiThreadIoEventLoopGroup(
                NioIoHandler.newFactory()
        );

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();

bootstrap.group(bossGroup,workerGroup)
    .channel(NioServerSocketChannel.class)
    .handler(new LoggingHandler(LogLevel.INFO))
    .childHandler(new ChannelInitializer<SocketChannel>() {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            // 基于 Http 协议,使用 http 的编码和解码器
            pipeline.addLast(new HttpServerCodec());
            // 以块方式写
            pipeline.addLast(new ChunkedWriteHandler());
            // http 数据在传输过程中是分段的,HttpObjectAggregator 可以把多个段聚合
            // 浏览器发送大量数据时,会发出多次 http 请求
            pipeline.addLast(new HttpObjectAggregator(8192));
            // websocket 的数据是以 帧 (frame) 形式传递
            // 浏览器请求 ws://localhost:8000/hello 表示请求的 uri
            // WebSocketServerProtocolHandler 将 http 协议升级为 ws 协议
            pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
            // 自定义的 handler 处理业务逻辑
            pipeline.addLast(new MyTextWebSocketFrameHandler());
        }
    });

            ChannelFuture future = bootstrap.bind(8000).sync();
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

}

MyTextWebSocketFrameHandler

Websocket 数据是以帧的形式传输的,这里定义一个文本帧(TextWebSocketFrame)处理器

  • Netty 提供了 6 类 Websocket 帧

websocket帧.png

java
public class MyTextWebSocketFrameHandler extends
        SimpleChannelInboundHandler<TextWebSocketFrame> { 

    @Override
    protected void channelRead0(ChannelHandlerContext ctx,TextWebSocketFrame msg) 
            throws Exception {
        System.out.println("服务器收到消息: " + msg.text());

        //回复消息
        ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间 " 
                + LocalDateTime.now() + " 消息: " + msg.text()));

    }

    //当 web 客户端连接后
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // LongText 是唯一的, ShortText 不是唯一的
        System.out.println("新连接建立:" + ctx.channel().id().asLongText());
        System.out.println("新连接建立" + ctx.channel().id().asShortText());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 
            throws Exception {
        System.out.println("出现异常");
        ctx.close();
    }
}

前端测试页面

html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello World</title>
</head>
<script>
    let socket;
    //判断浏览器是否支持 websocket
    if (window.WebSocket) {
        socket = new WebSocket("ws://localhost:8000/hello")
        socket.onmessage = function (ev) {
            let rt = document.getElementById("responseText");
            rt.value = rt.value + "\n" + ev.data
        }

        socket.onopen = function () {
            let rt = document.getElementById("responseText")
            rt.value = "连接成功"
        }

        socket.onclose = function () {
            let rt = document.getElementById("responseText")
            rt.value = rt.value + "\n" + "连接关闭"
        }
    }else {
        alert("您的浏览器不支持 WebSocket")
    }

    function send(message) {
        if (socket.readyState === WebSocket.OPEN) {
            socket.send(message)
        }else {
            alert("WebSocket 连接失败")
        }
    }
</script>
<body>
    <form onsubmit="return false">
        <textarea name="message" style="height: 300px;width: 300px"></textarea>
        <input type="button" value="发送消息" onclick="send(this.form.message.value)">
        <textarea id="responseText" style="height: 300px;width: 300px"></textarea>
        <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
    </form>
</body>
</html>
  • 验证: 前端发送消息成功,也成功回显后端返回的消息

websocket.png

  • WebSocketServerProtocolHandler 的核心作用是将 http 协议转换为 ws 协议

websocket协议转换.png