IO
内核态和用户态
操作系统根据不同操作分为应用程序级别操作和系统级别的操作
应用程序运行在用户态,不能随便操作系统资源,保证系统资源的安全,如果用户程序需要操作系统资源,那么就去调用系统函数在内核态中由操作系统执行
IO
计算机角度的IO
我们常说的IO,就是计算机的输入输出,计算机是主体
计算机分为五个部分:运算器、控制器、存储器、输入设备、输出设备
操作系统角度的IO
真正的IO是在操作系统执行的。即应用程序的IO操作分为两种动作:
IO调用和IO执行。IO调用是由进程(应用程序的运行态)发起,IO执行是操作系统内核的工作
IO模型
阻塞IO
应用程序的发起IO调用,如果内核数据没有准备好,那应用程序进程就会一直阻塞等待,直到内核数据准备好,从内核拷贝到用户空间,才返回成功提示,此次IO操作,称为阻塞IO
阻塞IO的应用阻塞socket、javaBIO.阻塞IO的缺点:浪费性能,可以使用非阻塞式IO
非阻塞IO
应用程序的发起IO调用,如果内核数据没有准备好,就返回错误信息给用户进程,让它不需要等待,而是通过轮询的方式再来请求。这就是非阻塞式IO
非阻塞IO流程:
- 应用程序向操作系统内核发起 recvfrom( )读取数据
- 操作系统内核数据没准备好,立即返回EWOULDBLOCK 错误码
- 应用程序进程轮询调用继续操作向系统内核发起 recvfrom( )读取数据
- 操作系统内核数据准备好了,从内核缓冲区拷贝到用户空间。
- 完成调用,返回提示
recvfrom()用来接收远程主机经socket 传来的数据,并把数据传到由参数
buf 指向的内存空间
非阻塞IO模型简称NIO,相对于阻塞IO,虽然提升了性能,但时它依然存在性能问题,即频繁的轮询,同样会消耗大量的CPU资源。可以考虑IO复用模型解决这个问题
IO多路复用
由于NIO无效的轮询会导致CPU资源消耗,所以我们可以等到内核数据准备好了,主动通知应用进程再去进行系统调用
IO复用模型核心思路:系统给我们提供了一类函数(select、poll、epoll),他们可以同时监视多个fd的操作,任何一个返回内核数据就绪,应用程序再发起recvfrom()系统调用
1.select():应用程序发起一次调用之后,就由select()函数负责监听,不需要轮询,当有数据准备好了,那么数据向用户态返回
select()可以同时监控多个 fd,在 select 函数监控的 fd中,只要有任何一个数据状态准备就绪了,select 函数就会返回可读状态,这时应用进程再发起 recvfrom( )请求去读取数据。
优点:不用轮询
缺点:监听连接数量有限制,最大监听1024.不能找到直接就绪的,需要对整个集合进行遍历,直到找到就绪的为止
2.poll( ) : 与select相同, 没有监听连接数量限制, 但同样需要遍历
3.epoll(): 给每一个fd绑定一个回调函数,当数据准备就绪后触发回调函数处理。
select、poll、epoll 的区别
信号驱动 IO 模型 :用户态向内核态发送一个信号,当内核态数据准备好了之后,内核态向用户态发送信息,用户态发起请求获取数据.
不管是 BIO,还是 NIO,还是信号驱动,在数据从内核复制到应用缓冲的时候,都是阻塞的。
异步 IO(AIO asynchronous IO)
AIO 是真正的非阻塞IO , 用户进程只需要发起一次调用,在内核态就会一次性直接将数据封装好,然后返回。
应用进程发出系统调用后,是立即返回的,但是立即返回的不是处理结果,而是表示提交成功类似的意思。等内核数据准备好,将数据拷贝到用户进程缓冲区,发送信号通知用户进程 IO 操作执行完毕。
阻塞、非阻塞、同步、异步 IO 划分:
同步阻塞(blocking-IO)简称 BIO
同步非阻塞(non-blocking-IO)简称 NIO
异步非阻塞(asynchronous-non-blocking-IO)简称 AIO
JavaNIO
JavaNIO是一个新的IO API,可以代替 Java IO API,NIO是支持缓冲区的、基于通道的IO操作。它可以以更高效的方式来进行文件的读写操作
阻塞IO(BIO)
在进行同步I/O操作时,读取、写入数据时,代码会阻塞直至数据准备就绪或能够写入时。
传统的 Server/Client 模式就是阻塞IO,为了解决阻塞IO,服务器和每个客户端请求建立一个线程,由该线程单独负责客户端的请求。但这样会导致线程数量激增,增加服务器的开销
非阻塞IO(NIO)
NIO中非阻塞I/O调用不会被阻塞,核心是注册特定的I/O事件
在发生特定事件时,系统再通知,NIO中实现非阻塞I/O的核心对象就是Selector
Selector就是注册各种I/O事件的地方
非阻塞指的是IO事件本身不阻塞,但是获取IO事件的select()方法是需要阻塞等待的
区别:BIO阻塞在IO操作上,NIO阻塞在事件获取上,没有事件就没有IO,从高层次上看IO不阻塞
NIO
Java NIO的核心组成成分
虽然Java NIO有很多类和组件,但核心就是 Channer、Buffer、Selector。
Channer(通道)
Channer 是一个对象,可以通过它读取、写入数据。所有数据通过Buffer对象处理,不会将数据直接写入通道,而是将数据写入缓冲区,再由缓冲区写入通道。读取数据同样如此。
主要实现:
FileChannel:从文件中读取数据
DatagramChannel:通过UDP读写网络中的数据
SocketChannel:通过TCP读写网络中的数据
ServerSocketChannel:j监听新进来的TCP连接
常用方法:
流对象.getChannel() 通过流对象获取管道
read(byteBuffer); 将数据导入缓存数据
write(byteBuffer);将数据从缓存数组写出
Buffer(缓冲区)
Buffer用于和Channel进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中
缓冲区本质是一块可以写入和读取数据的内存。
实现:ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer
对数据的读取/写入需要使用buffer,buffer本质是一个数组
常用方法: ByteBuffer.allocate(1024);创建字节数组
byteBuffer.clear();清除缓存,写操作前使用
byteBuffer.filp();翻转缓存区,读操作前使用
FileInputStream in = new FileInputStream("E:/source.txt");
FileOutputStream out = new FileOutputStream("E:/dest.txt");
FileChannel inchannel = in.getChannel();
FileChannel outchannel = out.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
while(inchannel.read(byteBuffer)!=-1){
byteBuffer.flip();
outchannel.write(byteBuffer);
byteBuffer.clear();
}
Selector(选择器)
选择器可以监听多个通道,一旦某个通道有事件(读,写,连接)发生,都会被选择器捕捉.
一个选择器可以监听多个通道,是非阻塞的,通过一个线程,就可以处理多个通道任务.
NIO 案例:
List<SocketChannel> list = new ArrayList<>();
ServerSocketChannel serverSogketChannel = ServerSocketChannel.open();//创建服务器
serverSogketChannel.bind(new InetSocketAddress(9999), 1024);
serverSogketChannel.configureBlocking(false);//设置非阻塞
//注册选择器
Selector selector = Selector.open();//创建选择器
serverSogketChannel.register(selector, SelectionKey.OP_ACCEPT);//向selector注册管道
System.out.println("启动服务器");
for (; ; ) {
selector.select();
Set<SelectionKey> selectionKeys = selector.selectedKeys();//返回所有选择器接收到的操作
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {//连接
ServerSocketChannel serverSockChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel acceptSocketChannel = serverSockChannel.accept();
System.out.println(acceptSocketChannel.getRemoteAddress());
acceptSocketChannel.configureBlocking(false);
acceptSocketChannel.register(selector, SelectionKey.OP_WRITE);
}
if (selectionKey.isWritable()) {//写
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
String resp = "响应";
try {
Thread.sleep(500);
socketChannel.write(ByteBuffer.wrap(resp.getBytes()));
}catch(Exception e){
e.printStackTrace();
}
}
if(selectionKey.isReadable()){//读
SocketChannel channel = (SocketChannel)selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int length = channel.read(buffer);
String msg = "server receive msg:" + new String(buffer.array(), 0, length);
System.out.println(msg);
}
iterator.remove();
}
}