netty源码分析9-Unsafe

本文分享以下内容

  1. Unsafe概况
  2. AbstractUnsafe 分析
  3. AbstractNioUnsafe分析
  4. NioByteUnsafe分析
  5. NioMessageUnsafe分析

Unsafe概况

 

unsafe是Channel的内部逻辑实现,实际的IO操作都是由unsafe完成的。

unsafe调用原生的NIO API处理IO操作,使用Pipeline传递触发的channel事件,使用EventLoop进行无锁化处理。

unsafe调用原生Nio执行bind(),register(),触发各种事件 ,更新网络标识位。

类继承图如下

 

NioByteUnsafe用于客户端,NioMessageUnsafe用于服务端

AbstractUnsafe 分析

举例一个典型方法register

@Override

public final void register(final ChannelPromise promise) {

if (eventLoop.inEventLoop()) {//根据 eventLoop 属性判断 是否 使用 线程池执行

register0(promise);

} else {

try {

eventLoop.execute(new Runnable() {

@Override

public void run() {

register0(promise);

}

});

} catch (Throwable t) {

closeForcibly();// 调用原生 NIO 关闭

closeFuture.setClosed();//设置关闭

promise.setFailure(t);// save Exception

}

}

}

 

private void register0(ChannelPromise promise) {

try {

if (!ensureOpen(promise)) {

return;

}

doRegister();//由子类实现

registered = true;

promise.setSuccess();

pipeline.fireChannelRegistered();//使用pipeline 传递 注册事件

if (isActive()) {

pipeline.fireChannelActive();

}

} catch (Throwable t) {

//...

}

}

}

 

处理逻辑: 先看是否在eventLoop的线程池中执行 ,执行完IO处理,pipeline触发Registered事件.

而 bind,disconnect,close,write 不需要线程控制和触发事件参与。

具体的IO 处理都由子类实现。

 

public final void bind(final SocketAddress localAddress, final ChannelPromise promise) {

if (!ensureOpen(promise)) {

return;

}

 

boolean wasActive = isActive();

try {

doBind(localAddress);//调用原生NIO API 执行bind

} catch (Throwable t) {

promise.setFailure(t);

closeIfClosed();

return;

}

if (!wasActive && isActive()) {

invokeLater(new Runnable() { //提交任务执行bind()

@Override

public void run() {//

pipeline.fireChannelActive();

}

});

}

promise.setSuccess();

}

bind 实现比较简单 没有EventLoop介入。

doBind有2个实现

NioServerSocketChannel-doBind

 

protected void doBind(SocketAddress localAddress) throws Exception {

javaChannel().socket().bind(localAddress, config.getBacklog());

}

NioSocketChannel-doBind

protected void doBind(SocketAddress localAddress) throws Exception {

javaChannel().socket().bind(localAddress);

}

NioSocketChannel是客户端,这个bind没有啥用。

protected void doDeregister() throws Exception {

//取消当前eventLoop中SelectionKey的所有注册的channel。

eventLoop().cancel(selectionKey());

}

public void beginRead() {

if (!isActive()) {

return;

}

 

try {

doBeginRead();

} catch (final Exception e) {

//...

}

}

doBeginRead由AbstractNioChannel实现 用于修改网络标识位,NioServerSocketChannel 修改成SelectionKey.OP_ACCEPT,NioSocketChannel 修改成SelectionKey.OP_READ

write和flush方法在写数据有详细分析。

小结:AbstractUnsafe 定义了原生NIO,EventLoop,Pipeline协作处理IO 的框架的多个方法,是Netty IO的操作的模板类。

AbstractNioUnsafe分析

public interface NioUnsafe extends Unsafe {

/** underlying :底层的,可理解为原生NIO的SelectableChannel

* Return underlying {@link SelectableChannel} 返回底层的SelectableChannel

*/

SelectableChannel ch();//获取原生channel

/**

* Finish connect 完成连接

*/

void finishConnect();

/**

* Read from underlying {@link SelectableChannel} //从原生NIO的SelectableChannel读取数据

*/

void read();

void forceFlush();//调用flush0

}

Unsafe 中没定义 read, NioUnsafe 定义了 read 不知道为啥这样搞

AbstractNioUnsafe

实现了 返回原生SelectableChannel的方法

实现了connect的框架

 

先分析一下 doConnect方法

@Override

protected boolean doConnect(SocketAddress remoteAddress, SocketAddress localAddress) throws Exception {

if (localAddress != null) {

javaChannel().socket().bind(localAddress);

}

 

boolean success = false;

try {

boolean connected = javaChannel().connect(remoteAddress);

if (!connected) {

selectionKey().interestOps(SelectionKey.OP_CONNECT);//line A

}

success = true;

return connected;

} finally {

if (!success) {

doClose();

}

}

}

当SocketChannel设置非阻塞时 connect()不会等待服务端应答,会立即返回false, 当服务应答或连接被拒绝(服务端挂了,或网络不通)会触发 OP_CONNECT事件。netty 将SocketChannel默认设置成非阻塞。

OP_CONNECT事件触发执行finishConnect() ;

服务端接受连接后,客户端需要执行finishConnect()。

@Override

public void finishConnect() {

// Note this method is invoked by the event loop only if the connection attempt was

// neither cancelled nor timed out.

 

assert eventLoop().inEventLoop();

assert connectPromise != null;

 

try {

boolean wasActive = isActive();

doFinishConnect();

fulfillConnectPromise(connectPromise, wasActive);

} catch (Throwable t) {

if (t instanceof ConnectException) {

Throwable newT = new ConnectException(t.getMessage() + ": " + requestedRemoteAddress);

newT.setStackTrace(t.getStackTrace());

t = newT;

}

 

// Use tryFailure() instead of setFailure() to avoid the race against cancel().

connectPromise.tryFailure(t);

closeIfClosed();

} finally {

// Check for null as the connectTimeoutFuture is only created if a connectTimeoutMillis > 0 is used

// See https://github.com/netty/netty/issues/1770

if (connectTimeoutFuture != null) {

connectTimeoutFuture.cancel(false);

}

connectPromise = null;

}

}

处理逻辑:验证连接是否成功,如果失败 记录异常信息,尝试执行channel关闭。无论成功失败都执行取消定时任务,将connectPromise赋值为null。

 

connect是AbstractNioUnsafe中最重要的方法,用于客户端连接服务端。

@Override

public void connect(

final SocketAddress remoteAddress, final SocketAddress localAddress, final ChannelPromise promise) {

if (!ensureOpen(promise)) {// 校验SocketChannel isOpen

return;

}

 

try {

if (connectPromise != null) {//校验 connection是否已经执行

throw new IllegalStateException("connection attempt already made");

}

 

boolean wasActive = isActive();//记录活动状态

if (doConnect(remoteAddress, localAddress)) {//NioSocketChannel实现,使用原生NIO连接服务端

fulfillConnectPromise(promise, wasActive);//填充执行结果

} else {//还未成功连接 // line 1

connectPromise = promise;

requestedRemoteAddress = remoteAddress;

 

// Schedule connect timeout.

int connectTimeoutMillis = config().getConnectTimeoutMillis();//默认值30000

if (connectTimeoutMillis > 0) {//添加一个延迟任务,connectTimeoutMillis 毫秒后只执行1次

connectTimeoutFuture = eventLoop().schedule(new Runnable() {{// line 2

@Override//这个任务处理连接超时,记录异常, 执行关闭 将channel从selector上卸载。

public void run() {

ChannelPromise connectPromise = AbstractNioChannel.this.connectPromise;

ConnectTimeoutException cause =

new ConnectTimeoutException("connection timed out: " + remoteAddress);

if (connectPromise != null && connectPromise.tryFailure(cause))

close(voidPromise()); //line 3

}

}

}, connectTimeoutMillis, TimeUnit.MILLISECONDS);

}

promise.addListener(new ChannelFutureListener() {// line 4

@Override

public void operationComplete(ChannelFuture future) throws Exception {

if (future.isCancelled()) {// line5

if (connectTimeoutFuture != null) {

connectTimeoutFuture.cancel(false);

}

connectPromise = null;

close(voidPromise());

}

}

});

}

} catch (Throwable t) {

if (t instanceof ConnectException) {

Throwable newT = new ConnectException(t.getMessage() + ": " + remoteAddress);

newT.setStackTrace(t.getStackTrace());

t = newT;

}

promise.tryFailure(t);

closeIfClosed();

}

}

line 2 添加一个延迟任务处理连接超时,简称 "任务A" ,它用于处理连接超时,记录异常, 执行关闭 将channel从selector上卸载。

line 3 执行关闭 将channel从selector上卸载。

结合 doConnect() 的分析,通常会进入line1。如果超时时间大于0,添加任务A。注意当指定完doConnect() 后,当服务应答或连接被拒绝会触发事件调用finishConnect(),只要执行了finishConnect()后续都不会触发定时任务。推测当服务器长时间没有应答才有可能触发任务A。

line4 添加一个listener 执行connectPromise.tryFailure(cause) 后被触发。

line5 代码块内 取消定时任务,记录异常,执行关闭 将channel从selector上卸载。

future 类型是 DefaultChannelPromise future.isCancelled()==true 什么时候?

 

DefaultPromise

private static final CauseHolder CANCELLATION_CAUSE_HOLDER = new CauseHolder(new CancellationException());

public boolean cancel(boolean mayInterruptIfRunning) {

Object result = this.result;

if (isDone0(result) || result == UNCANCELLABLE) {

return false;// 操作执行执行完成 (result==SUCCESS) 或 不能取消

}

 

synchronized (this) {

// Allow only once.

result = this.result;

if (isDone0(result) || result == UNCANCELLABLE) {

return false;再次检查

}

 

this.result = CANCELLATION_CAUSE_HOLDER;//设置 被取消

if (hasWaiters()) {

notifyAll();//解除阻塞

}

}

 

notifyListeners();//触发监听器

return true;

}

DefaultPromise 继承了Future,实现了其 cancel(boolean b)方法,也就是DefaultPromise和它的子类调用了cancel(boolean b)方法,future.isCancelled()==true 才能成立

综上所述:当 promise执行取消后,line5 代码块执行了收尾工作,也就是说 promise.addListener() 是为了执行收尾工作。

 

填充连接用到的ChannelPromise

private void fulfillConnectPromise(ChannelPromise promise, boolean wasActive) {

// trySuccess() will return false if a user cancelled the connection attempt.

//trySuccess() 将返回false 如果用户试图取消连接。

boolean promiseSet = promise.trySuccess();//给promise填充 执行成功

 

// Regardless if the connection attempt was cancelled, channelActive() event should be triggered,不管连接是否被取消,channelActive事件读应该被触发

// because what happened is what happened. 因为 已经发生的就是已经发生的

if (!wasActive && isActive()) {//成功的 connect ,触发 ChannelActive事件。

pipeline().fireChannelActive();

}

 

// If a user cancelled the connection attempt, close the channel, which is followed by channelInactive().

if (!promiseSet) {//用户取消连接,执行关闭SocketChannel处理。

close(voidPromise());

}

}

// 真正写数据的地方将缓存好的数据

@Override

protected void flush0() {

// Flush immediately only when there's no pending flush.

// If there's a pending flush operation, event loop will call forceFlush() later,

// and thus there's no need to call it now.

if (isFlushPending()) {

return;

}

super.flush0();

}

 

 

@Override

public void forceFlush() {

// directly call super.flush0() to force a flush now

super.flush0();

}

forceFlush 依赖 super.flush0(),在写事件触发时调用。

flush系列的详细分析请看 《写数据》篇

//检查是否是写事件触发的flush

private boolean isFlushPending() {

SelectionKey selectionKey = selectionKey();

return selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0;

}

小结:AbstractNioUnsafe实现了 客户端连接系列方法,重写flush,如果是事件触发则不执行。

NioByteUnsafe分析

重要的方法 只有一个 read()

读数据分析。

@Override

public void read() {

final ChannelConfig config = config();

final ChannelPipeline pipeline = pipeline();

final ByteBufAllocator allocator = config.getAllocator();//默认UnpooledByteBufAllocator

final int maxMessagesPerRead = config.getMaxMessagesPerRead();//默认 16

RecvByteBufAllocator.Handle allocHandle = this.allocHandle;

if (allocHandle == null) {

this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();

}

if (!config.isAutoRead()) {

removeReadOp();

}

 

ByteBuf byteBuf = null;

int messages = 0;

boolean close = false;

try {

//allocHandle type is AdaptiveRecvByteBufAllocator$HandleImpl 用于容量预估

int byteBufCapacity = allocHandle.guess();////容量预估

int totalReadAmount = 0;//统计读取的总字节数,用于容量预估的根据。

do {

byteBuf = allocator.ioBuffer(byteBufCapacity);//创建ByteBuf type is UnpooledUnsafeDirectByteBuf

int writable = byteBuf.writableBytes();

//会出现一次只能读取部分数据的情况么??问题A

int localReadAmount = doReadBytes(byteBuf);

if (localReadAmount <= 0) {//没读到数据或channel关闭

// not was read release the buffer

byteBuf.release();

close = localReadAmount < 0;//channel 关闭 localReadAmount ==-1

break;

}

 

pipeline.fireChannelRead(byteBuf);//触发channelRead

byteBuf = null;

 

if (totalReadAmount >= Integer.MAX_VALUE - localReadAmount) {

// Avoid overflow. 避免 totalReadAmount 数值越界

totalReadAmount = Integer.MAX_VALUE;

break;

}

 

totalReadAmount += localReadAmount;

if (localReadAmount < writable) {//读取的总数比buf可写数小,说明可读的数据已经读完,跳出循环即可。

// Read less than what the buffer can hold,

// which might mean we drained the recv buffer completely.

break;

}

} while (++ messages < maxMessagesPerRead);//循环超过maxMessagesPerRead次数停止。

 

pipeline.fireChannelReadComplete();//触发 ChannelReadComplete事件

allocHandle.record(totalReadAmount);//调整容量。

 

if (close) {// 需要关闭 ,执行关闭流程

closeOnRead(pipeline);

close = false;

}

} catch (Throwable t) {//处理异常

handleReadException(pipeline, byteBuf, t, close);

}

}

}

问题A有能读取到部分数据, 因为只要select() 一次获取到数据 就会全部读取。

 

 

NioServerSocketChannel-doReadBytes

protected int doReadBytes(ByteBuf byteBuf) throws Exception {

return byteBuf.writeBytes(javaChannel(), byteBuf.writableBytes());

}

从SocketChannel中读取数据到byteBuf中去

 

 

容量分配器AdaptiveRecvByteBufAllocator分析

这段代码获取了容量分配器

this.allocHandle = allocHandle = config.getRecvByteBufAllocator().newHandle();

跟踪代码

public RecvByteBufAllocator getRecvByteBufAllocator() {

return rcvBufAllocator;

}

DefaultChannelConfig

private volatile RecvByteBufAllocator rcvBufAllocator = DEFAULT_RCVBUF_ALLOCATOR;

private static final RecvByteBufAllocator DEFAULT_RCVBUF_ALLOCATOR = AdaptiveRecvByteBufAllocator.DEFAULT;

AdaptiveRecvByteBufAllocator

public static final AdaptiveRecvByteBufAllocator DEFAULT = new AdaptiveRecvByteBufAllocator();

private AdaptiveRecvByteBufAllocator() {

this(DEFAULT_MINIMUM, DEFAULT_INITIAL, DEFAULT_MAXIMUM);//this(64,1024,65536)

}

 

static {

List<Integer> sizeTable = new ArrayList<Integer>();

for (int i = 16; i < 512; i += 16) {

sizeTable.add(i);

}

 

for (int i = 512; i > 0; i <<= 1) {

sizeTable.add(i);

}

 

SIZE_TABLE = new int[sizeTable.size()];

for (int i = 0; i < SIZE_TABLE.length; i ++) {

SIZE_TABLE[i] = sizeTable.get(i);

}

}

SIZE_TABLE数据:[16,32,48,64,80,96,112,128,144,160,176,192,208,224,240,256,272,288,304,320,336,352,368,384,400,416,432,448,464,480,496,512,1024,2048,4096,8192,16384,32768,65536,131072,262144,524288,1048576,2097152,4194304,8388608,16777216,33554432,67108864,134217728,268435456,536870912,1073741824]

SIZE_TABLE 是一个容量预估的数组,每个元素都是一个字节数, 512 之前 从16开始,每次递增16,512后每次 2倍。

public AdaptiveRecvByteBufAllocator(int minimum, int initial, int maximum) {

//...

int minIndex = getSizeTableIndex(minimum);//二分查找获取minimum的位置

if (SIZE_TABLE[minIndex] < minimum) {//调整预估值,使预估值一定大于等于minimum

this.minIndex = minIndex + 1;

} else {

this.minIndex = minIndex;

}//minIndex 最后等于3

 

int maxIndex = getSizeTableIndex(maximum);

if (SIZE_TABLE[maxIndex] > maximum) {

this.maxIndex = maxIndex - 1;

} else {

this.maxIndex = maxIndex;//

}//minIndex 最后等于38

 

this.initial = initial;//1024

}

其内部类创建

public Handle newHandle() {

return new HandleImpl(minIndex, maxIndex, initial);// minIndex==3,maxIndex==38,initial==1024

}

HandleImpl(int minIndex, int maxIndex, int initial) {

this.minIndex = minIndex;

this.maxIndex = maxIndex;

 

index = getSizeTableIndex(initial);//二分查找获取initial的index

nextReceiveBufferSize = SIZE_TABLE[index];

}

@Override

public void record(int actualReadBytes) {

//实际读取bytes数小于等于较小的容量数进缩容

if (actualReadBytes <= SIZE_TABLE[Math.max(0, index - INDEX_DECREMENT - 1)]) {

if (decreaseNow) {//是否立即缩容

index = Math.max(index - INDEX_DECREMENT, minIndex);

nextReceiveBufferSize = SIZE_TABLE[index];

decreaseNow = false;

} else {

decreaseNow = true;

}

//实际读取bytes数大于等于当前的容量数进行扩容

} else if (actualReadBytes >= nextReceiveBufferSize) {

 

index = Math.min(index + INDEX_INCREMENT, maxIndex);

nextReceiveBufferSize = SIZE_TABLE[index];

decreaseNow = false;

}

}

}

record实现了数组的容量调整

小结:AdaptiveRecvByteBufAllocator通过容量增长经验定义一个 容量预估的SIZE_TABLE ,实现了 容量预估,和容量调整的功能。

NioMessageUnsafe分析

服务端用到的Unsafe

private final List<Object> readBuf = new ArrayList<Object>();

NioMessageUnsafe中最重要的就是read方法了,这个方法用于初始化NioSocketChannel。

详情参照《NioSocketChanel初始化》篇

下面总结一下NioByteUnsafe和NioMessageUnsafe的区别

它们主要是read方法的区别,下面根据功能,使用频率,重要度区分。

NioByteUnsafe:read方法读取channel数据 常用 重要

NioMessageUnsafe:read方法初始化 NioSocketChannel 常用 重要

write方法也很重要,但不是NioByteUnsafe和NioMessageUnsafe的主要实现,详细见《写数据》篇。

总结:本文分析了Unsafe体系的各个类以及其重要的方法,AbstractUnsafe实现IO操作处理的框架,AbstractNioUnsafe实现了客户端连接系列方法强制刷新系列方法,NioByteUnsafe实现客户端读取数据的read方法,NioMessageUnsafe实现了 初始化 NioSocketChanne的read方法。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章