NIO-Channel
目錄
NIO-概覽
NIO-Buffer
NIO-Channel
NIO-Channel接口分析
NIO-SocketChannel源碼分析
NIO-FileChannel源碼分析
NIO-Selector源碼分析
NIO-WindowsSelectorImpl源碼分析
NIO-EPollSelectorIpml源碼分析
前言
本來是想學習Netty的,但是Netty是一個NIO框架,因此在學習netty之前,還是先梳理一下NIO的知識。通過剖析源碼理解NIO的設計原理。
本系列文章針對的是JDK1.8.0.161的源碼。
什么是Channel
通道(Channel)是對原I/O包中的流的模擬。與文件設備I/O交互的所有數據都必須通過一個Channel對象。
上一節我們提到在NIO中使用緩沖區來存放指定基元的數據,我們可以通過Buffer來讀寫數據。
將數據寫入到硬盤時,我們可以將字節數據寫入到緩沖區中;若我們要從硬盤讀取數據,則需要通過通道將數據寫入到緩沖區,然后再從緩沖區讀取數據。
通道類型
根據不同的使用方式,分為不同的通道。比如我們需要網絡讀寫,就需要網絡交互的通道。需要文件讀寫就需要文件交互的通道。
NIO實現了Sctp協議、TCP協議、UDP協議以及文件傳輸四種通道,同時還實現了Windows平台的異步Socket通道以及異步文件通道。
windows平台的異步I/O是通過重疊I/O和IOCP(I/O完成端口)實現的,想要了解windows異步I/O的知識可以看一下我另一篇文章《Windows內核原理-同步IO與異步IO》
類型 | 通道 |
---|---|
Sctp協議客戶端 | SctpChannel |
Sctp協議多播客戶端 | SctpMultiChannel |
Sctp協議服務端 | SctpServerChannel |
UDP協議 | DatagramChannel |
TCP協議同步I/O服務端 | ServerSocketChannel |
TCP協議同步I/O客戶端 | ServerChannel |
文件讀寫 | FileChannel |
對於Windows平台的異步通道
類型 | 通道 |
---|---|
TCP協議異步I/O服務端 | WindowsAsynchronousServerSocketChannel |
TCP協議異步I/O客戶端 | WindowsAsynchronousSocketChannel |
異步文件讀寫 | WindowsAsynchronousFileChannel |
另外NIO還實現了一個單向通訊管道(Pipe)的功能,通過引入
SourceChannel
和SinkChannel
實現,底層實際還是Socket通訊。
如何使用
在介紹不同的Channel的實現之前我們先介紹下Channel如何使用。
ServerSocketChannel
以TCP協議為例,我們進行網絡收發的時候,首先需要創建一個ServerSocketChannel用於監聽端口。
//創建一個服務端socket通道用於接收連接
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//綁定監聽地址
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
//等待連接
SocketChannel socketChannel = serverSocketChannel.accept();
我們監聽了8080端口。若沒有連接時,線程會阻塞在
accept
。
當有收到新的連接創建時,會獲取到SocketChannel,此時我們需要創建一個Buffer用來從Channel中讀取數據。
ByteBuffer buf = ByteBuffer.allocate(1024);
//數據將寫入到buffer中
int length = socketChannel.read(buf);
數據寫入到我們的Buffer中,我們就需要將他們讀出來
buf.flip(); //轉化為可讀模式
byte[] data = new byte[length];
buf.get(data);
將數據從Buffer寫入到Channel時
buf.clear();
byte[] resp = {'O','K'};
buf.put(resp);
buf.flip();//轉換為讀模式
socketChannel.write(buf);
這里為了方便直接使用原來的Buffer。
SocketChannel
作為客戶端我們需要創建一個SocketChannel。
SocketChannel.open();
client.connect(new InetSocketAddress("127.0.0.1", 6060));
發送HELLO
給服務端
ByteBuffer buffer = ByteBuffer.allocate(10);
byte[] data = {'H', 'E', 'L', 'L', 'O'};
buffer.put(data);
buffer.flip();//轉換為讀模式
client.write(buffer);
阻塞等待讀取數據
buffer.clear();
client.read(buffer);
buffer.flip();//轉換為讀模式
處理完成,需要關閉釋放連接
//關閉客戶端輸入流
client.socket().shutdownInput();
//關閉客戶端輸出流
client.socket().shutdownOutput();
//關閉客戶端socket時會關閉客戶端channel
client.socket().close();
//關閉客戶端channel,會同時關閉輸入和輸出流。
client.close();
關閉輸出流會發送FIN包,若輸入流未關閉仍然可以繼續接收數據,這就是TCP的半連接。若處理完最后需要確保channel關閉。
FileChannel
FileChannel只能被FileInputStream、FileOutputStream、RandomAccessFile創建
RandomAccessFile
使用RandomAccessFile
創建FileChannel
//第一個參數時文件名,第二個參數是讀寫方式
RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt","rw");
FileChannel channel = randomAccessFile.getChannel();
FileInputStream
使用RandomAccessFile
創建FileChannel
FileInputStream inputStream = new FileInputStream("1.txt");
channel = inputStream.getChannel();
inputStream獲取的FileChannel只能讀
FileOutputStream
使用RandomAccessFile
創建FileChannel
FileOutputStream outputStream = new FileOutputStream("1.txt");
channel = outputStream.getChannel();
inputStream獲取的FileChannel只能寫
關閉FileChannel
關閉FileChannel的方法和關閉SocketChannel方法一樣。
//關閉channel時會關閉文件
channel.close();
//關閉文件時會關閉channel
randomAccessFile.close();
//關閉文件流時會關閉channel
inputStream.close();
//關閉文件流時會關閉channel
inputStream.close();
總結
由於源碼解析的篇幅較長,因此將channel源碼單獨分出來講解。
相關文獻
微信掃一掃二維碼關注訂閱號傑哥技術分享
出處:https://www.cnblogs.com/Jack-Blog/p/12015516.html
作者:傑哥很忙
本文使用「CC BY 4.0」創作共享協議。歡迎轉載,請在明顯位置給出出處及鏈接。