Netty
1、基础回忆
基于NIO异步通讯框架
阅读源码首先需要了解四大组件
- 1、 Channel
1、通过管道可以设置参数,TCP相关参数,Netty对其参数进行封装,如果没有Netty则要修改Linux的配置文件。
2、Netty中各个组件的串联者
- 2、EventLoop
IO操作,定时任务、普通任务
- 3、Unsafe
直译:线程不安全
IO操作
REDA
Write
- ChannelPipline
流水线,两大比较重要的参数ChannelContext、ChannelHandler
2、Netty源码
首先思考原有的NIO服务端开发,编码如下:
public static void main(String[] args) throws IOException, InterruptedException {
//NIO
//EventLoop对其进行封装 Select成员变量 EventLoop的构造方法中进行初始化
Selector selector = Selector.open();
//创建服务端
//完成ServerSocketChannel创建过程,初始化操作
ServerSocketChannel socketChannel = ServerSocketChannel.open();
socketChannel.configureBlocking(false);
//ServerSocketChannel注册工作 注册操作
SelectionKey selectionKey = socketChannel.register(selector, 0, null);
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
//端口绑定的操场
socketChannel.bind(new InetSocketAddress(8080));
}
原有Netty服务端开发代码为:
/**
* @author :和振斌
* @date : 2022-11-22 14:41
* @Describe:类的描述信息
*/
public class NettyServer {
private static final Logger log = LoggerFactory.getLogger(NettyServer.class);
public static void main(String[] args) {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
final LoggingHandler loggingHandler = new LoggingHandler();
try {
serverBootstrap.group(bossGroup, workerGroup);
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler());
}
});
Channel channel = serverBootstrap.bind(8080).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
log.debug("server error is {}", e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
对比两者的代码,思考如下三个问题
1、Netty怎么对Nio代码进行封装的,为什么两者的编码全不一样?
2、Netty中NioServerSocketChannel的作用:
1、Channel可以设置参数
2、是各个部件的串联者
3、Pipline的管理者
3、NioServerSocketChannel Netty提供的怎么和Nio代码结合在一起的?
附件形式,Netty设置过程中是通过附件形式将NioServerSocketChannel与原有的Nio结合在一起的
2.1、分析一下NioServerSocket的类关系图
2.2、源码阅读
2.2.1、ServerSocketChannel的创建
1、完成上述工作的主要依赖于bind方法
看ServerBootStrap.bind完成工作
1、init SSC创建
2、register SSC在select注册
3、端口绑定
2、AbstractBootstrap.bind()
3、AbstractBootstrap.doBind()
4、AbstractBootstrap.daBind()中的initAndRegister()负责
(1)ServerSocketChannel的创建
(2)ServerSocketChannel的注册
(3)异步操作
5、AbstractBootstrap.initAndRegister()
final ChannelFuture initAndRegister() {
Channel channel = null;
try {
//1、主要负责ServerSocketChannel的创建
channel = channelFactory.newChannel();
//为ServerSocketChannel添加一个handler为ChannelInitializer
init(channel);
} catch (Throwable t) {
if (channel != null) {
channel.unsafe().closeForcibly();
return new DefaultChannelPromise(channel, GlobalEventExecutor.INSTANCE).setFailure(t);
}
return new DefaultChannelPromise(new FailedChannel(), GlobalEventExecutor.INSTANCE).setFailure(t);
}
//2、注册Selector
ChannelFuture regFuture = config().group().register(channel);
if (regFuture.cause() != null) {
if (channel.isRegistered()) {
channel.close();
} else {
channel.unsafe().closeForcibly();
}
}
6、channelFactory.newChannel()创建ServerSocketChannel,利用反射调用无参构造创建NioServerSocketChannel
以下对上面采用反射构建NioServerSokcetChannel的详述
调用newSocket(DEFAULT_SELECTOR_PROVIDER)
其中priverder参数为SelectorProvider.provider()
即为:return SelectorProvider.provider().openServerSocketChannel()
返回值为ServerSocketChannel,NioServerSocketChannel创建ServerSocketChannle
并且设置为非阻塞的
在Netty中获取ServerSocketchannel的方式为
综上所述:ServerSocketChannel创建的方式是采用反射的方式调用NioServerSocketChannel的无参构造,进行创建的,这个创建方式对Nio的ServerSocketChannel进行深入的封装。
2.2.2、ServerSocketChannel初始化
1、AbstractBootstrap.initAndRegister()中的init()方法
void init(Channel channel) {
//1、设置参数
setChannelOptions(channel, options0().entrySet().toArray(EMPTY_OPTION_ARRAY), logger);
setAttributes(channel, attrs0().entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY));
//2、初始化ServerSocketChannel的pipeline
ChannelPipeline p = channel.pipeline();
final EventLoopGroup currentChildGroup = childGroup;
final ChannelHandler currentChildHandler = childHandler;
final Entry<ChannelOption<?>, Object>[] currentChildOptions =
childOptions.entrySet().toArray(EMPTY_OPTION_ARRAY);
final Entry<AttributeKey<?>, Object>[] currentChildAttrs = childAttrs.entrySet().toArray(EMPTY_ATTRIBUTE_ARRAY);
//3、添加handler但没有调用initChannel()这个会回调
p.addLast(new ChannelInitializer<Channel>() {
@Override
public void initChannel(final Channel ch) {
final ChannelPipeline pipeline = ch.pipeline();
ChannelHandler handler = config.handler();
if (handler != null) {
pipeline.addLast(handler);
}
ch.eventLoop().execute(new Runnable() {
@Override
public void run() {
pipeline.addLast(new ServerBootstrapAcceptor(
ch, currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
}
});
}
});
}
2、ServerBootstrap.init(Channel)
1、设置ServerSocketChannel的必要参数
2、为ServerSocket添加一个hanndler为:ChannelInitializer但这只是注册进去,里面的initChannel
并没有进行调用。这个时候ServerSocketChannel中的pipeline中有三个handler,分别为Head、
ChannelInitializer、Tail。
2.2.3、ServerSocketChannel注册
1、AbstractBootstrap.initAndRegister()
2、MultithreadEventLoopGroup.register(Channel channel)其中next()就是EventLoop
3、SingleThreadEventLoop.register(Channel channel)
4、AbstractChannel.register(EventLoop eventLoop, final ChannelPromise promise)
if (eventLoop.inEventLoop()) {//判断当前线程是否为Nio线程,如果当前线程不是Nio线程则走else
register0(promise);
}
5、AbstractChannel.register0(ChannelPromise promise)中的doRegister()进行ServerSocketChannel的注册
6、AbstractNioChannel.doRegister() 进行ServerSocketChanel的注册
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
可以写成serverSocketChannel.register(select,0,this) 这个格式和Nio注册Channel是一样的
这个方法的执行直译为执行Hanndler如果需要,这个执行的代码是上面初始化channel的时候注册了一个
ChannelInitializer但里面的initChannel()方法并没有执行,在这里将会执行,
这个方法的执行会回调ServerSocketChannel初始化过程中添加的那个handler中的ChannelInitializer中的initChannel()为其ServerSocketChannel添加一个处理Accept事件的Handler
safeSetSuccess(promise),这个promise为initAndRegister()的返回值,这行代码执行完成之后,会将该线程的结果返回给Main线程。
返回success之后将进行端口绑定的工作
AbstractBootstrap.doBind0()进行端口绑定
AbstractChannelHandlerContext.bind()进行端口绑定操作
NioServerSocketChannel底层执行端口绑定的操作
protected void doBind(SocketAddress localAddress) throws Exception {
//JDK版本在7以上
if (PlatformDependent.javaVersion() >= 7) {
//javaChannel()为ServerSocketChannel其代码可以表示为
//ServerSocketChannel.bind(localAddress, config.getBacklog()这个表示形式和原始Nio端口绑定是
//一样的
javaChannel().bind(localAddress, config.getBacklog());
//Jdk在7以下
} else {
javaChannel().socket().bind(localAddress, config.getBacklog());
}
}
端口绑定完成之后,开始注册ServerScketChannel监听的事件,这个监听的代码在AbstractChannel.bind()方法调用。
pipeline.fireChannelActive(); 这行代码是重点,这个代码会将pipline中的所以Handler进行一一调用,现在pileline中主要有三个handler,分别是Head、ServerBootstrapAcceptor()、Tail三个,这三个其实是一个双向链表,调用这个方法会执行Pipeline中的所以handler重点ChannelActive方法,向ServerSocketChannel中的SelectKey设置监听的事件的方法主要需要关注一下,Head的ChannelActive方法。
注册Accept事件的方法主要在AbstractChannel.doBeginRead()
原生Nio注册Accept事件的代码如下所示:
SelectionKey selectionKey = serverSocketChannel.register(selector, 0, null);
selectionKey.interestOps(SelectionKey.OP_ACCEPT);
我们可以知道SelectionKey.OP_ACCEPT为16,因此我们可以证明在netty中是通过HeadContext中的Read事件对Accept进行注册的
3、总结
Netty是基于NIO异步通讯的,我们会发现Netty代码和Nio的代码完全不同,其所有封装在bind()
方法中,在这个方法中通过initAndRegister()方法,首先通过反射的方式创建ServetSocketChannel,
采用调用NioServerSocketChannel的无参构造,进行ServerSocketChannel的创建,
其实是对ServerSocketChannel中的Pipline中的handler,初始化过程中会首先加入ServerSocketChannel中的
ChannelInitializer()但是并不会回调里面的initChannel()方法,最后进行Channel的注册,同时会回调
ChannelInitializer()中的initChannel(),向Pipeline中添加一个ServerBootstrapAcceptor()进行Accept事件的
监控,设置promise为Success,这时会进行端口的绑定,端口的绑定其实也是对原始Nio的底层封装,然
后会设置ServerSocketChannel监控的事件,这个触发是在设置好监听端口之后,会调用pipeline中的所有ChannelActive方法,
Accept监听主要设置在HeadContext中的ChannelActive方法中。综上所述,在Netty方
法的Bind方法中,对Netty服务端中的ServerSocketChannel进行创建,pipline的初始化,监听事件的注
册,端口的绑定都在Bind方法中完成。