UNIX提供5种I/O模型
var code = “7cfcb088-556d-478a-b21d-12b255236dbd”
BIO模型
在进程空间调用recvfrom时被阻塞,直到有数据才返回。
NIO模型
调用recvfrom时先返回EWOULDBLOCK错误,然后轮询是否有数据。
I/O复用
linux提供select/poll,其支持多个fd的NIO,但是select/poll本身是阻塞的。epoll采用事件驱动的方式代替顺序扫描,其性能更高。
信号驱动I/O模型
调用信号处理函数,返回SINGO信号时开始调用recvfrom。
异步I/O
通知内核某个操作,并整个操作完成的时候通知我们。
I/O多路复用技术
- select打开FD限制1024,epoll最大限制是操作系统最大文件句柄数
- select/poll在socket很大时,需要扫描全部集合。epoll通过fd回调函数实现。
- 使用mmap技术进行消息传递
- epoll API更简单
Java的IO演进
- 1.4 NIO
增加NIO包。 - 1.7 NIO2.0
TCP粘包/拆包问题的解决之道
TCP粘包/拆包问题
TCP协议是”流“协议,流是没有间隔的。tcp会根据缓存大小将业务上的大包划分成多个小包发送出去、也可能多个小包合成一个大包发送出去。
TCP粘包/拆包发生的原因
应用层:应用程序写入的字节大小大于套接字接口缓冲区大小。
TCP层:进行MSS大小的tcp分段。
IP层:以太网帧的payload大于MTU进行ip分片。
TCP为了避免被发送方分片,它主动把数据分成小段再交给网络层。 最大的分段大小称为 MSS(Maximum Segment Size )
TCP粘包/拆包问题的解决策略
- 消息定长len,例如每个报文固定200字节。那么读取到定长len后就重置计数器开始读取下一个包。
- 包尾加换行符分割,如ftp。
- 消息头+消息体。消息头包含消息总长度的字段。
- 更复杂的应用协议。
Netty解决tcp粘包问题
为了解决tcp粘包/拆包导致的半包读写问题,Netty默认提供了多种编解码器用于处理半包。
LineBasedFrameDecoder:原理是遍历ByteBuf中字节,以换行符分割。
StringDecoder:将接收的byte对象转换为字符串,然后调用后面的handler
如果发送的消息不是以换行符结束的,netty也有其他解码器支持。
分隔符和定长解码器的应用
TCP以流的方式进行数据传输,上层应用协议为了对消息进行区分,通常采用以下4中方式:
- 消息长度固定,累计读取到长度综合为定长LEN的报文后,就认为读取到了一个完整的消息,将计数器置位,重新开始读取下一个数据报;
- 将回车换行符作为消息结束符,例如FTP协议,这种方式在文本协议中应用比较广泛;
- 将特殊的分隔符作为消息的结束标志,回车换行符就是一种特殊的分隔符;
- 通过在消息头中定义长度字段来标识消息的总长度。
DelimiterBasedFrameDecoder
支持任意字符为分隔符
支持设置单条消息最大长度,如果找了最大长度还没找到分隔符就抛出异常。
FixedLengthFrameDecoder
使用简单,指定包长度就ok。
编解码技术
Java序列化的目的主要有两个:网络传输和对象持久化。
Java序列化缺点
- 不支持跨语言
- 序列化后的码流太大
- 序列化性能低
业界主流的编解码框架
- Google 的protobuf
- Facebook的Thrift
- JBoss的Marshalling
Netty高性能之道
-
异步非阻塞通信
Netty提供了SocketChannel和ServerSocketChannel两种不同的套接字通道实现,并且这两种都支持阻塞和非阻塞模式。NioEventLoop由于聚合了Selector(多路复用器),可以同时并发处理上千个SocketChannel,这可以充分提升I/O线程的运行效率。避免频繁I/O阻塞导致的线程挂起。
-
高效的Reactor线程模型
单线程Reactor线程模型
一个accpetThread接受任务,之后转发到reactor线程中进行处理。
多线程reactor多线程模型
有多个accpet线程
主从Reactor多线程模型
有多个accpet线程 -
无锁的串行化设计
为了尽可能地避免锁竞争带来的性能损耗,可以通过串行化设计,即消息的处理尽可能在同一个线程内完成,期间不进行线程切换,这样就避免了多线程竞争和同步锁。 -
高效的并发编程
-
高效的系列化框架
-
零拷贝
-
内存池
-
灵活的TCP参数配置能力