Netty组件简单介绍
1、NioEventLoop组件
(1)NioEventLoop与Thread对应关系
Thread包括两部分线程:
- 服务端接受客户端连接线程
- 处理每个连接读写线程
服务端接收客户端连接的线程:
客户端处理读和写的线程:
Netty中NioEventLoop能做上面两件事情
(2)引入Netty包版本
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
(3)NioEventLoop源码逻辑类比Socket中Server、Client逻辑
NioEventLoop的run()方法->while循环->select方法对应Server中accept()方法
NioEventLoop的run()方法->while循环->select方法对应Client中输出流操作
Server中while循环:Socket client = serverSocket.accept();
、
Client中while循环:socket.getOutputStream()
2、Channel组件
(1)Channel与Socket对应关系
Channel组件底层封装了Socket,一个Channel对应一个Socket
(2)源码分析前需要下载源码
(3)Channel源码分析
Channel源码详解:
NioEventLoop类中有这样一个方法:processSelectedKey(SelectionKey k, AbstractNioChannel ch)
private void processSelectedKey(SelectionKey k, AbstractNioChannel ch) {
final AbstractNioChannel.NioUnsafe unsafe = ch.unsafe();
if (!k.isValid()) {
final EventLoop eventLoop;
try {
eventLoop = ch.eventLoop();
} catch (Throwable ignored) {
// If the channel implementation throws an exception because there is no event loop, we ignore this
// because we are only trying to determine if ch is registered to this event loop and thus has authority
// to close ch.
return;
}
// Only close ch if ch is still registerd to this EventLoop. ch could have deregistered from the event loop
// and thus the SelectionKey could be cancelled as part of the deregistration process, but the channel is
// still healthy and should not be closed.
// See https://github.com/netty/netty/issues/5125
if (eventLoop != this || eventLoop == null) {
return;
}
// close the channel if the key is not valid anymore
unsafe.close(unsafe.voidPromise());
return;
}
try {
int readyOps = k.readyOps();
// We first need to call finishConnect() before try to trigger a read(...) or write(...) as otherwise
// the NIO JDK channel implementation may throw a NotYetConnectedException.
if ((readyOps & SelectionKey.OP_CONNECT) != 0) {
// remove OP_CONNECT as otherwise Selector.select(..) will always return without blocking
// See https://github.com/netty/netty/issues/924
int ops = k.interestOps();
ops &= ~SelectionKey.OP_CONNECT;
k.interestOps(ops);
unsafe.finishConnect();
}
// Process OP_WRITE first as we may be able to write some queued buffers and so free memory.
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
// Call forceFlush which will also take care of clear the OP_WRITE once there is nothing left to write
ch.unsafe().forceFlush();
}
// Also check for readOps of 0 to workaround possible JDK bug which may otherwise lead
// to a spin loop
if ((readyOps & (SelectionKey.OP_READ | SelectionKey.OP_ACCEPT)) != 0 || readyOps == 0) {
unsafe.read();
if (!ch.isOpen()) {
// Connection already closed - no need to handle write.
return;
}
}
} catch (CancelledKeyException ignored) {
unsafe.close(unsafe.voidPromise());
}
}
SelectionKey.OP_ACCEPT表示有一个Accept事件进来,然后执行unsafe.read()方法
NioByteUnsafe表示对应一个client数据流读写
NioMessageUnsafe表示对应是否有新连接进来的情况
我们这里是处理一个新连接进来的情况,进入到NioMessageUnsafe中:
public void read() {
assert eventLoop().inEventLoop();
final ChannelConfig config = config();
final ChannelPipeline pipeline = pipeline();
final RecvByteBufAllocator.Handle allocHandle = unsafe().recvBufAllocHandle();
allocHandle.reset(config);
boolean closed = false;
Throwable exception = null;
try {
try {
do {
int localRead = doReadMessages(readBuf);
if (localRead == 0) {
break;
}
if (localRead < 0) {
closed = true;
break;
}
allocHandle.incMessagesRead(localRead);
} while (allocHandle.continueReading());
} catch (Throwable t) {
exception = t;
}
int size = readBuf.size();
for (int i = 0; i < size; i ++) {
readPending = false;
pipeline.fireChannelRead(readBuf.get(i));
}
readBuf.clear();
allocHandle.readComplete();
pipeline.fireChannelReadComplete();
if (exception != null) {
closed = closeOnReadError(exception);
pipeline.fireExceptionCaught(exception);
}
if (closed) {
inputShutdown = true;
if (isOpen()) {
close(voidPromise());
}
}
} finally {
// Check if there is a readPending which was not processed yet.
// This could be for two reasons:
// * The user called Channel.read() or ChannelHandlerContext.read() in channelRead(...) method
// * The user called Channel.read() or ChannelHandlerContext.read() in channelReadComplete(...) method
//
// See https://github.com/netty/netty/issues/2254
if (!readPending && !config.isAutoRead()) {
removeReadOp();
}
}
}
NioServerSocketChannel是用来处理新连接的,然后进入到doReadMessage方法,在这里你可以看到java底层相关逻辑,javaChannel()其实返回ServerSocketChannel对象,这里的ServerSocketChannel其实就是NIO模型的SocketChannel,也对应IO模型中的ServerSocket
这里把SocketChannel封装成Netty中自定义的NioSocketChannel
doReadMessages方法:
protected int doReadMessages(List<Object> buf) throws Exception {
SocketChannel ch = javaChannel().accept();
try {
if (ch != null) {
buf.add(new NioSocketChannel(this, ch));
return 1;
}
} catch (Throwable t) {
logger.warn("Failed to create a new channel from an accepted socket.", t);
try {
ch.close();
} catch (Throwable t2) {
logger.warn("Failed to close a socket.", t2);
}
}
return 0;
}
结论:最后只要基于NioSocketChannel进行读写就行了。
3、ByteBuf组件
(1)ByteBuf与IO Bytes对应关系
(2)ByteBuf类讲解
ByteBuf主要用于流的读写。
read方法(对应输入流)
write方法(对应输出流)
4、Pipleline组件
(1)Pipeline与Logic Chain对应关系
(2)NioSocketChannel与逻辑链
逻辑链:每一层父类构造方法就是一层逻辑链。
NioSocketChannel构造方法如下(倒数第一逻辑链)
进入上一级super构造方法(倒数第二逻辑链)
进入上一级super构造方法(倒数第三逻辑链)
进入上一级super构造方法(倒数第四逻辑链)
(3)Pipeline创建过程
进入newChannelPipeline()方法下一级
接着进入DefaultChannelPipeline(this)方法下一级
结论:每个Channel都会有一个Pipeline,Channel每个数据流的读写都会经过pipeline逻辑链。
5、ChannelHandler组件
(1)ChannelHandler与Logic关系
一个ChannelHandler对应一个Logic逻辑块。
(2)DefaultChannelPipeline类讲解
ChannelPipeline增加逻辑和删除逻辑方法
(3)添加ChannelHandler方法
在Pipeline中添加ChannelHandler逻辑
ChannelHandler类中回调方法:
public interface ChannelHandler {
/**
* Gets called after the {@link ChannelHandler} was added to the actual context and it's ready to handle events.
*/
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
/**
* Gets called after the {@link ChannelHandler} was removed from the actual context and it doesn't handle events
* anymore.
*/
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
/**
* Gets called if a {@link Throwable} was thrown.
*
* @deprecated is part of {@link ChannelInboundHandler}
*/
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
/**
* Indicates that the same instance of the annotated {@link ChannelHandler}
* can be added to one or more {@link ChannelPipeline}s multiple times
* without a race condition.
* <p>
* If this annotation is not specified, you have to create a new handler
* instance every time you add it to a pipeline because it has unshared
* state such as member variables.
* <p>
* This annotation is provided for documentation purpose, just like
* <a href="http://www.javaconcurrencyinpractice.com/annotations/doc/">the JCIP annotations</a>.
*/
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {
// no value
}
}
6、Netty组件总结
(1)NioEventLoop是最重要的组件,它起着两种作用,它会起两个线程,一个监听新连接线程,一个负责读写的线程;
(2)Channel封装了数据流读写API,通过Channel API可以完成数据流的读写操作;
(3)Pipeline就是在封装在Channel中的逻辑链;
(4)ChannelHandler就是可以添加进Pipeline中的单个逻辑块;
(5)ByteBuf用于读写,Channel数据流读写就是基于ByteBuf。