關于IO模式,線程模型陵珍?
Java的io模型分為三種寝杖,(BIO,NIO,AIO),Netty現(xiàn)在主要推薦的(NIO),剩下的兩個曾經(jīng)實現(xiàn)互纯,現(xiàn)在不推薦使用瑟幕。IO模型于線程模型之間的關系,如下表:
BIO | NIO | AIO |
---|---|---|
Thread-Per-Connection | Reactor | Proactor |
- Thread-Per-Connection :一個客戶端對應一個線程處理 讀>業(yè)務邏輯>寫留潦。
- Reactor只盹,因為是多路復用,所以有一個select選擇器兔院,可以注冊事件和獲取相應事件殖卑,則其核心流程:注冊感興趣的事件 -> 掃描是否有感興趣的事件發(fā)生 -> 事件發(fā)生之后做出相應的邏輯處理。具體不同注冊的事件如下表:
client/server | SocketChannel/ServerSocketCannel | OP_ACCEPT | OP_CONNECT | OP_WRITE | OP_READ |
---|---|---|---|---|---|
client | SocketChannel | - | 1 | 1 | 1 |
server | ServerSocketChannel | 1 | - | - | - |
server | SocketChannel | - | - | 1 | 1 |
- Proactor【現(xiàn)在還不知道】
還是說說Reactor把坊萝,畢竟是Netty總結:
Reactor開發(fā)模式有三種:
具體可以從線程種類(線程的工作不同分為不同的種類如:++處理連接請求的線程++孵稽,++處理業(yè)務邏輯的線程++==自己瞎理解的==),線程數(shù)量兩個維度來展開十偶。Reactor的三種模型設計:
Reactor單線程模式
線程不分種類菩鲜,線程數(shù)量一個。即:一個線程搞定客戶端的連接請求和客戶端的讀寫惦积。
NIO代碼實現(xiàn):
/**
* 順便說下 OP_WRITE 的情況接校。
* 在寫數(shù)據(jù)到 SocketChannel 之前,沒必要先調用 key.isWritable()狮崩,而是應該在調用 channel.write(buffer) 的時候蛛勉,
* 如果返回值是 0 或者和實際的緩存區(qū)大小不一致的值,這個時候才去注冊監(jiān)聽 OP_WRITE 事件厉亏,因為此時往往是由于數(shù)據(jù)接收方的問題或網(wǎng)絡問題導致寫操作掛起了董习。
* @throws IOException
*/
@Test
public void server() throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//配置非阻塞
serverSocketChannel.configureBlocking(false);
//綁定
serverSocketChannel.bind(new InetSocketAddress(9898));
//選擇器,綁定選擇器爱只,和選擇的事件
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//輪詢獲取選擇器上已經(jīng)準備就緒的事件>0有一個key準備就緒
while (selector.select() > 0) {
//獲取當前選擇器中所有注冊的已經(jīng)就緒的監(jiān)聽事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isAcceptable()) {
//接收狀態(tài)就緒
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
//可以給這個channel關聯(lián)一個buffer
//socketChannel.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(1024));
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isReadable()) {
//1--------------------------------
//讀就緒狀態(tài)的通道
SocketChannel socketChannel = (SocketChannel) key.channel();
//讀取數(shù)據(jù)
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = 0;
while ((len = socketChannel.read(buffer)) != 0) {
if (len == -1) {
System.out.println("-1");
//key.interestOps(key.interestOps() & ~SelectionKey.OP_READ); 與 key.cancel();同意皿淋,不刪除就一直響應可讀事件招刹。輸出-1
key.cancel();
break;
}
buffer.flip();
System.out.println(new String(buffer.array(), 0, len));
buffer.clear();
}
//2---------------------------------
}
//取消選擇鍵SelectionKey
iterator.remove();
}
}
serverSocketChannel.close();
}
Netty中代碼體現(xiàn):部分
NioEventLoopGroup eventGroup = new NioEventLoopGroup(1);
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventGroup);
出現(xiàn)的問題:業(yè)務處理很慢的情況下,連接請求進來可能超時處理不到窝趣。
非主從Reactor多線程模式
一個線程處理鏈接請求和讀寫請求疯暑,只不過這里的讀寫的邏輯處理方法其他線程中去處理了。
Nio代碼哑舒,在上面代碼修改妇拯,把//1---- //2---這部分邏輯放在其他線程中處理就可以了。
Netty中代碼體現(xiàn):
NioEventLoopGroup eventGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventGroup);
主從Reactor多線程模式
從這里開起區(qū)分線程種類了:一類線程處理連接請求,一類是處理讀寫請求的洗鸵。
NIO中代碼實現(xiàn):
實現(xiàn)思路:首先開兩個及其以上Selector
越锈,其中一個serverSocketChannel注冊OP_ACCEPT,剩下的socketChannel的OP_WRITE和OP_READ事件膘滨,每個Selector在不同的線程中輪訓遍歷準備好的事件甘凭。Netty中代碼體現(xiàn):
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workGroup);
其中關于處理連接請求的線程數(shù)的問題?如果服務端只開了一個端口的話火邓,處理連接請求的線程用一個就可以了丹弱,所以上面的Nio代碼實現(xiàn)思路中默認只開一個端口。只用一個線程輪訓一個Selector就可以了铲咨,所以在Netty中NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
可以直接寫入1躲胳,不寫開一個端口其實也只是用到了一個線程。