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()被调用 → 执行业务逻辑
相关推荐
开心就好202517 分钟前
不同阶段的 iOS 应用混淆工具怎么组合使用,源码混淆、IPA混淆
后端·ios
架构师沉默24 分钟前
程序员如何避免猝死?
java·后端·架构
椰奶燕麦42 分钟前
Windows PackageManager (winget) 核心故障排错与通用修复指南
后端
Zzxy1 小时前
快速搭建SpringBoot项目并整合MyBatis-Plus
java·spring boot
zjjsctcdl1 小时前
springBoot发布https服务及调用
spring boot·后端·https
观测云2 小时前
SpringBootAI 接入观测云 MCP 最佳实践
spring boot·观测云·mcp
zdl6862 小时前
Spring Boot文件上传
java·spring boot·后端
世界哪有真情2 小时前
哇!绝了!原来这么简单!我的 Java 项目代码终于被 “拯救” 了!
java·后端
RMB Player2 小时前
Spring Boot 集成飞书推送超详细教程:文本消息、签名校验、封装工具类一篇搞定
java·网络·spring boot·后端·spring·飞书
重庆小透明2 小时前
【搞定面试之mysql】第三篇 mysql的锁
java·后端·mysql·面试·职场和发展