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()被调用 → 执行业务逻辑
相关推荐
疯狂的程序猴2 小时前
用 HBuilder 上架 iOS 应用时如何管理Bundle ID、证书与描述文件
后端
ShaneD7712 小时前
Redis 实战:从零手写分布式锁(误删问题与 Lua 脚本优化)
后端
我命由我123452 小时前
Python Flask 开发问题:ImportError: cannot import name ‘Markup‘ from ‘flask‘
开发语言·后端·python·学习·flask·学习方法·python3.11
無量2 小时前
Java并发编程基础:从线程到锁
后端
小信啊啊3 小时前
Go语言数组与切片的区别
开发语言·后端·golang
中国胖子风清扬3 小时前
SpringAI和 Langchain4j等 AI 框架之间的差异和开发经验
java·数据库·人工智能·spring boot·spring cloud·ai·langchain
计算机学姐3 小时前
基于php的摄影网站系统
开发语言·vue.js·后端·mysql·php·phpstorm
Java水解3 小时前
【SpringBoot3】Spring Boot 3.0 集成 Mybatis Plus
spring boot·后端
whoops本尊3 小时前
Golang-Data race【AI总结版】
后端