NIO教程 ——檢視閱讀(上)

NIO教程 ——檢視閱讀

簡(jiǎn)介

NIO中的N可以理解為Non-blocking 债朵,不單純是New 纵势。

不同點(diǎn):
  • 標(biāo)準(zhǔn)的IO編程接口是面向字節(jié)流和字符流的绒净。而NIO是面向通道和緩沖區(qū)的贡耽,數(shù)據(jù)總是從通道中讀到buffer緩沖區(qū)內(nèi)枫绅,或者從buffer寫(xiě)入到通道中鲁捏。
  • Java NIO使我們可以進(jìn)行非阻塞IO操作芯砸。比如說(shuō),單線程中從通道讀取數(shù)據(jù)到buffer碴萧,同時(shí)可以繼續(xù)做別的事情乙嘀,當(dāng)數(shù)據(jù)讀取到buffer中后,線程再繼續(xù)處理數(shù)據(jù)破喻。寫(xiě)數(shù)據(jù)也是一樣的虎谢。
  • NIO中有一個(gè)“slectors”的概念。selector可以檢測(cè)多個(gè)通道的事件狀態(tài)(例如:鏈接打開(kāi)曹质,數(shù)據(jù)到達(dá))這樣單線程就可以操作多個(gè)通道的數(shù)據(jù)婴噩。

概覽

NIO包含下面3個(gè)核心的組件,Channel,Buffer和Selector組成了這個(gè)核心的API:

  • Channels ——通道
  • Buffers ——緩沖區(qū)
  • Selectors ——選擇器

通常來(lái)說(shuō)NIO中的所有IO都是從Channel開(kāi)始的羽德。Channel和流有點(diǎn)類似几莽。通過(guò)Channel,我們即可以從Channel把數(shù)據(jù)寫(xiě)到Buffer中宅静,也可以把數(shù)據(jù)沖Buffer寫(xiě)入到Channel 章蚣。

http://tutorials.jenkov.com/images/java-nio/overview-channels-buffers.png

有很多的Channel,Buffer類型姨夹。下面列舉了主要的幾種:

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

正如你看到的纤垂,這些channel基于于UDP和TCP的網(wǎng)絡(luò)IO矾策,以及文件IO。 和這些類一起的還有其他一些比較有趣的接口峭沦,在本節(jié)中暫時(shí)不多介紹贾虽。為了簡(jiǎn)潔起見(jiàn),我們會(huì)在必要的時(shí)候引入這些概念吼鱼。 下面是核心的Buffer實(shí)現(xiàn)類的列表:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

這些Buffer涵蓋了可以通過(guò)IO操作的基礎(chǔ)類型:byte,short,int,long,float,double以及characters. NIO實(shí)際上還包含一種MappedBytesBuffer,一般用于和內(nèi)存映射的文件蓬豁。

選擇器允許單線程操作多個(gè)通道。如果你的程序中有大量的鏈接菇肃,同時(shí)每個(gè)鏈接的IO帶寬不高的話地粪,這個(gè)特性將會(huì)非常有幫助。比如聊天服務(wù)器巷送。 下面是一個(gè)單線程中Slector維護(hù)3個(gè)Channel的示意圖:

http://tutorials.jenkov.com/images/java-nio/overview-selectors.png

要使用Selector的話驶忌,我們必須把Channel注冊(cè)到Selector上矛辕,然后就可以調(diào)用Selector的select()方法笑跛。這個(gè)方法會(huì)進(jìn)入阻塞,直到有一個(gè)channel的狀態(tài)符合條件聊品。當(dāng)方法返回后飞蹂,線程可以處理這些事件。

Java NIO Channel通道

Java NIO Channel通道和流非常相似翻屈,主要有以下3點(diǎn)區(qū)別:

  • 通道可以讀也可以寫(xiě)陈哑,流一般來(lái)說(shuō)是單向的(只能讀或者寫(xiě))。
  • 通道可以異步讀寫(xiě)伸眶。
  • 通道總是基于緩沖區(qū)Buffer來(lái)讀寫(xiě)惊窖。

Channel的實(shí)現(xiàn)

下面列出Java NIO中最重要的集中Channel的實(shí)現(xiàn):

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

FileChannel用于文件的數(shù)據(jù)讀寫(xiě)。 DatagramChannel用于UDP的數(shù)據(jù)讀寫(xiě)厘贼。 SocketChannel用于TCP的數(shù)據(jù)讀寫(xiě)界酒。 ServerSocketChannel允許我們監(jiān)聽(tīng)TCP鏈接請(qǐng)求,每個(gè)請(qǐng)求會(huì)創(chuàng)建會(huì)一個(gè)SocketChannel.

RandomAccessFile擴(kuò)展:

RandomAccessFile(隨機(jī)訪問(wèn)文件)類嘴秸。該類是Java語(yǔ)言中功能最為豐富的文件訪問(wèn)類 毁欣。RandomAccessFile類支持“隨機(jī)訪問(wèn)”方式,這里“隨機(jī)”是指可以跳轉(zhuǎn)到文件的任意位置處讀寫(xiě)數(shù)據(jù)岳掐。在訪問(wèn)一個(gè)文件的時(shí)候凭疮,不必把文件從頭讀到尾,而是希望像訪問(wèn)一個(gè)數(shù)據(jù)庫(kù)一樣“隨心所欲”地訪問(wèn)一個(gè)文件的某個(gè)部分串述,這時(shí)使用RandomAccessFile類就是最佳選擇执解。

四種模式:R RW RWD RWS

r 以只讀的方式打開(kāi)文本,也就意味著不能用write來(lái)操作文件
rw 讀操作和寫(xiě)操作都是允許的
rws 每當(dāng)進(jìn)行寫(xiě)操作纲酗,同步的刷新到磁盤衰腌,刷新內(nèi)容和元數(shù)據(jù)
rwd 每當(dāng)進(jìn)行寫(xiě)操作逝淹,同步的刷新到磁盤,刷新內(nèi)容
RandomAccessFile的用處:

1桶唐、大型文本日志類文件的快速定位獲取數(shù)據(jù):

得益于seek的巧妙設(shè)計(jì)栅葡,我認(rèn)為我們可以從超大的文本中快速定位我們的游標(biāo),例如每次存日志的時(shí)候尤泽,我們可以建立一個(gè)索引緩存欣簇,索引是日志的起始日期,value是文本的poiniter 也就是光標(biāo)坯约,這樣我們可以快速定位某一個(gè)時(shí)間段的文本內(nèi)容

2熊咽、并發(fā)讀寫(xiě)

也是得益于seek的設(shè)計(jì),我認(rèn)為多線程可以輪流操作seek控制光標(biāo)的位置闹丐,從未達(dá)到不同線程的并發(fā)寫(xiě)操作横殴。

3、更方便的獲取二進(jìn)制文件

通過(guò)自帶的讀寫(xiě)轉(zhuǎn)碼(readDouble卿拴、writeLong等)衫仑,我認(rèn)為可以快速的完成字節(jié)碼到字符的轉(zhuǎn)換功能,對(duì)使用者來(lái)說(shuō)比較友好堕花。
RandomAccessFile參考

實(shí)例:

public class FileChannelTest {

    public static void main(String[] args) throws IOException {
        RandomAccessFile file = new RandomAccessFile("D:\\text\\1_loan.sql", "r");
        //mode只有4中文狱,如果不是讀寫(xiě)的mode或者給的不是4種中的,就會(huì)報(bào)錯(cuò)缘挽。
        RandomAccessFile copyFile = new RandomAccessFile("D:\\text\\1_loan_copy.sql", "r");
        try {
            FileChannel fileChannel = file.getChannel();
            FileChannel copyFileChannel = copyFile.getChannel();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            int read = fileChannel.read(byteBuffer);
            while (read != -1) {
                System.out.println("read:" + read);
                //byteBuffer緩沖區(qū)切換為讀模式
                byteBuffer.flip();
                copyFileChannel.write(byteBuffer);
                //“清空”byteBuffer緩沖區(qū)瞄崇,以滿足后續(xù)寫(xiě)入操作
                byteBuffer.clear();
                //注意,每次讀時(shí)都要返回讀后的狀態(tài)read值賦值給循環(huán)判斷體read壕曼,否則會(huì)陷入死循環(huán)true
                read = fileChannel.read(byteBuffer);
            }
        } finally {
            file.close();
            copyFile.close();
        }
    }
}

報(bào)錯(cuò):

RandomAccessFile copyFile = new RandomAccessFile("D:\\text\\1_loan_copy.sql", "w");
//因?yàn)闆](méi)有"w"的mode
Exception in thread "main" java.lang.IllegalArgumentException: Illegal mode "w" must be one of "r", "rw", "rws", or "rwd"
    at java.io.RandomAccessFile.<init>(RandomAccessFile.java:221)
    
RandomAccessFile copyFile = new RandomAccessFile("D:\\text\\1_loan_copy.sql", "r");
//因?yàn)闆](méi)有"w"的權(quán)限
Exception in thread "main" java.nio.channels.NonWritableChannelException
    at sun.nio.ch.FileChannelImpl.write(FileChannelImpl.java:194)
    at com.niotest.FileChannelTest.main(FileChannelTest.java:33)    

NIO Buffer緩沖區(qū)

Java NIO Buffers用于和NIO Channel交互苏研。正如你已經(jīng)知道的,我們從channel中讀取數(shù)據(jù)到buffers里腮郊,從buffer把數(shù)據(jù)寫(xiě)入到channels.

buffer本質(zhì)上就是一塊內(nèi)存區(qū)摹蘑,可以用來(lái)寫(xiě)入數(shù)據(jù),并在稍后讀取出來(lái)伴榔。這塊內(nèi)存被NIO Buffer包裹起來(lái)纹蝴,對(duì)外提供一系列的讀寫(xiě)方便開(kāi)發(fā)的接口。

Buffer基本用法

利用Buffer讀寫(xiě)數(shù)據(jù)踪少,通常遵循四個(gè)步驟

  • 把數(shù)據(jù)寫(xiě)入buffer塘安;
  • 調(diào)用flip;
  • 從Buffer中讀取數(shù)據(jù)援奢;
  • 調(diào)用buffer.clear()或者buffer.compact()

當(dāng)寫(xiě)入數(shù)據(jù)到buffer中時(shí)兼犯,buffer會(huì)記錄已經(jīng)寫(xiě)入的數(shù)據(jù)大小。當(dāng)需要讀數(shù)據(jù)時(shí),通過(guò)flip()方法把buffer從寫(xiě)模式調(diào)整為讀模式切黔;在讀模式下砸脊,可以讀取所有已經(jīng)寫(xiě)入的數(shù)據(jù)。

當(dāng)讀取完數(shù)據(jù)后纬霞,需要清空buffer凌埂,以滿足后續(xù)寫(xiě)入操作。清空buffer有兩種方式:調(diào)用clear()或compact()方法诗芜。clear會(huì)清空整個(gè)buffer瞳抓,compact則只清空已讀取的數(shù)據(jù),未被讀取的數(shù)據(jù)會(huì)被移動(dòng)到buffer的開(kāi)始位置伏恐,寫(xiě)入位置則緊跟著未讀數(shù)據(jù)之后孩哑。

Buffer的容量,位置翠桦,上限(Buffer Capacity, Position and Limit)

buffer緩沖區(qū)實(shí)質(zhì)上就是一塊內(nèi)存横蜒,用于寫(xiě)入數(shù)據(jù),也供后續(xù)再次讀取數(shù)據(jù)销凑。這塊內(nèi)存被NIO Buffer管理丛晌,并提供一系列的方法用于更簡(jiǎn)單的操作這塊內(nèi)存。

一個(gè)Buffer有三個(gè)屬性是必須掌握的闻鉴,分別是:

  • capacity容量
  • position位置
  • limit限制

position和limit的具體含義取決于當(dāng)前buffer的模式茵乱。capacity在兩種模式下都表示容量。

下面有張示例圖孟岛,描訴了不同模式下position和limit的含義:

buffers-modes.png
容量(Capacity)

作為一塊內(nèi)存,buffer有一個(gè)固定的大小督勺,叫做capacity容量渠羞。也就是最多只能寫(xiě)入容量值的字節(jié),整形等數(shù)據(jù)智哀。一旦buffer寫(xiě)滿了就需要清空已讀數(shù)據(jù)以便下次繼續(xù)寫(xiě)入新的數(shù)據(jù)次询。

位置(Position)

當(dāng)寫(xiě)入數(shù)據(jù)到Buffer的時(shí)候需要中一個(gè)確定的位置開(kāi)始,默認(rèn)初始化時(shí)這個(gè)位置position為0瓷叫,一旦寫(xiě)入了數(shù)據(jù)比如一個(gè)字節(jié)屯吊,整形數(shù)據(jù),那么position的值就會(huì)指向數(shù)據(jù)之后的一個(gè)單元摹菠,position最大可以到capacity-1.

當(dāng)從Buffer讀取數(shù)據(jù)時(shí)盒卸,也需要從一個(gè)確定的位置開(kāi)始。buffer從寫(xiě)入模式變?yōu)樽x取模式時(shí)次氨,position會(huì)歸零太雨,每次讀取后罐氨,position向后移動(dòng)乱凿。

上限(Limit)

寫(xiě)模式芬骄,limit的含義是我們所能寫(xiě)入的最大數(shù)據(jù)量。它等同于buffer的容量抡驼。

一旦切換到讀模式limit則代表我們所能讀取的最大數(shù)據(jù)量,他的值等同于寫(xiě)模式下position的位置外臂。

數(shù)據(jù)讀取的上限時(shí)buffer中已有的數(shù)據(jù),也就是limit的位置(原寫(xiě)模式下position所指的位置)律胀。

Buffer Types

Java NIO有如下具體的Buffer類型:

  • ByteBuffer
  • MappedByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

正如你看到的专钉,Buffer的類型代表了不同數(shù)據(jù)類型,換句話說(shuō)累铅,Buffer中的數(shù)據(jù)可以是上述的基本類型跃须;

分配一個(gè)Buffer(Allocating a Buffer)

為了獲取一個(gè)Buffer對(duì)象,你必須先分配娃兽。每個(gè)Buffer實(shí)現(xiàn)類都有一個(gè)allocate()方法用于分配內(nèi)存菇民。

    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
    CharBuffer charBuffer = CharBuffer.allocate(48);

寫(xiě)入數(shù)據(jù)到Buffer(Writing Data to a Buffer)

寫(xiě)數(shù)據(jù)到Buffer有兩種方法:

  • 從Channel中寫(xiě)數(shù)據(jù)到Buffer
  • 手動(dòng)寫(xiě)數(shù)據(jù)到Buffer,調(diào)用put方法
//從Channel中寫(xiě)數(shù)據(jù)到Buffer
int read = fileChannel.read(byteBuffer);
//調(diào)用put方法寫(xiě)
buf.put(3);  
//把數(shù)據(jù)寫(xiě)到特定的位置
public ByteBuffer put(int i, byte x);
//把一個(gè)具體類型數(shù)據(jù)寫(xiě)入buffer
public ByteBuffer putInt(int x)投储;

flip()——翻轉(zhuǎn)

flip()方法可以把Buffer從寫(xiě)模式切換到讀模式第练。調(diào)用flip方法會(huì)把position歸零,并設(shè)置limit為之前的position的值玛荞。 也就是說(shuō)娇掏,現(xiàn)在position代表的是讀取位置,limit標(biāo)示的是已寫(xiě)入的數(shù)據(jù)位置勋眯。

從Buffer讀取數(shù)據(jù)(Reading Data from a Buffer)

從Buffer讀數(shù)據(jù)也有兩種方式婴梧。

  • 從buffer讀數(shù)據(jù)到channel。
  • 從buffer直接讀取數(shù)據(jù)客蹋,調(diào)用get方法塞蹭。
//讀取數(shù)據(jù)到channel的例子:
int bytesWritten = inChannel.write(buf);
//調(diào)用get讀取數(shù)據(jù)的例子:
byte aByte = buf.get(); 

rewind()——倒帶

Buffer.rewind()方法將position置為0,這樣我們可以重復(fù)讀取buffer中的數(shù)據(jù)讶坯。limit保持不變番电。

clear() and compact()

一旦我們從buffer中讀取完數(shù)據(jù),需要復(fù)用buffer為下次寫(xiě)數(shù)據(jù)做準(zhǔn)備辆琅。只需要調(diào)用clear或compact方法漱办。

clear方法會(huì)重置position為0,limit為capacity婉烟,也就是整個(gè)Buffer清空娩井。實(shí)際上Buffer中數(shù)據(jù)并沒(méi)有清空,我們只是把標(biāo)記為修改了隅很。(重新寫(xiě)入的時(shí)候這些存在的數(shù)據(jù)就會(huì)被新的數(shù)據(jù)覆蓋)

如果Buffer還有一些數(shù)據(jù)沒(méi)有讀取完撞牢,調(diào)用clear就會(huì)導(dǎo)致這部分?jǐn)?shù)據(jù)被“遺忘”率碾,因?yàn)槲覀儧](méi)有標(biāo)記這部分?jǐn)?shù)據(jù)未讀。

針對(duì)這種情況屋彪,如果需要保留未讀數(shù)據(jù)所宰,那么可以使用compact()。 因此compact()和clear()的區(qū)別就在于對(duì)未讀數(shù)據(jù)的處理畜挥,是保留這部分?jǐn)?shù)據(jù)還是一起清空仔粥。

mark() and reset()

通過(guò)mark方法可以標(biāo)記當(dāng)前的position,通過(guò)reset來(lái)恢復(fù)mark的位置蟹但,這個(gè)非常像canva的save和restore:

buffer.mark();

//call buffer.get() a couple of times, e.g. during parsing.

buffer.reset();  //set position back to mark.

equals() and compareTo()

可以用eqauls和compareTo比較兩個(gè)buffer

equals()

判斷兩個(gè)buffer相對(duì)躯泰,需滿足:

  • 類型相同
  • buffer中剩余字節(jié)數(shù)相同
  • 所有剩余字節(jié)相等

從上面的三個(gè)條件可以看出,equals只比較buffer中的部分內(nèi)容华糖,并不會(huì)去比較每一個(gè)元素麦向。

compareTo()

compareTo也是比較buffer中的剩余元素,只不過(guò)這個(gè)方法適用于比較排序的:

NIO Scatter (分散)/ Gather(聚集)

——分散讀和聚集寫(xiě)的場(chǎng)景客叉。

Java NIO發(fā)布時(shí)內(nèi)置了對(duì)scatter / gather的支持诵竭。scatter / gather是通過(guò)通道讀寫(xiě)數(shù)據(jù)的兩個(gè)概念。

Scattering read指的是從通道讀取的操作能把數(shù)據(jù)寫(xiě)入多個(gè)buffer兼搏,也就是scatters代表了數(shù)據(jù)從一個(gè)channel到多個(gè)buffer的過(guò)程卵慰。

gathering write則正好相反,表示的是從多個(gè)buffer把數(shù)據(jù)寫(xiě)入到一個(gè)channel中佛呻。

Scatter/gather在有些場(chǎng)景下會(huì)非常有用裳朋,比如需要處理多份分開(kāi)傳輸?shù)臄?shù)據(jù)。舉例來(lái)說(shuō)吓著,假設(shè)一個(gè)消息包含了header和body鲤嫡,我們可能會(huì)把header和body保存在不同獨(dú)立buffer中,這種分開(kāi)處理header與body的做法會(huì)使開(kāi)發(fā)更簡(jiǎn)明夜矗。

Scattering Reads

"scattering read"是把數(shù)據(jù)從單個(gè)Channel寫(xiě)入到多個(gè)buffer泛范,下面是示意圖:

scatter.png

觀察代碼可以發(fā)現(xiàn),我們把多個(gè)buffer寫(xiě)在了一個(gè)數(shù)組中紊撕,然后把數(shù)組傳遞給channel.read()方法。read()方法內(nèi)部會(huì)負(fù)責(zé)把數(shù)據(jù)按順序?qū)戇M(jìn)傳入的buffer數(shù)組內(nèi)赡突。一個(gè)buffer寫(xiě)滿后对扶,接著寫(xiě)到下一個(gè)buffer中。

實(shí)際上惭缰,scattering read內(nèi)部必須寫(xiě)滿一個(gè)buffer后才會(huì)向后移動(dòng)到下一個(gè)buffer浪南,因此這并不適合消息大小會(huì)動(dòng)態(tài)改變的部分,也就是說(shuō)漱受,如果你有一個(gè)header和body络凿,并且header有一個(gè)固定的大小(比如128字節(jié)),這種情形下可以正常工作。

gathering Writes

"gathering write"把多個(gè)buffer的數(shù)據(jù)寫(xiě)入到同一個(gè)channel中.

gather.png

傳入一個(gè)buffer數(shù)組給write,內(nèi)部會(huì)按順序?qū)?shù)組內(nèi)的內(nèi)容寫(xiě)進(jìn)channel絮记,這里需要注意摔踱,寫(xiě)入的時(shí)候針對(duì)的是buffer中position到limit之間的數(shù)據(jù)。也就是如果buffer的容量是128字節(jié)怨愤,但它只包含了58字節(jié)數(shù)據(jù)派敷,那么寫(xiě)入的時(shí)候只有58字節(jié)會(huì)真正寫(xiě)入。因此gathering write是可以適用于可變大小的message的撰洗,這和scattering reads不同篮愉。

NIO Channel to Channel Transfers通道傳輸接口

在Java NIO中如果一個(gè)channel是FileChannel類型的,那么他可以直接把數(shù)據(jù)傳輸?shù)搅硪粋€(gè)channel差导。這個(gè)特性得益于FileChannel包含的transferTo和transferFrom兩個(gè)方法试躏。

transferFrom()——目標(biāo)channel用,參數(shù)為源數(shù)據(jù)channel设褐。

transferFrom的參數(shù)position和count表示目標(biāo)文件的寫(xiě)入位置和最多寫(xiě)入的數(shù)據(jù)量颠蕴。如果通道源的數(shù)據(jù)小于count那么就傳實(shí)際有的數(shù)據(jù)量。 另外络断,有些SocketChannel的實(shí)現(xiàn)在傳輸時(shí)只會(huì)傳輸哪些處于就緒狀態(tài)的數(shù)據(jù)裁替,即使SocketChannel后續(xù)會(huì)有更多可用數(shù)據(jù)。因此貌笨,這個(gè)傳輸過(guò)程可能不會(huì)傳輸整個(gè)的數(shù)據(jù)弱判。

transferTo()——源數(shù)據(jù)用,參數(shù)為目標(biāo)channel

SocketChannel的問(wèn)題也存在于transferTo.SocketChannel的實(shí)現(xiàn)可能只在發(fā)送的buffer填充滿后才發(fā)送锥惋,并結(jié)束昌腰。

實(shí)例:

public class ChannelTransferTest {

    public static void main(String[] args) throws IOException {
        RandomAccessFile fromfile = new RandomAccessFile("D:\\text\\1_loan.sql", "rw");
        //mode只有4中,如果不是讀寫(xiě)的mode或者給的不是4種中的膀跌,就會(huì)報(bào)錯(cuò)遭商。
        RandomAccessFile toFile = new RandomAccessFile("D:\\text\\1_loan_copy.sql", "rw");

        FileChannel fromfileChannel = fromfile.getChannel();
        FileChannel toFileChannel = toFile.getChannel();
        //==========================transferTo=================================
        //transferTo方法把fromfileChannel數(shù)據(jù)傳輸?shù)搅硪粋€(gè)toFileChannel
        //long transferSize = fromfileChannel.transferTo(0, fromfileChannel.size(), toFileChannel);
        //System.out.println(transferSize);

        //=============================transferFrom==============================
        //把數(shù)據(jù)從通道源傳輸?shù)絫oFileChannel,相比通過(guò)buffer讀寫(xiě)更加的便捷
        long transferSize1 = toFileChannel.transferFrom(fromfileChannel, 0, fromfileChannel.size());
        //參數(shù)position和count表示目標(biāo)文件的寫(xiě)入位置和最多寫(xiě)入的數(shù)據(jù)量
        //long transferSize1 = toFileChannel.transferFrom(fromfileChannel, 0, fromfileChannel.size()-1000);
        //如果通道源的數(shù)據(jù)小于count那么就傳實(shí)際有的數(shù)據(jù)量。
        //long transferSize1 = toFileChannel.transferFrom(fromfileChannel, 0, fromfileChannel.size()+1000);
        System.out.println(transferSize1);
    }
}

NIO Selector選擇器

Selector是Java NIO中的一個(gè)組件捅伤,用于檢查一個(gè)或多個(gè)NIO Channel的狀態(tài)是否處于可讀劫流、可寫(xiě)。如此可以實(shí)現(xiàn)單線程管理多個(gè)channels,也就是可以管理多個(gè)網(wǎng)絡(luò)鏈接丛忆。

為什么使用Selector

用單線程處理多個(gè)channels的好處是我需要更少的線程來(lái)處理channel祠汇。實(shí)際上,你甚至可以用一個(gè)線程來(lái)處理所有的channels熄诡。從操作系統(tǒng)的角度來(lái)看可很,切換線程開(kāi)銷是比較昂貴的,并且每個(gè)線程都需要占用系統(tǒng)資源凰浮,因此暫用線程越少越好我抠。

需要留意的是苇本,現(xiàn)代操作系統(tǒng)和CPU在多任務(wù)處理上已經(jīng)變得越來(lái)越好,所以多線程帶來(lái)的影響也越來(lái)越小菜拓。如果一個(gè)CPU是多核的瓣窄,如果不執(zhí)行多任務(wù)反而是浪費(fèi)了機(jī)器的性能。不過(guò)這些設(shè)計(jì)討論是另外的話題了尘惧。簡(jiǎn)而言之康栈,通過(guò)Selector我們可以實(shí)現(xiàn)單線程操作多個(gè)channel。

image.png

創(chuàng)建Selector

創(chuàng)建一個(gè)Selector可以通過(guò)Selector.open()方法

Selector selector = Selector.open();

注冊(cè)Channel到Selector上

先把Channel注冊(cè)到Selector上喷橙,這個(gè)操作使用SelectableChannel的register()啥么。SocketChannel等都有繼承此抽象類。

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

Channel必須是非阻塞的贰逾。所以FileChannel不適用Selector悬荣,因?yàn)?strong>FileChannel不能切換為非阻塞模式。Socket channel可以正常使用疙剑。

注意register的第二個(gè)參數(shù)氯迂,這個(gè)參數(shù)是一個(gè)“關(guān)注集合”,代表我們關(guān)注的channel狀態(tài)言缤,有四種基礎(chǔ)類型可供監(jiān)聽(tīng):

  1. Connect——連接就緒(連接成功后)
  2. Accept——可連接就緒(接受請(qǐng)求連接時(shí))
  3. Read——讀就緒
  4. Write——寫(xiě)就緒

一個(gè)channel觸發(fā)了一個(gè)事件也可視作該事件處于就緒狀態(tài)嚼蚀。因此當(dāng)channel與server連接成功后,那么就是“連接就緒”狀態(tài)管挟。server channel接收請(qǐng)求連接時(shí)處于“可連接就緒”狀態(tài)轿曙。channel有數(shù)據(jù)可讀時(shí)處于“讀就緒”狀態(tài)。channel可以進(jìn)行數(shù)據(jù)寫(xiě)入時(shí)處于“寫(xiě)就緒”狀態(tài)僻孝。

上述的四種就緒狀態(tài)用SelectionKey中的常量表示如下:

  1. SelectionKey.OP_CONNECT
  2. SelectionKey.OP_ACCEPT
  3. SelectionKey.OP_READ
  4. SelectionKey.OP_WRITE

如果對(duì)多個(gè)事件感興趣可利用位的或運(yùn)算結(jié)合多個(gè)常量导帝,比如:

int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;  

SelectionKey's

在上一小節(jié)中,我們利用register方法把Channel注冊(cè)到了Selectors上穿铆,這個(gè)方法的返回值是SelectionKeys您单,這個(gè)返回的對(duì)象包含了一些比較有價(jià)值的屬性:

  • The interest set
  • The ready set
  • The Channel
  • The Selector
  • An attached object (optional)
Interest Set

這個(gè)“關(guān)注集合”實(shí)際上就是我們希望處理的事件的集合它的值就是注冊(cè)時(shí)傳入的參數(shù)荞雏,我們可以用按為與運(yùn)算把每個(gè)事件取出來(lái):

int interestSet = selectionKey.interestOps();

boolean isInterestedInAccept  = interestSet & SelectionKey.OP_ACCEPT;
boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE; 
Ready Set

"就緒集合"中的值是當(dāng)前channel處于就緒的值虐秦,一般來(lái)說(shuō)在調(diào)用了select方法后都會(huì)需要用到就緒狀態(tài)

int readySet = selectionKey.readyOps();

從“就緒集合”中取值的操作類似于“關(guān)注集合”的操作,當(dāng)然還有更簡(jiǎn)單的方法凤优,SelectionKey提供了一系列返回值為boolean的的方法:

selectionKey.isAcceptable();
selectionKey.isConnectable();
selectionKey.isReadable();
selectionKey.isWritable();
Channel + Selector

從SelectionKey操作Channel和Selector非常簡(jiǎn)單:

Channel  channel  = selectionKey.channel();
Selector selector = selectionKey.selector();  
Attaching Objects

我們可以給一個(gè)SelectionKey附加一個(gè)Object羡疗,這樣做一方面可以方便我們識(shí)別某個(gè)特定的channel,同時(shí)也增加了channel相關(guān)的附加信息别洪。例如,可以把用于channel的buffer附加到SelectionKey上:

selectionKey.attach(theObject);

Object attachedObj = selectionKey.attachment();

附加對(duì)象的操作也可以在register的時(shí)候就執(zhí)行:

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

從Selector中選擇channel

一旦我們向Selector注冊(cè)了一個(gè)或多個(gè)channel后柳刮,就可以調(diào)用select來(lái)獲取channel挖垛。select方法會(huì)返回所有處于就緒狀態(tài)的channel痒钝。 select方法具體如下:

  • int select()
  • int select(long timeout)
  • int selectNow()

select()方法在返回channel之前處于阻塞狀態(tài)。 select(long timeout)和select做的事一樣痢毒,不過(guò)他的阻塞有一個(gè)超時(shí)限制送矩。

selectNow()不會(huì)阻塞,根據(jù)當(dāng)前狀態(tài)立刻返回合適的channel哪替。

select()方法的返回值是一個(gè)int整形栋荸,代表有多少channel處于就緒了。也就是自上一次select后有多少channel進(jìn)入就緒凭舶。舉例來(lái)說(shuō)晌块,假設(shè)第一次調(diào)用select時(shí)正好有一個(gè)channel就緒,那么返回值是1帅霜,并且對(duì)這個(gè)channel做任何處理匆背,接著再次調(diào)用select,此時(shí)恰好又有一個(gè)新的channel就緒身冀,那么返回值還是1钝尸,現(xiàn)在我們一共有兩個(gè)channel處于就緒,但是在每次調(diào)用select時(shí)只有一個(gè)channel是就緒的搂根。

selectedKeys()

在調(diào)用select并返回了有channel就緒之后珍促,可以通過(guò)選中的key集合來(lái)獲取channel,這個(gè)操作通過(guò)調(diào)用selectedKeys()方法:

Set<SelectionKey> selectedKeys = selector.selectedKeys();    

還記得在register時(shí)的操作吧剩愧,我們r(jià)egister后的返回值就是SelectionKey實(shí)例猪叙,也就是我們現(xiàn)在通過(guò)selectedKeys()方法所返回的SelectionKey。

遍歷這些SelectionKey可以通過(guò)如下方法:

Set<SelectionKey> selectedKeys = selector.selectedKeys();

Iterator<SelectionKey> 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();
}

上述循環(huán)會(huì)迭代key集合隙咸,針對(duì)每個(gè)key我們單獨(dú)判斷他是處于何種就緒狀態(tài)沐悦。

注意:keyIterater.remove()方法的調(diào)用,Selector本身并不會(huì)移除SelectionKey對(duì)象五督,這個(gè)操作需要我們手動(dòng)執(zhí)行藏否。當(dāng)下次channel處于就緒是,Selector仍然會(huì)把這些key再次加入進(jìn)來(lái)充包。

SelectionKey.channel返回的channel實(shí)例需要強(qiáng)轉(zhuǎn)為我們實(shí)際使用的具體的channel類型副签,例如ServerSocketChannel或SocketChannel.

wakeUp()

由于調(diào)用select而被阻塞的線程,可以通過(guò)調(diào)用Selector.wakeup()來(lái)喚醒即便此時(shí)已然沒(méi)有channel處于就緒狀態(tài)基矮。具體操作是淆储,在另外一個(gè)線程調(diào)用wakeup,被阻塞與select方法的線程就會(huì)立刻返回家浇。

close()

當(dāng)操作Selector完畢后本砰,需要調(diào)用close方法。close的調(diào)用會(huì)關(guān)閉Selector并使相關(guān)的SelectionKey都無(wú)效钢悲。channel本身不會(huì)被關(guān)閉点额。

示例:首先打開(kāi)一個(gè)Selector,然后注冊(cè)channel舔株,最后監(jiān)聽(tīng)Selector的狀態(tài)。

public class NIOServer {

    public static void main(String[] args) throws IOException {

        // 1.獲取通道
        ServerSocketChannel server = ServerSocketChannel.open();

        // 2.切換成非阻塞模式
        server.configureBlocking(false);

        // 3. 綁定連接
        server.bind(new InetSocketAddress(6666));

        // 4. 獲取選擇器
        Selector selector = Selector.open();

        // 4.1將通道注冊(cè)到選擇器上还棱,指定接收“監(jiān)聽(tīng)通道”事件
        server.register(selector, SelectionKey.OP_ACCEPT);

        // 5. 輪訓(xùn)地獲取選擇器上已“就緒”的事件--->只要select()>0载慈,說(shuō)明已就緒
        while (selector.select() > 0) {
            // 6. 獲取當(dāng)前選擇器所有注冊(cè)的“選擇鍵”(已就緒的監(jiān)聽(tīng)事件)
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            // 7. 獲取已“就緒”的事件,(不同的事件做不同的事)
            while (iterator.hasNext()) {

                SelectionKey selectionKey = iterator.next();

                // 接收事件就緒
                if (selectionKey.isAcceptable()) {

                    // 8. 獲取客戶端的鏈接
                    SocketChannel client = server.accept();

                    // 8.1 切換成非阻塞狀態(tài)
                    client.configureBlocking(false);

                    // 8.2 注冊(cè)到選擇器上-->拿到客戶端的連接為了讀取通道的數(shù)據(jù)(監(jiān)聽(tīng)讀就緒事件)
                    client.register(selector, SelectionKey.OP_READ);

                } else if (selectionKey.isReadable()) { // 讀事件就緒

                    // 9. 獲取當(dāng)前選擇器讀就緒狀態(tài)的通道
                    SocketChannel client = (SocketChannel) selectionKey.channel();

                    // 9.1讀取數(shù)據(jù)
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    // 9.2得到文件通道珍手,將客戶端傳遞過(guò)來(lái)的圖片寫(xiě)到本地項(xiàng)目下(寫(xiě)模式办铡、沒(méi)有則創(chuàng)建)
                    FileChannel outChannel = FileChannel.open(Paths.get("2_loan.sql"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

                    while (client.read(buffer) > 0) {
                        // 在讀之前都要切換成讀模式
                        buffer.flip();

                        outChannel.write(buffer);

                        // 讀完切換成寫(xiě)模式,能讓管道繼續(xù)讀取文件的數(shù)據(jù)
                        buffer.clear();
                    }
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    byteBuffer.put("yeah,i know,i got your message!".getBytes());
                    byteBuffer.flip();
                    client.write(byteBuffer);
                }
                // 10. 取消選擇鍵(已經(jīng)處理過(guò)的事件琳要,就應(yīng)該取消掉了)
                iterator.remove();
            }
        }
    }
}
public class NIOClientTwo {

    public static void main(String[] args) throws IOException {

        // 1. 獲取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

        // 1.1切換成非阻塞模式
        socketChannel.configureBlocking(false);

        // 1.2獲取選擇器
        Selector selector = Selector.open();

        // 1.3將通道注冊(cè)到選擇器中寡具,獲取服務(wù)端返回的數(shù)據(jù)
        socketChannel.register(selector, SelectionKey.OP_READ);

        // 2. 發(fā)送一張圖片給服務(wù)端吧
        FileChannel fileChannel = FileChannel.open(Paths.get("D:\\text\\1_loan.sql"), StandardOpenOption.READ);

        // 3.要使用NIO,有了Channel焙蹭,就必然要有Buffer晒杈,Buffer是與數(shù)據(jù)打交道的呢
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 4.讀取本地文件(圖片),發(fā)送到服務(wù)器
        while (fileChannel.read(buffer) != -1) {

            // 在讀之前都要切換成讀模式
            buffer.flip();

            socketChannel.write(buffer);

            // 讀完切換成寫(xiě)模式孔厉,能讓管道繼續(xù)讀取文件的數(shù)據(jù)
            buffer.clear();
        }


        // 5. 輪訓(xùn)地獲取選擇器上已“就緒”的事件--->只要select()>0拯钻,說(shuō)明已就緒
        while (selector.select() > 0) {
            // 6. 獲取當(dāng)前選擇器所有注冊(cè)的“選擇鍵”(已就緒的監(jiān)聽(tīng)事件)
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            // 7. 獲取已“就緒”的事件,(不同的事件做不同的事)
            while (iterator.hasNext()) {

                SelectionKey selectionKey = iterator.next();

                // 8. 讀事件就緒
                if (selectionKey.isReadable()) {

                    // 8.1得到對(duì)應(yīng)的通道
                    SocketChannel channel = (SocketChannel) selectionKey.channel();

                    ByteBuffer responseBuffer = ByteBuffer.allocate(1024);

                    // 9. 知道服務(wù)端要返回響應(yīng)的數(shù)據(jù)給客戶端撰豺,客戶端在這里接收
                    int readBytes = channel.read(responseBuffer);

                    if (readBytes > 0) {
                        // 切換讀模式
                        responseBuffer.flip();
                        System.out.println(new String(responseBuffer.array(), 0, readBytes));
                    }
                }
                // 10. 取消選擇鍵(已經(jīng)處理過(guò)的事件粪般,就應(yīng)該取消掉了)
                iterator.remove();
            }
        }
    }
}

NIO FileChannel文件通道

Java NIO中的FileChannel是用于連接文件的通道。通過(guò)文件通道可以讀污桦、寫(xiě)文件的數(shù)據(jù)亩歹。Java NIO的FileChannel是相對(duì)標(biāo)準(zhǔn)Java IO API的可選接口。

FileChannel不可以設(shè)置為非阻塞模式凡橱,他只能在阻塞模式下運(yùn)行小作。

打開(kāi)文件通道

在使用FileChannel前必須打開(kāi)通道,打開(kāi)一個(gè)文件通道需要通過(guò)輸入/輸出流或者RandomAccessFile稼钩,下面是通過(guò)RandomAccessFile打開(kāi)文件通道的案例:

RandomAccessFile aFile = new RandomAccessFile("D:\text\1_loan.sql", "rw");
FileChannel inChannel = aFile.getChannel();
從文件通道內(nèi)讀取數(shù)據(jù)

讀取文件通道的數(shù)據(jù)可以通過(guò)read方法:

ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);

首先開(kāi)辟一個(gè)Buffer顾稀,從通道中讀取的數(shù)據(jù)會(huì)寫(xiě)入Buffer內(nèi)。接著就可以調(diào)用read方法坝撑,read的返回值代表有多少字節(jié)被寫(xiě)入了Buffer静秆,返回-1則表示已經(jīng)讀取到文件結(jié)尾了。

向文件通道寫(xiě)入數(shù)據(jù)

寫(xiě)數(shù)據(jù)用write方法巡李,入?yún)⑹荁uffer:

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);
}

注意這里的write調(diào)用寫(xiě)在了wihle循環(huán)匯總抚笔,這是因?yàn)閣rite不能保證有多少數(shù)據(jù)真實(shí)被寫(xiě)入,因此需要循環(huán)寫(xiě)入直到?jīng)]有更多數(shù)據(jù)侨拦。

關(guān)閉通道

操作完畢后殊橙,需要把通道關(guān)閉:

channel.close();    

FileChannel Position

當(dāng)操作FileChannel的時(shí)候讀和寫(xiě)都是基于特定起始位置的(position),獲取當(dāng)前的位置可以用FileChannel的position()方法,設(shè)置當(dāng)前位置可以用帶參數(shù)的position(long pos)方法蛀柴。

//獲取當(dāng)前的位置
long position = fileChannel.position();
//設(shè)置當(dāng)前位置為pos +123
fileChannel.position(pos +123);

假設(shè)我們把當(dāng)前位置設(shè)置為文件結(jié)尾之后螃概,那么當(dāng)我們視圖從通道中讀取數(shù)據(jù)時(shí)就會(huì)發(fā)現(xiàn)返回值是-1,表示已經(jīng)到達(dá)文件結(jié)尾了鸽疾。 如果把當(dāng)前位置設(shè)置為文件結(jié)尾之后,再向通道中寫(xiě)入數(shù)據(jù)训貌,文件會(huì)自動(dòng)擴(kuò)展以便寫(xiě)入數(shù)據(jù)制肮,但是這樣會(huì)導(dǎo)致文件中出現(xiàn)類似空洞,即文件的一些位置是沒(méi)有數(shù)據(jù)的递沪。

FileChannel Size

size()方法可以返回FileChannel對(duì)應(yīng)的文件的文件大胁虮恰:

long fileSize = channel.size();    

FileChannel Truncate

利用truncate方法可以截取指定長(zhǎng)度的文件

FileChannel truncateFile = fileChannel.truncate(1024);

FileChannel Force

force方法會(huì)把所有未寫(xiě)磁盤的數(shù)據(jù)都強(qiáng)制寫(xiě)入磁盤催植。這是因?yàn)樵诓僮飨到y(tǒng)中出于性能考慮回把數(shù)據(jù)放入緩沖區(qū)噩斟,所以不能保證數(shù)據(jù)在調(diào)用write寫(xiě)入文件通道后就及時(shí)寫(xiě)到磁盤上了,除非手動(dòng)調(diào)用force方法诫惭。 force方法需要一個(gè)布爾參數(shù)檩奠,代表是否把meta data也一并強(qiáng)制寫(xiě)入桩了。

channel.force(true);

NIO SocketChannel套接字通道

在Java NIO體系中,SocketChannel是用于TCP網(wǎng)絡(luò)連接的套接字接口埠戳,相當(dāng)于Java網(wǎng)絡(luò)編程中的Socket套接字接口井誉。創(chuàng)建SocketChannel主要有兩種方式,如下:

  1. 打開(kāi)一個(gè)SocketChannel并連接網(wǎng)絡(luò)上的一臺(tái)服務(wù)器整胃。
  2. 當(dāng)ServerSocketChannel接收到一個(gè)連接請(qǐng)求時(shí)颗圣,會(huì)創(chuàng)建一個(gè)SocketChannel。
建立一個(gè)SocketChannel連接

打開(kāi)一個(gè)SocketChannel可以這樣操作:

SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://www.google.com", 80));  
關(guān)閉一個(gè)SocketChannel連接

關(guān)閉一個(gè)SocketChannel只需要調(diào)用他的close方法屁使,如下:

socketChannel.close();
從SocketChannel中讀數(shù)據(jù)

從一個(gè)SocketChannel連接中讀取數(shù)據(jù)在岂,可以通過(guò)read()方法,如下:

ByteBuffer buf = ByteBuffer.allocate(48);

int bytesRead = socketChannel.read(buf);

首先需要開(kāi)辟一個(gè)Buffer蛮寂。從SocketChannel中讀取的數(shù)據(jù)將放到Buffer中蔽午。

接下來(lái)就是調(diào)用SocketChannel的read()方法.這個(gè)read()會(huì)把通道中的數(shù)據(jù)讀到Buffer中。read()方法的返回值是一個(gè)int數(shù)據(jù)共郭,代表此次有多少字節(jié)的數(shù)據(jù)被寫(xiě)入了Buffer中祠丝。如果返回的是-1,那么意味著通道內(nèi)的數(shù)據(jù)已經(jīng)讀取完畢,到底了(鏈接關(guān)閉)除嘹。

向SocketChannel寫(xiě)數(shù)據(jù)

向SocketChannel中寫(xiě)入數(shù)據(jù)是通過(guò)write()方法写半,write也需要一個(gè)Buffer作為參數(shù)。下面看一下具體的示例:

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);
}

非阻塞模式

我們可以把SocketChannel設(shè)置為non-blocking(非阻塞)模式尉咕。這樣的話在調(diào)用connect(), read(), write()時(shí)都是異步的叠蝇。

socketChannel.configureBlocking(false);
connect()

如果我們?cè)O(shè)置了一個(gè)SocketChannel是非阻塞的,那么調(diào)用connect()后年缎,方法會(huì)在鏈接建立前就直接返回悔捶。為了檢查當(dāng)前鏈接是否建立成功铃慷,我們可以調(diào)用finishConnect(),如下:

socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://www.google.com", 80));

while(! socketChannel.finishConnect() ){
    //wait, or do something else...    
}
write()

在非阻塞模式下,調(diào)用write()方法不能確保方法返回后寫(xiě)入操作一定得到了執(zhí)行蜕该。因此我們需要把write()調(diào)用放到循環(huán)內(nèi)犁柜。這和前面在講write()時(shí)是一樣的,此處就不在代碼演示堂淡。

read()

在非阻塞模式下馋缅,調(diào)用read()方法也不能確保方法返回后,確實(shí)讀到了數(shù)據(jù)绢淀。因此我們需要自己檢查的整型返回值萤悴,這個(gè)返回值會(huì)告訴我們實(shí)際讀取了多少字節(jié)的數(shù)據(jù)

Selector結(jié)合非阻塞模式

SocketChannel的非阻塞模式可以和Selector很好的協(xié)同工作皆的。把一個(gè)或多個(gè)SocketChannel注冊(cè)到一個(gè)Selector后覆履,我們可以通過(guò)Selector指導(dǎo)哪些channels通道是處于可讀,可寫(xiě)等等狀態(tài)的费薄。

NIO ServerSocketChannel服務(wù)端套接字通道

在Java NIO中硝全,ServerSocketChannel是用于監(jiān)聽(tīng)TCP鏈接請(qǐng)求的通道,正如Java網(wǎng)絡(luò)編程中的ServerSocket一樣义锥。

ServerSocketChannel實(shí)現(xiàn)類位于java.nio.channels包下面柳沙。

void test() throws IOException {
    //打開(kāi)一個(gè)ServerSocketChannel我們需要調(diào)用他的open()方法
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    serverSocketChannel.socket().bind(new InetSocketAddress(9999));
    while(true) {
        SocketChannel socketChannel = serverSocketChannel.accept();
        //do something with socketChannel...
        if (socketChannel.isConnected()) {
            break;
        }
    }
    //關(guān)閉一個(gè)ServerSocketChannel我們需要調(diào)用close()方法
    serverSocketChannel.close();
}

監(jiān)聽(tīng)鏈接

通過(guò)調(diào)用accept()方法,我們就開(kāi)始監(jiān)聽(tīng)端口上的請(qǐng)求連接拌倍。當(dāng)accept()返回時(shí)赂鲤,他會(huì)返回一個(gè)SocketChannel連接實(shí)例,實(shí)際上accept()是阻塞操作柱恤,他會(huì)阻塞帶去線程知道返回一個(gè)連接数初; 很多時(shí)候我們是不滿足于監(jiān)聽(tīng)一個(gè)連接的,因此我們會(huì)把a(bǔ)ccept()的調(diào)用放到循環(huán)中梗顺,就像這樣:

while(true){
    SocketChannel socketChannel = serverSocketChannel.accept();
    //do something with socketChannel...
}

當(dāng)然我們可以在循環(huán)體內(nèi)加上合適的中斷邏輯泡孩,而不是單純的在while循環(huán)中寫(xiě)true,以此來(lái)結(jié)束循環(huán)監(jiān)聽(tīng)寺谤;

非阻塞模式

實(shí)際上ServerSocketChannel是可以設(shè)置為非阻塞模式的仑鸥。在非阻塞模式下,調(diào)用accept()函數(shù)會(huì)立刻返回变屁,如果當(dāng)前沒(méi)有請(qǐng)求的鏈接眼俊,那么返回值為空null。因此我們需要手動(dòng)檢查返回的SocketChannel是否為空粟关,例如:

ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

serverSocketChannel.socket().bind(new InetSocketAddress(9999));
//設(shè)置為非阻塞模式
serverSocketChannel.configureBlocking(false);
while(true){
    SocketChannel socketChannel = serverSocketChannel.accept();
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末疮胖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌澎灸,老刑警劉巖院塞,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異性昭,居然都是意外死亡拦止,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門巩梢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)创泄,“玉大人,你說(shuō)我怎么就攤上這事括蝠。” “怎么了饭聚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵忌警,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我秒梳,道長(zhǎng)法绵,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任酪碘,我火速辦了婚禮朋譬,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘兴垦。我一直安慰自己徙赢,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布探越。 她就那樣靜靜地躺著狡赐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钦幔。 梳的紋絲不亂的頭發(fā)上枕屉,一...
    開(kāi)封第一講書(shū)人閱讀 49,764評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音鲤氢,去河邊找鬼搀擂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛卷玉,可吹牛的內(nèi)容都是我干的哨颂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼揍庄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼咆蒿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤沃测,失蹤者是張志新(化名)和其女友劉穎缭黔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體蒂破,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馏谨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了附迷。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惧互。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖喇伯,靈堂內(nèi)的尸體忽然破棺而出喊儡,到底是詐尸還是另有隱情,我是刑警寧澤稻据,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布艾猜,位于F島的核電站,受9級(jí)特大地震影響捻悯,放射性物質(zhì)發(fā)生泄漏匆赃。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一今缚、第九天 我趴在偏房一處隱蔽的房頂上張望算柳。 院中可真熱鬧,春花似錦姓言、人聲如沸瞬项。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)滥壕。三九已至,卻和暖如春兽泣,著一層夾襖步出監(jiān)牢的瞬間绎橘,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工唠倦, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留称鳞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓稠鼻,卻偏偏與公主長(zhǎng)得像冈止,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子候齿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • Non-blocking Server非阻塞服務(wù)器 非阻塞服務(wù)器代碼[https://github.com/jje...
    卡斯特梅的雨傘閱讀 462評(píng)論 0 1
  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API熙暴,可以替代標(biāo)準(zhǔn)的Java I...
    zhisheng_blog閱讀 1,115評(píng)論 0 7
  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API闺属,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,546評(píng)論 1 143
  • Java NIO(New IO)是從Java 1.4版本開(kāi)始引入的一個(gè)新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    編碼前線閱讀 2,263評(píng)論 0 5
  • 16宿命:用概率思維提高你的勝算 以前的我是風(fēng)險(xiǎn)厭惡者周霉,不喜歡去冒險(xiǎn)掂器,但是人生放棄了冒險(xiǎn),也就放棄了無(wú)數(shù)的可能俱箱。 ...
    yichen大刀閱讀 6,038評(píng)論 0 4