Netty 是 JBoss 推出的一款 Java 开源网络通信框架,核心定位是为各类网络应用提供异步、事件驱动的编程模型。通俗来说,它是一套帮助开发者快速搭建高性能、高可用网络服务端与客户端的开发工具。作为基于 NIO 的开发框架,Netty 大幅简化了网络编程流程,不管是基于 TCP、UDP 的 Socket 通信,还是 FTP、SMTP、HTTP 等复杂二进制或文本协议的实现,都可以借助 Netty 更高效地完成开发。需要强调的是,Netty 所追求的“开发快速”和“使用简单”,并不是以牺牲性能为代价。凭借优秀的架构设计,Netty 在开发效率、系统性能、稳定性以及可扩展性之间实现了良好的平衡,因此也成为 Java 网络编程中非常常用的技术方案。
本文将带领大家学习如何在SpringBoot项目中集成Netty
一、Netty服务端
由于这里是 SpringBoot 项目示例,因此 SpringBoot 相关依赖不再单独展开说明。
1、导入依赖
org.projectlombok lombok io.netty netty-all 4.1.36.Final 2、编写netty处理器
/** * Socket拦截器,用于处理客户端的行为 * * @author Gjing **/@Slf4jpublic class SocketHandler extends ChannelInboundHandlerAdapter {public static final ChannelGroup clients = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);/** * 读取到客户端发来的消息 * * @param ctx ChannelHandlerContext * @param msg msg * @throws Exception e */@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// 由于我们配置的是 字节数组 编解码器,所以这里取到的用户发来的数据是 byte数组byte[] data = (byte[]) msg;log.info("收到消息: " new String(data));// 给其他人转发消息for (Channel client : clients) {if (!client.equals(ctx.channel())) {client.writeAndFlush(data);}}}@Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {log.info("新的客户端链接:" ctx.channel().id().asShortText());clients.add(ctx.channel());}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {clients.remove(ctx.channel());}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.channel().close();clients.remove(ctx.channel());}}3、编写netty初始化器
/** * Socket 初始化器,每一个Channel进来都会调用这里的 InitChannel 方法 * @author Gjing **/@Componentpublic class SocketInitializer extends ChannelInitializer {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {ChannelPipeline pipeline = socketChannel.pipeline();// 添加对byte数组的编解码,netty提供了很多编解码器,你们可以根据需要选择pipeline.addLast(new ByteArrayDecoder());pipeline.addLast(new ByteArrayEncoder());// 添加上自己的处理器pipeline.addLast(new SocketHandler());}} 4、编写netty服务
/** * @author Gjing **/@Slf4j@Componentpublic class SocketServer {@Resourceprivate SocketInitializer socketInitializer;@Getterprivate ServerBootstrap serverBootstrap;/** * netty服务监听端口 */@Value("${netty.port:8088}")private int port;/** * 主线程组数量 */@Value("${netty.bossThread:1}")private int bossThread;/** * 启动netty服务器 */public void start() {this.init();this.serverBootstrap.bind(this.port);log.info("Netty started on port: {} (TCP) with boss thread {}", this.port, this.bossThread);}/** * 初始化netty配置 */private void init() {// 创建两个线程组,bossGroup为接收请求的线程组,一般1-2个就行NioEventLoopGroup bossGroup = new NioEventLoopGroup(this.bossThread);// 实际工作的线程组NioEventLoopGroup workerGroup = new NioEventLoopGroup();this.serverBootstrap = new ServerBootstrap();this.serverBootstrap.group(bossGroup, workerGroup) // 两个线程组加入进来.channel(NioServerSocketChannel.class)// 配置为nio类型.childHandler(this.socketInitializer); // 加入自己的初始化器}}5、启动netty
因为当前使用的是 SpringBoot,所以我们可以在项目启动完成后自动触发 Netty 服务启动。也就是说,只要 SpringBoot 应用成功启动,Netty 服务器就会一并运行。
/** * 监听Spring容器启动完成,完成后启动Netty服务器 * @author Gjing **/@Componentpublic class NettyStartListener implements ApplicationRunner {@Resourceprivate SocketServer socketServer;@Overridepublic void run(ApplicationArguments args) throws Exception {this.socketServer.start();}} 效果图
二、Netty客户端
客户端部分这里使用 NIO 来实现,因此不再继续用 Netty 编写。实际开发中,很多场景都是 Netty 作为服务端,而客户端可能是 WebSocket 或普通 Socket。本文为了便于理解,就以 Socket 客户端为例进行演示。由于 NIO 是 Java 原生提供的能力,所以无需额外引入依赖。
1、编写客户端线程
由于不能阻塞主线程,因此需要额外开启一个子线程来处理客户端监听逻辑。
/** * @author Gjing **/public class ClientThread implements Runnable{private final Selector selector;public ClientThread(Selector selector) {this.selector = selector;}@Overridepublic void run() {try {for (; ; ) {int channels = selector.select();if (channels == 0) {continue;}Set selectionKeySet = selector.selectedKeys();Iterator keyIterator = selectionKeySet.iterator();while (keyIterator.hasNext()) {SelectionKey selectionKey = keyIterator.next();// 移除集合当前得selectionKey,避免重复处理keyIterator.remove();if (selectionKey.isReadable()) {this.handleRead(selector, selectionKey);}}}} catch (IOException e) {e.printStackTrace();}}// 处理可读状态private void handleRead(Selector selector, SelectionKey selectionKey) throws IOException {SocketChannel channel = (SocketChannel) selectionKey.channel();ByteBuffer byteBuffer = ByteBuffer.allocate(1024);StringBuilder message = new StringBuilder();if (channel.read(byteBuffer) > 0) {byteBuffer.flip();message.append(StandardCharsets.UTF_8.decode(byteBuffer));}// 再次注册到选择器上,继续监听可读状态channel.register(selector, SelectionKey.OP_READ);System.out.println(message);}} 2、客户端逻辑
/** * 聊天客户端 * * @author Gjing **/public class ChatClient {public void start(String name) throws IOException {SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8088));socketChannel.configureBlocking(false);Selector selector = Selector.open();socketChannel.register(selector, SelectionKey.OP_READ);// 监听服务端发来得消息new Thread(new ClientThread(selector)).start();// 监听用户输入Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String message = scanner.nextLine();if (StringUtils.hasText(message)) {socketChannel.write(StandardCharsets.UTF_8.encode(name ": " message));}}}}3、客户端1
/** * @author Gjing **/public class Client1 {public static void main(String[] args) throws IOException {new ChatClient().start("李四");}}4、客户端2
/** * @author Gjing **/public class Client2 {public static void main(String[] args) throws IOException {new ChatClient().start("张三");}}5、启动这两个客户端

此时服务端日志也会打印对应信息,说明已经成功监听到客户端接入。

rip|imageView2/2/w/1240)
接下来我们通过客户端2发送一条消息进行测试。

可以看到,客户端1已经成功收到了客户端2发送的消息。同样地,我们也可以通过客户端1继续发送消息进行验证。

与此同时,服务端也会同步输出对应的消息日志。

本文到这里就结束了。以上就是一个基于 SpringBoot 集成 Netty 的入门小案例,适合快速理解 Netty 服务端与 Socket 客户端通信的基本流程。更多进阶知识,大家可以前往 Netty 官方文档继续学习,Demo源代码地址:SpringBoot-Netty
