== 基础篇 走进JavaNIO==
Java1.4之前对I/O的支持并不完善,给开发带来的问题有:
linux中所有的外部设备都是一个文件,socket也是一个文件,有文件描述符(fd)指向它。
UNIX提供5中I/O模型:
epoll优点:
2011年JDK7发布:
CLient/Server模型中,Server负责绑定IP,启动监听端口;Client发起链接请求,经过三次握手建立连接,通过输入输出流进行同步阻塞式通信。
通过Acceptor处理多个client连接请求,处理完成后销毁线程。
该模型的问题就是支持的线程数量有限。
伪异步是为了解决BIO一个链路需要一个线程的问题。
通过一个线程池处理多个客户端的请求接入
缓冲区buf
本质是一个字节数组(ByteBuff),同时提供数据的结构化访问以及维护读写位置。
通道 channel
channel是全双工的。
流是单向的。
多路复用 selector
selector简单来说就是轮训注册在其上的channel,
== 入门篇 Netty NIO开发指南==
TCP协议是”流“协议,流是没有间隔的。tcp会根据缓存大小将业务上的大包划分成多个小包发送出去、也可能多个小包合成一个大包发送出去。
支持任意字符为分隔符
支持设置单条消息最大长度,如果找了最大长度还没找到分隔符就抛出异常
使用简单,指定包长渡就ok。
== 中门篇 Netty 编解码开发指南==
可以用Netty提供的ObjectEncoder编码器和ObjectDecoder解码器实现对普通对象的序列化。
而且不存在粘包问题。
protobuf支持跨语言使用。
protobufDecoder仅仅负责解码,不支解决持粘包问题。因此,在protobufDecoder之前要有能解决粘包问题的解码器,有三种选择:
== 高级篇 Netty 多协议开发和应用==
Netty 源码分析
JDK NIO也有ByteBuffer,其缺点有:
ByteBuf工作原理
static final ResourceLeakDetector<ByteBuf> leakDetector =
ResourceLeakDetectorFactory.instance().newResourceLeakDetector(ByteBuf.class);//用于对象是否泄漏,定义为static,意味着所有byteBuf共享
int readerIndex;//读索引
int writerIndex;//写索引
private int markedReaderIndex;//读mark
private int markedWriterIndex;//写mark
private int maxCapacity;//最大容量
Byte数组不在这里,因为AbstractByteBuf无法确定使用直接内存还是堆内存。
readBytes(ByteBuf dst, int dstIndex, int length)
public ByteBuf readBytes(ByteBuf dst, int dstIndex, int length) {
checkReadableBytes(length);//校验可读性
getBytes(readerIndex, dst, dstIndex, length);//读取。从readerIndex开始读取length个字节到目标数组中
readerIndex += length;//移动读指针
return this;
}
再看一下checkReadableBytes():
/** * Throws an {@link IndexOutOfBoundsException} if the current * {@linkplain #readableBytes() readable bytes} of this buffer is less * than the specified value. */
protected final void checkReadableBytes(int minimumReadableBytes) {
if (minimumReadableBytes < 0) {
throw new IllegalArgumentException("minimumReadableBytes: " + minimumReadableBytes + " (expected: >= 0)");
}
checkReadableBytes0(minimumReadableBytes);
}
private void checkReadableBytes0(int minimumReadableBytes) {
ensureAccessible();
if (readerIndex > writerIndex - minimumReadableBytes) {
throw new IndexOutOfBoundsException(String.format(
"readerIndex(%d) + length(%d) exceeds writerIndex(%d): %s",
readerIndex, minimumReadableBytes, writerIndex, this));
}
}
writeBytes(byte[] src, int srcIndex, int length)
public ByteBuf writeBytes(byte[] src, int srcIndex, int length) {
ensureWritable(length);//可写校验和扩容
setBytes(writerIndex, src, srcIndex, length);//从writerIndex开始写length长度
writerIndex += length;//移动写指针
return this;
}
ensureWritable(int minWritableBytes)
public ByteBuf ensureWritable(int minWritableBytes) {
if (minWritableBytes < 0) {
throw new IllegalArgumentException(String.format(
"minWritableBytes: %d (expected: >= 0)", minWritableBytes));
}
ensureWritable0(minWritableBytes);
return this;
}
final void ensureWritable0(int minWritableBytes) {
ensureAccessible();//检查这个buf是否还有引用(如果已经没有引用那就没必要在写了)
if (minWritableBytes <= writableBytes()) {//写入的字节小于可写字节,校验通过
return;
}
if (minWritableBytes > maxCapacity - writerIndex) {//写入的字节大于最大可写入字节,抛异常
throw new IndexOutOfBoundsException(String.format(
"writerIndex(%d) + minWritableBytes(%d) exceeds maxCapacity(%d): %s",
writerIndex, minWritableBytes, maxCapacity, this));
}
// Normalize the current capacity to the power of 2.
int newCapacity = alloc().calculateNewCapacity(writerIndex + minWritableBytes, maxCapacity);
// Adjust to the new capacity.
capacity(newCapacity);
}
重用缓冲区
public ByteBuf discardReadBytes() {
ensureAccessible();
if (readerIndex == 0) {
return this;
}
if (readerIndex != writerIndex) {
setBytes(0, this, readerIndex, writerIndex - readerIndex);//复制缓冲区
writerIndex -= readerIndex;//重置写指针
adjustMarkers(readerIndex);//调整mark指针
readerIndex = 0;//重置读指针
} else {
adjustMarkers(readerIndex);
writerIndex = readerIndex = 0;
}
return this;
}
15.2.2 AbstractReferenceCountedByteBuf源码分析
public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {
private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater =
AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");//通过原子的方式对成员变量进行更新,消除锁
private volatile int refCnt;//跟踪对象的引用次数,采用CAS对其自增1,默认值为1
}
15.2.2 UnPooledHeapByteBuf源码分析
非池化的基于堆内存,频繁的大块内存分配和回收会对性能造成影响,但是相比对外内存的申请和释放,成本还是低一些。
相比HeapByteBuf, UnPooledHeapByteBuf的实现更加加单,也不容易出现内存管理方面的问题,因此在满足性能的条件下,推荐使用UnPooledHeapByteBuf。
public class UnpooledHeapByteBuf extends AbstractReferenceCountedByteBuf {
private final ByteBufAllocator alloc;
byte[] array;//这里直接使用JDK的ByteBuffer也可以,之所以使用Byte数组是因为性能和便捷的位操作
private ByteBuffer tmpNioBuf;//用于实现将netty的byteBuf转换为JDK的ByteBuffer
}
转换JDK Buffer
netty基于byte数组实现
jdk的nio buf提供wrap方法,可直接实现 转换
看下转换方法
public ByteBuffer nioBuffer(int index, int length) {
ensureAccessible();
return ByteBuffer.wrap(array, index, length).slice();
}
public ByteBuffer slice() {
return new HeapByteBuffer(hb,//仍然使用的是原buffer的全局数组,只是改变了position和limit的位置,所以新buf和原buf内容是相互影响的
-1,
0,
this.remaining(),
this.remaining(),
this.position() + offset);
}
slice方法的作用:copy position到limit之间的内容,
15.2.3 pooledByteBuf内存池原理分析
PoolArena是netty的内存池显现类。
为了集中管理内存,提供内存申请是释放的效率,很多框架会申请一大块内存,提供相应的接口分配和释放内存,这样就不再频繁的使用系统调用来使用内存,可以提高性能。预先申请的那块内存就叫Memory Arena。PoolArena是netty对Memory Arena的实现。
Netty的PoolArena由多个chunk组成,每个chunk由多个Page组成。
PoolArena源码:
abstract class PoolArena<T> implements PoolArenaMetric {
static final boolean HAS_UNSAFE = PlatformDependent.hasUnsafe();
enum SizeClass {
Tiny,
Small,
Normal
}
static final int numTinySubpagePools = 512 >>> 4;
final PooledByteBufAllocator parent;
private final int maxOrder;
final int pageSize;
final int pageShifts;
final int chunkSize;
final int subpageOverflowMask;
final int numSmallSubpagePools;
final int directMemoryCacheAlignment;
final int directMemoryCacheAlignmentMask;
private final PoolSubpage<T>[] tinySubpagePools;
private final PoolSubpage<T>[] smallSubpagePools;
private final PoolChunkList<T> q050;
private final PoolChunkList<T> q025;
private final PoolChunkList<T> q000;
private final PoolChunkList<T> qInit;
private final PoolChunkList<T> q075;
private final PoolChunkList<T> q100;
}
PoolChunk的实现
PoolChunk负责多个Page的内存管理,PoolChunk将其负责的多个Page构建成一棵二叉树。
假设一个chunk由16个page组成,则其组织形式:
Page大小是4字节,chunk大小是64字节。
每个节点都记录自己在整个Memory Arena中的偏移地址,一旦被分配,则该节点及其子节点在接下来的内存分配过程中会被忽略。
举例来说,我们申请16个字节空间,则第三层的某个节点会被标记为已分配,则再次分配内存的时候会从其他三个节点中分配。
分配内存时对树采用深度优先算法,但是从哪棵子树开始深度遍历是随机的。
PoolSubPage的实现
申请内存小于一个page,则内存分配在page中完成,每个page会被分为大小相等的多个块。
被分的单位块大小等于第一次申请的内存大小,例如一个Page8字节,第一次申请2字节,则该page被切分成4块,每块2字节。而且这个page以后只能用于分配2字节的内存申请,如果再来一个4字节的内存申请,只能在另一个Page中申请。
Page使用标识位来表示内存块是否可用。维护一个long数组,每个位表示一个块的使用情况。
例如page为128字节,第一次申请内存为1字节,则该page被分为128块,则long数组中有2个元素,(每个long64位,两个long可以表示128位)。0、1表示该块是否可用。
15.2.4 PooledDirectByteBuf内存池原理分析
创建字节缓冲区
由于采用内存池实现,所以创建PooledDirectByteBuf对象不能new一个实例,而是从内存池获取。然后设置引用计数器。
static PooledDirectByteBuf newInstance(int maxCapacity) {
PooledDirectByteBuf buf = RECYCLER.get();
buf.reuse(maxCapacity);
return buf;
}
final void reuse(int maxCapacity) {
maxCapacity(maxCapacity);
setRefCnt(1);
setIndex0(0, 0);
discardMarks();
}
复制字节缓冲区
会从内存池中获取一个新的buffer而不是new一个。
15.3.1 ByteBufHolder
对消息体进行包装和抽象,不同的子类有不同的实现。
实现ByteBufHolder的子类可以自己实现一些实用的方法。
Netty也有一些子类继承自ByteBufHolder。
15.3.2 ByteBufAllocator
字节缓冲区分配器,其实现类有两种:基于池的和普通的。
其API:
15.3.3 CompositeByteBuf
允许将多个ByteBuf组装到一起。
使用场景:如某协议包含消息头和消息体,当对消息进行编码的时候需要进行整合。
这种场景有两种实现方式:
看下源码:
public class CompositeByteBuf extends AbstractReferenceCountedByteBuf implements Iterable<ByteBuf> {
private static final ByteBuffer EMPTY_NIO_BUFFER = Unpooled.EMPTY_BUFFER.nioBuffer();
private static final Iterator<ByteBuf> EMPTY_ITERATOR = Collections.<ByteBuf>emptyList().iterator();
private final ByteBufAllocator alloc;
private final boolean direct;
private final ComponentList components;//维护buf的容器
private final int maxNumComponents;
private boolean freed;
}
再看下ComponentList:
private static final class ComponentList extends ArrayList<Component> {
ComponentList(int initialCapacity) {
super(initialCapacity);
}
// Expose this methods so we not need to create a new subList just to remove a range of elements.
@Override
public void removeRange(int fromIndex, int toIndex) {
super.removeRange(fromIndex, toIndex);
}
}
private static final class Component {
final ByteBuf buf;
final int length;
int offset;//在集合中的位置偏移
int endOffset;
Component(ByteBuf buf) {
this.buf = buf;
length = buf.readableBytes();
}
void freeIfNecessary() {
buf.release(); // We should not get a NPE here. If so, it must be a bug.
}
}
15.3.4 ByteBufUtil
几个常用的工具方法:
Unsafe是Netty实现的channel的内部接口,设计初衷就是只是内部使用。负责协助netty进行网络读写操作。
16.1.1 的工作原理
JDK已经提供了channel,为什么netty要再实现一个呢?主要原因在于:
JDK的channel没有提供统一的接口,开发者使用不方便
JDk的channel主要职责就是网络IO操作,由于他们是SPI类接口,由具体的虚拟机厂家提供实现类,所以通过集成SPI类来扩展其功能的难度增大。
Netty的channel可以和Netty框架融合在一起。
基于以上4点,netty的channel设计理念有:
在channel接口层,通过外观(facade)模式封装。
channel接口的定义功能尽量大而全。
具体实现采用聚合而非包含的方式,更加灵活。
16.1.2 功能介绍
/* * Copyright 2012 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */
package io.netty.channel;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.socket.DatagramChannel;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.util.AttributeMap;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public interface Channel extends AttributeMap, ChannelOutboundInvoker, Comparable<Channel> {
/** * Returns the globally unique identifier of this {@link Channel}. */
ChannelId id();
/** * Return the {@link EventLoop} this {@link Channel} was registered to. * 获取当前channel所在的EventLoop * netty的EventLoop不仅仅是处理网络IO和可以处理用户自定义的NioTask */
EventLoop eventLoop();
/** * Returns the parent of this channel. * * @return the parent channel. * {@code null} if this channel does not have a parent channel. */
Channel parent();
/** * Returns the configuration of this channel. */
ChannelConfig config();
/** * Returns {@code true} if the {@link Channel} is open and may get active later */
boolean isOpen();
/** * Returns {@code true} if the {@link Channel} is registered with an {@link EventLoop}. */
boolean isRegistered();
/** * Return {@code true} if the {@link Channel} is active and so connected. */
boolean isActive();
/** * Return the {@link ChannelMetadata} of the {@link Channel} which describe the nature of the {@link Channel}. * 在Netty中每个Channel都对应一个物理连接,每个连接都有自己的TCP链接参数 * 如tcp缓冲区大小、超时时间等 */
ChannelMetadata metadata();
/** * Returns the local address where this channel is bound to. The returned * {@link SocketAddress} is supposed to be down-cast into more concrete * type such as {@link InetSocketAddress} to retrieve the detailed * information. * * @return the local address of this channel. * {@code null} if this channel is not bound. */
SocketAddress localAddress();
/** * Returns the remote address where this channel is connected to. The * returned {@link SocketAddress} is supposed to be down-cast into more * concrete type such as {@link InetSocketAddress} to retrieve the detailed * information. * * @return the remote address of this channel. * {@code null} if this channel is not connected. * If this channel is not connected but it can receive messages * from arbitrary remote addresses (e.g. {@link DatagramChannel}, * use {@link DatagramPacket#recipient()} to determine * the origination of the received message as this method will * return {@code null}. */
SocketAddress remoteAddress();
/** * Returns the {@link ChannelFuture} which will be notified when this * channel is closed. This method always returns the same future instance. */
ChannelFuture closeFuture();
/** * Returns {@code true} if and only if the I/O thread will perform the * requested write operation immediately. Any write requests made when * this method returns {@code false} are queued until the I/O thread is * ready to process the queued write requests. */
boolean isWritable();
/** * Get how many bytes can be written until {@link #isWritable()} returns {@code false}. * This quantity will always be non-negative. If {@link #isWritable()} is {@code false} then 0. */
long bytesBeforeUnwritable();
/** * Get how many bytes must be drained from underlying buffers until {@link #isWritable()} returns {@code true}. * This quantity will always be non-negative. If {@link #isWritable()} is {@code true} then 0. */
long bytesBeforeWritable();
/** * Returns an internal-use-only object that provides unsafe operations. */
Unsafe unsafe();
/** * Return the assigned {@link ChannelPipeline}. */
ChannelPipeline pipeline();
/** * Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s. */
ByteBufAllocator alloc();
@Override
Channel read();
@Override
Channel flush();
/** * Unsafe operations that should never be called from user-code. These methods * are only provided to implement the actual transport, and must be invoked from an I/O thread except for the * following methods: * * - {@link #localAddress()}
* - {@link #remoteAddress()}
* - {@link #closeForcibly()}
* - {@link #register(EventLoop, ChannelPromise)}
* - {@link #deregister(ChannelPromise)}
* - {@link #voidPromise()}
*
*/
interface Unsafe {
/** * Return the assigned {@link RecvByteBufAllocator.Handle} which will be used to allocate {@link ByteBuf}'s when * receiving data. */
RecvByteBufAllocator.Handle recvBufAllocHandle();
/** * Return the {@link SocketAddress} to which is bound local or * {@code null} if none. */
SocketAddress localAddress();
/** * Return the {@link SocketAddress} to which is bound remote or * {@code null} if none is bound yet. */
SocketAddress remoteAddress();
/** * Register the {@link Channel} of the {@link ChannelPromise} and notify * the {@link ChannelFuture} once the registration was complete. */
void register(EventLoop eventLoop, ChannelPromise promise);
/** * Bind the {@link SocketAddress} to the {@link Channel} of the {@link ChannelPromise} and notify * it once its done. */
void bind(SocketAddress localAddress, ChannelPromise promise);
/** * Connect the {@link Channel} of the given {@link ChannelFuture} with the given remote {@link SocketAddress}. * If a specific local {@link SocketAddress} should be used it need to be given as argument. Otherwise just * pass {@code null} to it. * * The {@link ChannelPromise} will get notified once the connect operation was complete. */
void connect(SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise);
/** * Disconnect the {@link Channel} of the {@link ChannelFuture} and notify the {@link ChannelPromise} once the * operation was complete. */
void disconnect(ChannelPromise promise);
/** * Close the {@link Channel} of the {@link ChannelPromise} and notify the {@link ChannelPromise} once the * operation was complete. */
void close(ChannelPromise promise);
/** * Closes the {@link Channel} immediately without firing any events. Probably only useful * when registration attempt failed. */
void closeForcibly();
/** * Deregister the {@link Channel} of the {@link ChannelPromise} from {@link EventLoop} and notify the * {@link ChannelPromise} once the operation was complete. */
void deregister(ChannelPromise promise);
/** * Schedules a read operation that fills the inbound buffer of the first {@link ChannelInboundHandler} in the * {@link ChannelPipeline}. If there's already a pending read operation, this method does nothing. */
void beginRead();
/** * Schedules a write operation. */
void write(Object msg, ChannelPromise promise);
/** * Flush out all write operations scheduled via {@link #write(Object, ChannelPromise)}. */
void flush();
/** * Return a special ChannelPromise which can be reused and passed to the operations in {@link Unsafe}. * It will never be notified of a success or error and so is only a placeholder for operations * that take a {@link ChannelPromise} as argument but for which you not want to get notified. */
ChannelPromise voidPromise();
/** * Returns the {@link ChannelOutboundBuffer} of the {@link Channel} where the pending write requests are stored. */
ChannelOutboundBuffer outboundBuffer();
}
}
16.2.1 成员变量
public abstract class AbstractChannel extends DefaultAttributeMap implements Channel {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(AbstractChannel.class);
//定义全局异常
private static final ClosedChannelException FLUSH0_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractUnsafe.class, "flush0()");
private static final ClosedChannelException ENSURE_OPEN_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractUnsafe.class, "ensureOpen(...)");
private static final ClosedChannelException CLOSE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractUnsafe.class, "close(...)");
private static final ClosedChannelException WRITE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractUnsafe.class, "write(...)");
private static final NotYetConnectedException FLUSH0_NOT_YET_CONNECTED_EXCEPTION = ThrowableUtil.unknownStackTrace(
new NotYetConnectedException(), AbstractUnsafe.class, "flush0()");//物理链路尚未建立异常
//聚合所有Channel使用到的能力对象
private final Channel parent;
private final ChannelId id;
private final Unsafe unsafe;
private final DefaultChannelPipeline pipeline;//IO操作的实现
private final VoidChannelPromise unsafeVoidPromise = new VoidChannelPromise(this, false);
private final CloseFuture closeFuture = new CloseFuture(this);
private volatile SocketAddress localAddress;
private volatile SocketAddress remoteAddress;
private volatile EventLoop eventLoop;
private volatile boolean registered;
private boolean closeInitiated;
/** Cache for the string representation of this channel */
private boolean strValActive;
private String strVal;
}
16.2.2 核心API源码
先看下网络读写操作。
IO操作会触发Netty事件,事件在ChannelPipeline中传播,由对应的ChannelHandler对事件拦截处理。
事件驱动的方式功能和AOP等价,但是性能高于AOP。
DefaultChannelPipeline中的ChannelHandler具体处理IO操作。
16.2.3 AbstractNioChannel源码分析
public abstract class AbstractNioChannel extends AbstractChannel {
private static final InternalLogger logger =
InternalLoggerFactory.getInstance(AbstractNioChannel.class);
private static final ClosedChannelException DO_CLOSE_CLOSED_CHANNEL_EXCEPTION = ThrowableUtil.unknownStackTrace(
new ClosedChannelException(), AbstractNioChannel.class, "doClose()");
private final SelectableChannel ch;//java.nio.socketChannel和java.nio.serverSocketChannel的公共父类
protected final int readInterestOp;//channel监听的事件类型,比如,读、写、客户端链接服务端事件等
volatile SelectionKey selectionKey;//Channel注册到EventLoop后的返回键
boolean readPending;
private final Runnable clearReadPendingRunnable = new Runnable() {
@Override
public void run() {
clearReadPending0();
}
};
/** * The future of the current connection attempt. If not null, subsequent * connection attempts will fail. */
private ChannelPromise connectPromise;//链接操作的结果
private ScheduledFuture<?> connectTimeoutFuture;//链接超时定时器
private SocketAddress requestedRemoteAddress;//通信地址信息
}
再看几个核心API:
doRegister():
@Override
protected void doRegister() throws Exception {
boolean selected = false;//是否注册成功
for (;;) {
try {
selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);//参数1是要注册的channel,参数2是注册的channel感性的网络事件,0代表对任何事件都感兴趣(abstract只完成注册),参数3是指定的附件,这里将子类设置为附件,后续channel收到网络事件时可以从selectionKey中获取该附件
return;
} catch (CancelledKeyException e) {//如果当前返回的selectionKey已经被取消,抛出CancelledKeyException
if (!selected) {//第一次操作该异常,从多路复用器中删掉selectionKey
// Force the Selector to select now as the "canceled" SelectionKey may still be
// cached and not removed because no Select.select(..) operation was called yet.
eventLoop().selectNow();
selected = true;//表示已经删掉失效的selectionKey
} else {
// We forced a select operation on the selector before but the SelectionKey is still cached
// for whatever reason. JDK bug ?
throw e;//JDK BUG?
}
}
}
}
public abstract SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException;
注册channel的时候需要指定监听的网络操作位来表示channel对哪几类网络事件感兴趣,具体定义如下:
public static final int OP_READ = 1 << 0;//读操作位
public static final int OP_WRITE = 1 << 2;//写操作位
public static final int OP_CONNECT = 1 << 3;//客户端链接服务端操作位
public static final int OP_ACCEPT = 1 << 4;//服务端链接客户端操作位
16.2.4 AbstractNioByteChannel 源码分析
//成员变量只有一个 负责写半包消息
private final Runnable flushTask = new Runnable() {
@Override
public void run() {
// Calling flush0 directly to ensure we not try to flush messages that were added via write(...) in the
// meantime.
((AbstractNioUnsafe) unsafe()).flush0();
}
};
最重要的就是doWrite方法:
@Override
protected void doWrite(ChannelOutboundBuffer in) throws Exception {
int writeSpinCount = config().getWriteSpinCount();
do {
Object msg = in.current();
if (msg == null) {//说明消息都已经发完了
// Wrote all messages.
clearOpWrite();//清除半包标识
// Directly return here so incompleteWrite(...) is not called.
return;
}
writeSpinCount -= doWriteInternal(in, msg);//继续写
} while (writeSpinCount > 0);
incompleteWrite(writeSpinCount < 0);//处理半包发送任务的方法
}
//清除半包标识
protected final void clearOpWrite() {
final SelectionKey key = selectionKey();
// Check first if the key is still valid as it may be canceled as part of the deregistration
// from the EventLoop
// See https://github.com/netty/netty/issues/2104
if (!key.isValid()) {
return;
}
final int interestOps = key.interestOps();
if ((interestOps & SelectionKey.OP_WRITE) != 0) {//当前channel的网络标志位与SelectionKey.OP_WRITE按位与!=0 说明可写
key.interestOps(interestOps & ~SelectionKey.OP_WRITE);//置为不可写
}
}
//发送消息
private int doWriteInternal(ChannelOutboundBuffer in, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
ByteBuf buf = (ByteBuf) msg;
if (!buf.isReadable()) {//可读字节为0 消息不可读
in.remove();//移除该消息
return 0;
}
final int localFlushedAmount = doWriteBytes(buf);//尝试全量全量发送
if (localFlushedAmount > 0) {//发送成功,但不表示全量都发送出去了
in.progress(localFlushedAmount);//修改发送状态
if (!buf.isReadable()) {//没有可发的数据
in.remove();
}
return 1;
}
} else if (msg instanceof FileRegion) {
FileRegion region = (FileRegion) msg;
if (region.transferred() >= region.count()) {
in.remove();
return 0;
}
long localFlushedAmount = doWriteFileRegion(region);
if (localFlushedAmount > 0) {
in.progress(localFlushedAmount);
if (region.transferred() >= region.count()) {
in.remove();
}
return 1;
}
} else {
// Should not reach here.
throw new Error();
}
return WRITE_STATUS_SNDBUF_FULL;
}
//处理半包发送任务的方法
//将selectKey置为可写,eventLoop就会轮训这个key,将其flush掉
protected final void incompleteWrite(boolean setOpWrite) {
// Did not write completely.
if (setOpWrite) {
setOpWrite();
} else {
// It is possible that we have set the write OP, woken up by NIO because the socket is writable, and then
// use our write quantum. In this case we no longer want to set the write OP because the socket is still
// writable (as far as we know). We will find out next time we attempt to write if the socket is writable
// and set the write OP if necessary.
clearOpWrite();
// Schedule flush again later so other tasks can be picked up in the meantime
eventLoop().execute(flushTask);
}
}
ChannelPipeline是对Channel的封装,ChannelPipeline持有时间拦截器ChannelHandler的链表,由ChannelHandler对I/O事件拦截和处理,可以通过新增和删除ChannelHandler来实现不同业务的逻辑定制。
ChannelPipeline是ChannelHandler的容器,负责ChannelHandler的管理和事件拦截
17.1.1 ChannelPipeline的事件处理
Netty中的事件分为inbound和outbound事件
inbound事件通常由IO线程触发,如tcp连接建立、断开、异常通知等
outbound事件通常是由用户主动发起的网络IO操作,如用户发起的绑定本地地址,发送消息等
17.1.2 自定义拦截器
通常ChannelHandler只需要继承ChannelHandlerAdapter类覆盖自己关心的方法即可。
17.1.3 ChannelPipeline的主要特性
ChannelPipeline支持动态添加或者删除ChannelHandler。使用场景(业务高峰期加入拥塞保护ChannelHandler,高峰过后删掉ChannelHandler)。
ChannelPipeline是线程安全的,但是ChannelHandler不是线程安全的。
实际上是ChannelHandler 的容器,内部维护了一个ChannelHandler链表和迭代器。
EventLoop负责处理连接中的事件, 和EvenvLoop有关的几个概念是:Channel, EventLoopGroup。
对于基于Netty的网络服务,Client端启动需要一个EventLoopGroup, Server端启动需要两个EventLoopGroup, 因为Server端需要两种Channel, 一种是ServerChannel, 只有一个,负责接受连接,另一种是用于处理连接的一组Channel。
EventLoop继承了concurrent包里的 ScheduledExecutorService,这使得它可以接受Callable或者Runnable并执行。EventLoop中的parent()方法返回包含这个EventLoop的EventLoopGroup
ChannelFuture是netty中异步Future。
netty中future的特点:
=结构和行业应用篇 Netty 高级特性==