一、NIO簡介
NIO 是 Java SE 1.4 引入的一組新的 I/O 相關(guān)的 API,它提供了非阻塞式 I/O、選擇器、通道、緩沖區(qū)等新的概念和機(jī)制。相比與傳統(tǒng)的 I/O 多出的 N 不是單純的 New,更多的是代表了 Non-blocking 非阻塞,NIO具有更高的并發(fā)性、可擴(kuò)展性以及更少的資源消耗等優(yōu)點(diǎn)。
二、NIO 與傳統(tǒng)BIO
NIO:是同步非阻塞的,服務(wù)器實(shí)現(xiàn)模式為 一個(gè)線程處理多個(gè)連接。服務(wù)端只會(huì)創(chuàng)建一個(gè)線程負(fù)責(zé)管理Selector(多路復(fù)用器),Selector(多路復(fù)用器)不斷的輪詢注冊其上的Channel(通道)中的 I/O 事件,并將監(jiān)聽到的事件進(jìn)行相應(yīng)的處理。每個(gè)客戶端與服務(wù)端建立連接時(shí)會(huì)創(chuàng)建一個(gè) SocketChannel 通道,通過 SocketChannel 進(jìn)行數(shù)據(jù)交互。
BIO:全稱是Blocking IO,同步阻塞式IO,是JDK1.4之前的傳統(tǒng)IO模型,服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程。每當(dāng)客戶端有連接請求時(shí)服務(wù)器端就需要啟動(dòng)一個(gè)線程進(jìn)行處理。
兩者主要區(qū)別如下:
- 阻塞和非阻塞:NIO 使用非阻塞式 I/O,而 BIO 使用阻塞式 I/O。在阻塞式 I/O 中,當(dāng)一個(gè) I/O 操作完成之前,線程會(huì)一直被阻塞,直到 I/O 操作完成;在非阻塞式 I/O 中,線程可以繼續(xù)執(zhí)行其他任務(wù),直到 I/O 操作完成并返回結(jié)果。
- 線程模型:NIO 中的線程模型是基于事件驅(qū)動(dòng)的,當(dāng)一個(gè) I/O 操作完成時(shí),會(huì)觸發(fā)相應(yīng)的事件通知線程處理;而在 BIO 中,每個(gè)線程都負(fù)責(zé)處理一個(gè)客戶端連接,需要不斷地輪詢客戶端的輸入輸出流,以便及時(shí)響應(yīng)客戶端的請求。
- 內(nèi)存消耗:NIO 中使用的緩沖區(qū)(Buffer)可以重復(fù)利用,減少了頻繁的內(nèi)存分配和回收,從而減少了內(nèi)存的消耗;而在 BIO 中,每個(gè)客戶端連接都需要單獨(dú)分配一個(gè)緩沖區(qū),容易造成內(nèi)存的浪費(fèi)。
- 并發(fā)性能:NIO 中使用非阻塞式 I/O,可以同時(shí)處理多個(gè)客戶端連接,從而提高了并發(fā)處理能力;而在 BIO 中,由于每個(gè)客戶端連接都需要一個(gè)線程來處理,當(dāng)連接數(shù)量增加時(shí),容易出現(xiàn)線程饑餓和資源耗盡的問題。
三、NIO 工作流程
- 創(chuàng)建 Selector:Selector 是 NIO 的核心組件之一,它可以同時(shí)監(jiān)聽多個(gè)通道上的 I/O 事件,并且可以通過 select() 方法等待事件的發(fā)生。
- 注冊 Channel:通過 Channel 的 register() 方法將 Channel 注冊到 Selector 上,這樣 Selector 就可以監(jiān)聽 Channel 上的 I/O 事件。
- 等待事件:調(diào)用 Selector 的 select() 方法等待事件的發(fā)生,當(dāng)有事件發(fā)生時(shí),Selector 就會(huì)通知相應(yīng)的線程進(jìn)行處理。
- 處理事件:根據(jù)不同的事件類型,調(diào)用對(duì)應(yīng)的處理邏輯。
- 關(guān)閉 Channel:當(dāng) Channel 不再需要使用時(shí),需要調(diào)用 Channel 的 close() 方法關(guān)閉 Channel,同時(shí)也需要調(diào)用 Buffer 的 clear() 方法清空 Buffer 中的數(shù)據(jù),以釋放內(nèi)存資源。
Java NIO 的工作流程可以簡單概括為:通過 Selector 監(jiān)聽多個(gè) Channel 上的 I/O 事件,當(dāng)事件發(fā)生時(shí),通過對(duì)應(yīng)的 Channel 進(jìn)行讀寫操作,并在 Channel 不再需要使用時(shí)關(guān)閉 Channel。
四、NIO 核心的組件
1. Channel(通道)
Channel 是應(yīng)用程序與操作系統(tǒng)之間交互事件和傳遞內(nèi)容的直接交互渠道,應(yīng)用程序可以從管道中讀取操作系統(tǒng)中接收到的數(shù)據(jù),也可以向操作系統(tǒng)發(fā)送數(shù)據(jù)。Channel和傳統(tǒng)IO中的Stream很相似,其主要區(qū)別為:通道是雙向的,通過一個(gè)Channel既可以進(jìn)行讀,也可以進(jìn)行寫;而Stream只能進(jìn)行單向操作,通過一個(gè)Stream只能進(jìn)行讀或者寫,比如InputStream只能進(jìn)行讀取操作,OutputStream只能進(jìn)行寫操作。
1.1 常用的Channel實(shí)現(xiàn)類
- FileChannel:本地文件IO通道,從文件中讀寫數(shù)據(jù)。一般流程為:
1.獲取文件通道,通過 FileChannel 的靜態(tài)方法 open() 來獲取,獲取時(shí)需要指定文件路徑和文件打開方式
FileChannel channel = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);
2.創(chuàng)建字節(jié)緩沖區(qū)
ByteBuffer buf = ByteBuffer.allocate(1024);
3.讀/寫操作
(1)、讀操作
// 循環(huán)讀取通道中的數(shù)據(jù),并寫入到 buf 中
while (channel.read(buf) != -1){
// 緩存區(qū)切換到讀模式
buf.flip();
// 讀取 buf 中的數(shù)據(jù)
while (buf.position() < buf.limit()){
// 將buf中的數(shù)據(jù)追加到文件中
text.append((char)buf.get());
}
// 清空已經(jīng)讀取完成的 buffer,以便后續(xù)使用
buf.clear();
}
(2)、寫操作
// 循環(huán)讀取文件中的數(shù)據(jù),并寫入到 buf 中
for (int i = 0; i < text.length(); i++) {
// 填充緩沖區(qū),需要將 2 字節(jié)的 char 強(qiáng)轉(zhuǎn)為 1 自己的 byte
buf.put((byte)text.charAt(i));
// 緩存區(qū)已滿或者已經(jīng)遍歷到最后一個(gè)字符
if (buf.position() == buf.limit() || i == text.length() - 1) {
// 將緩沖區(qū)由寫模式置為讀模式
buf.flip();
// 將緩沖區(qū)的數(shù)據(jù)寫到通道
channel.write(buf);
// 清空已經(jīng)讀取完成的 buffer,以便后續(xù)使用
buf.clear();
}
}
4.將數(shù)據(jù)刷出到物理磁盤
channel.force(false);
5.關(guān)閉通道
channel.close();
- SocketChannel:網(wǎng)絡(luò)套接字IO通道,TCP協(xié)議,客戶端通過 SocketChannel 與服務(wù)端建立TCP連接進(jìn)行通信交互。與傳統(tǒng)的Socket操作不同的是,SocketChannel基于非阻塞IO模式,可以在同一個(gè)線程內(nèi)同時(shí)管理多個(gè)通信連接,從而提高系統(tǒng)的并發(fā)處理能力。
1.打開一個(gè) SocketChannel 通道
SocketChannel channel = SocketChannel.open();
2.連接到服務(wù)端
channel.connect(new InetSocketAddress("localhost", 9001));
3.分配緩沖區(qū)
ByteBuffer buf = ByteBuffer.allocate(1024);
4.配置是否為阻塞方式(默認(rèn)為阻塞方式)
channel.configureBlocking(false); // 配置通道為非阻塞模式
5.將channel的連接、讀、寫等事件注冊到selector中,每個(gè)chanel只能注冊一個(gè)事件,最后注冊的一個(gè)生效,
同時(shí)注冊多個(gè)事件可以使用"|"操作符將常量連接起來
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE | SelectionKey.OP_READ);
6.與服務(wù)端進(jìn)行讀寫操作
channel.read(buf);
channel.write(buf);
7.關(guān)閉通道
channel.close();
- ServerSocketChannel:網(wǎng)絡(luò)套接字IO通道,TCP協(xié)議,服務(wù)端通過ServerSocketChannel監(jiān)聽來自客戶端的連接請求,并創(chuàng)建相應(yīng)的SocketChannel對(duì)象進(jìn)行通信交互。ServerSocketChannel同樣也是基于非阻塞IO模式,可以在同一個(gè)線程內(nèi)同時(shí)管理多個(gè)通信連接,從而提高系統(tǒng)的并發(fā)處理能力。
1.打開一個(gè) ServerSocketChannel 通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
2.綁定本地端口
serverChannel.bind(new InetSocketAddress(9001));
3.配置是否為阻塞方式(默認(rèn)為阻塞方式)
serverChannel.configureBlocking(false); // 配置通道為非阻塞模式
4.分配緩沖區(qū)
ByteBuffer buf = ByteBuffer.allocate(1024);
5.將serverChannel 的連接、讀、寫等事件注冊到selector中,每個(gè)chanel只能注冊一個(gè)事件,最后注冊的一個(gè)生效,
同時(shí)注冊多個(gè)事件可以使用"|"操作符將常量連接起來
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT| SelectionKey.OP_WRITE | SelectionKey.OP_READ);
6.與客服端進(jìn)行讀寫操作
serverChannel.read(buf);
serverChannel.write(buf);
7.關(guān)閉通道
serverChannel.close();
- DatagramChannel:DatagramChannel是Java NIO中對(duì)UDP協(xié)議通信的封裝。通過DatagramChannel對(duì)象,我們可以實(shí)現(xiàn)發(fā)送和接收UDP數(shù)據(jù)包。它與TCP協(xié)議不同的是,UDP協(xié)議沒有連接的概念,所以無需像SocketChannel一樣先建立連接再開始通信。
1.打開一個(gè) DatagramChannel 通道
DatagramChannel channel = DatagramChannel.open();
2.分配緩沖區(qū)
ByteBuffer buf = ByteBuffer.allocate(1024);
3.配置是否為阻塞方式(默認(rèn)為阻塞方式)
channel.configureBlocking(false); // 配置通道為非阻塞模式
4.與客服端進(jìn)行讀寫操作
buffer.flip();
// 發(fā)送消息給服務(wù)端
channel.send(buffer, new InetSocketAddress("localhost", 9001));
buffer.clear();
// 接收服務(wù)端的響應(yīng)信息
channel.receive(buffer);
buffer.flip();
// 打印出響應(yīng)信息
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
7.關(guān)閉通道
channel.close();
1.2 常用的Channel方法
- read(ByteBuffer):從 Channel 中讀取數(shù)據(jù)到 ByteBuffer 中。如果 Channel 中沒有可讀數(shù)據(jù),則會(huì)阻塞等待直到有數(shù)據(jù)可讀。
- write(ByteBuffer):將數(shù)據(jù)寫入到 Channel 中。如果 Channel 中沒有可寫空間,則會(huì)阻塞等待直到有可寫空間。
- read(ByteBuffer, long):從 Channel 中讀取數(shù)據(jù)到 ByteBuffer 中,并設(shè)置讀取超時(shí)時(shí)間。如果超時(shí)時(shí)間到了還沒有讀取到數(shù)據(jù),則會(huì)拋出 TimeoutException 異常。
- write(ByteBuffer, long):將數(shù)據(jù)寫入到 Channel 中,并設(shè)置寫入超時(shí)時(shí)間。如果超時(shí)時(shí)間到了還沒有寫入完成,則會(huì)拋出 TimeoutException 異常。
- flush():將 Channel 中的緩沖區(qū)數(shù)據(jù)刷新到底層設(shè)備中,如果沒有數(shù)據(jù)需要刷新,則會(huì)立即返回。
- register(SelectionKey, int):將 Channel 注冊到 Selector 上,并設(shè)置注冊的事件類型和操作??梢酝ㄟ^ Selector 監(jiān)聽 Channel 上的事件,當(dāng)有事件發(fā)生時(shí),Selector 就會(huì)通知相應(yīng)的線程進(jìn)行處理。
- configureBlocking(boolean):設(shè)置 Channel 是否為阻塞模式。如果為阻塞模式,則在讀取或?qū)懭霐?shù)據(jù)時(shí)會(huì)一直阻塞等待,直到有數(shù)據(jù)可讀或?qū)懭胪瓿桑蝗绻麨榉亲枞J?,則在讀取或?qū)懭霐?shù)據(jù)時(shí)會(huì)立即返回,如果沒有數(shù)據(jù)可讀或?qū)懭胪瓿?,則會(huì)返回 -1。
- socket():獲取底層的 Socket 對(duì)象。
- isConnected():判斷 Channel 是否已經(jīng)連接到了遠(yuǎn)程主機(jī)。
- isWritable():判斷 Channel 是否可以寫入數(shù)據(jù)。
- isReadable():判斷 Channel 是否可以讀取數(shù)據(jù)。
- isOpen():檢查 Channel 是否已經(jīng)打開。
- getRemoteAddress():獲取 Channel 對(duì)應(yīng)的遠(yuǎn)程地址。
- getLocalAddress():獲取 Channel 對(duì)應(yīng)的本地地址。
2. Buffer(緩沖區(qū))
NIO 中的數(shù)據(jù)都是通過 Buffer 對(duì)象來處理的,每個(gè) Buffer 對(duì)象都關(guān)聯(lián)著一個(gè)字節(jié)數(shù)組,可以保存多個(gè)相同類型的數(shù)據(jù)。在讀取數(shù)據(jù)時(shí),是從Buffer 中讀取的,在寫入數(shù)據(jù)時(shí),也是寫入到 Buffer 中的。
2.1 Buffer 常用子類
- ByteBuffer:用于存儲(chǔ)字節(jié)數(shù)據(jù);
- CharBuffer:用于存儲(chǔ)字符數(shù)據(jù);
- ShortBuffer:用于存儲(chǔ)Short類型數(shù)據(jù);
- IntBuffer:用于存儲(chǔ)Int類型數(shù)據(jù);
- LongBuffer:用于存儲(chǔ)Long類型數(shù)據(jù);
- FloatBuffer:用于存儲(chǔ)Float類型數(shù)據(jù);
- DoubleBuffer:用于存儲(chǔ)Double類型數(shù)據(jù);
2.2 Buffer 重要屬性
- capacity(容量):表示 Buffer 所占的內(nèi)存大小,capacity不能為負(fù),并且創(chuàng)建后不能更改。
- limit(限制):表示 Buffer 中可以操作數(shù)據(jù)的大小,limit不能為負(fù),并且不能大于其capacity。寫模式下,表示最多能往 Buffer 里寫多少數(shù)據(jù),即 limit 等于 Buffer 的capacity。讀模式下,表示你最多能讀到多少數(shù)據(jù),其實(shí)就是能讀到之前寫入的所有數(shù)據(jù)。
- position(位置):表示下一個(gè)要讀取或?qū)懭氲臄?shù)據(jù)的索引。緩沖區(qū)的位置不能為負(fù),并且不能大于其限制。初始的 position 值為 0,最大可為 capacity – 1。當(dāng)一個(gè) byte、long 等數(shù)據(jù)寫到 Buffer 后, position 會(huì)向前移動(dòng)到下一個(gè)可插入數(shù)據(jù)的 Buffer 單元。
- mark(標(biāo)記):表示記錄當(dāng)前 position 的位置。可以通過 reset() 恢復(fù)到 mark 的位置。
2.3 Buffer 常見方法
- clear():清空緩沖區(qū)并返回對(duì)緩沖區(qū)的引用;
- flip():將緩沖區(qū)的界限設(shè)置為當(dāng)前位置,并將當(dāng)前位置重置為 0;
- capacity():返回 Buffer 的 capacity 大??;
- limit():返回 Buffer 的界限(limit) 的位置;
- limit(int n):將設(shè)置緩沖區(qū)界限為 n,并返回一個(gè)具有新 limit 的緩沖區(qū)對(duì)象;
- position():返回緩沖區(qū)的當(dāng)前位置 position;
- position(int n):將設(shè)置緩沖區(qū)的當(dāng)前位置為 n, 并返回修改后的 Buffer 對(duì)象;
- mark():對(duì)緩沖區(qū)設(shè)置標(biāo)記;
- reset():將位置 position 轉(zhuǎn)到以前設(shè)置的mark 所在的位置;
- rewind():將位置設(shè)為為 0, 取消設(shè)置的 mark;
- hasRemaining():判斷緩沖區(qū)中是否還有元素;
- get():讀取單個(gè)字節(jié);
- get(byte[] dst):讀取多個(gè)字節(jié);
- get(int index):讀取指定索引位置的字節(jié);
- put(byte b):將給定單個(gè)字節(jié)寫入緩沖區(qū)的當(dāng)前位置;
- put(byte[] src):將數(shù)組中的字節(jié)從當(dāng)前位置依次寫入到緩沖區(qū)中;
- put(int index, byte b):將指定字節(jié)寫入緩沖區(qū)的索引位置;
2.4 Buffer 內(nèi)存分配
- 普通緩沖區(qū):通過allocate()方法進(jìn)行分配,可以在jvm堆上申請堆上內(nèi)存。如果要作IO操作,會(huì)先從本進(jìn)程的堆上內(nèi)存復(fù)制到直接內(nèi)存,再利用本地IO處理。
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
- 直接緩沖區(qū):通過allocateDirect()方法進(jìn)行分配,直接從本地內(nèi)存中申請。如果要作IO操作,直接從本地內(nèi)存中利用本地IO處理。使用直接內(nèi)存會(huì)具有更高的效率,但是它比申請普通的堆內(nèi)存需要耗費(fèi)更高的性能。直接內(nèi)存中的數(shù)據(jù)是在JVM之外的,因此它不會(huì)占用應(yīng)用的內(nèi)存,當(dāng)有很大的數(shù)據(jù)要緩存,并且它的生命周期又很長,那么就比較適合使用直接內(nèi)存。一般來說,如果不是能帶來很明顯的性能提升,還是推薦使用堆內(nèi)存。
ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(1024);
- 緩沖區(qū)分片:通過slice()方法可以根據(jù)現(xiàn)有的緩沖區(qū)對(duì)象來創(chuàng)建一個(gè)子緩沖區(qū),即在現(xiàn)有緩沖區(qū)上切出一片來作為一個(gè)新的緩沖區(qū),但現(xiàn)有的緩沖區(qū)與創(chuàng)建的子緩沖區(qū)在底層數(shù)組層面上是數(shù)據(jù)共享的。
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer sliceBuffer = readBuffer.slice();
- 只讀緩沖區(qū):通過asReadOnlyBuffer()方法可以將任何常規(guī)緩沖區(qū)轉(zhuǎn)換為只讀緩沖區(qū),這個(gè)方法返回 一個(gè)與原緩沖區(qū)完全相同的緩沖區(qū),并與原緩沖區(qū)共享數(shù)據(jù),只不過它是只讀的。如果原緩沖區(qū)的內(nèi)容發(fā)生了變化,只讀緩沖區(qū)的內(nèi)容也隨之發(fā)生變化。
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer readonlyBuffer = readBuffer.asReadOnlyBuffer();
3. Selector(選擇器)
Selector 提供了選擇已經(jīng)就緒的任務(wù)的能力,Selector會(huì)不斷的輪詢注冊在上面的所有channel,進(jìn)行后續(xù)的IO操作。只需通過一個(gè)單獨(dú)的線程就可以管理多個(gè)channel,從而管理多個(gè)網(wǎng)絡(luò)連接。這就是Nio與傳統(tǒng)I/O最大的區(qū)別,不用為每個(gè)連接都去創(chuàng)建一個(gè)線程。文章來源:http://www.zghlxwxcb.cn/news/detail-849229.html
3.1 Selector使用流程
1.獲取選擇器
Selector selector = Selector.open();
2.通道注冊到選擇器,進(jìn)行監(jiān)聽
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
3.獲取可操作的 Channel
selector.select();
4.獲取可操作的 Channel 中的就緒事件集合
Set<SelectionKey> keys = selector.selectedKeys();
5.處理就緒事件
while (keys.iterator().hasNext()){
SelectionKey key = keys.iterator().next();
if (!key.isValid()){
continue;
}
if (key.isAcceptable()){
accept(key);
}
if(key.isReadable()){
read(key);
}
if (key.isWritable()){
write(key);
}
keyIterator.remove(); //移除當(dāng)前的key
}
3.2 SelectionKey事件類型
每個(gè) Channel向Selector 注冊時(shí),都會(huì)創(chuàng)建一個(gè) SelectionKey 對(duì)象,通過 SelectionKey 對(duì)象向Selector 注冊,且 SelectionKey 中維護(hù)了 Channel 的事件。常見的四種事件如下:文章來源地址http://www.zghlxwxcb.cn/news/detail-849229.html
- OP_READ:當(dāng)操作系統(tǒng)讀緩沖區(qū)有數(shù)據(jù)可讀時(shí)就緒。
- OP_WRITE:當(dāng)操作系統(tǒng)寫緩沖區(qū)有空閑空間時(shí)就緒。
- OP_CONNECT:當(dāng) SocketChannel.connect()請求連接成功后就緒,該操作只給客戶端使用。
- OP_ACCEPT:當(dāng)接收到一個(gè)客戶端連接請求時(shí)就緒,該操作只給服務(wù)器使用。
五、簡單實(shí)例
1. 服務(wù)端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NioServiceTest {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//調(diào)整緩沖區(qū)大小為1024字節(jié)
private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
String str;
public NioServiceTest(int port) throws IOException {
// 打開服務(wù)器套接字通道
this.serverSocketChannel = ServerSocketChannel.open();
// 服務(wù)器配置為非阻塞 即異步IO
this.serverSocketChannel.configureBlocking(false);
// 綁定本地端口
this.serverSocketChannel.bind(new InetSocketAddress(port));
// 創(chuàng)建選擇器
this.selector = Selector.open();
// 注冊接收連接事件
this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void handle() throws IOException {
// 無限判斷當(dāng)前線程狀態(tài),如果沒有中斷,就一直執(zhí)行while內(nèi)容。
while(!Thread.currentThread().isInterrupted()){
// 獲取準(zhǔn)備就緒的channel
if (selector.select() == 0) {
continue;
}
// 獲取到對(duì)應(yīng)的 SelectionKey 對(duì)象
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
// 遍歷所有的 SelectionKey 對(duì)象
while (keyIterator.hasNext()){
// 根據(jù)不同的SelectionKey事件類型進(jìn)行相應(yīng)的處理
SelectionKey key = keyIterator.next();
if (!key.isValid()){
continue;
}
if (key.isAcceptable()){
accept(key);
}
if(key.isReadable()){
read(key);
}
// 移除當(dāng)前的key
keyIterator.remove();
}
}
}
/**
* 客服端連接事件處理
*
* @param key
* @throws IOException
*/
private void accept(SelectionKey key) throws IOException {
SocketChannel socketChannel = this.serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 注冊客戶端讀取事件到selector
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("client connected " + socketChannel.getRemoteAddress());
}
/**
* 讀取事件處理
*
* @param key
* @throws IOException
*/
private void read(SelectionKey key) throws IOException{
SocketChannel socketChannel = (SocketChannel) key.channel();
//清除緩沖區(qū),準(zhǔn)備接受新數(shù)據(jù)
this.readBuffer.clear();
int numRead;
try{
// 從 channel 中讀取數(shù)據(jù)
numRead = socketChannel.read(this.readBuffer);
}catch (IOException e){
System.out.println("read failed");
key.cancel();
socketChannel.close();
return;
}
str = new String(readBuffer.array(),0,numRead);
System.out.println("read String is: " + str);
}
public static void main(String[] args) throws Exception {
System.out.println("sever start...");
new NioServiceTest(8000).handle();
}
}
2. 客戶端
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.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class NioClientTest {
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
private SocketChannel sc;
private Selector selector;
public NioClientTest(String hostname, int port) throws IOException {
// 打開socket通道
sc = SocketChannel.open();
// 配置為非阻塞 即異步IO
sc.configureBlocking(false);
// 連接服務(wù)器端
sc.connect(new InetSocketAddress(hostname,port));
// 創(chuàng)建選擇器
selector = Selector.open();
// 注冊請求連接事件
sc.register(selector, SelectionKey.OP_CONNECT);
}
public void send() throws IOException{
Scanner scanner = new Scanner(System.in);
// 無限判斷當(dāng)前線程狀態(tài),如果沒有中斷,就一直執(zhí)行while內(nèi)容。
while (!Thread.currentThread().isInterrupted()){
// 獲取準(zhǔn)備就緒的channel
if (selector.select() == 0) {
continue;
}
// 獲取到對(duì)應(yīng)的 SelectionKey 對(duì)象
Set<SelectionKey> keys = selector.selectedKeys();
System.out.println("all keys is:"+keys.size());
Iterator<SelectionKey> iterator = keys.iterator();
// 遍歷所有的 SelectionKey 對(duì)象
while (iterator.hasNext()){
SelectionKey key = iterator.next();
//判斷此通道上是否在進(jìn)行連接操作
if (key.isConnectable()){
sc.finishConnect();
//注冊寫操作
sc.register(selector, SelectionKey.OP_WRITE);
System.out.println("server connected...");
break;
}else if (key.isWritable()){
System.out.println("please input message:");
String message = scanner.nextLine();
writeBuffer.clear();
writeBuffer.put(message.getBytes());
//將緩沖區(qū)各標(biāo)志復(fù)位,因?yàn)橄蚶锩鎝ut了數(shù)據(jù)標(biāo)志被改變要想從中讀取數(shù)據(jù)發(fā)向服務(wù)器,就要復(fù)位
writeBuffer.flip();
sc.write(writeBuffer);
}
// 移除當(dāng)前的key
iterator.remove();
}
}
}
public static void main(String[] args) throws Exception {
new NioClientTest("localhost", 8000).send();
}
}
到了這里,關(guān)于Java NIO 詳解的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!