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()被调用 → 执行业务逻辑
相关推荐
道友可好18 分钟前
Git Worktree:一个仓库,多个分身
前端·后端·程序员
鱼鳞_27 分钟前
苍穹外卖-Day10(Spring task)
java·后端·spring
我是一颗柠檬32 分钟前
【Redis】事务与Lua脚本Day7(2026年)
数据库·redis·后端·lua·database
farerboy1 小时前
15-Java while 和 do...while循环
java·后端
i220818 Faiz Ul1 小时前
民谣网站|基于Springboot的民谣网站管理系统(源码+数据库+文档)
java·数据库·spring boot·后端·论文·毕设·民谣网站
Oneslide1 小时前
windows cmd输入输出都很卡
后端
ihuyigui2 小时前
国际企业办公短信接口
前端·后端·架构
雪隐2 小时前
AI股票小助手03-Tushare数据采集
人工智能·后端
foggyprojects2 小时前
Java 里动态 SQL 为什么总是越写越乱
后端
越努力越幸运662 小时前
MAF的塑智能体边界,从AIAgent抽象类开始
后端