分布式网络通信框架Netty
深入Linux网络IO模型
学习总结
田超凡 2019年10月20日
转载请注明原作者
1 同步和异步处理
在IO处理模式中,同步指的是程序从上往下执行;异步是指重新开启一个新分支,相互不会影响;我们的Http协议请求默认情况下同步形式调用,如果调用过程非常耗时的情况下,客户端等待时间就非常长,这这种形式我们可以理解为阻塞式处理模式。
2 Linux网络IO模型
Linux常用的五种IO模型按照实现方式不同可以分为两类:同步IO模型和异步IO模型
2.1 阻塞I/O(BIO)

专业话术:
当我们在调用一个io函数的时候,如果没有获取到数据的情况下,那么就会一直等待;等待的过程中会导致整个应用程序一直是一个阻塞的过程,无法去做其他的实现。
白话文:
比如我们现在每特教育第六期要开班啦,由于口碑非常好、就业薪资非常高 这个时候有很多程序猿来排队报名学习;这个时候小军前面还有100个学员正在排队报名,小军必须要等待前面100个学员报名完成之后,轮训到小军才可以报名;小军为了保证当前的排队位置存在,也不能做其他的事情,这个过程我们可以称作为阻塞式;
阻塞IO的特点:高并发模式下执行阻塞,程序无法正常继续执行。
2.2 非阻塞I/O (NIO)
专业术语:
不管是否有获取到数据,都会立马获取结果,如果没有获取数据的话、那么就不间断的循环重试,但是我们整个应用程序不会实现阻塞。
白话文:
小军安排黄牛帮我们代替排队,每次间隔一段时间咨询黄牛 是否轮到自己呢。那么这个时候小军可以实现做其他的事情
非阻塞IO特点:非常消耗CPU的资源,但是程序不会阻塞。类似线程安全中的乐观锁(非阻塞IO模型)与悲观锁(阻塞IO模型)
2.3 I/O复用(select 和poll) (IOM)
专业术语:
IO复用实际指的就是网络的IO、多路也就是多个不同的tcp连接;复用也就是指使用同一个线程合并处理多个不同的IO操作,这样的话可以减少CPU资源。
白话文:
单个线程可以同时处理多个不同的io操作,应用场景非常广泛,如redis原理,Mysql连接原理,都是采用非阻塞IO模型+多路IO复用机制实现IO操作
2.4 信号驱动I/O (SIGIO)
专业术语:
发出一个请求实现观察监听,当有数据的时候直接走我们异步回调;
白话文:
小军在排队的时候 只需要去领取一个排队的号码,等到叫到了小军的时候才开始处理业务,这时候小军实际上还是可以去做其他的事情。
2.5异步I/O (AIO)
异步io也就是发出请求数据之后,剩下的事情完全实现异步完成。
3 NIO与BIO的核心区别
Java的NIO是在Jdk1.4版本之后推出了一套新的io方案,这种io方案对原有io做了一次性能上的升级。
NIO与BIO区别
BIO
|
Nio
|
面向流(Stream oriented)
|
面向缓冲区(Buffer oriented)
|
阻塞式(Blocking IO)
|
非阻塞式(Non blocking IO)
|
|
选择器(多路IO复用)(Selectors)
|
BIO(阻塞IO模型):当我们没有获取到数据的时候,整个应用程序会实现阻塞等待,不能实现做其他的事情。
NIO(非阻塞IO模型):不管是否有获取到数据,都必须立马获取到结果,如果没有获取数据的情况下,就会不断的重试获取数据,类似于cas、悲观和乐观锁。
BIO是面向文件流传输的,而NIO是面向缓冲区传输的,NIO最大的亮点就是具备选择器,实现多路IO复用机制。
4 NIO核心组件
4.1 选择器(Selector)
Selector可以称做为选择器,也可以把它叫做多路复用器,可以在单线程的情况下可以去维护多个Channel,也可以去维护多个连接;
4.2 通道(Channel)
通常我们nio所有的操作都是通过通道开始的,所有的通道都会注册到统一个选择器(Selector)上实现管理,在通过选择器将数据统一写入到 buffer中。
4.3 缓冲区(Buffer)
Buffer本质上就是一块内存区,可以用来读取数据,也就先将数据写入到缓冲区中、在统一的写入到硬盘上。
NIO设计思想伪代码
public class SocketNioTcpServer {
private static List<SocketChannel> listSocketChannel = new ArrayList<>();
private static ByteBuffer byteBuffer = ByteBuffer.allocate(512);
public static void main(String[] args) {
try {
// 1.创建一个ServerSocketChannel ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2. 绑定地址 serverSocketChannel.bind(new InetSocketAddress(8080)); serverSocketChannel.configureBlocking(false);
while (true) { SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel != null) { socketChannel.configureBlocking(false);
listSocketChannel.add(socketChannel); }
for (SocketChannel scl : listSocketChannel) {
try {
int read = scl.read(byteBuffer);
if (read > 0) {
byteBuffer.flip(); Charset charset = Charset.forName("UTF-8"); String receiveText = charset.newDecoder().decode (byteBuffer.asReadOnlyBuffer()).toString(); System.out.println("receiveText:" + receiveText); } } catch (Exception e) { e.printStackTrace(); } } } } catch (Exception e) { e.printStackTrace(); } } }
|
JDK1.4+ NIO模型底层实现代码
public class NIOServer {
/** * 创建一个选择器 */ private Selector selector;
public void initServer(int port) throws IOException {
// 获得一个ServerSocketChannel通道 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 设置通道为非阻塞 serverSocketChannel.configureBlocking(false);
// 将该通道对应的ServerSocket绑定到port端口 serverSocketChannel.bind(new InetSocketAddress(port));
// 获得一个通道管理器 this.selector = Selector.open();
// 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后, // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); }
public void listen() throws IOException { System.out.println("服务端启动成功!");
// 轮询访问selector while (true) {
// 当注册的事件到达时,方法返回;否则,该方法会一直阻塞 int select = selector.select();
if (select == 0) {
continue; }
// 获得selector中选中的项的迭代器,选中的项为注册的事件 Iterator<SelectionKey> ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next();
// 删除已选的key,以防重复处理 ite.remove();
if (key.isAcceptable()) {// 客户端请求连接事件 ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 获得和客户端连接的通道 SocketChannel channel = server.accept();
// 设置成非阻塞 channel.configureBlocking(false);
// 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。 channel.register(this.selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {// 获得了可读的事件 read(key); }
}
} }
public void read(SelectionKey key) throws IOException {
// 服务器可读取消息:得到事件发生的Socket通道 SocketChannel channel = (SocketChannel) key.channel();
// 创建读取的缓冲区 ByteBuffer buffer = ByteBuffer.allocate(512); channel.read(buffer);
byte[] data = buffer.array(); String msg = new String(data).trim(); System.out.println("服务端收到信息:" + msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes("utf-8")); channel.write(outBuffer);// 将消息回送给客户端 }
public static void main(String[] args) throws IOException { NIOServer server = new NIOServer(); server.initServer(8000); server.listen(); } }
|
在Linux系统中NIO的选择器采用的是Epoll,而Windows系统中选择器采用的是Selector
转载请注明原作者