Netty WebSocket长连接

该文章展示了如何使用Netty框架构建一个WebSocket服务器,包括设置线程组、配置HTTP和WebSocket的编码解码器,以及处理WebSocket帧的自定义处理器。客户端通过HTML页面与服务器建立WebSocket连接,实现双向通信。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

WebSocketServer:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;

public class WebSocketServer {
    public static void main(String[] args) throws InterruptedException {
        // 创建两个线程组
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .handler(new LoggingHandler(LogLevel.INFO)) //在 bossGroup 增加一个日志处理器
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();

                            // 因为基于 Http协议, 使用 http 的编码和解码器
                            pipeline.addLast(new HttpServerCodec());

                            // 是以块方式写,添加 ChunkedWrite 处理器
                            pipeline.addLast(new ChunkedWriteHandler());

                            /**
                             * http的数据在传输过程中是分段的,HttpObjectAggregator 就是可以将多个段聚合起来(浏览器发送大量数据时,就会发出多次http请求)
                             */
                            pipeline.addLast(new HttpObjectAggregator(8192));

                            /**
                             * 对于 websocket,它的数据是以帧(frame)形式传递
                             * 可以看到WebSocketFrame 下面有六个子类
                             * 浏览器请求时 ws://localhost:9527/xxx xxx 表示请求的uri
                             * WebSocketServerProtocolHandler 核心功能是将 http协议升级为 ws 协议,保持长连接
                             * 是通过一个状态码 101 进行切换
                             */
                            pipeline.addLast(new WebSocketServerProtocolHandler("/socket"));

                            // 自定义的 handler, 处理业务逻辑
                            pipeline.addLast(new WebTextSocketFrameHandler());
                        }
                    });
            ChannelFuture channelFuture = bootstrap.bind(9527).sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
WebTextSocketFrameHandler:
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;

import java.time.LocalDateTime;

/**
 * TextWebSocketFrame 表示一个文本帧(frame)
 */
public class WebTextSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
        System.out.println("服务器收到消息 " + textWebSocketFrame.text());

        // 回复消息
        channelHandlerContext.channel().writeAndFlush(new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + " " + textWebSocketFrame.text()));
    }

    /**
     * 当 web客户端连接后, 触发方法
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        // id 表示唯一的值 LongText 唯一 ShortText 不唯一
        System.out.println("handlerAdded 被调用" + ctx.channel().id().asLongText());
        System.out.println("handlerAdded 被调用" + ctx.channel().id().asShortText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved 被调用" + ctx.channel().id().asLongText());
    }

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

WebSocket.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var socket;
        // 判断当前浏览器是否支持websocket
        if(window.WebSocket){
            // go on
            socket = new WebSocket("ws://localhost:9527/socket");
            // 相当于 channelRead0, ev 收到服务器端回送的消息
            socket.onmessage = function(ev) {
                var rt = document.getElementById("responseText");
                rt.value = rt.value + "\n" + ev.data
            }
            // 相当于 连接开启
            socket.onopen = function(ev) {
                var rt = document.getElementById("responseText");
                rt.value = "连接开启了.."
            }

            // 相当于 连接关闭
            socket.onclose = function(ev) {
                var rt = document.getElementById("responseText");
                rt.value = rt.value + "\n" + "连接关闭了.."
            }
        } else {
            alert("当前浏览器不支持 WebSocket")
        }

        // 发送消息到服务器
        function send(message) {
            // 先判断 socket 是否创建好
            if(!window.socket) {
                return;
            }
            if(socket.readyState == WebSocket.OPEN) {
                // 通过socket 发送消息
                socket.send(message)
            } else {
                alert("连接没有开启")
            }
        }
    </script>
    <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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值