首先来看一个简单的Netty服务器和客户端通讯的例子
服务器代码如下:
public class SimpleNettyServer {
public static void main(String[] args) {
new SimpleNettyServer(8878).runServer();
}
private final int serverPort;
ServerBootstrap serverBootstrap = new ServerBootstrap();
public SimpleNettyServer(int serverPort) {
this.serverPort = serverPort;
}
public void runServer() {
//创建反应器线程组
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//1.设置反应器线程组
serverBootstrap.group(bossGroup, workerGroup);
//2.设置NIO类型的通道
serverBootstrap.channel(NioServerSocketChannel.class);
//3.设置通道的参数
serverBootstrap.option(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
serverBootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
//4.装配子通道流水线
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
//流水线管理子通道中的Handler处理器
//向子通道流水线中添加一个Handler处理器
ch.pipeline().addLast(new SimpleNettyServerHandler());
}
});
//5.开始绑定通道
//通过调用sync同步方法阻塞直到绑定成功
ChannelFuture channelFuture = serverBootstrap.bind(serverPort).sync();
channelFuture.addListener((ChannelFutureListener) future -> {
if (channelFuture.isSuccess()) {
System.out.println("监听端口" + serverPort + " 成功");
} else {
System.out.println("监听端口" + serverPort + "失败");
}
});
//对关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
服务端Handler代码如下:
public class SimpleNettyServerHandler extends ChannelInboundHandlerAdapter {
//收到数据读取
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf= (ByteBuf) msg;
System.out.println("收到消息"+buf.toString(CharsetUtil.UTF_8));
}
//数据读取完毕
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
//writeAndFlush 是 write + flush
//将数据写入到缓存,并刷新
ctx.writeAndFlush(Unpooled.copiedBuffer("客户端消息已收到并处理", CharsetUtil.UTF_8));
}
}
客户端代码:
public class SimpleNettyClient {
public static void main(String[] args) {
new SimpleNettyClient("127.0.0.1",8878).startClient();
}
EventLoopGroup group = new NioEventLoopGroup();
private final int serverPort;
private final String serverAddress;
public SimpleNettyClient(String serverAddress, int serverPort ) {
this.serverAddress=serverAddress;
this.serverPort = serverPort;
}
public void startClient() {
try {
//1.创建客户端启动对象
Bootstrap bootstrap = new Bootstrap();
//2.设置相关参数
bootstrap.group(group) //设置线程组
.channel(NioSocketChannel.class) // 设置客户端通道的实现类(反射)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new SimpleNettyClientHandler()); //加入自己的处理器
}
});
//3.启动客户端去连接服务器端
ChannelFuture channelFuture = bootstrap.connect(serverAddress, serverPort).sync();
channelFuture.addListener((ChannelFutureListener)future->{
if (channelFuture.isSuccess()) {
System.out.println("连接服务器成功");
System.out.println("客户端 ok..");
} else {
System.out.println("连接服务器失败");
}
});
//给关闭通道进行监听
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
客户端Handler代码:
public class SimpleNettyClientHandler extends ChannelInboundHandlerAdapter {
//当通道就绪就会触发该方法
@Override
public void channelActive(ChannelHandlerContext ctx) {
String msg="hello, server。This is client。";
System.out.println("发往服务端的消息为"+msg);
ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
}
//当通道有读取事件时,会触发
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));
System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
下面我们结合上面的代码来简单分析下Netty的启动流程:
一、NioEventLoopGroup
在服务端启动时,我们创建了两个NioEventLoopGroup对象,这两个对象可以看做是传统IO编程模型的两大线程组,bossGroup
表示监听端口,accept 新连接的线程组,workerGroup表示处理每一条连接的数据读写的线程组。
先看一下NioEventLoopGroup初始化的过程:
根据debug进入初始化流程可以看到最终的调用的是其父类MultithreadEventExecutorGroup的初始化方法
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
EventExecutorChooserFactory chooserFactory, Object... args)
参数解析:
在父类的构造函数中我们可以看到这些参数是怎么初始化的。
1. nThreads是指定的,如果没有填的话默认是cup处理器核心数*2(静态代码块中初始化)。
2. executor此处为null,在方法体中将实例化。
3. chooserFactory指定为DefaultEventExecutorChooserFactory.INSTANCE,在静态属性中可以看到为 new DefaultEventExecutorChooserFactory(),主要是用于选择下一个可用的EventExecutor即NioEventLoop。
4. args为传递给创建NioEventLoop的newChild方法的调用的参数
* @param args arguments which will passed to each {@link #newChild(Executor, Object...)} call
4.1 SelectorProvider.provider();-->SelectorProvider.open()返回一个Selector。
4.2 DefaultSelectStrategyFactory.INSTANCE 默认选择策略工厂
4.3 RejectedExecutionHandlers.reject();拒绝策略处理器
构造函数解析:
1.初始化executor,创建了一个线程创建器。
if (executor == null) {
executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
}
在此方法中有以下代码:
@Override
public void execute(Runnable command) {
threadFactory.newThread(command).start();
}
当有任务传进来的时候,都将创建一个线程实体去处理这个任务。并将传进来的可执行任务包装为FastThreadLocalThread
protected Thread newThread(Runnable r, String name) {
return new FastThreadLocalThread(threadGroup, r, name);
}
2. 初始化children -->创建一个 EventExecutor[nThreads]数组,用来保存NioEventLoop,接下来是循环初始化NioEventLoop,指定的参数为上方的参数args...。
3.线程选择器,主要是为新连接绑定对应的NioEventLoop,选择NioEventLoop的方法是chooser的next(),就是 Netty 需要一个 NioEventLoop 时, 会调用EventExecutorChooser的next() 方法获取一个可用的 EventLoop。
chooser = chooserFactory.newChooser(children);
根据方法isPowerOfTwo来判断对应的是哪个选择器。
此处executors.length为12,根据 isPowerOfTwo(executors.length)(是否为2的幂),此处返回的为GenericEventExecutorChooser选择器。
public EventExecutorChooser newChooser(EventExecutor[] executors) {
if (isPowerOfTwo(executors.length)) {
return new PowerOfTowEventExecutorChooser(executors);
} else {
return new GenericEventExecutorChooser(executors);
}
}
下面是DefaultEventExecutorChooserFactory内部的两种事件选择器(主要是做循环选择,二进制的与运算明显效率要高)。代码如下:
private static final class PowerOfTowEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
PowerOfTowEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[idx.getAndIncrement() & executors.length - 1];
}
}
private static final class GenericEventExecutorChooser implements EventExecutorChooser {
private final AtomicInteger idx = new AtomicInteger();
private final EventExecutor[] executors;
GenericEventExecutorChooser(EventExecutor[] executors) {
this.executors = executors;
}
@Override
public EventExecutor next() {
return executors[Math.abs(idx.getAndIncrement() % executors.length)];
}
}
完成实例化的BossGroup Debug截图如下:
可以看到内部存储了一个EventExecutor数组,其中的每个NioEventLoop相当于一个子反应器。Netty的NioEventLoopGroup就相当于一个多线程版的反应器。在之前的Reactor反应器模式中,一般使用两个反应器,一个负责新连接的监听和接受,一个负责IO事件处理。对应到Netty服务器程序中则是设置两个NioEventLoopGroup线程组,一个EventLoopGroup负责监听和接受,一个负责IO的事件处理。
二、NIOEventLoop
先看一下Debug看到的其中一个NioEventLoop:
NIOEventLoop类图:
NioEventLoop是在Group创建时通过children[i] = newChild(executor, args);方法创建的。
@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
return new NioEventLoop(this, executor, (SelectorProvider) args[0],
((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
这三个参数在上面已有说明,看一下创建过程:
1. 通过SelectorProvider去创建Selector。SelectorProvider是Java NIO中的抽象类,它的作用是调用Windows或者Linux底层NIO的实现,为JavaNIO提供服务,比如经常用的Selector.open()方法内部就是通过调用SelectorProvider.openSelector()来得到多路复用器selector并包装。一个Selector和一个NioEventLoop绑定。
provider = selectorProvider;
selector = openSelector();
2. 创建tailTasks = newTaskQueue(maxPendingTasks);此处maxPendingTasks(最大待处理任务)值为Integer.MAX_VALUE。
protected static final int DEFAULT_MAX_PENDING_TASKS = Math.max(16,
SystemPropertyUtil.getInt("io.netty.eventLoop.maxPendingTasks", Integer.MAX_VALUE));
返回的是一个PlatformDependent.newMpscQueue(maxPendingTasks);
MpscQueue是针对Netty中NIO任务设计的一种队列,允许有多个生产者(外部线程),只有一个消费者(NioEventLoop)的队列。
3. 创建taskQueue = newTaskQueue(this.maxPendingTasks)
--->PlatformDependent.newMpscQueue(maxPendingTasks)。
4. 保存线程执行器。在MultithreadEventExecutorGroup构造函数中执行new ThreadPerTaskExecutor(newDefaultThreadFactory())并传入了newChild方法中,最终也传入该构造函数。
this.executor = ObjectUtil.checkNotNull(executor, "executor");
并且在SingleThreadEventExecutor类中有一个属性private volatile Thread thread,它用来引用支撑该EventExecutor的线程,用来处理I/O事件和执行任务,叫支撑线程或者I/O线程均可,thread所引用的线程即来自executor。
5.指定rejectedExecutionHandler拒绝任务处理策略。
重要属性:
1. Thread线程类的成员:NioEventLoop 继承于 SingleThreadEventLoop, 而 SingleThreadEventLoop 又继承于 SingleThreadEventExecutor。SingleThreadEventExecutor 是 Netty 中对本地线程的抽象, 它内部有一个 Thread thread 属性, 存储了一个本地 Java 线程。因此我们可以认为, 一个 NioEventLoop 其实和一个特定的线程绑定,并且在其生命周期内, 绑定的线程都不会再改变。
2.NIO选择器:通过前面提供的 provider.openSelector()返回一个Selector
3.TaskQueue任务队列。
三、服务端启动流程
- 服务端的Socket在哪里初始化?
- 在哪里Accept 连接?
1.创建Channel
从入口方法blind(port)进入:
-------->bind(new InetSocketAddress(inetPort));
--------->doBind(localAddress);
-------------->initAndRegister();初始化和注册Channel,下面看一下源码:
channel = channelFactory.newChannel();
init(channel);
channelFactory.newChannel();方法返回的为 clazz.newInstance();
此处的clazz为serverBootstrap.channel(NioServerSocketChannel.class)绑定的NioServerSocketChannel.class类。
看一下构造方法,
public NioServerSocketChannel() {
this(newSocket(DEFAULT_SELECTOR_PROVIDER));
}
1.1 newSocket方法参数为静态属性SelectorProvider.provider()(Java NIO包下的),该方法返回的为一个ServerSocketChannel(NIO 包下)。
1.2 config = new NioServerSocketChannelConfig(this, javaChannel().socket());Tcp参数配置类
1.3 父类AbstractNioChannel构造函数配置非阻塞:ch.configureBlocking(false);
1.4 父类AbstractChannel构造函数:
- 创建Channel的唯一标识:newId();
- newUnsafe():Tcp底层的读写操作类;
- 新建ChannelPipeLine
protected AbstractChannel(Channel parent) {
this.parent = parent;
id = newId();
unsafe = newUnsafe();
pipeline = newChannelPipeline();
}
2.初始化服务端Channel
由init()入口方法进入,主要作用就是保存用户自定义的属性,通过这个属性创建一个连接器。
2.1 channel.config().setOptions(options)和channel.attr(key).set(e.getValue())
此处设置的options和attrs都是在设置服务端通道的参数时候设置的。
2.2 设置currentChildOptions和currentChildAttrs属性值,这两个属性值也是在服务端装配子通道流水线时设置的参数。每次在处理新连接 时都会将该属性配置上去。
synchronized (childOptions) {
currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
}
synchronized (childAttrs) {
currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
}
2.3 配置服务端的Handler
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(Channel ch) throws Exception {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
2.4 添加连接器,服务端的pipeLine都会默认有一个ServerBootstrapAcceptor,主要目的就是为新连接分配NIO的一个线程。
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
3. 注册Selector
由initAndRegister()中的register(channel)进入:
ChannelFuture regFuture = config().group().register(channel);
3.1 先调用了MultithreadEventLoopGroup的register方法,利用chooser.next()方法返回EventLoop
3.2 调用SingleThreadEventLoop的register方法,传入一个DefaultChannelPromise,绑定了EventLoop和channel
3.3 实际调用的为AbstractChannel的register方法:
1) AbstractChannel.this.eventLoop = eventLoop绑定线程
2) register0()实际注册:
1. doRegister()NIO底层的事件,并绑定attachment为自身
selectionKey = javaChannel().register(eventLoop().selector, 0, this);
2.主要做一些事件的回调,服务端pipeline,触发事件的回调。
// Ensure we call handlerAdded(...) before we actually notify the promise. This is needed as the
// user may already fire events through the pipeline in the ChannelFutureListener.
pipeline.invokeHandlerAddedIfNeeded();
3)pipeline.fireChannelRegistered();把channel注册成功的事件传播到用户的代码里去。
4.服务端口的绑定
入口方法为AbstractBootstrap的doBind(final SocketAddress localAddress)方法:
4.1 调用 AbstractBootstrap的doBind0(regFuture, channel, localAddress, promise)方法
最终调用的为NioServerSocketChannel的doBind方法,即Java底层的blind方法,此处的javaChannel就是之前创建的Channel。
调用堆栈
doBind:126, NioServerSocketChannel (io.netty.channel.socket.nio)
bind:554, AbstractChannel$AbstractUnsafe (io.netty.channel)
bind:1258, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeBind:512, AbstractChannelHandlerContext (io.netty.channel)
bind:497, AbstractChannelHandlerContext (io.netty.channel)
bind:980, DefaultChannelPipeline (io.netty.channel)
bind:250, AbstractChannel (io.netty.channel)
源码:
@Override
protected void doBind(SocketAddress localAddress) throws Exception {
if (PlatformDependent.javaVersion() >= 7) {
javaChannel().bind(localAddress, config.getBacklog());
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
4.2端口绑定成功之后,会调用pipeline.fireChannelActive()方法。
if (!wasActive && isActive()) {
invokeLater(new Runnable() {
@Override
public void run() {
pipeline.fireChannelActive();
}
});
}
调用堆栈为:
doBeginRead:412, AbstractNioChannel (io.netty.channel.nio)
doBeginRead:55, AbstractNioMessageChannel (io.netty.channel.nio)
beginRead:769, AbstractChannel$AbstractUnsafe (io.netty.channel)
read:1286, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeRead:704, AbstractChannelHandlerContext (io.netty.channel)
read:684, AbstractChannelHandlerContext (io.netty.channel)
read:1011, DefaultChannelPipeline (io.netty.channel)
read:280, AbstractChannel (io.netty.channel)
readIfIsAutoRead:1346, DefaultChannelPipeline$HeadContext (io.netty.channel)
channelActive:1324, DefaultChannelPipeline$HeadContext (io.netty.channel)
invokeChannelActive:224, AbstractChannelHandlerContext (io.netty.channel)
invokeChannelActive:210, AbstractChannelHandlerContext (io.netty.channel)
fireChannelActive:902, DefaultChannelPipeline (io.netty.channel)
run:565, AbstractChannel$AbstractUnsafe$2 (io.netty.channel)
可以看到最终调用的方法为AbstractNioChannel的doBeginRead方法:
@Override
protected void doBeginRead() throws Exception {
// Channel.read() or ChannelHandlerContext.read() was called
final SelectionKey selectionKey = this.selectionKey;
if (!selectionKey.isValid()) {
return;
}
readPending = true;
final int interestOps = selectionKey.interestOps();
if ((interestOps & readInterestOp) == 0) {
selectionKey.interestOps(interestOps | readInterestOp);
}
}
可以看到为通道注册了readInterestOp事件。在NIOServerSocketChannel初始化时可以看到readInterestOp就是SelectionKey.OP_ACCEPT事件。
public NioServerSocketChannel(ServerSocketChannel channel) {
super(null, channel, SelectionKey.OP_ACCEPT);
config = new NioServerSocketChannelConfig(this, javaChannel().socket());
}
protected AbstractNioMessageChannel(Channel parent, SelectableChannel ch, int readInterestOp) {
super(parent, ch, readInterestOp);
}
至此,服务端程序启动完成。