Java 網(wǎng)絡編程 —— 非阻塞式編程
在生活中,最常見的阻塞現(xiàn)象是公路上汽車的堵塞。汽車在公路上快速行駛,如果前方交通受阻,就只好停下來等待,等到公路順暢,才能恢復行駛。
【資料圖】
線程在運行中也會因為某些原因而阻塞。所有處于阻塞狀態(tài)的線程的共同特征:放棄 CPU,暫停運行,只有等到導致阻塞的原因消除,才能恢復運行,或者被其他線程中斷該線程會退出阻塞狀態(tài),并且拋出 InterruptedException
導致線程阻塞的原因主要有以下方面:
線程執(zhí)行了Threadsleep(int n)
方法,線程放棄 CPU,睡眠 n ms,然后恢復運行線程要執(zhí)行一段同步代碼,由于無法獲得相關的同步鎖,只好進入阻塞狀態(tài),等到獲取同步鎖再恢復運行線程執(zhí)行了一個對象的 wait()
方法,進入阻塞狀態(tài),只有等到其他線程執(zhí)行了該對象的 notify()
或 notifyAll()
方法,才可能將其喚醒線程執(zhí)行 IO 操作或進行遠程通信時,會因為等待相關的資源而進入阻塞狀態(tài)進行遠程通信時,在客戶程序中,線程在以下情況下可能進入阻塞狀態(tài):
請求與服務器建立連接時,即當線程執(zhí)行 Socket 的帶參數(shù)的構造方法,或執(zhí)行 Socke 的 connect()
方法時,會進入阻塞狀態(tài),直到連接成功,此線程才從 Socket 的構造方法或 connect()
方法返回
線程從 Socket 的輸入流讀入數(shù)據(jù)時,如果沒有足夠的數(shù)據(jù),就會進入阻塞狀態(tài),直到讀到了足夠的數(shù)據(jù),或者到達輸入流的末尾,或者出現(xiàn)了異常,才從輸入流的 read()
方法返回或異常中斷
輸入流中有多少數(shù)據(jù)才算足夠呢?這要看線程執(zhí)行的 read()
方法的類:
int read()
:只要輸入流中有 1 字節(jié),就算足夠int read(byte[] buf)
:只要輸入流中的字節(jié)數(shù)目與參數(shù) buff 數(shù)組的長度相同,就算足夠String readLine()
:只要輸入流中有 1 行字符,就算足夠線程向 Socket 的輸出流寫一批數(shù)據(jù)時,可能會進入阻塞狀態(tài),等到輸出了所有的數(shù)據(jù),或者出現(xiàn)異常,才從輸出流的 write()
方法返回或異常中斷
如果調(diào)用 Socket 的 setSoLinger()
方法設置了關閉 Socket 的延遲時間,那么當線程執(zhí)行 Socket 的 close()
方法時,會進入阻塞狀態(tài),直到底層 Socket 發(fā)送完所有剩余數(shù)據(jù)或者超過了 setSoLinger()
方法設置的延遲時間,才從 close()
方法返回
在服務器程序中,線程在以下情況下可能會進入阻塞狀態(tài):
線程執(zhí)行 ServerSocket 的accept()
方法,等待客戶的連接,直到接收到了客戶連接才從 accept()
方法返回線程從 Socket 的輸入流讀入數(shù)據(jù)時,如果輸入流沒有足夠的數(shù)據(jù)就會進入阻塞狀態(tài)線程向 Socket 的輸出流寫一批數(shù)據(jù)時,可能會進入阻塞狀態(tài),等到輸出了所有的數(shù)據(jù),或者出現(xiàn)異常,才從輸出流的 write()
方法返回或異常中斷由此可見,無論是在服務器程序還是客戶程序中,當通過 Socket 的輸入流和輸出流讀寫數(shù)據(jù)時,都可能進入阻塞狀態(tài)。這種可能出現(xiàn)阻塞的輸入和輸出操作被稱為阻塞 IO。與此對照,如果執(zhí)行輸入和輸出操作時,不會發(fā)生阻塞,則稱為非阻塞 IO
非阻塞通信的基本思想假如同時要做兩件事:燒開水和煮粥
燒開水的步驟如下:
鍋子里放水,打開煤氣爐等待水燒開 // 阻塞關閉煤氣爐,把開水灌到水壺里
煮粥的步驟如下:
鍋子里放水和米,打開煤氣爐等待粥煮開 // 阻塞調(diào)整煤氣爐,改為小火等待粥煮熟 // 阻塞關閉煤氣爐
為了同時完成兩件事,一種方案是同時請兩個人分別做其中的一件事,這相當于采用多線程來同時完成多個任務。還有一種方案是讓一個人同時完成兩件事,這個人應該善于利用一件事的空閑時間去做另一件事,這個人一刻也不應該閑著:
鍋子里放水,打開煤氣爐 // 開始燒開水鍋子里放水和米,打開煤氣爐 // 開始煮粥while(一直等待,直到有水燒開、粥煮開或粥煮熟事件發(fā)生) { // 阻塞if(水燒開)關閉煤氣爐,把開水灌到水壺里;if((粥煮開)調(diào)整煤氣爐,改為小火;if(粥熟)關閉煤氣爐;}
這個人不斷監(jiān)控燒水和煮粥的狀態(tài),如果發(fā)生了條件中任一事件就去處理,處理完一件事后繼續(xù)監(jiān)控,直到所有的任務都完成
以上工作方式也可以被運用到服務器程序中,服務器程序只需要一個線程就能同時接收客戶的連接、接收各個客戶發(fā)送的數(shù)據(jù),以及向各個客戶發(fā)送響應數(shù)據(jù)。服務器程序的處理流程如下:
while(一直等待,直到有接收連接就緒事件、讀緒事件或?qū)懢途w事件發(fā)生) { //阻塞if(有客戶連接)接收客戶的連接; // 非阻塞if(某個socket的輸入流中有可讀數(shù)據(jù))從輸入流中讀數(shù)據(jù); // 非阻塞if(某個socket的輸出流可以寫數(shù)據(jù))向輸出流寫數(shù)據(jù); // 非阻塞}
以上處理流程采用了輪詢的工作方式,當某一種操作就緒,就執(zhí)行該操作,否則就查看是否還有其他就緒的操作可以執(zhí)行。線程不會因為某一個操作還沒有就緒,就進入阻塞狀態(tài),一直傻傻地在那里等待這個操作就緒
為了使輪詢的工作方式順利進行,接收客戶的連接、從輸入流讀數(shù)據(jù),以及向輸出流寫數(shù)據(jù)的操作都應該以非阻寒的方式運行。所謂非阻塞,指當線程執(zhí)行這些方法時,如果操作還沒有就緒,就立即返回,而不會一直等到操作就緒
非阻塞通信 APIjava.nio.channels
包提供了支持非阻塞通信的類,如下所述:
ServerSocketChannel
:ServerSocket
的替代類,支持阻塞通信與非阻塞通信SocketChannel
:Socket
的替代類,支持阻塞通信與非阻塞通信Selector
:為 ServerSocketChannel
監(jiān)控接收連接就緒事件,為 SocketChannel
監(jiān)控連接就緒、讀就緒和寫就緒事件SelectionKey
:代表 ServerSocketChannel
以及 SocketChannel
向 Selector
注冊事件的句柄。當一個 SelectionKey
對象位于 Selector
對象的 selected-keys
集合中,就表示與這個 SelectionKey
對象相關的事件發(fā)生了ServerSocketChannel
及 SocketChannel
都是 SelectableChannel
的子類,如圖所示,SelectableChannel
類及其子類都能委托 Selector
來監(jiān)控它們可能發(fā)生的一些事件,這種委托過程也被稱為注冊事件過程
ServerSocketChannel
向 Selector
注冊接收連接就緒事件的代碼如下:
SelectionKey key = serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
SelectionKey
類的一些靜態(tài)常量表示事件類型,ServerSockerChamnel
只可能發(fā)生一種事件:
SelectionKey.OP_ACCEPT
:接收連接緒事件,表示至少有了一個客戶連接,服務器可以接收這個連接、SocketChannel
可能發(fā)生以下三種事件:
SelectionKey.OP_CONNECT
:連接就緒事件,表示客戶與服務器的連接已經(jīng)建立成功SelectionKey.OP_READ
:讀就緒事件,表示輸入流中已經(jīng)有了可讀數(shù)據(jù),可以執(zhí)行讀操作了SelectionKey.OP_WRITE
: 寫就緒事件,表示已經(jīng)可以向輸出流寫數(shù)據(jù)了SocketChannel
提供了接收和發(fā)送數(shù)據(jù)的方法:
read(ByteBuffer buffer)
:接收數(shù)據(jù),把它們存放到參數(shù)指定的 ByteBufferwrite(ByteBuffer buffer)
:把參數(shù)指定的 ByteBuffer 中的數(shù)據(jù)發(fā)送出去ByteBuffer
表示字節(jié)緩沖區(qū),SocketChannel
的 read()
和 write()
方法都會操縱 ByteBuffer
。ByteBuffer
類繼承于 Buffer
類。ByteBuffer
中存放的是字節(jié),為了把它們轉(zhuǎn)換為字符串還需要用到 Charset
類,Charset
類代表字符編碼,它提供了把字節(jié)流轉(zhuǎn)換為字符串(解碼過程)和把字符串轉(zhuǎn)換為字節(jié)流(編碼過程)的實用方法
下面分別介紹 Buffer
、Charset
、SelectableChannel
、ServerSocketChannel
、SocketChannel
、Selector
和 SelectionKey
的用法
數(shù)據(jù)輸入和輸出往往是比較耗時的操作,緩沖區(qū)從兩個方面提高 I/O 操作的效率:
減少實際的物理讀寫次數(shù)緩沖區(qū)在創(chuàng)建時被分配內(nèi)存,這塊內(nèi)存區(qū)域一直被重用,這可以減少動態(tài)分配和回收內(nèi)存區(qū)域的次數(shù)java.nio
包公開了 Buffer
類的 API,使得 Java 程序可以直接控制和運用緩沖區(qū),所有的緩沖區(qū)都有以下屬性:
以上三個屬性的關系為:容量 > 極限 >= 位置 >= 0
緩沖區(qū)提供了用于改變以上三個屬性的方法:
// 把極限設為容量,把位置設為0clear();// 把極限設為位置,把位置設為 0flip();// 不改變極限,把位置設為0rewind();
Buffer
類的 remaining()
方法返回緩沖區(qū)的剩余容量,取值等于 極限 - 位置
Buffer
類的 compact()
方法刪除緩沖區(qū)內(nèi)從 0 到當前位置 position 的內(nèi)容,然后把從當前位置 position 到極限limit 的內(nèi)容拷貝到 0 到 limit - position 的區(qū)域內(nèi)
java.nio.Buffer
類是一個抽象類,不能被實例化。它共有 8 個具體的緩沖區(qū)類,其中最基本的緩沖區(qū)是 ByteBuffer
,它存放的數(shù)據(jù)單元是字節(jié),ByteBufer
類并沒有提供公開的構造方法,但是提供了兩個獲得 ByteBuffer
實例的靜態(tài)工廠方法:
// 返回一個ByteBuffer對象,參數(shù)capacity指定緩沖區(qū)的容量allocate(int capacity);// 返回一個ByteBuffer對象,參數(shù)capacity指定緩沖區(qū)的容量// 該方法返回的緩沖區(qū)被稱為直接緩沖區(qū),能進一步提高 I/O 操作的速度// 分配直接緩沖區(qū)的系統(tǒng)開銷很大,因此只有在緩沖區(qū)較大并且長期存在,或經(jīng)常重用時,才使用該緩沖區(qū)directAllocate(int capacity);
除 boolean 類型以外,每種基本類型都有對應的緩沖區(qū)類,包括 CharBuffer
,DoubleBuffer
,FloatBuffer
,IntBuffer
,LongBuffer
,ShortBuffer
。在 CharBuffer
中存放的數(shù)據(jù)單元為字符,以此類推。還有一種緩沖區(qū)是 MappedByteBuffer
,它是 ByteBuffer
的子類,能夠把緩沖區(qū)和文件的某個區(qū)域直接映射
所有具體緩沖區(qū)類都提供了讀寫緩沖區(qū)的方法:
// 相對讀,從緩沖區(qū)的當前位置讀取一個單元的數(shù)據(jù),讀完后把位置加1get();// 絕對讀,從參數(shù) index 指定的位置讀取一個單元的數(shù)據(jù)get(int index);// 相對寫,向緩沖區(qū)的當前位置寫一個單元的數(shù)據(jù),寫完后把位置加1put(單元數(shù)據(jù)類型 data);// 絕對寫,向參數(shù)index指定的位置寫入一個單元的數(shù)據(jù)put(int index, 單元數(shù)據(jù)類型 data);
ByteBuffer
類不僅可以讀取和寫入一個單元的字節(jié),還可以讀取和寫入 int、char、float 和 double 等基本類型的數(shù)據(jù),例如:
getInt()getInt(int index)
以上不帶 index 參數(shù)的方法會在當前位置讀取或?qū)懭霐?shù)據(jù),稱為相對讀寫。帶 index 參數(shù)的方法會在 index 參數(shù)指定的位置讀取或?qū)懭霐?shù)據(jù),稱為絕對讀寫
字符編碼 Charsetjava.nio.Charset
類的每個實例代表特定的字符編碼類型,把字節(jié)序列轉(zhuǎn)換為字符串的過程稱為解碼,把字符串轉(zhuǎn)換為字節(jié)序列的過程稱為編碼
Charset
類提供了編碼與解碼的方法:
// 對參數(shù)str指定的字符串進行編碼,把得到的字節(jié)序列存放在一個ByteBuffer對象并將其返回ByteBuffer encode(String str);// 對參數(shù)cb指定的字符緩沖區(qū)中的字符進行編碼,把得到的字節(jié)序列存放在一個ByteBuffer對象并將其返回ByteBuffer encode(CharBuffer cb);// 對參數(shù)bb指定的ByteBuffer的字節(jié)序列進行解碼,把得到的字符序列存放在一個CharBuffer對象并將其返回CharBuffer decode(ByteBuffer bb);
Charset
類的靜態(tài) forName(String encode)
方法返回一個 Charset
對象,參數(shù) encode
指定編碼類型。例如以下代碼創(chuàng)建了一個代表 GBK 編碼的 Charset
對象
Charset charset = Charset.forName("GBK");
Charset
類還有一個靜態(tài)方法 defaultCharset()
,它返回代表本地平臺的默認字符編碼的 Charset
對象
通道(Channel)用來連接緩沖區(qū)與數(shù)據(jù)源或數(shù)據(jù)匯(即數(shù)據(jù)目的地),數(shù)據(jù)源的數(shù)據(jù)經(jīng)過通道到達緩沖區(qū),緩沖區(qū)的數(shù)據(jù)經(jīng)過通道到達數(shù)據(jù)匯
Channel 的主要層次結(jié)構如下:
java.nio.channels.Channel
接口只聲明了兩個方法:
// 關閉通道close();// 判斷通道是否打開isOpen();
Channel
接口的兩個最重要的子接口是 ReadableByteChannel
和 WritableByteChannel
。ReadableByteChannel
接口聲明了 read(ByteBuffer dst)
方法,該方法把數(shù)據(jù)源的數(shù)據(jù)讀入?yún)?shù)指定的 ByteBuffer
緩沖區(qū)中。WritableByteChannel
接口聲明了 write(ByteBuffer src)
方法,該方法把參數(shù)指定的 ByteBuffer
緩沖區(qū)中的數(shù)據(jù)寫到數(shù)據(jù)匯中
ByteChannel
接口是一個便利接口,它擴展了 ReadableByteChannel
和 WritableByteChannel
接口,因而同時支持讀寫操作
ScatteringByteChannel
接口擴展了 ReadableByteChannel
接口,允許分散地讀取數(shù)據(jù)。分散讀取數(shù)據(jù)指單個讀取操作能填充多個緩沖區(qū),ScatteringByteChannel
接口聲明了 read(ByteBuffer[] dsts)
方法,該方法把從數(shù)據(jù)源讀取的數(shù)據(jù)依次填充到參數(shù)指定的各個 ByteBuffer
GatheringByteChannel
擴展了 WritableByteChannel
接口,允許集中地寫入數(shù)據(jù)。集中寫入數(shù)據(jù)指單個寫操作能把多個緩沖區(qū)的數(shù)據(jù)寫到數(shù)據(jù), GatheringByteChannel
接口聲明了 write(ByteBuffer[] srcs)
方法,該方法依次把參數(shù)指定的每個 ByteBuffer
中的數(shù)寫到數(shù)據(jù)匯
FileChannel
類是 Channel
接口的實現(xiàn)類,代表一個與文件相連的通道。該類實現(xiàn)了 ByteChannel
、ScatteringByteChannel
和 GatheringByteChannel
接口,支持讀操作、寫操作、分散讀操作和集中寫操作。FileChannel
類沒有提供公開的構造方法,因此不能用 new
語句來構造它的實例。不過,在FileInputStream
、FileOutputStream
和 RandomAccessFile
類中提供了 getChannel()
方法,該方法返回相應的 FileChannel
對象
SelectableChannel
也是一種通道,它不僅支持阻塞的 I/O
操作,還支持非阻塞的 I/O
。SelectableChannel
有兩個子類,ServerSocketChannel
和 SocketChannel
。SocketChannel
還實現(xiàn)了 ByteChannel
接口,具有 read(ByteBuffer dst)
和 write(ByteBuffer src)
方法
SelectableChannel
是一種支持阻塞 IO 和非阻塞 IO 的通道。在非阻塞模式下,讀寫數(shù)據(jù)不會阻塞,并且 SelectableChannel
可以向 Selector
注冊讀就緒和寫就緒等事件。Selector
負責監(jiān)控這些事件,等到事件發(fā)生時,比如發(fā)生了讀就緒事件,SelectableChannel
就可以執(zhí)行讀操作了
SelectableChannel
的主要方法如下:
// 當參數(shù)block為true,表示把SelectableChannel設為阻塞模式// 當參數(shù)block為false時,表示把SelectableChannel設為非阻塞模式// SelectableChannel默認采用阻塞模式// 該方法返回SelectableChannel對象本身的引用,相當于return thispublic SelectableChannel configureBlocking(boolean block) throws IOException// 以下兩個方法都向Selector注冊事件public SelectionKey register(Selector sel,int ops) throws ClosedChannelExceptionpublic SelectionKey register(Selector sel,int ops,Object attachment) throws ClosedChannelException
以下是 socketChannel
向 Selector
注冊讀就緒和寫就緒事件
SelectionKey key = socketChannel.register(selector.SelectionKey.OP_READ | SelectionKey.OP_WRITE);
register()
方法返回一個 SelectionKey
對象,SeletionKey
被用來跟蹤被注冊的事件。第二個 register()
方法還有一個 Object
類型的參數(shù) attachment
,用于為 SelectionKey
關聯(lián)附件,當被注冊事件發(fā)生后,需要處理該事件時,可以從 SelectionKey
中獲得這個附件,該附件可用來包含與處理這個事件相關的信息
ServerSocketChannel
繼承自 SelectableChannel
,是 ServerSocket
的替代類,通過它的靜態(tài)方法 open()
來創(chuàng)建。每個 ServerSockeChannel
對象都與一個 ServerSocket
對象關,通過 socket()
方法返回與它關聯(lián)的 ServerSocket
對象??赏ㄟ^以下方式把服務器進程綁定到一個本地端口:
serverSocketChannel.socket().bind(port);
ServerSocketChannel
的主要方法如下:
// 返回一個ServerSocketChannel對象,該對象沒有與任何本地端口綁定,并且處于阻塞模式public static ServerSocketChannel open() throws IOException// 用于接收客戶的連接,如果處于非阻塞狀態(tài),當沒有客戶連接時就立即返回nullpublic SocketChannel accept() throws IOException// 返回ServerSocketChannel所能產(chǎn)生的事件,這個方法總是返回SelectionKey.OP_ACCEPTpublic final int validOps()// 返回ServerSocketChannel關聯(lián)的ServerSocket對象public ServerSocket socket()
3. SocketChannel類SockeChannel
可以被看作是 Socket
的替代類,SockeChannel
不僅繼承了 SelectableChannel
,而且實現(xiàn)了 ByteChannel
。SockeChannel
同樣通過它的靜態(tài)方法 open()
來創(chuàng)建
public static SocketChannel open() throws IOException// 帶參數(shù)的構造方法還會建立與遠程服務器的連接public static SocketChannel open(SocketAddress remote) throws IOException
SocketChannel
的主要方法如下:
// 返回ServerSocketChannel所能產(chǎn)生的事件,這個方法總是返回以下值// SelectionKey.OP_CONNECT | SelectionKey.OP_READ | SelectionKey.OP_WRITEpublic final int validOps()// 返回SocketChannel關聯(lián)的Socket對象public Socket socket()// 建立遠程連接,當處于非阻塞模式時,如果立即連接成功返回true,否則返回falsepublic boolean connect(SocketAddress remote) throws IOException// 判斷底層Socket是否已經(jīng)建立遠程連接public boolean isConnected()// 判斷是否正在進行遠程連接,如果遠程連接操作已經(jīng)開始,但還沒有完成,則返回true,否則返回false// 也就是說,無論底層Socket還沒有開始連接,或者已經(jīng)連接成功,該方法都會返回falsepublic boolean isConnectionPending()// 試圖完成連接遠程服務器的操作// 非阻塞模式下,建立連接從調(diào)用connect()方法開始,到調(diào)用finishConnect()方法結(jié)束// 如果在調(diào)用此方法之前連接已經(jīng)建立,則立即返回true,否則立即返回false// 阻塞模式下,如果連接操作還沒有完成,則會進入阻塞狀態(tài),直到連接完成,或者出現(xiàn)IO異常public boolean finishConnect) throws IOException// 從Channel讀入若干字節(jié),存放到參數(shù)指定的ByteBuffer// 假設ByteBuffer剩余容量為r,阻塞模式下,該方法會爭取讀到r字節(jié)// 如果輸入流中不足r字節(jié),就進入阻塞狀態(tài),直到讀入了r字節(jié),或者讀到了輸入流末尾,或者出現(xiàn)了IO異常// 非阻塞模式下,該方法奉行能讀到多少數(shù)據(jù)就讀多少數(shù)據(jù)的原則// 通道中的可讀數(shù)據(jù),有可能不足r字節(jié),或者為0字節(jié),總是立即返回// 該方法返回實際上讀入的字節(jié)數(shù),有可能為0,如果返回-1,表示讀到了輸入流的末尾public int read(ByteBuffer dst) throws IOException// 把參數(shù)src指定的ByteBuffer的字節(jié)寫到Channel// 假設ByteBuffer剩余容量為r,阻塞模式下,該方法會爭取輸出r字節(jié)// 如果底層網(wǎng)絡的輸出緩沖區(qū)不能容納r字節(jié),就進入阻塞狀態(tài),直到輸出了r字節(jié),或者出現(xiàn)了IO異常// 非阻塞模式下,該方法奉行能輸出多少數(shù)據(jù)就輸出多少數(shù)據(jù)的原則,有可能不足r字節(jié),或者為0字節(jié),總是立即返回// 該方法返回實際上輸出的字節(jié),有可能為0public int write(ByteBuffer src) throws IOException
Selector 類只要 ServerSockerChannel
以及 SocketChannel
向 Selector
注冊了特定的事件,Selector
就會監(jiān)控這些事件是否發(fā)生。SelectableChannel
的 register()
方法負責注冊事件,該方法返回 SelectionKey
對象,該對象是用于跟蹤這些被注冊事件的句柄
Selector
對象中會包含三種類型的 SelectionKey
的集合:
all-keys
:當前所有向 Selector
注冊的 SelectionKey
的集合,Selector
的 keys()
方法返回該集合selected-keys
:相關事件已經(jīng)被 Selector
捕獲的 SelectionKey
的集合,Selector
的 selectedKeys()
方法返回該集合cancelled-keys
:已經(jīng)被取消的 SelectionKey
的集合,Selector
沒有提供訪問這種集合的方法當執(zhí)行 SelectableChannel
的 registe()
方法,會新建一個 SelectionKey
并加入 Selector
的 all-keys
集合中。如果關閉了與 SelectionKey
對象關聯(lián)的 Channel
對象,或者調(diào)用了 SelectionKey
對象的 cancel()
方法,那么這個 SelectionKey
對象就會被加入 cancelled-keys
集合,表示已經(jīng)被取消,在程序下一次執(zhí)行 Selector
的 select()
方法時,被取消的 SelectionKey
對象將從所有的集合(包括 all-keys
集合、selected-keys
集合和 cancelled-keys
集合)中被刪除
在執(zhí)行 Selector
的 select()
方法時,如果與 SelectionKey
相關的事件發(fā)生了,這個 SelectionKey
就被加入 selected-keys
集合中。程序直接調(diào)用 selected-keys
集合的 remove()
方法,或者調(diào)用它的 Iterator
的 remove()
方法,都可以從 selected-keys
集合中刪除一個 SelectionKey
對象
程序不允許直接通過集合接口的 remove()
方法刪除 all-keys
集合中的 SelectionKey
對象,這會導致 UnsupportedOperationException
Selector
類的主要方法如下:
// Selector的靜態(tài)工廠方法,創(chuàng)建一個Selector對象public static Selector open() throws IOException// 判斷Selector是否處于打開狀態(tài),Selector對象創(chuàng)建后就處于打開狀態(tài),當調(diào)用close()方法就進入關閉狀態(tài)public boolean isOpen()// 返回Seleclor的all-keys集合,包含了所有與Seclector關聯(lián)的SelectionKey對象public Set keys()// 返回相關事件已經(jīng)發(fā)生的SelectionKey對象的數(shù)目// 該方法采用非阻塞的工作方式,返回當前相關事件已經(jīng)發(fā)生的SelectionKey對象的數(shù)目,如果沒有,就立即返回0public int selectNow() throws IOException// 返回相關事件已經(jīng)發(fā)生的SelectionKey對象的數(shù)目// 該方法采用阻塞的工作方式,如果一個也沒有,就進入阻塞狀態(tài),直到出現(xiàn)以下情況之一,就會從select()返回:// 1.至少有一個SelectionKey的相關事件已經(jīng)發(fā)生// 2.其他線程調(diào)用了Selector的wakeup()方法// 3.當前執(zhí)行select()方法的線程被其他線程中斷// 4.超出了等待時間public int select() throws IOExceptionpublic int select(long timeout) throws IOException// 喚醒執(zhí)行Selector的select()方法 public Selector wakeup()// 關閉 Selector// 如果有其他線程正執(zhí)行這個Selector的select()方法并且處于阻塞狀態(tài),這個線程會立即返回// close()方法使得Selector占用的所有資源都被釋敗,所有關聯(lián)的SelectionKey都被取消public void close() throws IOException
SelectionKey 類SelectionKey
中定義了四種事件,分別用四個 int 類型的常量來表示:
SelectionKey.OP_ACCEPT
:接收連接就緒事件,表示服務器監(jiān)聽到了客戶連接,服務器可以接收這個連接了,常量值為 16SeiectionKey.OP_CONNECT
:連接就緒事件表示客戶與服務器的連接已經(jīng)建立成功,常量值為 8SelectionKey.OP_READ
:讀就緒事件,表示通道中已經(jīng)有了可讀數(shù)據(jù),可以執(zhí)行讀操作了,常量值為 1SelectionKey.OP_WRITE
:寫就緒事件表示已經(jīng)可以向通道寫數(shù)據(jù)了,常量值為 4以上常量分別占據(jù)不同的二進制位,因此可以通過二進制的或運算來將它們進行任意組合
一個 SelectionKey
對象中包含兩種類型的事件:
所有感興趣的事件:通過 SelectableChannel
的 register()
方法注冊事件時,可以在參數(shù)中指定 SelectionKey
感興趣的事件
SelectionKey key = socketChannel.register(selector,SelectionKey.OP_CONNECT | SelectionKey.OP_READ);
該代碼表示這個 SelectionKey
對讀就緒和寫就緒事件感興趣,與之關聯(lián)的 Selector
對象會負責監(jiān)控這些事件
SelectionKey
的帶參數(shù)的 interestOps(int ops)
方法也可以為 SelectionKey
對象增加一個感興趣的事件,如下代碼所示:
key.interestOps(SelectionKey.OP_WRITE);
所有已經(jīng)發(fā)生的事件:SeletionKey
的 readyOps()
方法返回所有已經(jīng)發(fā)生的事件,例如假定返回值為 SelectionKey.OP_WRITE | SelectionKey.OP_READ
,表示讀就緒和寫就緒事件已經(jīng)發(fā)生了,這意味著與之關聯(lián)的 SocketChannel
對象可以進行讀操作和寫操作了
SelectionKey
的主要方法如下:
// 返回與這個SelectionKey對象關聯(lián)的SelectableChannel對象public SelectableChannel channel()// 返回與這個SelectionKey對象關聯(lián)的Selector對象public Selector selector()// 判斷這個SelectionKey是否有效// 當SelectionKey對象創(chuàng)建后,它就一直處于有效狀態(tài)// 如果調(diào)用了它的cancel()方法,或關閉了與它關聯(lián)的SelectableChannel或Selector對象,它就失效public boolean isValid()// 使SelectionKey對象失效public void cancel()// 返回這個SelectionKey感興趣的事件public int interestOps()// 為SelectionKey增加感興趣的事件public SelectionKey interestOps(int ops)// 返回已經(jīng)就緒的事件public int readyOps()// 判斯與之關聯(lián)的SocketChannel的讀就緒事件是否已經(jīng)發(fā)生public final boolean isReadable()// 判斷與之關聯(lián)的SocketChannel的寫就緒事件是否已經(jīng)發(fā)生public final boolean isWritable()// 判斷與之關聯(lián)的SocketChannel的連接就緒事件是否已經(jīng)發(fā)生public final boolean isConnectable()// 判斷與之關聯(lián)的ServerSocketChannel的接收連接就緒事件是否已經(jīng)發(fā)生public final boolean isAcceptable()// 使SelectionKey關聯(lián)一個附件,一個SelectionKey對象只能關聯(lián)一個Object類型的附件// 如果多次調(diào)用該方法,則只有最后一個附件與SelectionKey對象關聯(lián)public final Object attach(Object obj)// 返回與SelectionKey對象關聯(lián)的附件public final Object attachment()
Channels 類Channels
類是一個簡單的工具類,提供了通道與傳統(tǒng)的基于 IO 的流、Reader
和 Writer
之間進行轉(zhuǎn)換的靜態(tài)方法
ReadableByteChannel newChannel(InputStream in) // 輸入流轉(zhuǎn)換成讀通道WritableByteChannel newChannel(OutputStream out) // 輸出流轉(zhuǎn)換成寫通道InputStream newInputStream(AsynchronousByteChannel ch) // 異步通道轉(zhuǎn)換成輸入流InputStream newInputStream(ReadableByteChannel ch) // 讀通道轉(zhuǎn)換成輸入流OutputStream newOutputStream(AsynchronousByteChannel ch) // 異步通道轉(zhuǎn)換成輸出流OutputStream newOutputStream(WritableByteChannel ch) // 寫通道轉(zhuǎn)換成輸出流Reader newReader(ReadableByteChannel ch,String csName) // 讀通道轉(zhuǎn)換成Reader,參數(shù)csName指定字符編碼Reader newReader(ReadableByteChannel ch,Charset charset)//讀通道轉(zhuǎn)換成Reader.參數(shù)charset指定字符編碼Reader newReader(ReadableByteChannel ch,CharsetDecoder dec, int minBufferCap) // 讀通道轉(zhuǎn)換成 Reader,參數(shù)dec指定字符解碼器,參數(shù)minBufferCap指定內(nèi)部字節(jié)緩沖區(qū)的最小容量Writer newWriter(WritableByeChannel ch, String csName) // 寫通道轉(zhuǎn)換Writer.參數(shù)csName指定字符編碼Writer newWriter(WritableByeChannel ch, Charset charset) // / 寫通道轉(zhuǎn)換Writer.參數(shù)charset指定字符編碼Writer newWriter(WritableByeChannel ch, CharsetEncoder enc, int minBufferCap) // 寫通道轉(zhuǎn)換成Writer,參數(shù)dec指定字符解碼器,參數(shù)minBufferCap指定內(nèi)部字節(jié)緩沖區(qū)的最小容量
Socket 選項從 JDK7 開始,SocketChannel
、ServerSocketChannel
、AsynchronousSocketChannel
、AsynchronousServerSocketChannel
和 DatagramChannel
都實現(xiàn)了新的 NetworkChannel
接口。NetworkChannel
接口的主要作用是設置和讀取各種 Socket 選項
NetworkChannel
接口提供了用于設置和讀取這些選項的方法:
T getOption(SocketOption name) // 獲取特定的Socket選項值 NetworkChannel setOption(SocketOption name, T value) // 設置特定的Socket選項Set> supportedOptions() // 獲取所有支持的Socket選項
SocketOptionl
類是一個泛型類,SocketOption
中的 T
代表特定選項的取值類型,可選值包括 Integer
、Boolean
和 NetworkInterface
StandardSocketOptions
類提供了以下表示特定選項的常量:
SocketOption -- StandardSocketOptions.IP_MULTICAST_IFSocketOption -- StandardSocketOptions.IP_MULTICAST_LOOPSocketOption -- StandardSocketOptions.IP_MULTICAST_TTLSocketOption -- StandardSocketOptions.IP_TOSSocketOption -- StandardSocketOptions.SO_BROADCASTSocketOption -- StandardSocketOptions.SO_KEEPALIVESocketOption -- StandardSocketOptions.SO_LINGERSocketOption -- StandardSocketOptions.SO_RCVBUFSocketOption -- StandardSocketOptions.SO_REUSEADDRSocketOption -- StandardSocketOptions.SO_REUSEPORTSocketOption -- StandardSocketOptions.SO_SNDBUFSocketOption -- StandardSocketOptions.TCP_NODELAY
關鍵詞:
相關閱讀
-
Java 網(wǎng)絡編程 —— 非阻塞式編程
線程阻塞概述在生活中,最常見的阻塞現(xiàn)象是公路上汽車的堵塞。汽車... -
湖南裕能:碳酸鋰價格向合理區(qū)間回歸有...
5月14日電,湖南裕能近日接受機構調(diào)研時表示,今年一季度行業(yè)受碳酸... -
oppo手機怎么root權限不用電腦 oppo手...
今天來聊聊關于oppo手機怎么root權限不用電腦,oppo手機怎么ROOT的... -
韓國總統(tǒng)辦公室:尹錫悅將在日本廣島同...
韓國總統(tǒng)辦公室5月14日宣布,韓國總統(tǒng)尹錫悅將出席下周在日本廣島舉... -
網(wǎng)友發(fā)現(xiàn)洪欣的致歉信不是繁體字,而且...
洪欣的致歉信不是繁體字 -
跑起來了!無錫錫澄S1線列車正式上線-當...
日前,無錫首條市域城際軌道交通線錫澄S1線開啟熱滑實驗,這標志著... -
世界即時:大眾ID.7 GTX預告圖發(fā)布,將...
5月14日消息,大眾ID 7GTX發(fā)布預告圖,新車是純電動中型三廂轎車ID... -
環(huán)球關注:特斯拉召回超110萬輛車,是因...
特斯拉又啟動召回了,這次召回的規(guī)模特別大,達到了110多萬臺。而且... -
恒大汽車剝離地產(chǎn)業(yè)務 聚焦新能源賽道
5月12日,恒大汽車(0708 HK)發(fā)布公告稱,股東大會已同意向中國恒大... -
環(huán)球看點!新聞采編與制作排名院校 ...
今天,大學路小編為大家?guī)砹诵侣劜删幣c*排名院校院校專業(yè):,希望... -
小手指發(fā)麻是什么病的前兆_起水痘的前兆...
1、水痘患兒一般沒有前驅(qū)癥狀,大多直接進入出疹階段。成年患者可能... -
特斯拉OTA 動能回收制動強度選擇和深踩...
說實話,如果只看“召回、1104622輛”這兩個關鍵詞,那確實夠嚇人的! -
避開福特通用,巴菲特語出驚人,它們是...
給你機會你不中用!連巴菲特都看不上,合資品牌中,誰能贏得機會? -
精彩看點:風雨欲來! 最新一批新車目...
目前像比亞迪宋ProDM-i、宋PLUSDM-i、梟龍MAX、深藍S7、銀河L7等一... -
濰柴動力與比亞迪簽署戰(zhàn)略合作協(xié)議 世...
5月12日上午,濰柴動力與比亞迪在深圳簽署戰(zhàn)略合作協(xié)議,濰柴動力董... -
沃爾沃EX90量產(chǎn)推遲至2024年上半年 世界視訊
5月14日,有媒體報道稱,沃爾沃EX90量產(chǎn)時間從原定于今年上半年更改... -
水利部等11部門:建立協(xié)調(diào)聯(lián)動機制 發(fā)...
人民網(wǎng)北京5月14日電(歐陽易佳)近日,水利部、中央精神文明建設辦... -
angelababy快樂大本營2011(angelababy快樂大本營)
1、有的。2、9月26期和蔡徐坤一起參加了。以上就是【angelababy快樂... -
房子進深是指什么(房屋進深是什么意思...
1、習慣上,我們把一個樓(或房間)的主要采光面稱為開間(或面寬)... -
零成本召回110萬輛汽車,特斯拉終于認錯...
超過110萬輛汽車召回,創(chuàng)造電動智能汽車歷史上單次最大規(guī)模召回記錄...