[網路]NIO學習筆記
最近為了一個開源專案,重新學習了下NIO的知識。在此分享下我的學習心得。
一、為什麼引入NIO?
NIO是new IO的簡稱,從1.4版本後引入。傳統的套接字(Socket)對於小規模的系統可以很好的執行,但是如果要同時處理上千個客戶機時,伺服器就需要產生上千個執行緒來等待使用者的輸入,這樣就產生了嚴重的資源浪費,那麼如何解決這個問題呢?NIO的提出正是解決了這個問題。
NIO採用輪詢的方式來查詢哪個客戶機需要服務,從而提供服務,這也正是NIO中的Selector和Channel抽象的關鍵點。一個Channel例項代表了一個“可輪詢的”I/O目標。NIO另外一個重要特性是Buffer類。
二、通道(Channel)與套接字(Socket)的不同點
通道需要通過呼叫靜態工廠方法來獲得例項:
SocketChannel sc = SocketChannel.open();
ServerSocketChannel ssc = ServerSocketChannel.open();
Channel使用的不是流,而是緩衝區來傳送和讀取資料。Buffer類或其任何子類的例項都可以看作是一個定長度的JAVA基本資料型別元素序列。與流不同,緩衝區具有固定的、有限的容量。還有一點需要注意,Buffer例項化是通過呼叫allocate()方法。
ByteBuffer buffer = ByteBuffer.allocate(256);//根據實際情況來定緩衝區大小
或者通過包裝一個已有的陣列來建立:
ByteBuffer buffer = ByteBuffer.wrap(byteArray);
NIO的強大功能部分來自於channel的非阻塞特性。Socket的某些操作可能會無限期的阻塞。例如,對accept()方法的呼叫可能會因為等待一個客戶端連線而阻塞;對read()方法的呼叫可能會因為沒有資料可讀而阻塞,直到連線的另一端傳來新的資料。NIO的channel抽象的一個特徵就是可以通過配置它的阻塞行為,以實現非阻塞的通道。
cc.configureBlocking(false);
在非阻塞式通道上呼叫一個方法總是會立即返回。這種呼叫的返回值指示了所請求的操作完成的程度。例如,在一個非阻塞式ServerSockerChannel上呼叫accept()方法,如果有連線請求在等待,則返回客戶端SocketChannel,否則返回null。
三、Selector介紹
Selector類可用於避免使用非阻塞式客戶端中很浪費資源的“忙等”方法。例如,考慮一個即時訊息傳送器。可能有上千個客戶端同時連線到了伺服器,但在任何時刻都只有非常少量的訊息需要讀取和分發。這就需要一種方法阻塞等待,直到至少有一個通道可以進行I/O操作,並指出是哪個通道。NIO的Selector就實現了這個功能。一個Selector例項可以同時檢查一組通道的I/O狀態。
那麼如何使用Selector來監聽呢?首先需要建立一個Selector例項(使用靜態工廠方法open())並將其註冊(一個通道可以註冊多個Selector例項)到想要監聽的通道上。如下:
Selector selector = Selector.open();
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false);
channel.socket().bind(new InetSocketAddress(servPort));//繫結埠
channel.register(selector, SelectionKey.OP_READ);//註冊
最後,呼叫選擇器上的select方法。
int num = selector.select();//獲取
獲取可進行I/O操作的通道數量。如果在一個單獨的執行緒中,通過呼叫sleect()方法就能檢查多個通道是否準備I/O操作。如果經過一段時間後任然沒有通道準備好,則返回0,並允許程式繼續執行其它任務。
那麼如何在通道上對“感興趣的”I/O操作進行監聽呢?Selector與Channel之間的關聯由一個SelectionKey例項表示。SelectionKey維護了一個通道上感興趣的操作型別資訊,並將這些資訊存放在一個int型的點陣圖(bitmap)中,該int型資料的每一位都有相應的含義。
SelectionKey類中的常量定義了通道上可能感興趣的操作型別,每個這種常量都是隻有一位設定為1的位掩碼。在API文件中,我們查知:
OP_ACCEPT 16 10000
OP_CONNECT 8 01000
OP_WRITE 4 00100
OP_READ 1 00001
通過對OP_ACCEPT,OP_CONNECT,OP_READ以及OP_WRITE中適當的常量進行按位OR,我們可以構造一個位向量來指定一組操作。例如,一個包含了讀和寫的操作集合可由表示式(OP_READ|OP_WRITE)來指定。
通過Channel類中的validOps()方法,我們可以知道該通道可以監聽哪些I/O操作。如果定義了OP_READ|OP_WRITE,則validOps()方法的返回值為5(00101);定義了上述四種操作,則其值應該為29(11101)。
下面筆者將實際使用興趣集中常見的錯誤進行下彙總(一般使用OP_WRITE和OP_READ是不會發生錯誤的):
Error1:
Exception in thread "main" java.lang.IllegalArgumentException
at java.nio.channels.spi.AbstractSelectableChannel.register(Unknown Source)
at java.nio.channels.SelectableChannel.register(Unknown Source)
at UdpServer.main(UdpServer.java:20)
錯誤使用OP_CONNECT,其正確使用方法應該是先建立連線。正確的一個例子如下(參考自:http://blog.csdn.net/zhouhl_cn/article/details/6582420)
TCP的NIO實現網路上很多,筆者在網路發現很少有關於UDP的NIO實現。下面給出筆者親測的NIO版本的UDP實現。
/**
* 伺服器的實現
*/
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.util.Iterator;
public class UdpServer {
public static void main(String args[]) throws IOException {
int servPort = 999;
Selector selector = Selector.open();
DatagramChannel channel = DatagramChannel.open();
channel.configureBlocking(false);
channel.socket().bind(new InetSocketAddress(servPort));
channel.register(selector, SelectionKey.OP_READ);
//channel.register(selector, 1);與上句子同效果
while (true) {
int num = selector.select();
if (num == 0) {
continue;
}
Iterator<SelectionKey> Keys = selector.selectedKeys().iterator();
while (Keys.hasNext()) {
SelectionKey k = Keys.next();
if ((k.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
DatagramChannel cc = (DatagramChannel) k.channel();
// 非阻塞
cc.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocateDirect(255);
// 接收資料並讀到buffer中
buffer.clear();
channel.receive( buffer ) ;
buffer.flip();
byte b[] = new byte[buffer.remaining()];
for (int i = 0; i < buffer.remaining(); i++) {
b[i] = buffer.get(i);
}
Charset charset = Charset.forName("UTF-8");
CharsetDecoder decoder = charset.newDecoder();
CharBuffer charBuffer = decoder.decode(buffer);
System.out.println("The imformation recevied:"+charBuffer.toString());
Keys.remove(); //一定要remove
}
}
}
}
}
/**
* 客戶端的實現
*/
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Scanner;
public class UdpClient {
@SuppressWarnings("resource")
public static void main(String agrs[]) throws IOException {
String s = null;
while ((s = new Scanner(System.in).nextLine()) != null) {
DatagramChannel dc = null;
dc = DatagramChannel.open();
SocketAddress address = new InetSocketAddress("localhost", 999);
ByteBuffer bb = ByteBuffer.allocate(255);
byte[] b = new byte[130];
b = s.getBytes();
bb.clear();
bb.put(b);
bb.flip();
dc.send(bb, address);
}
}
}
如對NIO有興趣的朋友,可以參考下《JAVA TCP/IP Socket程式設計》(原書第二版),筆者也有部分地方是參考該本書的,歡迎各位同行的批評指正!
相關文章
- Java NIO 核心元件學習筆記Java元件筆記
- 【學習筆記】網路流筆記
- 網路流學習筆記筆記
- Java NIO 緩衝區學習筆記Java筆記
- Java IO學習筆記五:BIO到NIOJava筆記
- 學習筆記16:殘差網路筆記
- 深度學習與神經網路學習筆記一深度學習神經網路筆記
- iOS學習筆記14 網路(三)WebViewiOS筆記WebView
- Java IO學習筆記六:NIO到多路複用Java筆記
- 深度學習筆記------卷積神經網路深度學習筆記卷積神經網路
- 深度學習卷積神經網路筆記深度學習卷積神經網路筆記
- 全連線神經網路學習筆記神經網路筆記
- 網路流最大流、最小割學習筆記筆記
- 【OCP學習筆記】配置網路環境 -- 3筆記
- 【OCP學習筆記】配置網路環境 -- 4筆記
- 【OCP學習筆記】配置網路環境 -- 1筆記
- 【OCP學習筆記】配置網路環境 -- 2筆記
- 尤拉路徑學習筆記筆記
- 網路流練習筆記筆記
- 網路學習筆記(二):TCP可靠傳輸原理筆記TCP
- 小白計算機網路學習筆記(更新中)計算機網路筆記
- 幾種型別神經網路學習筆記型別神經網路筆記
- 卷積神經網路學習筆記——SENet卷積神經網路筆記SENet
- muduo網路庫學習筆記(1):Timestamp類筆記
- muduo網路庫學習筆記(2):原子性操作筆記
- muduo網路庫學習筆記(3):Thread類筆記thread
- 計算機網路 - 運輸層 - 學習筆記計算機網路筆記
- C/C++學習路線———學習筆記C++筆記
- 關於網路安全的逆向分析方向學習筆記筆記
- 吳恩達機器學習筆記 —— 9 神經網路學習吳恩達機器學習筆記神經網路
- 計算機網路學習筆記:第二章計算機網路筆記
- substrate學習筆記5:使用substrate構建私有網路筆記
- 計算機網路傳輸層學習筆記---(四)計算機網路筆記
- muduo網路庫學習筆記(14):chargen服務示例筆記
- 比特幣學習筆記——————6、比特幣網路比特幣筆記
- Stanford機器學習課程筆記——神經網路學習機器學習筆記神經網路
- 初學Docker容器網路不得不看的學習筆記Docker筆記
- Web 開發學習筆記——關於網際網路和網際網路應用Web筆記