IO模型
IO模型就是說用什么樣的通道進行數(shù)據(jù)的發(fā)送和接收械念,Java共支持3種網(wǎng)絡(luò)編程IO模式:BIO夺巩,NIO板熊,AIO
BIO (Blocking IO)
同步阻塞IO模型蛋济,一個客戶端對應(yīng)一個服務(wù)端
服務(wù)端:
@Slf4j
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
log.info("服務(wù)端已啟動西设,等待連接");
// 阻塞
Socket socket = serverSocket.accept();
log.info("客戶端已連接");
// 單線程處理鏈接
// handler(socket);
// 多線程處理鏈接
new Thread(new Runnable() {
@SneakyThrows
@Override
public void run() {
log.info("local-thread-{}", Thread.currentThread().getName());
handler(socket);
}
}).start();
}
}
private static void handler(Socket socket) throws IOException {
byte[] bytes = new byte[1024];
log.info("獲取客戶端發(fā)送數(shù)據(jù)");
// 接收數(shù)據(jù)起宽,阻塞方法,沒有數(shù)據(jù)可讀時就阻塞
int read = socket.getInputStream().read(bytes);
if (read != -1) {
log.info("接收客戶數(shù)據(jù): {}", new String(bytes, 0, read));
}
// 響應(yīng)客戶端
OutputStream outputStream = socket.getOutputStream();
outputStream.write("server is connecting".getBytes());
outputStream.flush();
}
}
客戶端:
@Slf4j
public class BIOClient {
private static final String HOST = "localhost";
private static final int PORT = 8080;
public static void main(String[] args) throws IOException {
Socket socket = new Socket(HOST, PORT);
// 發(fā)送數(shù)據(jù)
OutputStream os = socket.getOutputStream();
os.write("request server connect".getBytes());
os.flush();
// 接收數(shù)據(jù)
byte[] bytes = new byte[1024];
InputStream is = socket.getInputStream();
int read = is.read(bytes);
if (read != -1) {
log.info("接收到來自服務(wù)端的數(shù)據(jù): {}", new String(bytes, 0, read));
}
socket.close();
}
}
缺點
- 1济榨、IO代碼里
read
操作是阻塞操作坯沪,如果連接不做數(shù)據(jù)讀寫操作會導致線程阻塞,浪費資源 - 2擒滑、如果線程很多腐晾,會導致服務(wù)器線程太多叉弦,壓力太大,比如C10K問題
應(yīng)用場景
BIO 方式適用于連接數(shù)目比較小且固定的架構(gòu)藻糖,這種方式對服務(wù)器資源要求比較高淹冰,但程序簡單易理解。
NIO (NON Blocking IO)
同步非阻塞IO模型巨柒,服務(wù)器實現(xiàn)模式為一個線程可以處理多個請求(連接)樱拴,客戶端發(fā)送的連接請求都會注冊到多路復用器selector
上,
多路復用器輪詢到連接有IO請求就進行處理洋满,JDK1.4開始引入晶乔。
普通模型:
@Slf4j
public class NIOServer {
static List<SocketChannel> channelList = Lists.newArrayList();
public static void main(String[] args) throws IOException {
// 創(chuàng)建 NIO 通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 綁定服務(wù)端口地址
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
// 設(shè)置通道為非阻塞模式
serverSocketChannel.configureBlocking(false);
log.info("服務(wù)端已啟動,等待連接");
while (true) {
// 非阻塞模式 accept() 方法不會阻塞牺勾。阻塞模式則會阻塞正罢,即 socketChannel.configureBlocking(ture)
// NIO的非阻塞是由操作系統(tǒng)內(nèi)部實現(xiàn)的,底層調(diào)用了linux內(nèi)核的accept函數(shù)
SocketChannel socketChannel = serverSocketChannel.accept();
if (!ObjectUtils.isEmpty(socketChannel)) {
log.info("客戶端已連接: {}", socketChannel.getRemoteAddress());
socketChannel.configureBlocking(false);
// 連接成功放到 channelList 中
channelList.add(socketChannel);
}
// 讀取 channel
Iterator<SocketChannel> iterator = channelList.iterator();
while (iterator.hasNext()) {
SocketChannel channel = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
// 非阻塞模式 read() 方法不會阻塞驻民。阻塞模式則會阻塞
int read = channel.read(byteBuffer);
if (read > 0) {
log.info("接收客戶 {}, 數(shù)據(jù): {}", channel.getRemoteAddress(), new String(byteBuffer.array()));
} else if (read < 0) {
//
iterator.remove();
log.info("客戶端已斷開連接");
}
}
}
}
}
如上翻具,如果有很多連接,每一個連接都需要通過iterator
遍歷獲取數(shù)據(jù)回还,如果該連接無數(shù)據(jù)發(fā)送裆泳,則會產(chǎn)生很多無用的遍歷。
多路復用器模型:
@Slf4j
public class NIOSelectorServer {
public static void main(String[] args) throws IOException {
// 創(chuàng)建 NIO 通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 綁定服務(wù)端口地址
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
// 設(shè)置通道為非阻塞模式
serverSocketChannel.configureBlocking(false);
// 打開 Selector 處理 Channel柠硕,即創(chuàng)建 epoll
Selector selector = Selector.open();
// Channel 注冊到 selector 上工禾,并 selector 對客戶端 accept 操作監(jiān)聽
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
log.info("服務(wù)端已啟動,等待連接");
while (true) {
// 阻塞等待需要處理的事件發(fā)生
selector.select();
// 獲取 selector 中注冊的全部事件中的 selectedKeys 實例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
// 遍歷對 selectionKeys 事件進行處理
while (keyIterator.hasNext()) {
SelectionKey selectionKey = keyIterator.next();
// 是 OP_ACCEPT 事件仅叫,則進行后續(xù)的獲取數(shù)據(jù)和事件注冊
if (selectionKey.isAcceptable()) {
ServerSocketChannel serverSocket = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = serverSocket.accept();
socketChannel.configureBlocking(false);
// 注冊 OP_READ 事件帜篇,需要給客戶端發(fā)送數(shù)據(jù),則注冊 OP_WRITE 即可
socketChannel.register(selector, SelectionKey.OP_READ);
log.info("客戶端已連接: {}", socketChannel.getRemoteAddress());
// 是 OP_READ 事件诫咱,則獲取客戶端發(fā)送的數(shù)據(jù)
} else if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int read = socketChannel.read(byteBuffer);
if (read > 0) {
log.info("接收客戶 {}, 數(shù)據(jù): {}", socketChannel.getRemoteAddress(), new String(byteBuffer.array()));
} else if (read < 0) {
socketChannel.close();
log.info("客戶端已斷開連接");
}
}
// selectionKeys 沒有對應(yīng)事件即移除笙隙,防止下次 seletor 重復處理
keyIterator.remove();
}
}
}
}
NIO 有三大核心組件: Channel(通道), Buffer(緩沖區(qū))坎缭,Selector(多路復用器)
1竟痰、channel
類似于流,每個 channel
對應(yīng)一個 buffer
緩沖區(qū)掏呼,buffer
底層就是個數(shù)組
2坏快、channel
會注冊到 selector
上,由 selector
根據(jù) channel
讀寫事件的發(fā)生將其交由某個空閑的線程處理
3憎夷、NIO 的 Buffer
和 channel
都是既可以讀也可以寫
應(yīng)用場景
NIO方式適用于連接數(shù)目多且連接比較短(輕操作) 的架構(gòu)莽鸿, 比如聊天服務(wù)器, 彈幕系統(tǒng), 服務(wù)器間通訊祥得,編程比較復雜
AIO (NIO 2.0)
異步非阻塞兔沃, 由操作系統(tǒng)完成后回調(diào)通知服務(wù)端程序啟動線程去處理, 一般適用于連接數(shù)較多且連接時間較長的應(yīng)用
異步模型:
@Slf4j
public class AIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
AsynchronousServerSocketChannel assc = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(8080));
assc.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() {
@SneakyThrows
@Override
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
log.info("connet -- {}", Thread.currentThread().getName());
// 在此接收客戶端連接级及,否則后面的客戶端連接不上服務(wù)端
assc.accept(attachment, this);
log.info("客戶端:{}", socketChannel.getRemoteAddress());
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
socketChannel.read(byteBuffer, byteBuffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
log.info("read -- {}", Thread.currentThread().getName());
byteBuffer.flip();
log.info("客戶端請求數(shù)據(jù):{}", new String(byteBuffer.array(), 0, result));
socketChannel.write(ByteBuffer.wrap("This is response data".getBytes()));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
log.error("read error: {}", exc.getMessage());
exc.printStackTrace();
}
});
}
@Override
public void failed(Throwable exc, Object attachment) {
log.error("connect error: {}", exc.getMessage());
exc.printStackTrace();
}
});
log.info("main -- {}", Thread.currentThread().getName());
Thread.sleep(Integer.MAX_VALUE);
}
}
應(yīng)用場景
AIO方式適用于連接數(shù)目多且連接比較長(重操作)的架構(gòu)乒疏,JDK7 開始支持
對比
資料
【公眾號】網(wǎng)絡(luò) IO 演變發(fā)展過程和模型介紹
收錄時間: 2021/02/24