
用户进程一直等待数据准备好,在复制完成之前都是阻塞的

用户进程需要不断轮询查看是否数据准备好
优化了提升并发连接数量,但是每一个请求都需要创建一个socket建立连接,每个线程都需要去遍历轮询,导致cpu的消耗,直到数据返回成功

单个线程可以处理多个网络连接。相对于BIO多了一步,注册到selector的过程,进程被selector函数阻塞。
select单个进程所能打开的fd()【就是文件描述符,linux内把所有的外部设备都看成一个文件操作】,对于每一个文件的读写操作都会调用内核提供的系统命令来返回给fd,对于socket的读写也会返回一个fd,所以放fd的时候,说明数据是可读或者是可写。
所以这个模型在单进程里,操作的连接数默认是1024,可以修改,但是会带来网络性能的下降,因为会去加大轮询次数,带来网络延迟,因为只有少数连接处于活跃状态,而每次轮询是查询所有的连接。
jdk1.6之前是使用这种模型
jdk1.6后使用此模型。解决select-poll的缺陷


基于通道和缓冲区操作的:
非阻塞的:
选择器:
FileChannel:从文件中读写数据(不适合Selector,因为不能非阻塞)
DatagramChannel:通过UDP协议读写网络中的数据
SocketChannel:通过TCP协议读写网络中的数据
ServerSocketChannel:监听一个TCP连接,对于每一个新的客户端连接都会创建一个SocketChannel。
buffer是一个对象,包含需要写入或者刚读出的数据,常用的缓冲区类型是ByteBuffer
- public class aaa {
- public static void main(String[] args) throws IOException {
- try {
- FileInputStream fis = new FileInputStream(new File("D:/test.txt"));
- FileOutputStream fos = new FileOutputStream(new File("D:/test.txt"));
- FileChannel fin = fis.getChannel();
- FileChannel fout = fos.getChannel();
-
- //初始化一个缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- //读取数据到缓冲区
- fin.read(buffer);
- //从读转化为写
- buffer.flip();
- fout.write(buffer);
- //重置缓冲区
- buffer.clear();
-
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
| 类型 | 操作区域 | 处理数据 | IO |
| IO | 面向最初的数据源 | 每次读取时=读取所有字节或字符,无缓存 无法前后移动读取流中的数据 | 当一个线程在读或者写的时候,当数据被完全读取/写入之后,并且数据未准备好时,线程不能做其他任务,只能一直等待。 当线程处于活跃状态并且外部未准备好时,阻塞。 |
| NIO | 面相缓冲区 | 先将数据读取到缓冲区 可在缓冲区前后移动数据流 | 当一个线程向某个通道发送请求时,当数据被完全读取/写入,并且数据未准备好时,线程可以操作其他任务,直到数据准备后再切换回原通道,继续读写,也就是selector的使用。 外部准备好时才唤醒线程,则不会阻塞。 |
缓冲区本质是一块可以写入数据,以及从中读取数据的内存,实际上也是一个byte[]数据,只是在NIO中被封装成了NIO Buffer对象,并提供了一组方法来访问这个内存块。
- capacity:容量
- position:位置。读是跟踪从缓冲区读取了多少数据;写是数据放入数组哪个位置;
- limit:写是还有多少数据取出来写到通道;读是还有多少空间可以放出去;
例如:写入
初始话时,capacity是8,就是一个8长度的格子,当写入“hello”的时候,就是ficn.reds("hello);时,postion和limit都会指向4(从0开始),当flip()时,position指向0,limit是4,0-4就是要写入的字符,最后write()写入。
IO流程:内核给磁盘发送命令需要读取磁盘的数据,在DMA的控制下,把磁盘上的数据读入到内核缓冲区,内核把数据从内核缓冲区复制到用户缓冲区。用户缓冲区再将数据拷贝到Socket buffer(也是内核),最后发送到网卡缓冲区,四个步骤。
设计到一个用户态到内核态的切换,影响cpu的性能。

从内核态到用户缓冲区没有用,为什么要设计呢
为了提升IO性能,假设应用程序进行读,内核缓冲区对于读相当于一个缓冲空间,当用户只读取一小部分数据的时候,但是内核从磁盘会读取一块数据,下次用户再读其他的数据的时候,在内核缓冲区已经存在就不需要再去磁盘获取,从这个角度是提升了性能的。
使用内存映射缓存mmap,通过内核和用户空间映射同一块内存空间,来减少内存复制。
零拷贝就是减少拷贝次数,减少内核态和用户态之间的数据的复制,提升IO性能。

- public class aaa {
- public static void main(String[] args) throws IOException {
- try {
- FileChannel in = FileChannel.open(Paths.get("D:/logo.png"), StandardOpenOption.READ);
- FileChannel out = FileChannel.open(Paths.get("D:/logo_cp.png"), StandardOpenOption.READ, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
-
- MappedByteBuffer inMap = in.map(FileChannel.MapMode.READ_ONLY, 0, in.size());
- MappedByteBuffer outMap = in.map(FileChannel.MapMode.READ_WRITE, 0, in.size());
-
- byte[] bytes = new byte[inMap.limit()];
- inMap.get(bytes);
- outMap.put(bytes);
-
- in.close();
- out.close();
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
-
- public class ccc {
- public static void main(String[] args) {
- try {
- SocketChannel socketChannel = SocketChannel.open();
- socketChannel.connect(new InetSocketAddress("localhost", 9090));
- FileChannel channel = new FileInputStream("D:/1.txt").getChannel();
- int position = 0;
- long size = channel.size();
- while (size > 0) {
- long tf = channel.transferTo(position, size, socketChannel);
- if (tf > 0) {
- position += tf;
- size = tf;
- }
- }
- socketChannel.close();
- channel.close();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- public class ddd {
- public static void main(String[] args) {
- try {
- ServerSocketChannel channel = ServerSocketChannel.open();
- channel.socket().bind(new InetSocketAddress(9090));
- SocketChannel accept = channel.accept();
- ByteBuffer allocate = ByteBuffer.allocate(1024);
- int r = 0;
- FileChannel fileChannel = new FileOutputStream("D:/text_cp.txt").getChannel();
- while (r != -1){
- r = accept.read(allocate);
- allocate.flip();
- fileChannel.write(allocate);
- allocate.clear();
- }
-
- } catch (IOException e) {
- e.printStackTrace();
- }
-
- }
- }
服务端
- public class aaa {
- public static void main(String[] args) throws IOException {
- try {
- //支持两种模式:阻塞、非阻塞
- ServerSocketChannel open = ServerSocketChannel.open();
- open.configureBlocking(false);
- //绑定端口号
- open.socket().bind(new InetSocketAddress(9090));
- while (true) {
- SocketChannel accept = open.accept();
- //存在连接
- if (accept != null) {
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- accept.read(buffer);
- System.out.println(new String(buffer.array()));
- //再把消息写回到客户端
- Thread.sleep(10000);
- buffer.filp();
- accept.write(buffer);
- } else {
- Thread.sleep(1000);
- System.out.println("无客户端连接");
- }
- }
- } catch (FileNotFoundException | InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
客户端
- public class aaa {
- public static void main(String[] args) throws IOException {
- try {
- SocketChannel open = SocketChannel.open();
- //把客户端设置为非阻塞,在非阻塞模式下,不一定是等到连接建立之后再往下执行
- open.configureBlocking(false);
- open.connect(new InetSocketAddress("localhost", 9090));
- if(open.isConnectionPending()) {
- open.finishConnect();
- }
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- buffer.flip();
- open.write(buffer);
- //读取服务端返回的数据
- buffer.clear();
- //非阻塞模式,这里不阻塞
- int r = open.read(buffer);
- if(r > 0) {
- System.out.println("收到服务端的消息" + new String(buffer.array()));
- }
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- }
- }
- }
一个单独的线程可以管理多个channel,从而管理多个网络连接。

- public class aaa {
-
- static Selector selector;
-
- public static void main(String[] args) throws IOException {
- //创建一个多路复用器
- Selector selector = Selector.open();
- ServerSocketChannel channel = ServerSocketChannel.open();
- //连接的非阻塞
- channel.configureBlocking(false);
- channel.socket().bind(new InetSocketAddress(9090));
- //监听连接事件,会返回一个SelectionKey,通道的唯一标识
- channel.register(selector, SelectionKey.OP_ACCEPT);
- //轮询
- while (true) {
- //阻塞,所有注册到复用器上事件
- selector.select();
- //一旦某个channel准备就绪,就返回他们的key
- Set<SelectionKey> selectionKeys = selector.selectedKeys();
- Iterator<SelectionKey> iterator = selectionKeys.iterator();
- while (iterator.hasNext()) {
- //一定是已经就绪的通道
- SelectionKey next = iterator.next();
- //拿到通道后,可以处理了,避免重复处理
- iterator.remove();
- if(next.isAcceptable()){
- //连接事件
- HandleAccept(next);
- }else if(next.isReadable()){
- //读事件
- HandleRead(next);
- }
- }
- }
-
- }
-
- private static void HandleAccept(SelectionKey selectionKey) throws IOException {
- ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
- SocketChannel accept = serverSocketChannel.accept();
- //IO的非阻塞
- accept.configureBlocking(false);
- accept.write(ByteBuffer.wrap("Server write".getBytes()));
- //注册的是accept的读事件
- accept.register(selector, SelectionKey.OP_ACCEPT);
- }
-
- private static void HandleRead(SelectionKey selectionKey) throws IOException {
- SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- socketChannel.read(buffer);
- System.out.println("Server receive msg: " + new String(buffer.array()));
-
- }
- }
- public class bbb {
- static Selector selector;
-
- public static void main(String[] args) {
- try {
- selector = Selector.open();
- SocketChannel socketChannel = SocketChannel.open();
- socketChannel.configureBlocking(false);
- socketChannel.connect(new InetSocketAddress("localhost", 9090));
- socketChannel.register(selector, SelectionKey.OP_CONNECT);
- while (true) {
- selector.select();
- Set<SelectionKey> selectionKeys = selector.selectedKeys();
- Iterator<SelectionKey> iterator = selectionKeys.iterator();
- while (iterator.hasNext()) {
- SelectionKey next = iterator.next();
- iterator.remove();
- if(next.isConnectable()){
- //连接事件
- HandleConnect(next);
- }else if(next.isReadable()){
- //读事件
- HandleRead(next);
- }
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- private static void HandleConnect(SelectionKey selectionKey) throws IOException {
- SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
- if(socketChannel.isConnectionPending()){
- socketChannel.finishConnect();
- }
- socketChannel.configureBlocking(false);
- socketChannel.write(ByteBuffer.wrap("Client receive".getBytes()));
- socketChannel.register(selector, SelectionKey.OP_READ);
- }
-
- //读取服务端返回的数据
- private static void HandleRead(SelectionKey selectionKey) throws IOException {
- SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
- ByteBuffer buffer = ByteBuffer.allocate(1024);
- socketChannel.read(buffer);
- System.out.println("Client receive msg: " + new String(buffer.array()));
- }
- }
9、BIO\NIO\AIO的区别
| BIO | NIO | AIO | |
| 阻塞 | 阻塞:一个线程执行IO操作会被阻塞 | 非阻塞:线程可以同时处理多个IO请求 | 非阻塞 |
| 同步 | 同步:需要等待IO操作完成后才能继续执行 | 异步:轮询方式=channel+buffer+selector | 回调机制 |
| 处理 | 面向流 | 缓冲区 | 面向事件 |
| 并发 | 低,需要创建大量线程 | 通过单线程或少量线程处理 | 同左 |
| 场景 | 连接数少且吞吐量不高 | 连接数较多但请求量较小 | 高并发 |