Netty源码解析
- Netty是目前非常流行的通信框架,Redis,Dubbo的底层都使用了Netty
- Netty之所以如此流行和他易用且高效是分不开的,后续我将会写多篇文章对Netty的源码进行分析
- 同时要想深入理解Netty,看懂Netty源码和框架也需要储备许多基础的知识,下面就将从这些基本知识一一展开,逐步揭开Netty源码神秘的面纱
JAVA中的IO
- 首先要知道Netty是一个处理网络IO的框架,Java本身就要处理IO的框架,其中就包括我们非常熟悉的InputStream和OutputStream
- Java的IO采用装饰者模式
BufferedInputStream
BufferedInputStream->FilterInputStream->InputStream
这些类的核心read函数最后都是调用InputStream里面的read函数,一个byte一个byte的读取(int->byte),效率不高
public int read(byte b[], int off, int len) throws IOException {
if (b == null) {
throw new NullPointerException();
} else if (off < 0 || len < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return 0;
}
int c = read();
if (c == -1) {
return -1;
}
b[off] = (byte)c;
int i = 1;
try {
for (; i < len ; i++) {
c = read();
if (c == -1) {
break;
}
b[off + i] = (byte)c;
}
} catch (IOException ee) {
}
return i;
}
FileInputStream
FileInputStream(FileChannel(NIO))->InputStream
上面的BufferedInputStream读取太慢,FileInputStream引入了NIO中的FileChannel(rt.jar java.nio.channels)提供了缓冲区,提高了InputStream的读取效率
JAVA中的NIO
Java NIO有三大核心部件:
-
Buffer ---
缓冲区
-
Channel ---
IO操作通道,可以操作设备、文件、网络套接字或程序组件
-
Selector ---
多路复用选择器
通道(Channel)和缓冲区(Buffer)。通道表示打开到 IO 目标的连接。若需要使用 NIO 系统,需要获取用于连接 IO 目标的通道以及用于容纳数据的缓冲区。然后操作缓冲区,对数据进行处理
Buffer 缓冲区
缓冲区(Buffer):缓冲区是一个线性有限的用来存放从NIO Channel中读取出得数据的区域
Buffer就是一个数组,用来存放一些特定的数据类型的元素,每种数据类型都有一个对应的缓冲区(除了boolean类型),他们都是Buffer类的子类,其中包括:
- ByteBuffer(最常用)
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
allocate (int capacity) 是用来分配capacity大小空间返回缓冲区对象的函数
缓冲区(Buffer)的重要属性
- mark(标记):缓冲区的标记是在调用reset方法时将其position重置到的索引位置。mark并不总是被定义,mark()函数定义mark为当前position,但当它被定义时,从不为负,也不大于position。如果定义了mark, 而将position或limit调整为小于mark的值时,该mark将被自动丢弃。如果未定义mark,则调用reset方法将引发InvalidMarkException
public final Buffer mark() {
mark = position;
return this;
}
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
- position(位置):是下一个被读或写的元素的index,缓冲区的位置从不为负,也从不大于其极限
- limit(临界点):是第一个不能被读写的元素的index,缓冲区的界限永远不可能为负,也不会大于其容量
-
capacity(容量):是缓冲区包含的元素个数,缓冲区的容量永远不会是负的,也不会改变
mark、position、limit、capcity之间的关系:mark <= position <= limit <= capacity
缓冲区的重要方法
-
get:获取 Buffer 中的数据
- get() :读取单个字节
- get(byte[] dst):批量读取多个字节到 dst 中
- get(int index):读取指定索引位置的字节(不会移动 position)
-
put:放入数据到 Buffer 中
- put(byte b):将给定单个字节写入缓冲区的当前位置
- put(byte[] src):将 src 中的字节写入缓冲区的当前位置
- put(int index, byte b):将指定字节写入缓冲区的索引位置(不会移动 position)
- clear:清除方法,使缓冲区为新的通道读取或相对put操作序列做好准备:它将limit设为capacity,position设置为零
- flip:翻转方法,使缓冲区为新的通道写入或相对get操作序列做好准备:它将limit设置为当前位置,然后将位置设置为零
- rewind:倒带方法,使缓冲区准备好重新读取它已经包含的数据:它保持限制不变,并将位置设置为零
直接缓冲区 vs 非直接缓冲区
-
非直接内存:数据要从磁盘拷贝到内核缓冲区,从内核缓冲区拷贝到JVM缓冲区,再从JVM拷缓冲区贝到进程的堆中,堆内存是
new byte[capacity]
- 直接内存:直接内存就是用Unsafe类操作系统指针进行内存分配,分配完成后返回内存的指针,这样netty就可以对这块内存进行读写操作,但是这块内存操作不受GC的管理。直接内存可以进行零拷贝提高性能
- 大端序传输:ByteBuffer缓冲区的初始字节序总是大端序
- 零拷贝
直接和间接缓冲区说明
- 如果是直接字节缓冲区,Java虚拟机将尽最大努力直接在其上执行本机I/O操作。也就是说,它将尝试避免在每次调用底层操作系统的本机I/O操作之前(或之后)将缓冲区的内容复制到(或从)中间缓冲区
- 可以通过调用此类的
allocateDirect(int)
工厂方法创建直接字节缓冲区。此方法返回的缓冲区通常比非直接缓冲区具有更高的分配和释放成本。直接缓冲区的内容可能位于正常垃圾收集堆之外,因此它们对应用程序内存占用的影响可能不明显。因此,建议将直接缓冲区主要分配给受底层系统本机I/O操作影响的大型、长寿命缓冲区。一般来说,只有当直接缓冲区在程序性能上产生可测量的增益时,才分配直接缓冲区 - 直接字节缓冲区也可以由java.nio.channels.FileChannel(mmap)将文件的一个区域直接映射到内存中
-
isDirect
方法可以判断该缓冲区是不是直接缓冲区未完待续:一篇文章讲不完Java NIO,Channel和Selector将在下一篇文章中进行介绍
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。