Netty基于SpringBoot实现WebSocket

先来进行一下相关名词解释:

Netty

Netty是构建在Java NIO之上的一个强大的网络编程框架,它通过其灵活的Channel机制支持多种协议,包括WebSocket,能够高效地开发出高性能的网络应用程序,简化了NIO的使用,同时提供了对WebSocket等现代网络协议的支持。

Channel

在Netty中,Channel是Java NIO的一个核心概念,代表一个开放的连接或者绑定到一个特定设备的开放连接,比如一个硬件设备、一个文件、一个网络套接字等。Channel提供了一种与底层操作系统交互的方法,允许数据在网络或文件系统之间传输。通过使用Channel,可以执行读取和写入操作,而不需要直接处理流。

WebSocket

WebSocket是一种应用层通信协议,提供了全双工通信通道,可以在单个TCP连接上进行双向数据传输。WebSocket协议使得客户端和服务器之间的消息交换更加简单,允许服务器主动向客户端推送数据。这种特性非常适合需要实时更新的应用场景,如在线游戏、聊天应用等。

NIO

NIO是Java提供的面向块的I/O操作的一种方式,NIO引入了几个新的抽象概念,如Buffer、Channel和Selector,它们共同作用以提高I/O效率,特别是在处理大量并发连接时。NIO支持非阻塞模式,线程可以在等待数据到来的同时做其他事情,从而提高资源利用率。

下面看一下具体的代码实现:

java 复制代码
@Slf4j
@Component
public class NettyWebSocketServer {

    private final int port = 8081; 
    private EventLoopGroup bossGroup;
    private EventLoopGroup workerGroup;
    private Channel serverChannel;

    @PostConstruct
    public void start() throws Exception {
        log.info("Starting Netty WebSocket server on port {}", port);
        bossGroup = new NioEventLoopGroup(1);
        workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .localAddress(new InetSocketAddress(port))
                    .childHandler(new NettyWebSocketInitializer());

            serverChannel = b.bind().sync().channel();
            log.info("Netty WebSocket server started successfully on ws://localhost:{}", port);
        } catch (InterruptedException e) {
            log.error("Failed to start Netty server", e);
            Thread.currentThread().interrupt();
            throw new RuntimeException("Netty server startup interrupted", e);
        }
    }

    @PreDestroy
    public void stop() {
        log.info("Shutting down Netty WebSocket server...");
        if (serverChannel != null) {
            serverChannel.closeFuture().syncUninterruptibly();
        }
        if (bossGroup != null) {
            bossGroup.shutdownGracefully().syncUninterruptibly();
        }
        if (workerGroup != null) {
            workerGroup.shutdownGracefully().syncUninterruptibly();
        }
        log.info("Netty WebSocket server shut down.");
    }
}
java 复制代码
@Component
public class NettyWebSocketInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) {
        ChannelPipeline pipeline = ch.pipeline();

        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpObjectAggregator(65536));
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        pipeline.addLast(new NettyWebSocketHandler());
    }
}
java 复制代码
@Slf4j
@Component
public class NettyWebSocketHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

    private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        Channel channel = ctx.channel();
        channels.add(channel);
        log.info("Client connected: {}", channel.remoteAddress());
        channels.writeAndFlush(new TextWebSocketFrame("[SERVER] User joined: " + formatTime()));
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) {
        Channel channel = ctx.channel();
        channels.remove(channel);
        log.info("Client disconnected: {}", channel.remoteAddress());
        channels.writeAndFlush(new TextWebSocketFrame("[SERVER] User left: " + formatTime()));
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) {
        if (frame instanceof TextWebSocketFrame) {
            String message = ((TextWebSocketFrame) frame).text();
            log.info("Received message from {}: {}", ctx.channel().remoteAddress(), message);

            String response = "[BROADCAST] " + formatTime() + " - " + message;
            channels.writeAndFlush(new TextWebSocketFrame(response));
        } else {
            log.warn("Unsupported WebSocket frame type: {}", frame.getClass().getSimpleName());
            ctx.disconnect();
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        log.error("WebSocket error from {}", ctx.channel().remoteAddress(), cause);
        ctx.close();
    }

    private String formatTime() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss"));
    }
}

启动项目后可用Postman进行测试:

看一下具体的核心类:

NettyWebSocketServer:Netty 服务的生命周期管理器

  • 在 Spring 容器启动时启动 Netty 服务器
  • 在 Spring 容器关闭时优雅地关闭 Netty
  • 持有 EventLoopGroup和Channel 引用,用于资源释放

NettyWebSocketInitializer:初始化Channel

  • 为每个新连接的客户端 构建 ChannelPipeline
  • 添加编解码器、协议处理器、自定义 Handler

NettyWebSocketHandler:业务逻辑处理器(

  • 处理 WebSocket 帧(如 TextWebSocketFrame)
  • 实现消息广播、存储、私聊等业务
  • 管理连接上线/下线(通过 handlerAdded / handlerRemoved)

请求处理流程:

  1. 应用启动 → Spring 创建NettyWebSocketServer → 调用start()
  2. Netty 启动 → 绑定端口,设置 childHandler = new NettyWebSocketInitializer()
  3. 客户端连接 → Netty 调用 NettyWebSocketInitializer.initChannel()
  4. Pipeline 构建 → 添加 Codec + ProtocolHandler + new NettyWebSocketHandler(service)
  5. WebSocket 握手 → WebSocketServerProtocolHandler自动处理 HTTP → WebSocket 升级
  6. 消息到达 → NettyWebSocketHandler.channelRead0()被调用 → 执行业务逻辑
相关推荐
神奇小汤圆11 分钟前
浅析二叉树、B树、B+树和MySQL索引底层原理
后端
文艺理科生21 分钟前
Nginx 路径映射深度解析:从本地开发到生产交付的底层哲学
前端·后端·架构
千寻girling21 分钟前
主管:”人家 Node 框架都用 Nest.js 了 , 你怎么还在用 Express ?“
前端·后端·面试
南极企鹅23 分钟前
springBoot项目有几个端口
java·spring boot·后端
Luke君6079725 分钟前
Spring Flux方法总结
后端
define952729 分钟前
高版本 MySQL 驱动的 DNS 陷阱
后端
忧郁的Mr.Li1 小时前
SpringBoot中实现多数据源配置
java·spring boot·后端
闲人编程2 小时前
使用FastAPI和WebSocket构建高性能实时聊天系统
websocket·网络协议·网络编程·fastapi·持久化·实时聊天·codecapsule
暮色妖娆丶2 小时前
SpringBoot 启动流程源码分析 ~ 它其实不复杂
spring boot·后端·spring
Coder_Boy_2 小时前
Deeplearning4j+ Spring Boot 电商用户复购预测案例中相关概念
java·人工智能·spring boot·后端·spring