初识Netty-前置知识
一、基本概念
1、IO的概念
- input-读入数据:线程从某个源头读入数据放到缓冲区
- output-输出数据:线程向某个目的地输出缓冲区内的数据
注:输入输出数据一般都需要操作系统支持,有些特殊情况不需要,如嵌入式(无操作系统)
2、 IO模型
数据不会自主地从缓冲区进出到外部设备中,因此需要有线程等类似的驱动者去驱动数据的流动,即数据在线程驱动下,从缓冲区通过端口(如socket或其他)进出到外部设备,而IO的参照对象是缓冲区Buffer,进Buffer为读入,出Buffer为输出
- 驱动者:驱动数据输入输出的线程或进程或控制流(cpu或类似的东西)
- Buffer:缓冲区
- socket:端口
3、IO的同步异步和阻塞非阻塞
(1)同步和异步
标准:不同的动作是否按照固定的顺序执行,同步是按照固定顺序执行,俗称排队,异步反之
IO概念中即是指,在IO请求返回和IO动作尝试之间是否有一个明确的顺序
- 同步:IO请求返回的时候,IO尝试动作已经做完了,即返回的动作明确发生在IO尝试之后
- 异步:请求返回只表明收到了请求,对实际的IO尝试不做任何保证,即在请求结果返回之前,IO动作可能其实已经做完了。如光纤传数据,有可能发送数据的指令刚做完,IO光纤就已经将数据发送完了,而cpu还正在执行return代码
异步举例:Linux的AIO是纯异步的,其他IO基本都是同步的
可参见:linux AIO_aio linux_西门吹大雪的博客-CSDN博客
(2)IO阻塞和非阻塞
一般在同步下才有意义
- 阻塞:彻底完成IO动作,或达到某个条件(如超时),或发生错误之后才返回请求
- 非阻塞:尝了IO的动作后就返回请求,不保证全部IO动作都完成之后才返回
(3)组合
- 同步+阻塞:IO动作既是按照顺序执行的又保证每个IO动作有个终结性结果才返回请求(一般)
- 同步+非阻塞:动作按顺序执行,但不保证每个IO动作有个终结性结果才返回请求(多线程)
- 异步+阻塞:动作不按顺序执行,但每个IO动作有个终结性结果才返回请求
- 异步+非阻塞:动作既不按照顺序执行,也不保证每个IO动作有个终结性结果才返回请求
二、各层的IO
(一)操作系统OS中IO(POSIX接口为例)
1、特点
- 操作系统提供了封装好的IO操作接口,屏蔽了IO动作细节
- 接口为文件形式操作接口:open、close、read、write、ioctl(一切皆文件)
- 应用线程或进程自己去确定调用IO接口的时机(驱动方为线程或进程)
- 提供稳定的同步(阻塞或非阻塞)IO接口(默认阻塞)和异步接口(AIO)
2、同步阻塞IO接口
(1)特点
- 阻塞模式下,一个线程处理一个对象
- 响应最快,cpu资源最省
(2)问题
需要处理的IO对象多了,就需要很多的线程,随之而来多线程的问题:
- 线程内存消耗(每个线程都需要对应的内存)
- 线程调度操作变多
- 线程/进程切换会导致Cache line,进程内存映射的TLB块表失效等问题,需要大量的cpu来处理线程切换
(3)解决多线程带来的资源损耗问题--减少线程数
- IO对象设置为同步非阻塞模式,提供专门的轮询线程去管理所有需要做IO的端口并交给对应的业务线程去做
- 问题:IO操作是没有时表不确定的,因此大量cpu浪费在轮询上,如果设置轮询线程的sleep时间,又会增加响应时间
(4)解决轮询线程空转问题---通知机制select
- 由于IO的状态变化是通过OS的动作来的,因此通过OS内核的支持将轮询改为通知
- 这样的通知机制抽象为一个IO状态通知器(同步阻塞/非阻塞形式的接口)
- 特点:
<1>通知器本身不做任何IO操作,只负责状态变更通知管理
<2>接口的形式与OS强相关
注:上述方法讲述的是如何用更少的线程 解决更多的IO,另一种方式是通过减少资源消耗大方面,让线程变得轻量级,从内核级别到用户级别:
•
用户级线程,
Windows Fiber
,协程,用户级通知库
libevent•
Java 中Project Loom, go中
Goroutine
,
Erlang•
宏
内核
/
微
内核•
软件固件化(电路的并行化特点)
(二)JAVA中IO
1、JVM层
- JAVA基于虚拟机,屏蔽系统差异性,操作系统解决的问题在虚拟机层面上要重新解决一次(以JAVA的接口形式提供)
- 一个java的线程对应一个OS的线程
- 旧IO(OIO) ----线程太多开销大----轮询线程CPU浪费----提供IO通知器的新IO(NIO)
2、OIO和NIO的类
(1)OIO(包括同步阻塞BIO和同步非阻塞)
java.net.ServerSocket
java.net.Socket
java.net.DatagramSocket
(2)NIO
java.nio.Buffer
java.nio.channels.Channel
java.nio.channels.Selector
3、两个IO对比
(1)OIO
OIO中的Socket基于OS的同步IO接口完成IO动作,提供的是流式的接口,不是基于buffer的
(2)NIO
•
各种
Channel
基于
OS
的同步
IO
接口做
IO
动作•
Java
又基于
OS
提供的通知机制,面向用户提供纯
Java
语言统一的
IO
通知接口
Selector•
在不同的
OS
上依赖的接口是不一样的
3、详解NIO
(1)NIO的使用
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("localhost", 80));
//默认是同步阻塞的,设置成非阻塞模式,防止selector阻塞在channel中
channel.configureBlocking(false);
Selector selector = Selector.open();
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
int readyChannels = selector.selectNow();
if(readyChannels == 0) continue;
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
while(keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if(key.isReadable()) {
// a channel is ready for reading
..... //Dispatch to process reading
}
keyIterator.remove();
}
}
}
(2)流程图
(3)组成
- Buffer---最基础的实现为ByteBuffer,可以理解为扩展了功能的byte数组(byte[]),支持字节转换成其他基本类型
注:directBuffer较heapBuffer少一次copy,为保证数据一致性,对于堆内的数据受GC影响,因此使用堆数据之前先将堆内的数据copy到用户区,然后再进行后续操作
- selector---java层面实现的IO通知器
<1>定义了4种可以侦听和通知的事件,当通知某个Channel发生了事件时,准确的含义是“这个Channel准备好了做这个事件”:Accept, Connect, Read, Write
<2>当Channel被注册到Selector上时,会获得一个SelectionKey,作为存根,以便后续的操作
•
InterestSet
,
感兴趣,也就是期望侦听的事件•
ReadySet
,准备好了的事件•
Selector
,
Channel
和
AttachedObject
(
即用户自定义数据
)
<3>选择(侦听)动作(方法)
•
int
select()•
i
nt
select(long
timeout
)•
i
nt
selectNow
()
<4>唤醒等待中的Selector
•
有期望的事件发生•
其他线程调用
wakeUp
()
或者
close()•
有异常发生
(三)Nettyt中IO
三层IO的调用关系: