== 基础篇 走进JavaNIO==
第一章 java的I/O演进之路
1.1 I/O基础入门
Java1.4之前对I/O的支持并不完善,给开发带来的问题有:
- 没有数据缓冲区,I/O性能存在问题
- 没有C/C++中channel的概念,只有输入输出流
- BIO会导致通信被长时间阻塞
- 支持的字符集优先,硬件移植性不好
1.1.1 linux网络I/O模型简介
linux中所有的外部设备都是一个文件,socket也是一个文件,有文件描述符(fd)指向它。
UNIX提供5中I/O模型:
- BIO模型:在进程空间调用recvfrom,直到有数据才返回。
- NIO模型:轮训调用recvfrom。
- I/O复用:linux提供select/poll,其支持多个fd的NIO,但是select/poll本身是阻塞的。epoll采用事件驱动的方式代替顺序扫描,其性能更高。
- 信号驱动I/O模型:
- 异步I/O:通知内核某个操作,并整个操作完成的时候通知我们。
I/O多路复用技术
epoll优点:
- 支持一个进程打开的fd不受限制
- IO效率不会随着fd数量增加而下降
- 使用mmap加速内核和用户空间的消息传递
- epoll API更加简单
1.2 Java的IO演进
2011年JDK7发布:
- 提供了能批量获取文件属性的API
- 提供AIO功能
第二章 NIO入门
2.1 传统的BIO编程
CLient/Server模型中,Server负责绑定IP,启动监听端口;Client发起链接请求,经过三次握手建立连接,通过输入输出流进行同步阻塞式通信。
2.1.1 BIO通信模型图
通过Acceptor处理多个client连接请求,处理完成后销毁线程。
该模型的问题就是支持的线程数量有限。
2.2 伪异步IO模型
伪异步是为了解决BIO一个链路需要一个线程的问题。
通过一个线程池处理多个客户端的请求接入
- 线程的数量不会大量膨胀导致资源耗尽
- 问题是:没有解决同步IO导致的线程阻塞问题
2.3 NIO模型
2.3.1 NIO简介
缓冲区buf
本质是一个字节数组(ByteBuff),同时提供数据的结构化访问以及维护读写位置。
通道 channel
channel是全双工的。
流是单向的。
多路复用 selector
selector简单来说就是轮训注册在其上的channel,
2.3.2 NIO服务序列图
2.4 AIO
== 入门篇 Netty NIO开发指南==
第三章 Netty入门应用
第四章 TCP粘包/拆包问题的解决之道
4.1 TCP粘包/拆包
4.1.1 TCP粘包/拆包问题说明
TCP协议是”流“协议,流是没有间隔的。tcp会根据缓存大小将业务上的大包划分成多个小包发送出去、也可能多个小包合成一个大包发送出去。
4.1.2 TCP粘包/拆包发生的原因
- 应用层:大于套接字接口缓冲区大小
- TCP层:MSS
- IP层:MTU
4.1.3 TCP粘包/拆包问题的解决策略
- 消息定长len,例如每个报文固定200字节。那么读取到定长len后就重置计数器开始读取下一个包。
- 包尾加换行符分割,如ftp。
- 消息头+消息体。消息头包含消息长度信息。
- 更复杂的应用协议,如netty.
4.3 Netty解决tcp粘包问题
- LineBasedFrameDecoder:原理是遍历ByteBuf中字节,以换行符分割
- StringDecoder:将接收的byte对象转换为字符串,然后调用后面的handler
如果发送的消息不是以换行符结束的,netty也有其他解码器支持。
第五章 分隔符和定长解码器的应用
5.1 DelimiterBasedFrameDecoder
支持任意字符为分隔符
支持设置单条消息最大长度,如果找了最大长度还没找到分隔符就抛出异常
5.2 FixedLengthFrameDecoder
使用简单,指定包长渡就ok。
== 中门篇 Netty 编解码开发指南==
第六章 编解码技术
6.1Java序列化缺点
- 不支持跨语言
- 序列化后的码流太大
- 序列化性能低
6.2 业界主流的编解码框架
- Google 的protobuf
- Facebook的Thrift
- JBoss的Marshalling
第七章 Java序列化
可以用Netty提供的ObjectEncoder编码器和ObjectDecoder解码器实现对普通对象的序列化。
而且不存在粘包问题。
第八章 Google Protobuf编解码
8.1 入门
protobuf支持跨语言使用。
8.3 注意事项
protobufDecoder仅仅负责解码,不支解决持粘包问题。因此,在protobufDecoder之前要有能解决粘包问题的解码器,有三种选择:
- Netty提供的ProtobuffVarint32FrameDecoder
- Netty提供的通用粘包解码器LengthFieldBasedFrameDecoder
- 继承ByteToMessageDecoder类,自己处理粘包问题
第九章 JBoss Marshalling编解码
== 高级篇 Netty 多协议开发和应用==
第十章 Http协议开发应用
10.1 Http协议特点
- 支持client/server模式
- 简单 - 指定URL和参数就可以访问
- 灵活 - 支持传输任意类型的对象
- 无状态 - 无状态协议
HttpRequest包含三部分:HTTP请求行、HTTP消息头、HTTP请求正文。
如:GET /netty5 HTTP/1.1
HttpResponse包含三部分:状态行、消息报头、相应正文。
第十一章 WebSocket协议开发
第十二章 UDP协议开发
第十三章 文件传输
第十四章 私有协议栈开发
Netty 源码分析
第十五章 ByteBuf和相关辅助类
JDK NIO也有ByteBuffer,其缺点有:
- 不支持动态扩容缩容
- 只有一个position指针,调用者需要手工调用flip()和rewind(),给使用者带来难度和出错的机会
- API支持有限
基于这些,Netty提供了自己的Bytebuffer实现-ByteBuf
15.1 ByteBuf
ByteBuf工作原理
- 通过两个指针来操作读写,readerIndex和writerIndex
- 支持动态扩容
Discardable bytes
缓冲区的分配和释放是个耗时的操作
缓冲区的动态扩张需要字节复制,它也是个耗时的操作
因此,为提高性能,要最大程度提高缓冲区的重用率。
可调用discardReadBytes来回收已经读过的内存,但是会发生内存复制,所以频繁调用会导致性能下降。
15.2 ByteBuf源码分析
继承关系
从内存分配的角度看,byteBuf分为两类:
- 堆内存缓冲区:优点是内存的分配和回收快。缺点是进行IO读写时需要一次内存复制,用户空间和内核空间的复制。
- 直接内存缓冲区:优缺点和堆内存缓冲区整好相反。
经验表明ByteBuf的最佳实践是在IO通信线程的读写缓冲区使用DirectByteBuf,后端业务的编解码模块使用HeapByteBuf,这样的组合可以达到性能最优。
从内存回收的角度看、ByteBuf分两类,基于对象池的ByteBuf和普通ByteBuf。使用内存池后的Netty在高并发和高负载环境下内存和GC更加平稳。
15.2.1 AbstractByteBuf源码分析
实现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