代码参考:https://github.com/wuyinxian124/nettybook2.git
netty底层调用了nio相关功能,但是由于netty代码量较大,将nio层层封装,导致没接触过nio开发的程序员无法快速理解netty底层究竟怎样调用nio,又对nio实现了什么封装。本篇文章尽可能(水平有限,只能做到"尽可能")在不依赖netty库的前提下模拟netty对nio的调用,尽量把每一行代码在netty中的相应位置阐释出来。
未完待续,部分注解后期补充。
接收服务端消息并打印
package io.netty.channel.nio;
import io.netty.util.internal.PlatformDependent;
import io.netty.util.internal.SocketUtils;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.AbstractSelector;
import java.nio.channels.spi.SelectorProvider;
import java.security.AccessController;
import java.security.PrivilegedAction;
class DemoApplicationTests3 {
public static void main(String[] args) throws Throwable {
/**
* 关键对象:io.netty.channel.nio.NioEventLoop#selectedKeys
* 用户层:
* netty层:netty中将NioEventLoop#selectedKeys以反射的方式设置到selector中,这样selector中的key就可以通过NioEventLoop#selectedKeys直接看到
*
*/
SelectedSelectionKeySet selectedKeys = new SelectedSelectionKeySet();
/**
* 关键代码:io.netty.channel.nio.NioEventLoopGroup#NioEventLoopGroup(int, java.util.concurrent.Executor)
* 用户层:初始化loopGroup
* netty层:单例模式。
*/
SelectorProvider provider = SelectorProvider.provider();
/**
* 关键代码:io.netty.channel.nio.NioEventLoop#openSelector()
* 关键对象:io.netty.channel.nio.NioEventLoop#selector#delegate
* 用户层:初始化loopGroup
* netty层:初始化loopGroup的过程中会创建loop,创建loop的过程中会创建selector,并将其存储在NioEventLoop#selector#delegate。然后通过反射的方式修改selector中的selectedKeys
*/
AbstractSelector unwrappedSelector = provider.openSelector();
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
});
Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
PlatformDependent.putObject(
unwrappedSelector, selectedKeysFieldOffset, selectedKeys);
/**
* 关键代码:sun.nio.ch.SelectorProviderImpl#openServerSocketChannel()
* 关键对象:io.netty.channel.nio.AbstractNioChannel#ch
* 用户层:服务端绑定端口
* netty层:绑定端口时首先会创建channel(io.netty.bootstrap.AbstractBootstrap#initAndRegister()),
* netty创建NioServerSocketChannel时会同时创建ServerSocketChannel
* (io.netty.channel.socket.nio.NioServerSocketChannel#NioServerSocketChannel(java.nio.channels.spi.SelectorProvider, io.netty.channel.socket.InternetProtocolFamily)),
* 创建好的ServerSocketChannel会被赋值到AbstractNioChannel#ch中(NioServerSocketChannel是AbstractNioChannel的子类)
*/
ServerSocketChannel serverSocketChannel = provider.openServerSocketChannel();
/**
* 关键代码:io.netty.channel.nio.AbstractNioChannel#AbstractNioChannel(io.netty.channel.Channel, java.nio.channels.SelectableChannel, int)
* 用户层:服务端绑定端口
* netty层:在AbstractNioChannel#AbstractNioChannel对ServerSocketChannel进行初始化设置
*/
serverSocketChannel.configureBlocking(false);
/**
* 关键代码:io.netty.channel.nio.AbstractNioChannel#doRegister()
* 关键对象:io.netty.channel.nio.AbstractNioChannel#selectionKey
* 用户层:服务端绑定端口
* netty层:
* 主线程中调用io.netty.channel.AbstractChannel.AbstractUnsafe#register(io.netty.channel.EventLoop, io.netty.channel.ChannelPromise)
*/
SelectionKey key = serverSocketChannel.register(unwrappedSelector, 0, null);
/**
* 关键代码:io.netty.channel.socket.nio.NioServerSocketChannel#doBind(java.net.SocketAddress)
* io.netty.bootstrap.AbstractBootstrap#doBind(java.net.SocketAddress)
* 用户层:服务端绑定端口
* netty层:两次任务提交
* io.netty.util.concurrent.DefaultPromise#notifyListeners()
* io.netty.bootstrap.AbstractBootstrap#doBind0(io.netty.channel.ChannelFuture, io.netty.channel.Channel, java.net.SocketAddress, io.netty.channel.ChannelPromise)
* 主线程中,绑定端口时会调用AbstractBootstrap#doBind注册listener,listener的目的是为了调用AbstractBootstrap#doBind0
* 添加完listener之后会尝试触发所有listener(根据条件判断立刻执行or抛给loop),触发io.netty.util.concurrent.DefaultPromise#notifyListenersNow(),将一个新的任务添加到loop
* 新的任务会调用AbstractBootstrap#doBind0将操作打包成任务抛给channel所属的loop(io.netty.channel.AbstractChannel#eventLoop)里
*/
serverSocketChannel.bind(SocketUtils.socketAddress("127.0.0.1", 9004), 200);
/**
* 关键代码:io.netty.channel.nio.AbstractNioChannel#doBeginRead()
* 用户层:服务端绑定端口
* netty层:
* 主线程中,调用io.netty.bootstrap.AbstractBootstrap#doBind(java.net.SocketAddress)添加listener,其中doBind0(regFuture, channel, localAddress, promise);是触发下一步逻辑的代码
* loop任务中,调用io.netty.bootstrap.AbstractBootstrap#doBind0(io.netty.channel.ChannelFuture, io.netty.channel.Channel, java.net.SocketAddress, io.netty.channel.ChannelPromise)
* loop任务中,调用io.netty.channel.AbstractChannel.AbstractUnsafe#bind(java.net.SocketAddress, io.netty.channel.ChannelPromise)
* fireChannelActive事件中调用io.netty.channel.nio.AbstractNioChannel#doRegister()
*/
key.interestOps(SelectionKey.OP_ACCEPT);
while (true) {
/**
* 关键代码:io.netty.channel.nio.SelectedSelectionKeySetSelector#select()
* netty层:调用selector前清空key的集合,避免之前处理过的key又重复触发selector
*/
selectedKeys.reset();
/**
* 关键代码:io.netty.channel.nio.SelectedSelectionKeySetSelector#select()
* netty层:loop中调用select(),若无待处理的channel,就阻塞,直到有新事件到达。新事件通过NioEventLoop#selectedKeys获取
*/
int select = unwrappedSelector.select();
System.out.println("asdaaa:" + select);
/**
* 关键代码:io.netty.channel.nio.NioEventLoop#processSelectedKeysOptimized()
* netty层:遍历key集合,处理key
*/
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
selectedKeys.keys[i] = null;
/**
* 关键代码:io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)
* netty层:NioEventLoop#processSelectedKeysOptimized中遍历key之后,会调用NioEventLoop#processSelectedKey处理key
*/
handleInput(k, unwrappedSelector);
}
System.out.println("over");
}
}
private static void handleInput(SelectionKey key, Selector selector) throws IOException {
if (key.isValid()) {
// 处理新接入的请求消息
if (key.isAcceptable()) {
// Accept the new connection
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
// Add the new connection to the selector
sc.register(selector, SelectionKey.OP_READ);
}
if (key.isReadable()) {
// Read the data
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("The time server receive order : "
+ body);
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else {
// 读到0字节,忽略
}
}
}
}
private static void doWrite(SocketChannel channel, String response)
throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
以上代码包路径一定必须是
io.netty.channel.nio
,否则无法使用SelectedSelectionKeySet
。
客户端,负责链接服务端并发送消息
package io.netty.channel.nio;
import io.netty.util.internal.PlatformDependent;
import java.io.IOException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.AbstractSelector;
import java.nio.channels.spi.SelectorProvider;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
class DemoApplicationTests2 {
private static Thread thread = new Thread(() -> {
try {
while (true) {
Thread.sleep(1000);
doWrite(DemoApplicationTests2.socketChannel, "wjwljwjlj");
System.out.println("send msg");
}
} catch (Throwable e) {
throw new RuntimeException(e);
}
});
private static SocketChannel socketChannel;
public static void main(String[] args) throws Throwable {
/**
* 关键对象:io.netty.channel.nio.NioEventLoop#selectedKeys
* 用户层:
* netty层:netty中将NioEventLoop#selectedKeys以反射的方式设置到selector中,这样selector中的key就可以通过NioEventLoop#selectedKeys直接看到
*/
SelectedSelectionKeySet selectedKeys = new SelectedSelectionKeySet();
/**
* 关键代码:io.netty.channel.nio.NioEventLoopGroup#NioEventLoopGroup(int, java.util.concurrent.Executor)
* 用户层:初始化loopGroup
* netty层:单例模式。
*/
SelectorProvider provider = SelectorProvider.provider();
/**
* 关键代码:io.netty.channel.nio.NioEventLoop#openSelector()
* 关键对象:io.netty.channel.nio.NioEventLoop#selector#delegate
* 用户层:初始化loopGroup
* netty层:初始化loopGroup的过程中会创建loop,创建loop的过程中会创建selector,并将其存储在NioEventLoop#selector#delegate。然后通过反射的方式修改selector中的selectedKeys
*/
AbstractSelector unwrappedSelector = provider.openSelector();
Object maybeSelectorImplClass = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
return Class.forName(
"sun.nio.ch.SelectorImpl",
false,
PlatformDependent.getSystemClassLoader());
} catch (Throwable cause) {
return cause;
}
}
});
Class<?> selectorImplClass = (Class<?>) maybeSelectorImplClass;
Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
long selectedKeysFieldOffset = PlatformDependent.objectFieldOffset(selectedKeysField);
PlatformDependent.putObject(
unwrappedSelector, selectedKeysFieldOffset, selectedKeys);
/**
* 关键代码:io.netty.channel.socket.nio.NioSocketChannel#newChannel(java.nio.channels.spi.SelectorProvider, io.netty.channel.socket.InternetProtocolFamily)
* 关键对象:io.netty.channel.nio.AbstractNioChannel#ch
* 用户层:主线程中客户端创建链接
* netty层:初始化NioSocketChannel时会初始化SocketChannelImpl,此时SocketChannelImpl处于unconnected状态。然后将SocketChannelImpl赋值到AbstractNioChannel#ch
*/
SocketChannel socketChannel = provider.openSocketChannel();
/**
* 关键代码:io.netty.channel.nio.AbstractNioChannel#AbstractNioChannel(io.netty.channel.Channel, java.nio.channels.SelectableChannel, int)
* 用户层:主线程中客户端创建链接
* netty层:初始化SocketChannelImpl之后会进行一些简单的设置,比如设置阻塞状态
*/
socketChannel.configureBlocking(false);
/**
* 关键代码:io.netty.channel.nio.AbstractNioChannel#doRegister()
* 关键对象:io.netty.channel.nio.AbstractNioChannel#selectionKey
* 用户层:主线程中客户端创建链接
* netty层:
* 添加任务io.netty.channel.AbstractChannel.AbstractUnsafe#register(io.netty.channel.EventLoop, io.netty.channel.ChannelPromise)
* 主线程中客户端创建链接时会添加任务,之后会在loop线程中将生成的key保存在AbstractNioChannel#selectionKey中
*/
SelectionKey key = socketChannel.register(unwrappedSelector, 0, null);
// 如果直接连接成功,则注册到多路复用器上,发送请求消息,读应答
if (socketChannel.connect(new InetSocketAddress("127.0.0.1", 9004))) {
/**
* 关键代码:io.netty.channel.nio.AbstractNioChannel#doBeginRead()
* 用户层:
* netty层:
* 客户端接收到服务端返回的OP_CONNECT之后,
* 在io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)
* 触发active事件,然后在责任链io.netty.channel.DefaultChannelPipeline.HeadContext#channelActive(io.netty.channel.ChannelHandlerContext)中
* 将OP_READ设置到key中
*
* 由于AbstractNioChannel#doBeginRead是增量修改,所以之前设置的SelectionKey.OP_CONNECT
* 会在io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)
* 清除
*/
key.interestOps(SelectionKey.OP_READ);
} else {
/**
* 关键代码:io.netty.channel.socket.nio.NioSocketChannel#doConnect(java.net.SocketAddress, java.net.SocketAddress)
* 用户层:主线程中客户端创建链接
* netty层:
* 添加任务io.netty.channel.AbstractChannel.AbstractUnsafe#register(io.netty.channel.EventLoop, io.netty.channel.ChannelPromise)
* 添加监听io.netty.bootstrap.Bootstrap#doResolveAndConnect(java.net.SocketAddress, java.net.SocketAddress)
* 触发监听io.netty.util.concurrent.DefaultPromise#setValue0(java.lang.Object)
* 添加任务io.netty.bootstrap.Bootstrap#doConnect(java.net.SocketAddress, java.net.SocketAddress, io.netty.channel.ChannelPromise)
*
*/
key.interestOps(SelectionKey.OP_CONNECT);
}
/**
* 为了便于发送消息的线程可以获取到channel,所以将channel赋值到静态变量中
*/
DemoApplicationTests2.socketChannel = socketChannel;
while (true) {
/**
* 关键代码:io.netty.channel.nio.SelectedSelectionKeySetSelector#select()
* netty层:调用selector前清空key的集合,避免之前处理过的key又重复触发selector
*/
selectedKeys.reset();
/**
* 关键代码:io.netty.channel.nio.SelectedSelectionKeySetSelector#select()
* netty层:loop中调用select(),若无待处理的channel,就阻塞,直到有新事件到达。新事件通过NioEventLoop#selectedKeys获取
*/
int select = unwrappedSelector.select();
System.out.println("asdaaa:" + select);
/**
* 关键代码:io.netty.channel.nio.NioEventLoop#processSelectedKeysOptimized()
* netty层:遍历key集合,处理key
*/
for (int i = 0; i < selectedKeys.size; ++i) {
final SelectionKey k = selectedKeys.keys[i];
selectedKeys.keys[i] = null;
/**
* 关键代码:io.netty.channel.nio.NioEventLoop#processSelectedKey(java.nio.channels.SelectionKey, io.netty.channel.nio.AbstractNioChannel)
* netty层:NioEventLoop#processSelectedKeysOptimized中遍历key之后,会调用NioEventLoop#processSelectedKey处理key
*/
handleInput(k, unwrappedSelector);
}
System.out.println("over");
/**
* 开启线程,开始向服务端发送数据
*/
if (!DemoApplicationTests2.thread.isAlive()) {
System.out.println("thread start");
DemoApplicationTests2.thread.start();
}
}
}
private static void handleInput(SelectionKey key, Selector selector) throws IOException {
if (key.isValid()) {
// 判断是否连接成功
SocketChannel sc = (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
doWrite(sc, "connected");
} else
System.exit(1);// 连接失败,进程退出
}
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("Now is : " + body);
// this.stop = true;
} else if (readBytes < 0) {
// 对端链路关闭
key.cancel();
sc.close();
} else
; // 读到0字节,忽略
}
}
}
private static void doWrite(SocketChannel channel, String response)
throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes = response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
channel.write(writeBuffer);
}
}
}
任务
添加任务时会调用io.netty.channel.nio.SelectedSelectionKeySetSelector#wakeup终止selector的阻塞状态。