io的基本原理-nio

使用者bPcVmzR發表於2023-02-12

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高併發程式設計

相關文章