Netty网络框架学习笔记-8(WebSocket实现服务器和客户端长连接)

本文详细介绍了如何使用Netty框架实现WebSocket服务器,支持服务器主动推送消息并实现实时双向通信。通过自定义处理器,处理客户端发送的文本消息,并展示了WebSocket协议的工作原理和应用实例。

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

Netty网络框架学习笔记-8(WebSocket 编程实现服务器和客户端长连接)

实现的目标

实现基于 webSocket 的长连接的全双工的交互

改变 Http 协议多次请求的约束,实现长连接了, 服务器可以发送消息给浏览器

客户端浏览器和服务器端会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器会感知

1.0 服务端

@Slf4j
public class WebSocketServer {

    public static void main(String[] args) {
        NioEventLoopGroup boosGroup = new NioEventLoopGroup(2);
        // cpu核心数*2=线程数(EventLoop)
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(boosGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .handler(new LoggingHandler(LogLevel.INFO)) // boosGroup服务器处理程序, 日志
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ChannelPipeline pipeline = socketChannel.pipeline();
                        // 因为WebSocket 是独立的、创建在TCP 上的协议。
                        // Websocket 通过HTTP/1.1 协议的101状态码进行握手。  (HTTP 协议解析,用于握手阶段)
                        pipeline.addLast(new HttpServerCodec());
                        // 分块写入, 支持异步写大型数据流,而又不会导致大量的内存消耗
                        pipeline.addLast(new ChunkedWriteHandler());
                        //将多段信息聚合起来, 最大支持1024*1024
                        pipeline.addLast(new HttpObjectAggregator(1024 * 1024));
                        // WebSocket 数据压缩 ,压缩来自 client
                        pipeline.addLast(new WebSocketServerCompressionHandler());
                        // 对应 websocket ,它的数据是以 帧(frame) 形式传递,
                        // 可以看到 WebSocketFrame 下面有六个子类,
                        // 核心功能是将 http 协议升级为 ws 协议 , 保持长连接
                        pipeline.addLast(new WebSocketServerProtocolHandler("/build", null, true));
                        // 添加自定义处理器
                        pipeline.addLast(new MyWebSocketServerMessageHandler());
                    }
                });

        try {
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress("localhost", 8888)).sync();
            channelFuture.addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    log.info("WebSocketServer-启动成功, 等待客户端连接中!");
                }
            });
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            log.error("WebSocketServer-发生异常! 异常信息:{}", e);
        } finally {
            boosGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

1.0.1 自定义处理器

// 这里 TextWebSocketFrame 类型,表示一个文本帧(frame)
@Slf4j
public class MyWebSocketServerMessageHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext,
                                TextWebSocketFrame textWebSocketFrame) throws Exception {
        String text = textWebSocketFrame.text();
        log.info("读取到的信息:{}",text);
        String result = null;
        if ("你好".equals(text)){
            result = "你好";
        }
        if ("在干吗".equals(text)){
            result = "学习";
        }
        TextWebSocketFrame webSocketFrame = new TextWebSocketFrame(result);
        // 写回数据也是按照协议格式写TextWebSocketFrame
        channelHandlerContext.writeAndFlush(webSocketFrame);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("MyWebSocketServerMessageHandler-客户端连接成功:{}",ctx.channel().remoteAddress().toString());
        super.handlerAdded(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info("MyWebSocketServerMessageHandler-客户端断开连接:{}",ctx.channel().remoteAddress().toString());
        super.handlerRemoved(ctx);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
    }
}

进行测试

写完客户端后, 可以自己写个页面进行使用WebSocket协议进行建立连接发送信息。

或者百度搜索在线WebSocket 进行测试

结果:

19:17:41.580 [nioEventLoopGroup-2-1] INFO io.netty.handler.logging.LoggingHandler - [id: 0x066e99a6, L:/127.0.0.1:8888] ACTIVE
19:17:43.463 [nioEventLoopGroup-2-1] INFO com.zhihao.netty.webSocket.WebSocketServer - WebSocketServer-启动成功, 等待客户端连接中!
19:17:50.794 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.webSocket.MyWebSocketServerMessageHandler - MyWebSocketServerMessageHandler-客户端连接成功:/127.0.0.1:49219
19:19:55.845 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.webSocket.MyWebSocketServerMessageHandler - 读取到的信息:你好
19:20:00.266 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.webSocket.MyWebSocketServerMessageHandler - 读取到的信息:在干吗
19:24:15.278 [nioEventLoopGroup-3-1] INFO com.zhihao.netty.webSocket.MyWebSocketServerMessageHandler - MyWebSocketServerMessageHandler-客户端断开连接:/127.0.0.1:49219

LHr0P0.md.png

扩展:

WebSocket 协议介绍

WebSocket 协议是一种在单个 TCP 连接上进行全双工通信的协议,在建立连接完成握手阶段后,服务端也可以主动推送数据给客户端,使得 Web 浏览器和服务器之间的交互性更强大。

目前 WebSocket 协议应用非常广泛,大部分浏览器均已支持 WebSocket,不仅仅在 Web 应用中,其他很多类型应用(例如游戏)也经常用到 WebSocket 协议。

WebSocket 建立连接的过程

WebSocket 分为握手阶段( handshake )和数据传输阶段( data transfer )。

握手阶段( handshake )

在客户端和服务器建立 WebSocket 连接之前,客户端首先要发送一个 HTTP 协议的握手请求:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

其中请求头 Connection: UpgradeUpgrade: websocket 表示客户端想要升级协议为 WebSocket。服务器进行如下响应完成握手:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

完成握手后,接下来就是双向的数据传输的过程。

数据传输阶段( data transfer )=> (对应 WebSocketFrame)

数据传输阶段传输的内容以帧( frame )为单位,其中分为控制帧(Control Frame)和数据帧(Data Frame):

  • 控制帧(Control Frame):包括 ClosePingPong 帧,Close 用于关闭 WebSocket 连接,PingPong 用于心跳检测
  • 数据帧(Data Frame):包括 TextBinary 帧,分别用于传输文本和二进制数据

自定义网页:

<!DOCTYPE html>
<html lang="en">
               
               <head>
                              <meta charset="UTF-8">
                              <title>测试WebSocket</title>
               </head>
               
               <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>
                              <script>
                                             //判断当前浏览器是否支持
                                             var socket;
                                             if (window.WebSocket) {
                                                            // 使用WebSocket连接,  路径定义WebSocketServerProtocolHandler /build
                                                            socket = new WebSocket("ws://localhost:8888/build");

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

</html>

1

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

懵懵懂懂程序员

如果节省了你的时间, 请鼓励

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值