BIO(Blocking IO)
BIO(Blocking IO):同步阻塞模型,一個客戶端連接對應(yīng)一個處理線程,即:一個線程處理一個客戶端請求。
? ? 單線程版本: 服務(wù)端在處理完第一個客戶端的所有事件之前,無法為其他客戶端提供服務(wù)。
? ? 多線程版本:如果出現(xiàn)大量只連接不發(fā)數(shù)據(jù)的話,那么就會一直占用線程,浪費服務(wù)器資源。
? ? 線程池版本:用線程池根本不能根本的解決問題。假如我們有500個線程組成一個線程池,我們用這500個線程去為客戶端服務(wù)。那么,假設(shè)前500個客戶端請求進來,被占滿了這500個線程,并且這500個客戶端連接只連接不發(fā)數(shù)據(jù),那么我們的服務(wù)端不就崩掉了嗎?因為我們服務(wù)端最多只能處理500個客戶端連接,而你后面進來的,不管多少都會被我們的服務(wù)端拒之門外,所以用線程池也不能解決這個問題。
public class SocketServer {
public static void main(String[] args) throws Exception {
//創(chuàng)建了服務(wù)端,綁定到了9001端口
ServerSocket serverSocket = new ServerSocket(9001);
while (true) {
System.out.println("等待連接..");
//阻塞方法
Socket clientSocket = serverSocket.accept(); //監(jiān)聽客戶端的連接,有客戶端的連接代碼才會往下走,沒有連接就會阻塞在這里
System.out.println("有客戶端連接了..");
//1.單線程版本
//handler(clientSocket);
//2.多線程版本
new Thread(new Runnable() {
@Override
public void run() {
try {
handler(clientSocket);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
//3.線程池版本
ExecutorService executorService = Executors.newFixedThreadPool(100);
executorService.execute(new Runnable() {
@Override
public void run() {
try {
handler(clientSocket);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
//handler方法是處理客戶端事件的方法,比如客戶端發(fā)來數(shù)據(jù),在這里我們就做打印處理
private static void handler(Socket clientSocket) throws Exception {
byte[] bytes = new byte[1024];
System.out.println("準備read..");
//接收客戶端的數(shù)據(jù),阻塞方法,沒有數(shù)據(jù)可讀時就阻塞
int read = clientSocket.getInputStream().read(bytes);
System.out.println("read完畢。。");
if (read != -1) {
System.out.println("接收到客戶端的數(shù)據(jù):" + new String(bytes, 0, read));
}
}
}
NIO(Non Blocking IO)
NIO模型1.0版本(早期版本)
一個線程無限循環(huán)去list(存放著客戶端連接)輪訓(xùn),檢查是否有讀寫請求,如果有則處理,如果沒有跳過。這個版本如果連接數(shù)特別多的話,會有大量的無效遍歷,假如有1000個客戶端連接,只有其中100個有讀寫事件,那么還是會循環(huán)1000次,其中的900次循環(huán)都是無效的。
public class NioServer {
// 保存客戶端連接
static List<SocketChannel> channelList = new ArrayList<>();
public static void main(String[] args) throws IOException {
// 創(chuàng)建NIO ServerSocketChannel,與BIO的serverSocket類似,即創(chuàng)建了服務(wù)端并綁定了9001端口
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9001));
// 設(shè)置ServerSocketChannel為非阻塞
serverSocket.configureBlocking(false);
System.out.println("服務(wù)啟動成功");
while (true) {
// 非阻塞模式accept方法不會阻塞,否則會阻塞
// NIO的非阻塞是由操作系統(tǒng)內(nèi)部實現(xiàn)的,底層調(diào)用了linux內(nèi)核的accept函數(shù)
SocketChannel socketChannel = serverSocket.accept();
if (socketChannel != null) { // 如果有客戶端進行連接
System.out.println("連接成功");
// 設(shè)置SocketChannel為非阻塞
socketChannel.configureBlocking(false);
// 保存客戶端連接在List中
channelList.add(socketChannel);
}
// 遍歷連接進行數(shù)據(jù)讀取 讀寫事件
Iterator<SocketChannel> iterator = channelList.iterator();
while (iterator.hasNext()) {
SocketChannel sc = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
// 非阻塞模式read方法不會阻塞,否則會阻塞
int len = sc.read(byteBuffer);
// 如果有數(shù)據(jù),把數(shù)據(jù)打印出來,整個過程是一個main線程在處理
if (len > 0) {
System.out.println(Thread.currentThread().getName() + " 接收到消息:" + new String(byteBuffer.array()));
} else if (len == -1) { // 如果客戶端斷開,把socket從集合中去掉
iterator.remove();
System.out.println("客戶端斷開連接");
}
}
}
}
}
NIO模型2.0版本(啟用多路復(fù)用器,jdk1.4以上)
客戶端發(fā)送的連接請求都會注冊到多路復(fù)用器selector上,多路復(fù)用器輪詢到連接有IO請求就進行處理,JDK1.4開始引入。這個版本進行了很大程度的優(yōu)化,當客戶端連接以后,如果有讀寫事件,則會加入一個隊列里,處理事件的線程會阻塞等待這個隊列里有新的元素之后處理事件。
步驟:
- 創(chuàng)建ServerSocketChannel服務(wù)端
- 創(chuàng)建多路復(fù)用器Selector( 每個操作系統(tǒng)創(chuàng)建出來的是不一樣的,Centos創(chuàng)建的是EPollSelectorProviderImpl,Windows創(chuàng)建的是WindowsSelectorImpl,其實就是Linux的Epoll實例EPollArrayWrapper)
- ServerSocketChannel將建立連接事件注冊到Selector中(register方法往EPollArrayWrapper中添加元素)
- 處理事件
- 如果是建立連接事件,則把客戶端的讀寫請求也注冊到Selector中
- 如果是讀寫事件則按業(yè)務(wù)處理
public class NioSelectorServer {
public static void main(String[] args) throws IOException {
//指定感興趣的操作類型為 OP_ACCEPT
int OP_ACCEPT = 1 << 4;
System.out.println(OP_ACCEPT);
// 創(chuàng)建NIO ServerSocketChannel
ServerSocketChannel serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(9001));
// 設(shè)置ServerSocketChannel為非阻塞
serverSocket.configureBlocking(false);
// 打開Selector處理Channel,即創(chuàng)建epoll,這里啟用了多路復(fù)用器是與NIO1.0版本最大的區(qū)別
Selector selector = Selector.open();
// 把ServerSocketChannel注冊到selector上,并且selector對客戶端accept連接操作感興趣(把連接事件注冊到多路復(fù)用器里面)
SelectionKey selectionKey = serverSocket.register(selector, SelectionKey.OP_ACCEPT);//通過 SelectionKey.OP_ACCEPT 來識別和處理接受連接事件。
System.out.println("服務(wù)啟動成功");
while (true) {
// 阻塞等待需要處理的事件發(fā)生 已注冊事件發(fā)生后,會執(zhí)行后面邏輯
selector.select();
// 獲取selector中注冊的全部事件的 SelectionKey 實例
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
// 遍歷SelectionKey對事件進行處理
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 如果是OP_ACCEPT事件(連接事件),則進行連接獲取和事件注冊
if (key.isAcceptable()) {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
// 這里只注冊了讀事件,如果需要給客戶端發(fā)送數(shù)據(jù)可以注冊寫事件
SelectionKey selKey = socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("客戶端連接成功");
} else if (key.isReadable()) { // 如果是OP_READ事件(讀事件),則進行讀取和打印
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = socketChannel.read(byteBuffer);
// 如果有數(shù)據(jù),把數(shù)據(jù)打印出來
if (len > 0) {
System.out.println(Thread.currentThread().getName() + "接收到消息:" + new String(byteBuffer.array()));
} else if (len == -1) { // 如果客戶端斷開連接,關(guān)閉Socket
System.out.println("客戶端斷開連接");
socketChannel.close();
}
}
//從事件集合里刪除本次處理的key,防止下次select重復(fù)處理
iterator.remove();
}
}
}
}
Redis線程模型文章來源:http://www.zghlxwxcb.cn/news/detail-492721.html
Redis就是典型的基于epoll的NIO線程模型(nginx也是),epoll實例收集所有事件(連接與讀寫事件),由一個服務(wù)端線程連續(xù)處理所有事件命令。文章來源地址http://www.zghlxwxcb.cn/news/detail-492721.html
到了這里,關(guān)于BIO、NIO線程模型的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!