前言
书接上文,在计算机网络专栏中,笔者较为系统地揭示了网络通信的底层原理。正是基于这些基础认知,我们才能深入剖析 Netty 框架的核心机制。作为业界领先的开源 NIO 框架,Netty 以其高性能、易扩展的架构设计,在网络基础设施、中间件以及游戏服务器等场景中得到了广泛应用,具备不可替代的行业地位。
本系列文章适用于具备一定计算机网络与 Java 网络编程基础的研发人员,旨在帮助读者进一步提升对 Netty 的理解与实践能力。
服务端启动示例
java
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.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.AttributeKey;
public class NettyServer {
private final int port;
public NettyServer(int port) {
this.port = port;
}
public void start() throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// 创建 AttributeKey
AttributeKey<String> serverNameKey = AttributeKey.valueOf("serverName");
AttributeKey<String> clientKey = AttributeKey.valueOf("clientKey");
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
// 服务端 Channel 的初始化逻辑(通常用于启动日志)
.handler(new ChannelInitializer<NioServerSocketChannel>() {
@Override
protected void initChannel(NioServerSocketChannel ch) {
ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
}
})
// 客户端连接 SocketChannel 的初始化逻辑
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new SimpleServerHandler());
}
})
// 给服务端 ServerChannel 设置属性
.attr(serverNameKey, "nettyServer")
// 给每个客户端连接的 Channel 设置属性
.childAttr(clientKey, "clientValue")
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(port).sync();
System.out.println("Netty server started on port: " + port);
future.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new NettyServer(8080).start();
}
}
java
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
public class SimpleServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
String clientVal = ctx.channel().attr(AttributeKey.valueOf("clientKey")).get();
System.out.println("Client connected with attr: " + clientVal);
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
System.out.println("收到消息: " + msg);
ctx.writeAndFlush(msg); // 回写消息(回显服务器)
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
这是一个简单的服务端启动示例代码,我们通过构造一个辅助启动类ServerBootstrap并设置一些参数,最终调用这个辅助类的bind()
方法来启动服务端。bind()
方法返回ChannelFuture
对象,调用ChannelFuture
对象的sync()
方法阻塞当前线程,直到绑定操作完成。在netty中,所有的IO操作都是异步的。
future.channel().closeFuture().sync();
这行会让主线程等待服务端关闭事件发生,确保 Netty 服务一直运行,这是一个阻塞等待关闭的机制。
ServerBootstrap类和它的方法。
ServerBootstrap这个类继承抽象类AbstractBootstrap,定义了我们示例代码中传入的所有变量。
group()方法
java
bootstrap.group(bossGroup, workerGroup)
这个方法的实现如下
ServerBootstrap
java
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (childGroup == null) {
throw new NullPointerException("childGroup");
}
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = childGroup;
return this;
}
AbstractBootstrap
java
public B group(EventLoopGroup group) {
if (group == null) {
throw new NullPointerException("group");
}
if (this.group != null) {
throw new IllegalStateException("group set already");
}
this.group = group;
return (B) this;
}
将工作线程组保存在子类的childGroup中,将boss线程组保存在父类的group中。这里要说明下线程组的类型 NioEventLoopGroup
是childGroup和group的类型EventLoopGroup的实现类。
另外,这里使用抽象类的作用之一是:客户端没有boss线程,所以定义了ServerBootstrap类来保存服务端独有的一些参数。
channel()方法
java
.channel(NioServerSocketChannel.class)
java
// 1
public B channel(Class<? extends C> channelClass) {
if (channelClass == null) {
throw new NullPointerException("channelClass");
}
return channelFactory(new ReflectiveChannelFactory<C>(channelClass));
}
//3
@Deprecated
@SuppressWarnings("unchecked")
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
if (channelFactory == null) {
throw new NullPointerException("channelFactory");
}
if (this.channelFactory != null) {
throw new IllegalStateException("channelFactory set already");
}
this.channelFactory = channelFactory;
return (B) this;
}
//2
@SuppressWarnings({ "unchecked", "deprecation" })
public B channelFactory(io.netty.channel.ChannelFactory<? extends C> channelFactory) {
return channelFactory((ChannelFactory<C>) channelFactory);
}
ReflectiveChannelFactory这个类定义如下
java
public class ReflectiveChannelFactory<T extends Channel> implements ChannelFactory<T> {
private final Class<? extends T> clazz;
public ReflectiveChannelFactory(Class<? extends T> clazz) {
if (clazz == null) {
throw new NullPointerException("clazz");
}
this.clazz = clazz;
}
@Override
public T newChannel() {
try {
return clazz.newInstance();
} catch (Throwable t) {
throw new ChannelException("Unable to create Channel from class " + clazz, t);
}
}
@Override
public String toString() {
return StringUtil.simpleClassName(clazz) + ".class";
}
}
到这里就可看到,channel()
方法的作用是给channelFactory
属性赋值,在后续的流程中,会通过真正的工厂类ReflectiveChannelFactory
的newChannel()
方法来实例化NioServerSocketChannel
的一个实例。
handler()方法
java
handler(new ChannelInitializer<NioServerSocketChannel>() {
@Override
protected void initChannel(NioServerSocketChannel ch) {
ch.pipeline().addLast(new LoggingHandler(LogLevel.INFO));
}
})
这个方法的入参是抽象类ChannelInitializer
的一个实现并重写了initChannel()
方法,可以看到,我们在这个方法中获取NioServerSocketChannel
这个channel的pipline对象并给这个对象添加我们的自定义handler(),initChannel()
方法会再后续初始化和注册方法中真正执行。这个抽象类是ChannelInboundHandlerAdapter
的一个子类,这说明这个handler可以处理Inbound事件。
java
public B handler(ChannelHandler handler) {
if (handler == null) {
throw new NullPointerException("handler");
}
this.handler = handler;
return (B) this;
}
同样的,这个new出来的handler会被赋值给AbstractBootstrap
的handler属性。
childHandler()方法
java
// 客户端连接 SocketChannel 的初始化逻辑
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new SimpleServerHandler());
}
})
这个方法与handler()
方法类似,区别在于这个方法是处理客户端连接的逻辑,会给客户端的channel添加对象的handler
剩下的几个attr()和option()方法笔者就不再介绍了,各位读者可以自行翻阅源码。
bind()方法
这个方法是ServerBootstrap
这个类的核心方法,因为涉及到的类比较复杂,迭代较深,因此笔者将使用流程图的方式将bind()方法执行逻辑展示出来。
整个bind()
方法的流程过于长,如果将整个流程放到同一个时序图中,不方便观看。因此我将其拆分为数个部分。
1、实例化ServerSocketChannel-1

1、实例化ServerSocketChannel-2

2、初始化ServerSocketChannel-1

2、初始化ServerSocketChannel-2

到这里整个启动流程就结束了,我们对上述流程进行总结,可以看到netty的启动流程大致分为以下几个步骤。
- 创建JDK底层Channel,创建对应Config对象,设置该Channel为非阻塞模式。
- 创建Server对应的Channel,创建各大组件,包括ChannelConfig、ChannelId、ChannelPipeline、ChannelHandler、Unsafe等。
- 初始化Server对应的Channel,设置Option、Attr,以及设置子Channel的Option、Attr,给Server的Channel添加连接接入器,用于接收新连接,并触发addHandler、register等事件。
- 调用JDK底层注册Selector,将Netty领域的Channel当作attachment注册到Selector。
- 调用JDk底层做端口绑定,并触发active事件。当active事件被触发时,才真正做服务端口绑定。
总结
本节内容讲解了 Netty 服务端的启动流程,笔者带着大家一步步拆解了其中的各个环节。相信读到这里,大家对 Netty 的启动机制已经有了较为清晰的认识。
回顾开篇的启动示例代码,其中 EventLoopGroup bossGroup = new NioEventLoopGroup(1);
这段用于创建线程组 EventLoopGroup 的逻辑,我们尚未深入探讨。这个线程组是 Netty 高性能的重要支撑之一,我们将把它的原理与实现放到下一节进行详细解析。