关于io:io的基本原理nio

38次阅读

共计 5350 个字符,预计需要花费 14 分钟才能阅读完成。

I/O(Input/Output)是计算机科学中指计算机和外部设备进行数据交换的过程。I/ O 模型是指用于治理过程和设施之间数据传输的技术。

io 读写的基本原理

操作系统将内存(虚拟内存)划分为两局部:一部分是内核空间(Kernel-Space),另一部分是用户空间(User-Space)
应用程序不容许间接在内核空间区域进行读写,也不容许间接调用内核代码定义的函数。每个应用程序过程都有一个独自的用户空间,对应的过程处于用户态,用户态过程不能拜访内核空间中的数据,也不能间接调用内核函数,因而须要将过程切换到内核态能力进行零碎调用。

上面一个 read 或者一次 write 指令的大抵流程:
通过零碎调用,抉择不同的内核函数进行状态切换,而后把内核缓冲区的数据复制到用户缓冲区中,(内核缓冲区是惟一的)

io 的模型

1. 同步阻塞 IO

首先,解释一下阻塞与非阻塞。阻塞 IO 指的是须要内核 IO 操作彻底实现后才返回到用户空间执行用户程序的操作指令。“阻塞”指的是用户程序(发动 IO 申请的过程或者线程)的执行状态。能够说传统的 IO 模型都是阻塞 IO 模型,并且在 Java 中默认创立的 socket 都属于阻塞 IO 模型。
其次,解释一下同步与异步。简略来说,能够将同步与异步看成发动 IO 申请的两种形式。同步 IO 是指用户空间(过程或者线程)是被动发动 IO 申请的一方,零碎内核是被动接管方。异步 IO 则反过来,零碎内核是被动发动 IO 申请的一方,用户空间是被动接管方。
同步阻塞 IO(Blocking IO)指的是用户空间(或者线程)被动发动,须要期待内核 IO 操作彻底实现后才返回到用户空间的 IO 操作。在 IO 操作过程中,发动 IO 申请的用户过程(或者线程)处于阻塞状态。

2. 同步非阻塞 IO

非阻塞 IO(Non-Blocking IO,NIO)指的是用户空间的程序不须要期待内核 IO 操作彻底实现,能够立刻返回用户空间去执行后续的指令,即发动 IO 申请的用户过程(或者线程)处于非阻塞状态,与此同时,内核会立刻返回给用户一个 IO 状态值。
阻塞和非阻塞的区别是什么呢?阻塞是指用户过程(或者线程)始终在期待,而不能做别的事件;非阻塞是指用户过程(或者线程)取得内核返回的状态值就返回本人的空间,能够去做别的事件。在 Java 中,非阻塞 IO 的 socket 被设置为 NONBLOCK 模式。
阐明
同步非阻塞 IO 也能够简称为 NIO,然而它不是 Java 编程中的 NIO。Java 编程中的 NIO(New IO)类库组件所归属的不是根底 IO 模型中的 NIO 模型,而是 IO 多路复用模型。
同步非阻塞 IO 指的是用户过程被动发动,不须要期待内核 IO 操作彻底实现就能立刻返回用户空间的 IO 操作。在 IO 操作过程中,发动 IO 申请的用户过程(或者线程)处于非阻塞状态。

3. IO 多路复用

为了进步性能,操作系统引入了一种新的零碎调用,专门用于查问 IO 文件描述符(含 socket 连贯)的就绪状态。在 Linux 零碎中,新的零碎调用为 select/epoll 零碎调用。通过该零碎调用,一个用户过程(或者线程)能够监督多个文件描述符,一旦某个描述符就绪(个别是内核缓冲区可读 / 可写),内核就可能将文件描述符的就绪状态返回给用户过程(或者线程),用户空间能够依据文件描述符的就绪状态进行相应的 IO 零碎调用。
IO 多路复用(IO Multiplexing)属于一种经典的 Reactor 模式实现,有时也称为异步阻塞 IO,Java 中的 Selector 属于这种模型。

4. 异步 IO

异步 IO(Asynchronous IO,AIO)指的是用户空间的线程变成被动接收者,而内核空间成为被动调用者。在异步 IO 模型中,当用户线程收到告诉时,数据曾经被内核读取结束并放在了用户缓冲区内,内核在 IO 实现后告诉用户线程间接应用即可。
异步 IO 相似于 Java 中典型的回调模式,用户过程(或者线程)向内核空间注册了各种 IO 事件的回调函数,由内核去被动调用。
Java AIO 也被称为 NIO2.0,提供了异步 I / O 的形式,用法和规范的 I / O 有十分大的差别。

5. 半同步半阻塞半异步 IO

目前在 Thrift 中有所体现(ThreadedSelectorServer)
半同步半阻塞半异步 I / O 是一种计算机网络通信模型,是一种折衷的解决方案,旨在均衡同步 I /O、阻塞 I / O 和异步 I / O 的优缺点。
在半同步半阻塞半异步 I / O 模型中,服务器端通过阻塞形式期待客户端的连贯申请,一旦建设连贯,服务器端将该连贯转换为异步形式解决申请。这样既能够解决同步 I / O 的性能问题,又能够保障客户端的申请不会被服务器端长时间阻塞。
半同步半阻塞半异步 I / O 模型在性能和可靠性方面较好的均衡了同步 I / O 和异步 I / O 的优缺点,因而在许多网络系统中失去了宽泛的利用。

nio 是什么?

NIO 是“New I/O”的缩写,是一组 Java API,提供非阻塞、可扩大和高性能的 I / O 操作。NIO 在 Java 1.4 中被引入,作为传统的阻塞 I /O(BIO)模型的替代品,用于解决高性能和高并发利用。NIO 提供了若干要害个性,如通道、缓冲区、选择器和非阻塞 I / O 操作,使得 Java 应用程序可能更无效、高效和可扩大地解决 I / O 操作。NIO 宽泛用于各种类型的应用程序,包含服务器、代理和其余网络应用程序。

NIO 的外围原理:

** 缓冲区:NIO 应用缓冲区(Buffer)作为数据容器,数据读写时都是通过缓冲区进行的。
通道:NIO 应用通道(Channel)作为数据的读写入口,所有的数据读写操作都是通过通道实现的。
选择器:NIO 应用选择器(Selector)来治理多个通道,当通道筹备好读写操作时,选择器可能疾速地发现并告诉相应的线程。**

在 NIO 中,线程不再须要阻塞期待 I/O 操作实现,而是在读写操作实现时由选择器告诉线程,这大大提高了零碎的效率。

java 版代码

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

public class NioServer {public static void main(String[] args) throws IOException {Selector selector = Selector.open();
    ServerSocketChannel serverSocket = ServerSocketChannel.open();
    serverSocket.bind(new InetSocketAddress("localhost", 8080));
    serverSocket.configureBlocking(false);
    serverSocket.register(selector, SelectionKey.OP_ACCEPT); // 示意示通道感兴趣的事件类型 SelectionKey.OP_ACCEPT,从而将通道注册到选择器中。while (true) {selector.select();
      Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
      while (keys.hasNext()) {SelectionKey key = keys.next();
        keys.remove();
        if (!key.isValid()) {continue;}
        if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();
          SocketChannel client = server.accept();
          client.configureBlocking(false);
          client.register(selector, SelectionKey.OP_READ); // 将一个通道注册到选择器中
        } else if (key.isReadable()) {SocketChannel client = (SocketChannel) key.channel();
          ByteBuffer buffer = ByteBuffer.allocate(256);
          int read = client.read(buffer);
          if (read == -1) {client.close();
            key.cancel();
            continue;
          }
          buffer.flip();
          client.write(buffer);
        }
      }
    }
  }
}

cpp 版本

#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <vector>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>

using namespace std;

int set_nonblock(int fd) {
  int flags;
#if defined(O_NONBLOCK)
  if (-1 == (flags = fcntl(fd, F_GETFL, 0))) {  // 获取文件描述符 fd 的标记
    flags = 0;
  }
  return fcntl(fd, F_SETFL, flags | O_NONBLOCK); // flags 和 O_NONBLOCK 标记进行按位或运算,从而将 O_NONBLOCK 标记增加到原有的标记中。设置文件描述符 fd 为非阻塞模式
#else
  flags = 1;
  return ioctl(fd, FIOBIO, &flags); // 文件描述符设置为非阻塞模式。ioctl 操作通常用于特定的设施驱动程序
#endif
}

int main(int argc, char **argv) {int MasterSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  vector<int> SlaveSockets;

  sockaddr_in SockAddr;
  SockAddr.sin_family = AF_INET;
  SockAddr.sin_port = htons(12345);
  SockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  bind(MasterSocket, (sockaddr *)&SockAddr, sizeof(SockAddr));

  set_nonblock(MasterSocket); // 将 MasterSocket 设置成非阻塞状态

  listen(MasterSocket, SOMAXCONN);

  while (true) {
    fd_set Set;  
    FD_ZERO(&Set);  /* 将 set 清零使汇合中不含任何 fd*/
    FD_SET(MasterSocket, &Set);  /* 将 MasterSocket 退出 set 汇合 */
    for (int Slave : SlaveSockets) {FD_SET(Slave, &Set);  /* 将 Slave 注册到选择器中 */
    }

    int Max = max(MasterSocket, *max_element(SlaveSockets.begin(),
                                             SlaveSockets.end()));
    select(Max + 1, &Set, NULL, NULL, NULL); // 监督的最大文件描述符加 1

    if (FD_ISSET(MasterSocket, &Set)) { //*MasterSocket 是否在 set 汇合中 */
      int Slave = accept(MasterSocket, 0, 0); // 期待连贯
      set_nonblock(Slave);  // 设置非阻塞
      SlaveSockets.push_back(Slave);
    }

    for (int Slave : SlaveSockets) {if (FD_ISSET(Slave, &Set)) {static char Buffer[1024];
        int RecvSize = recv(Slave, Buffer, 1024, MSG_NOSIGNAL); // 从而从套接字中接收数据并将其存储到 Buffer 缓冲区中。if ((RecvSize == 0) && (errno != EAGAIN)) {shutdown(Slave, SHUT_RDWR);
          close(Slave);
          SlaveSockets.erase(remove(SlaveSockets.begin(),
                                     SlaveSockets.end(), Slave));
        } else if (RecvSize > 0) {send(Slave, Buffer, RecvSize, MSG_NOSIGNAL); // 将
        }
      }
    }
  }

  return 0;
}

摘要:
java 高并发编程

正文完
 0