上一篇【第02篇】Java IO进化史——从BIO到NIO再到AIO,一文搞懂网络IO
下一篇【第04篇】Netty核心架构图解——Bootstrap/Channel/Pipeline/Handler一次看懂


摘要

学任何技术,第一步永远是"把环境跑起来"。本文手把手教您搭建Netty开发环境:Maven/Gradle依赖怎么配、IDE插件有哪些好用的、第一个Netty Echo服务器怎么写。

更重要的是:每行代码都给您讲清楚为什么。服务端只用了30行Java代码就实现了一个高性能NIO服务器;客户端20行代码就能连上服务器并收发消息。附带启动调试技巧和5个常见报错的解决方案,让您一次配好、一次跑通。


一、Netty版本选择——用哪个版本不踩坑?

在开始写代码前,先解决一个关键问题:用哪个版本的Netty?

【Netty版本选择指南】

版本系列        状态        推荐指数    说明
──────────────────────────────────────────────────────
Netty 3.x      已废弃       ❌        不要用,API陈旧
Netty 4.0.x    停止维护     ⚠️        有项目在用,但不推荐新项目使用
Netty 4.1.x    ✅ 推荐使用  ⭐⭐⭐⭐⭐  当前最稳定版本,所有新项目都用它
Netty 5.x      已废弃       ❌       官方已放弃,不要踩坑

为什么Netty 5被废弃了? Netty 5引入了新的ForkJoinPool线程模型,但经过长期测试,性能提升有限(某些场景甚至更慢),反而增加了代码复杂度。最终Netty团队决定将Netty 5的优秀特性合并回Netty 4.1.x,然后废弃了Netty 5分支。

结论:新项目直接用 Netty 4.1.100.Final(或更高版本)。


二、Maven依赖配置——复制粘贴就能用

Netty采用模块化设计,按需引入依赖。最核心的依赖是 netty-all,它包含了Netty的所有模块。

2.1 Maven项目依赖配置

<!-- pom.xml -->
<project xmlns="http://maven.apache.org/POM/4.0.0">
    <modelVersion>4.0.0</modelVersion>
    
    <groupId>com.example</groupId>
    <artifactId>netty-demo</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <netty.version>4.1.100.Final</netty.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    
    <dependencies>
        <!-- ✅ 推荐:引入netty-all(包含所有模块) -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty.version}</version>
        </dependency>
        
        <!-- 或者:按需引入(如果你很清楚需要哪些模块) -->
        <!--
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-buffer</artifactId>
            <version>${netty.version}</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-codec</artifactId>
            <version>${netty.version}</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-transport</artifactId>
            <version>${netty.version}</version>
        </dependency>
        -->
        
        <!-- 单元测试依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2.2 Gradle依赖配置

// build.gradle
plugins {
    id 'java'
}

group = 'com.example'
version = '1.0-SNAPSHOT'

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

repositories {
    mavenCentral()
}

dependencies {
    // ✅ 推荐:引入netty-all
    implementation 'io.netty:netty-all:4.1.100.Final'
    
    // 单元测试
    testImplementation 'junit:junit:4.13.2'
}

2.3 Netty的模块化结构

虽然推荐用netty-all,但了解Netty的模块结构有助于排查依赖冲突:

【Netty模块化结构】

netty-all(聚合包)
    ├── netty-buffer          (ByteBuf缓冲区)
    ├── netty-codec           (编解码框架)
    ├── netty-codec-http      (HTTP编解码)
    ├── netty-codec-http2     (HTTP/2编解码)
    ├── netty-codec-socks     (SOCKS代理协议)
    ├── netty-common          (公共工具类)
    ├── netty-handler         (内置Handler)
    ├── netty-handler-ssl     (SSL/TLS支持)
    ├── netty-resolver        (地址解析器)
    ├── netty-transport       (核心传输层)
    ├── netty-transport-epoll (Linux Epoll原生传输)
    ├── netty-transport-kqueue(macOS KQueue原生传输)
    └── netty-transport-udt  (UDT传输协议,已废弃)

三、第一个Netty程序——Echo服务器

Echo服务器是最经典的Netty入门程序:客户端发送什么,服务端就原样返回什么。

3.1 EchoServer——30行代码的高性能NIO服务器

// EchoServer.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * Netty Echo服务器——30行代码实现高性能NIO服务器
 */
public class EchoServer {
    
    private final int port;
    
    public EchoServer(int port) {
        this.port = port;
    }
    
    public void start() throws Exception {
        // 1️⃣ 创建Boss线程组(接收连接)
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 2️⃣ 创建Worker线程组(处理IO读写)
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        
        try {
            // 3️⃣ 创建ServerBootstrap(服务端启动引导类)
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)       // 设置线程组
             .channel(NioServerSocketChannel.class) // 设置服务端Channel类型
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     // 4️⃣ 配置ChannelPipeline(责任链)
                     ChannelPipeline p = ch.pipeline();
                     // 字符串解码器(将ByteBuf解码为String)
                     p.addLast(new StringDecoder());
                     // 字符串编码器(将String编码为ByteBuf)
                     p.addLast(new StringEncoder());
                     // 自定义业务Handler
                     p.addLast(new EchoServerHandler());
                 }
             });
            
            // 5️⃣ 绑定端口,同步等待成功
            ChannelFuture f = b.bind(port).sync();
            System.out.println("Echo服务器启动,监听端口:" + port);
            
            // 6️⃣ 等待服务端监听端口关闭
            f.channel().closeFuture().sync();
        } finally {
            // 7️⃣ 优雅关闭线程组
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    
    public static void main(String[] args) throws Exception {
        new EchoServer(8080).start();
    }
}

/**
 * EchoServer的业务Handler——处理客户端消息
 */
class EchoServerHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        // 客户端发来的消息(已经是String类型,因为用了StringDecoder)
        String message = (String) msg;
        System.out.println("服务端收到:" + message);
        
        // 原样返回给客户端
        ctx.writeAndFlush("Echo: " + message + "\n");
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // 出现异常,关闭连接
        cause.printStackTrace();
        ctx.close();
    }
}

3.2 EchoClient——20行代码的Netty客户端

// EchoClient.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

/**
 * Netty Echo客户端
 */
public class EchoClient {
    
    private final String host;
    private final int port;
    
    public EchoClient(String host, int port) {
        this.host = host;
        this.port = port;
    }
    
    public void start() throws Exception {
        // 1️⃣ 创建客户端线程组(只需要一个)
        EventLoopGroup group = new NioEventLoopGroup();
        
        try {
            // 2️⃣ 创建Bootstrap(客户端启动引导类)
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ChannelPipeline p = ch.pipeline();
                     p.addLast(new StringDecoder());
                     p.addLast(new StringEncoder());
                     p.addLast(new EchoClientHandler());
                 }
             });
            
            // 3️⃣ 连接服务器
            ChannelFuture f = b.connect(host, port).sync();
            System.out.println("客户端连接成功:" + host + ":" + port);
            
            // 4️⃣ 发送消息
            f.channel().writeAndFlush("Hello Netty!\n");
            
            // 5️⃣ 等待连接关闭
            f.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }
    
    public static void main(String[] args) throws Exception {
        new EchoClient("localhost", 8080).start();
    }
}

/**
 * EchoClient的业务Handler
 */
class EchoClientHandler extends ChannelInboundHandlerAdapter {
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        String response = (String) msg;
        System.out.println("客户端收到响应:" + response);
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }
}

四、运行与调试——让程序"跑起来"

4.1 启动步骤

【启动Echo服务器的步骤】

1. 先运行EchoServer(服务端)
   └── 控制台输出:"Echo服务器启动,监听端口:8080"

2. 再运行EchoClient(客户端)
   └── 服务端控制台输出:"服务端收到:Hello Netty!"
   └── 客户端控制台输出:"客户端收到响应:Echo: Hello Netty!"

4.2 用telnet测试服务器

如果您不想写客户端代码,可以用系统自带的telnet命令测试:

# Linux/macOS
$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello Netty!           <-- 输入这行,然后按回车
Echo: Hello Netty!    <-- 服务端返回(注意:需要客户端Handler支持)

# Windows(需要先开启telnet功能)
> telnet localhost 8080

注意:上面的EchoServer使用了StringDecoder,所以可以直接用telnet测试。如果您用的是裸ByteBuf,telnet发送的数据需要手动解析。

4.3 IDEA断点调试技巧

在IDEA中调试Netty程序,有几个实用技巧:

【IDEA调试Netty技巧】

1. 在Handler的channelRead()方法设断点
   └── 可以查看客户端发来的消息内容

2. 在EventLoop的run()方法设断点(源码调试)
   └── 可以看到IO事件的处理流程(需要下载Netty源码)

3. 条件断点:右键断点 → 设置Condition
   └── 例如:ctx.channel().remoteAddress().toString().contains("192.168")
       (只有特定IP的连接才会触发断点)

4. 观察Expression:在Debug面板中添加Watch
   └── 输入:ctx.channel().pipeline().names()
       (查看当前Pipeline中有哪些Handler)

五、5个常见报错与解决方案

报错1:java.lang.NoClassDefFoundError: io/netty/bootstrap/ServerBootstrap

原因:没有引入Netty依赖
解决:检查pom.xmlbuild.gradle,确保netty-all依赖已正确引入

报错2:java.net.BindException: Address already in use

原因:端口被占用
解决

# Linux/macOS:查看谁占用了8080端口
$ lsof -i :8080
$ kill -9 <PID>

# Windows:
> netstat -ano | findstr 8080
> taskkill /F /PID <PID>

报错3:客户端连接后没有响应

原因:服务端Pipeline中没有配置Decoder/Encoder,导致消息无法解析
解决:确保在initChannel()中添加了正确的编解码器:

// ✅ 正确
p.addLast(new StringDecoder());
p.addLast(new StringEncoder());

// ❌ 错误:没有编解码器,消息无法解析
p.addLast(new EchoServerHandler()); // 直接用这个,消息类型是ByteBuf

报错4:io.netty.util.IllegalReferenceCountException: refCnt: 0

原因:ByteBuf被释放了多次(内存泄漏检测)
解决:检查代码中是否手动调用了buf.release(),而Netty已经自动释放了

报错5:服务端收不到客户端消息

原因:多种可能,最常见的是TCP粘包/拆包导致消息不完整
解决:使用LineBasedFrameDecoderDelimiterBasedFrameDecoder解决粘包问题:

p.addLast(new LineBasedFrameDecoder(1024)); // 按换行符分割
p.addLast(new StringDecoder());

六、IDE插件推荐——提升开发效率

【Netty开发推荐IDE插件】

插件名称              适用IDE        功能
────────────────────────────────────────────────────────────
Netty Assistant        IDEA           Netty Pipeline可视化
Grep Console          IDEA           高亮Netty日志
Maven Helper          IDEA           分析依赖冲突(netty版本冲突很常见!)
Lombok                IDEA/ Eclipse  减少样板代码(@Data/@Builder)

总结

  1. 版本选择:新项目直接用Netty 4.1.100.Final(或更高版本)
  2. 依赖配置:Maven/Gradle引入netty-all,复制粘贴就能用
  3. 第一个程序:Echo服务器只需要30行代码,客户端20行
  4. 核心APIServerBootstrap/Bootstrap(启动引导)、NioEventLoopGroup(线程组)、ChannelInitializer(初始化Pipeline)
  5. 下一步:理解Netty的核心架构(Bootstrap/Channel/Pipeline/Handler),这是第004篇的内容

上一篇【第02篇】Java IO进化史——从BIO到NIO再到AIO,一文搞懂网络IO
下一篇【第04篇】Netty核心架构图解——Bootstrap/Channel/Pipeline/Handler一次看懂


Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐