Netty之NIO

------NIO簡介(1)--------

NIO組件

channel,buffer,selector,pip,filelock。

Channel分為:

FileChannel

DatagramChannel

SocketChannel

ServerSocketChannel

UDP 和 TCP 網絡IO,以及文件IO

Buffer分為:

ByteBuffer

CharBuffer

DoubleBuffer

FloatBuffer

IntBuffer

LongBuffer

ShortBuffer

byte, short, int, long, float, double 和 char。

結構圖

? ? ? ? ? ? ? ? ? thread

? ? ? ? ? ? ? ? ? ? |

? ? ? ? ? ? ? ? selector

? ? |? ? ? ? ? ? ? |? ? ? ? ? ? |

channel? ? ? ? ? channel? ? ? channel

使用Selector,得向Selector注冊Channel,然后調用它的select()方法。

這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回

線程就可以處理這些事件,事件的例子有如新連接進來,數據接收等

--------channel(2)---------

channel 可以理解為pip

channel <=======> buffer

FileChannel 從文件中讀寫數據。

DatagramChannel 能通過UDP讀寫網絡中的數據。

SocketChannel 能通過TCP讀寫網絡中的數據。

ServerSocketChannel可以監聽新進來的TCP連接,像Web服務器那樣。

對每一個新進來的連接都會創建一個SocketChannel。?

例子:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? NIO

File? -? channel -? buffer =============== application

buf.flip() 的調用,首先讀取數據到Buffer,然后反轉Buffer,接著再從Buffer中讀取數據

--------NIO buffer(3)------------

四個步驟:

寫入數據到Buffer

調用flip()方法

從Buffer中讀取數據

調用clear()方法或者compact()方法

flip()方法

反轉IO? 寫=========》讀

capacity

position


capacity /limit? ? ? -

要想獲得一個Buffer對象首先要進行分配。

allocate方法。

ByteBuffer buf = ByteBuffer.allocate(48);

--------向Buffer中寫數據-----

從Channel寫到Buffer。

通過Buffer的put()方法寫到Buffer里。

int bytesRead = inChannel.read(buf); //read into buffer.

buf.put(127);

調用flip()方法會將position設回0,并將limit設置成之前position的值。

----從Buffer中讀取數據----

從Buffer讀取數據到Channel。

使用get()方法從Buffer中讀取數據。

int bytesWritten = inChannel.write(buf);

byte aByte = buf.get();

rewind()方法

Buffer.rewind()將position設回0,所以你可以重讀Buffer中的所有數據。limit保持不變

如果調用的是clear()方法.

position將被設回0,limit被設置成 capacity的值,Buffer 被清空了

compact()方法

如果Buffer中仍有未讀的數據,且后續還需要這些數據,但是此時想要先先寫些數據,

compact()方法將所有未讀的數據拷貝到Buffer起始處。然后將position設到最后一個未讀元素正后面

mark()與reset()方法

通過調用Buffer.mark()方法,可以標記Buffer中的一個特定position。

之后可以通過調用Buffer.reset()方法恢復到這個position。

equals()方法

有相同的類型(byte、char、int等)。

Buffer中剩余的byte、char等的個數相等。

Buffer中所有剩余的byte、char等都相同。

compareTo()方法

ompareTo()方法比較兩個Buffer的剩余元素

第一個不相等的元素小于另一個Buffer中對應的元素 。

第一個Buffer的元素個數比另一個少)

--------------Scatter/Gather(4)-----------------

scatter/gather用于描述從Channel

scatter? 讀操作時將讀取的數據寫入多個buffer中

gather? 寫操作時將多個buffer的數據寫入同一個Channel

scatter

經常用于需要將傳輸的數據分開處理的場合,例如傳輸一個由消息頭和消息體組成的消息,

你可能會將消息體和消息頭分散到不同的buffer中,這樣你可以方便的處理消息頭和消息體。

ByteBuffer header = ByteBuffer.allocate(128);

ByteBuffer body? = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.read(bufferArray);

read()方法按照buffer在數組中的順序將從channel中讀取的數據寫入到buffer,

當一個buffer被寫滿后,channel緊接著向另一個buffer中寫。

這也意味著它不適用于動態消息(譯者注:消息大小不固定)

----Gathering---------

Gathering Writes是指數據從多個buffer寫入到同一個channel。

ByteBuffer header = ByteBuffer.allocate(128);

ByteBuffer body? = ByteBuffer.allocate(1024);

ByteBuffer[] bufferArray = { header, body };

channel.write(bufferArray);

注意只有position和limit之間的數據才會被寫入。

因此,如果一個buffer的容量為128byte,但是僅僅包含58byte的數據

------------(五) 通道之間的數據傳輸---------------------

直接將數據從一個channel傳輸到另外一個channel。

transferFrom()

FileChannel? ===========>? FileChannel

RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");

FileChannel? ? ? fromChannel = fromFile.getChannel();

RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");

FileChannel? ? ? toChannel = toFile.getChannel();

long position = 0;

long count = fromChannel.size();

toChannel.transferFrom(position, count, fromChannel);

transferTo()

FileChannel ===========>? Channel

-----------------------(六) Selector--------------

Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道

一個單獨的線程可以管理多個channel

Selector的創建?

Selector selector = Selector.open();

向Selector注冊通道

SelectableChannel.register()

channel.configureBlocking(false);

SelectionKey key = channel.register(selector,

? ? Selectionkey.OP_READ);

Connect

Accept

Read

Write

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

SelectionKey

interest集合

ready集合

Channel

Selector

附加的對象(可選)

Channel? channel? = selectionKey.channel();

Selector selector = selectionKey.selector();

通過Selector選擇通道

下面是select()方法:

int select()

int select(long timeout)

int selectNow()

完整的示例

Selector selector = Selector.open();

channel.configureBlocking(false);

SelectionKey key = channel.register(selector, SelectionKey.OP_READ);

while(true) {

? int readyChannels = selector.select();

? if(readyChannels == 0) continue;

? Set selectedKeys = selector.selectedKeys();

? Iterator keyIterator = selectedKeys.iterator();

? while(keyIterator.hasNext()) {

? ? SelectionKey key = keyIterator.next();

? ? if(key.isAcceptable()) {

? ? ? ? // a connection was accepted by a ServerSocketChannel.

? ? } else if (key.isConnectable()) {

? ? ? ? // a connection was established with a remote server.

? ? } else if (key.isReadable()) {

? ? ? ? // a channel is ready for reading

? ? } else if (key.isWritable()) {

? ? ? ? // a channel is ready for writing

? ? }

? ? keyIterator.remove();

? }

}

-------(七) FileChannel--------------

連接到文件的通道

FileChannel無法設置為非阻塞模式,它總是運行在阻塞模式下。

打開FileChannel

InputStream、OutputStream或RandomAccessFile來獲取一個FileChannel實例

RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");

FileChannel inChannel = aFile.getChannel();

從FileChannel讀取數據

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = inChannel.read(buf);

向FileChannel寫數據

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {

? ? channel.write(buf);

}

關閉FileChannel

channel.close();

FileChannel的某個特定位置進行數據的讀/寫操作

position方法

long pos = channel.position();

channel.position(pos +123);

FileChannel的size方法

long fileSize = channel.size();

FileChannel的truncate方法

截取文件,后面的部分將被刪除

channel.truncate(1024);

FileChannel的force方法

FileChannel.force()方法將通道里尚未寫入磁盤的數據強制寫到磁盤上。

channel.force(true);

---------(八) SocketChannel------------

連接到TCP網絡套接字的通道

創建SocketChannel

打開一個SocketChannel并連接到互聯網上的某臺服務器。

一個新連接到達ServerSocketChannel時,會創建一個SocketChannel

打開 SocketChannel

SocketChannel socketChannel = SocketChannel.open();

socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

關閉 SocketChannel

socketChannel.close();

從 SocketChannel 讀取數據

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = socketChannel.read(buf);

分配一個Buffer。從SocketChannel讀取到的數據將會放到這個Buffer中

返回的int值表示讀了多少字節進Buffer里

寫入 SocketChannel

new string - new buf - buf clear - buf put - buf flip - buf remain - channel write

非阻塞模式

設置 SocketChannel 為非阻塞模式(non-blocking mode)

異步模式下調用connect(), read() 和write()了。

connect()

socketChannel.configureBlocking(false);

socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));

while(! socketChannel.finishConnect() ){

? ? //wait, or do something else...

}

非阻塞模式與選擇器

非阻塞模式與選擇器搭配會工作的更好,通過將一或多個SocketChannel注冊到Selector,

可以詢問選擇器哪個通道已經準備好了讀取,寫入等

--------(九) ServerSocketChannel---------

ServerSocketChannel 是一個可以監聽新進來的TCP連接的通道

例子:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));

while(true){

? ? SocketChannel socketChannel =

? ? ? ? ? ? serverSocketChannel.accept();

? ? //do something with socketChannel...

}

打開 ServerSocketChannel

關閉 ServerSocketChannel

監聽新進來的連接

ServerSocketChannel.accept() 方法監聽新進來的連接

返回一個包含新進來的連接的 SocketChannel

非阻塞模式

非阻塞模式下,accept() 方法會立刻返回

如果還沒有新進來的連接,返回的將是null。

需要檢查返回的SocketChannel是否是null

if(socketChannel != null){

? ? ? ? //do something with socketChannel...

? ? }

--------(十) Java NIO DatagramChannel-------

DatagramChannel是一個能收發UDP包的通道

UDP是無連接的網絡協議,所以不能像其它通道那樣讀取和寫入。它發送和接收的是數據包。

打開 DatagramChannel

DatagramChannel channel = DatagramChannel.open();

channel.socket().bind(new InetSocketAddress(9999));

接收數據

receive()

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

channel.receive(buf);

發送數據

send()方法

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

buf.put(newData.getBytes());

buf.flip();

int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));

連接到特定的地址

channel.connect(new InetSocketAddress("jenkov.com", 80));

當連接后,也可以使用read()和write()方法,就像在用傳統的通道一樣。

只是在數據傳送方面沒有任何保證。這里有幾個例子:

----------(十一) Pipe--------------

管道是2個線程之間的單向數據連接

Pipe有一個source通道和一個sink通道

數據會被寫到sink通道,從source通道讀取。

ThreadA - sink - source - ThreadB

創建管道

Pipe pipe = Pipe.open();

向管道寫數據

Pipe.SinkChannel sinkChannel = pipe.sink();

String newData = "New String to write to file..." + System.currentTimeMillis();

ByteBuffer buf = ByteBuffer.allocate(48);

buf.clear();

buf.put(newData.getBytes());

buf.flip();

while(buf.hasRemaining()) {

? ? sinkChannel.write(buf);

}

從管道讀取數據

Pipe.SourceChannel sourceChannel = pipe.source();

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = sourceChannel.read(buf);

------------(十二) Java NIO與IO-----------

IO? ? ? ? ? ? ? ? NIO

面向流? ? ? ? ? ? 面向緩沖

阻塞IO? ? ? ? ? ? 非阻塞IO

無? ? ? ? ? ? ? ? 選擇器

面向流與面向緩沖

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容