本节摘要:从 Maven 依赖开始,逐行写出一个可运行的 Netty Echo 服务端与客户端,解读启动日志里每行的含义,并对照时序图理解一次回声请求在车间里的完整旅程。这是全册所有实战代码的骨架模板。
环境要求很朴素:JDK 8 及以上,构建工具用 Maven 或 Gradle 均可。以 Maven 为例,在依赖中加入 Netty 全量包:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.108.Final</version> </dependency>
选 4.1.x 稳定线即可,生产项目也可以按模块拆分依赖(netty-handler、netty-codec 等)减小体积。验证安装是否成功,在交互式环境里执行:
System.out.println(io.netty.util.Version.identify()); // 期望输出包含:netty-common: 4.1.108.Final ...
能看到版本号,车间就通电了。下面直接开工:目标是服务端把客户端发来的每行文本原样送回。
服务端代码不到 60 行,但每一行都对应车间的一个部件,值得逐段看明白。
import io.netty.bootstrap.ServerBootstrap; 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; public class EchoServer { public static void main(String[] args) throws Exception { // boss 组:前台接待,只管接受新连接 EventLoopGroup boss = new NioEventLoopGroup(1); // worker 组:车间工人,管所有已建立连接的读写 EventLoopGroup workers = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); // 车间装配图 b.group(boss, workers) .channel(NioServerSocketChannel.class) // 用 NIO 传送带 .option(ChannelOption.SO_BACKLOG, 128) // 前台候客队列长度 .childOption(ChannelOption.TCP_NODELAY, true) // 小件立即发货 .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); // 每条连接的工序表 p.addLast(new StringDecoder()); // 工位1:字节解码成字符串 p.addLast(new StringEncoder()); // 工位2:字符串编码回字节 p.addLast(new EchoHandler()); // 工位3:回声加工 } }); ChannelFuture f = b.bind(8080).sync(); // 开业,同步等待绑定成功 System.out.println("EchoServer started on 8080"); f.channel().closeFuture().sync(); // 守着车间直到关门 } finally { boss.shutdownGracefully(); // 优雅停工 workers.shutdownGracefully(); } } } // 回声工位:读到什么就写回什么 class EchoHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println("recv: " + msg); ctx.writeAndFlush(msg + "\n"); // 送回发货口 } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); // 出事就关这条传送带 } }
几个初学者最容易迷惑的点,先按下不展开、只给结论:group(boss, workers) 的两组线程分别管"接单"和"干活",第二章会拆开讲;childHandler 与 handler 的区别是前者作用于每条新连接、后者作用于监听通道本身;sync() 的意思是"等这件事办完再往下走"。Echo 能跑起来之后,这些答案会自己长出来。
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.*; public class EchoClient { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); // 客户端只需一组人手 try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) { ch.pipeline().addLast(new StringDecoder(), new StringEncoder(), new EchoClientHandler()); } }); ChannelFuture f = b.connect("127.0.0.1", 8080).sync(); f.channel().closeFuture().sync(); } finally { group.shutdownGracefully(); } } } class EchoClientHandler extends SimpleChannelInboundHandler<String> { @Override public void channelActive(ChannelHandlerContext ctx) { ctx.writeAndFlush("hello netty workshop\n"); // 连接一通就发货 } @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) { System.out.println("echo back: " + msg); // 收到回声 ctx.close(); } }
先起服务端再起客户端,两端各自打印:
[服务端] EchoServer started on 8080 [服务端] recv: hello netty workshop [客户端] echo back: hello netty workshop
注意时序图里的分工:boss 只在"接单"时出场,之后的读写全部由被分配的那个 worker 独占处理——同一连接的字节永远被同一个工人按序加工,这是 Netty 免锁的关键,第六章细讲。
BindException,多半是上次的进程没退干净。换端口或杀进程。StringDecoder 配对 LineBasedFrameDecoder 语义,没换行的消息会一直躺在缓冲区。childHandler 里最后确实挂了业务 Handler,且没有把异常静默吞掉。IOException: 远程主机强迫关闭 属于正常现象,示例的 exceptionCaught 里 ctx.close() 即可。💡 关键直觉:Echo 车间麻雀虽小,但"装配图(Bootstrap)—工序表(Pipeline)—工位(Handler)—工人(EventLoop)"四个概念全部到位。后面八章只是把其中一个部件放大十倍来讲。