io的基本原理-nio

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事件的回调函数,由内核去主动调用。

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高并发编程

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值