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高併發編程