Linux IO模型
-
阻塞I/O模型
-
非阻塞I/O模型
-
I/O复用模型
-
信号驱动I/O模型
-
异步I/O模型
后面会有专门的文章讲五种模型的区别
I/O多路复用技术
I/O多路复用技术主要是为了同时处理多个客户端请求, I/O多路复用技术通过把多个I/O的阻塞复用到同一个select
的阻塞中, 从而使得系统在单线程的情况下可以同时处理多个客户端请求
以传统的多线程/多进程模型比, I/O多路复用的最大优势是系统开销小, 系统不需要创建新的额外进程或线程, 降低了系统的维护工作量, 节省了系统资源
主要应用场景:
- 同时处理多个套接字
- 同时处理多种网络协议的套接字
目前支持I/O多路复用的系统调用: select
pselect
poll
epoll
注意: 之前大多使用
select
进行轮询和网络事件通知, 但是现在新的Linux内核使用epoll
代替select
Java NIO
主要类和接口
- 异步I/O操作的缓冲区 ByteBuffer
- 异步I/O操作的管道 Pipe
- I/O操作(异步或同步)的 Channel, 包括 ServerSocketChannel 和 SocketChannel
- 多种字符集的编码和解码能力
- 非阻塞I/O操作的多路复用器 selector
- Perl实现的正则表达式类库
- 文件通道 FileChannel
- 批量获取文件属性API
- 提供AIO功能 支持基于文件的异步I/O操作和针对网络套接字的异步操作
传统的BIO编程
缺点
每当有一个新的客户端请求接入时, 服务端必须创建一个新的线程处理新接入的客户端, 而一个线程只能处理一个客户端连接
改进
通过线程池或者消息队列实现1个或者多个线程处理N个客户端的模型(底层通信机制依然使用同步阻塞I/O, 所以被称为"伪异步")
改进后的缺点
Netty 简介
NIO服务开发难点
- 处理各种网络问题
- 处理客户端的重复接入问题
- 处理客户端的安全认证问题
- 处理消息的编解码问题
- 处理半包读写问题
- NIO采用异步非阻塞编程模型, 一个I/O线程处理多条链路, 调试跟踪麻烦
Netty 优点
- API使用简单, 开发门槛低
- 功能强大, 支持各种编解码和主流协议
- 定制能力强, 可以通过ChannelHandler对通信框架进行灵活的扩展
- 性能高
- 修复了NIO的BUG
Netty 开发环境搭建
挖坑: 后面跟
TCP 粘包/拆包问题
TCP编程中, 无论是服务端还是客户端, 当我们读取或者发送消息的是否, 需要考虑TCP底层粘包/拆包机制
产生原因
- 程序中write写入的字节大小大于套接口发送缓冲区大小
- 进行MSS大小的TCP分段
- 以太网帧的payload大于MTU进行的IP分片
解决策略
底层TCP无法理解上层业务数据, 所以底层无法保证数据包不被拆分和重组, 所以这个问题只能通过上层的应用协议栈设计解决
业界主流协议解决方案
-
消息定长 (每个报文固定长度, 如果不够, 空位补空格)
-
在包尾增加回车换行符进行分割
- 将消息分为消息头和消息体, 消息头包含消息总长度
- 更复杂的应用层协议
Netty 解码器
Java序列化
Java 提供的对象输入/输出流ObjectInputStream
/ ObjectOutputStream
可以直接将Java对象作为可存储的字节数组写入文件, 也可以传输到网络中
Java序列化的主要目的
- 网络传输
- 对象持久化
Java 对象编解码技术
当进行远程跨进程服务调用时, 需要把传输的Java对象编码为字节数组或者ByteBuffer对象, 而当远程服务读取到ByteBuffer对象或者字节数组时, 需要将其解码为发送时的Java对象
Java序列化的缺点
-
无法跨语言
-
序列化后的码流太大
- 序列化性能太低
主流的编解码框架
-
Google Protobuf
- 结构化数据存储(xml, json)
- 高效的编解码性能
- 语言无关 扩展性好
-
MessagePack编解码
- 编解码高效, 性能高
- 序列化之后的码流小
评价一个编解码框架的优劣:
- 是否支持跨语言
- 编码后的码流大小
- 编解码的性能
- API友好程度
- 使用者开发难度和维护难度
推荐资料
- UNIX系统网络编程知识 <UNIX网络编程>
- Java bug问题 http://bugs.java.com/bugdatabase
疑问(代做)
-
半包消息
-
JDK默认的序列化机制
-
了解一下OpenSSL Heart bleed漏洞
- 长连接通信
- 心跳检测
- 防火墙