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()被调用 → 执行业务逻辑
相关推荐
f***28147 分钟前
Springboot中使用Elasticsearch(部署+使用+讲解 最完整)
spring boot·elasticsearch·jenkins
咕白m62522 分钟前
通过 C# 快速生成二维码 (QR code)
后端·.net
踏浪无痕29 分钟前
架构师如何学习 AI:三个月掌握核心能力的务实路径
人工智能·后端·程序员
小毅&Nora1 小时前
【后端】【SpringBoot】① 源码解析:从启动到优雅关闭
spring boot·后端·优雅关闭
嘻哈baby1 小时前
从TIME_WAIT爆炸到端口耗尽:Linux短连接服务排查与优化
后端
开心就好20251 小时前
iOS应用性能监控全面解析:CPU、内存、FPS、卡顿与内存泄漏检测
后端
问今域中2 小时前
Spring Boot 请求参数绑定注解
java·spring boot·后端
计算机程序设计小李同学2 小时前
婚纱摄影集成管理系统小程序
java·vue.js·spring boot·后端·微信小程序·小程序
一 乐3 小时前
绿色农产品销售|基于springboot + vue绿色农产品销售系统(源码+数据库+文档)
java·前端·数据库·vue.js·spring boot·后端·宠物
3***68843 小时前
Spring Boot中使用Server-Sent Events (SSE) 实现实时数据推送教程
java·spring boot·后端