1.2 开工第一课:环境搭建与跑通 Echo 车间


1.2 开工第一课:环境搭建与跑通 Echo 车间

本节摘要:从 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 ...

能看到版本号,车间就通电了。下面直接开工:目标是服务端把客户端发来的每行文本原样送回。

二、装第一条流水线:EchoServer 逐行拆解

服务端代码不到 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) 的两组线程分别管"接单"和"干活",第二章会拆开讲;childHandlerhandler 的区别是前者作用于每条新连接、后者作用于监听通道本身;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 免锁的关键,第六章细讲。

四、排错清单:第一晚最容易撞上的问题

  • 端口被占用:bind 抛 BindException,多半是上次的进程没退干净。换端口或杀进程。
  • 收不到回声但也不报错:检查是否忘了在消息末尾加换行——示例用了按行解码的默认 StringDecoder 配对 LineBasedFrameDecoder 语义,没换行的消息会一直躺在缓冲区。
  • 客户端 connect 后立刻断开:确认服务端 childHandler 里最后确实挂了业务 Handler,且没有把异常静默吞掉。
  • Windows 下日志刷红:关闭连接时出现 IOException: 远程主机强迫关闭 属于正常现象,示例的 exceptionCaughtctx.close() 即可。

💡 关键直觉:Echo 车间麻雀虽小,但"装配图(Bootstrap)—工序表(Pipeline)—工位(Handler)—工人(EventLoop)"四个概念全部到位。后面八章只是把其中一个部件放大十倍来讲。

本节要点回顾

  • 最小可运行要素:两个 EventLoopGroup、一个 ServerBootstrap、一条带编解码与业务 Handler 的 Pipeline。
  • boss 与 worker:接单与干活分离,是主从 Reactor 的雏形。
  • 日志即证据:启动日志的顺序暴露了线程分配与 Pipeline 装配的时机。
  • StringDecoder 有边界语义:它默认按行处理消息,换行符就是切分点——这已经预告了第五章的粘包问题。
  • 骨架模板:本章的代码结构可直接改造为任何自定义协议服务的脚手架。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U