摘要:使用線程池可以節(jié)省那種系統(tǒng)開銷,同時允許實現(xiàn)者利用并行硬件的優(yōu)勢。但是對于連接生存期比較長的協(xié)議來說,線程池的大小仍然限制了系統(tǒng)同時可以處理的客戶端數(shù)量。
NIO主要包含兩部分,Selector和Channel、Buffer。
為什么需要需要NIO基本的Java套接字對于小規(guī)模系統(tǒng)可以很好地運行,但當(dāng)涉及同時處理上千個客戶端的服務(wù)器時,可能就會產(chǎn)生一些問題。由于創(chuàng)建、維護、切換線程需要的系統(tǒng)開銷,一客戶一線程的方式在系統(tǒng)擴展性方面受到了限制。使用線程池可以節(jié)省那種系統(tǒng)開銷,同時允許實現(xiàn)者利用并行硬件的優(yōu)勢。
但是對于連接生存期比較長的協(xié)議來說,線程池的大小仍然限制了系統(tǒng)同時可以處理的客戶端數(shù)量。
另外對于服務(wù)器需要由不同客戶端同時訪問和修改的信息時,對于多線程就得進行同步,這會變得更加復(fù)雜,即使用同步機制將增加更多的系統(tǒng)調(diào)度和上下文切換開銷,而程序員對這些開銷又無法控制。
由于多線程的同步的復(fù)雜性,一些程序員寧愿繼續(xù)使用單線程方法,這類服務(wù)器只用一個線程來處理所有客戶端——不是順序處理,而是一次全部處理。這種服務(wù)器不能為任何客戶端提供I/O操作的阻塞等待,而必須排他地使用非阻塞方式(nonblocking)I/O。
前面的while true,不斷地輪詢(poll)accept方法,這種忙等(busy waiting)方法會引入系統(tǒng)開銷,因為程序需要反復(fù)循環(huán)地連接I/O源,卻又發(fā)現(xiàn)什么都不用做。
我們需要一種方法來一次輪詢一組客戶端,以查找那個客戶端需要服務(wù),這正是NIO要介紹的Selector和Channel的抽象關(guān)鍵點。
一個Channel實例代表了一個可輪詢(pollable)的I/O目標(biāo),如套接字(或一個文件、設(shè)備等)。Channel能夠注冊一個Selector類的實例。
Selector的select方法允許你詢問在一組信道中,哪一個當(dāng)前需要服務(wù)(被接受、讀、寫)。
Stream的抽象,好處是隱藏了底層緩沖區(qū)的有限性,提供了一個能夠容納任意長度數(shù)據(jù)的容器的假象。要實現(xiàn)這樣一個假象,要么會產(chǎn)生大量的內(nèi)存開銷,要么會引入大量的上下文切換,不好控制。
使用Buffer抽象的原因是:Buffer抽象代表了一個有限容量(finite-capacity)的數(shù)據(jù)容器——其本質(zhì)是一個數(shù)組,由指針指示了在哪存放數(shù)據(jù)和從哪讀取數(shù)據(jù)。使用Buffer的好處是:
1)與讀寫緩沖區(qū)數(shù)據(jù)相關(guān)聯(lián)的系統(tǒng)開銷都暴露給了程序員。例如,如果想要向緩沖區(qū)存入數(shù)據(jù),但是又沒有足夠的空間時,就必須采取一些措施來獲得空間(即移出一些數(shù)據(jù),或移開已經(jīng)在那個位置的數(shù)據(jù)來獲得空間,或者創(chuàng)建一個新的新的實例)。這意味著需要額外的工作,但是你可以控制它什么時候發(fā)生,如何發(fā)生,以及是否發(fā)生。
2)一些對Java對象的特殊Buffer映射操作能夠直接操作底層平臺的資源(例如操作系統(tǒng)的緩沖區(qū)),這些操作節(jié)省了在不同地址空間中復(fù)制數(shù)據(jù)的開銷。
綜上,Channel實例代表了一個與設(shè)備的連接,通過它可以進行輸入輸出操作。信道(channel)和套接字(socket)的不同之處在于:channel通常需要調(diào)用靜態(tài)工廠方法來獲取實例。channel使用的不是流,而是使用緩沖區(qū)來發(fā)送或讀取數(shù)據(jù)。
Buffer有固定的、有限的容量,并由內(nèi)部狀態(tài)記錄了有多少數(shù)據(jù)放入或取出,就像是一個有限容量的隊列一樣。
SelectorNIO的強大功能部分來自于channel的非阻塞特性。accept可能因為等待一個客戶端連接而阻塞,read可能因為沒有數(shù)據(jù)可讀而阻塞,直到連接的另一端傳來新數(shù)據(jù)。
總的來說,創(chuàng)建/接收連接或讀寫數(shù)據(jù)等I/O調(diào)用,都可能無限期地阻塞等待,直到底層的網(wǎng)絡(luò)實現(xiàn)發(fā)生了什么。慢速的、有損耗的網(wǎng)絡(luò),或僅僅是簡單的網(wǎng)絡(luò)故障都可能導(dǎo)致任意時間的延遲。
而NIO則立即返回:
public class TCPEchoClientNonblocking { public static void main(String args[]) throws Exception { if ((args.length < 2) || (args.length > 3)) // Test for correct # of args throw new IllegalArgumentException("Parameter(s):[ ]"); String server = args[0]; // Server name or IP address // Convert input String to bytes using the default charset byte[] argument = args[1].getBytes(); int servPort = (args.length == 3) ? Integer.parseInt(args[2]) : 7; // Create channel and set to nonblocking SocketChannel clntChan = SocketChannel.open(); clntChan.configureBlocking(false); // Initiate connection to server and repeatedly poll until complete if (!clntChan.connect(new InetSocketAddress(server, servPort))) { while (!clntChan.finishConnect()) { System.out.print("."); // Do something else } } ByteBuffer writeBuf = ByteBuffer.wrap(argument); ByteBuffer readBuf = ByteBuffer.allocate(argument.length); int totalBytesRcvd = 0; // Total bytes received so far int bytesRcvd; // Bytes received in last read while (totalBytesRcvd < argument.length) { if (writeBuf.hasRemaining()) { clntChan.write(writeBuf); } if ((bytesRcvd = clntChan.read(readBuf)) == -1) { throw new SocketException("Connection closed prematurely"); } totalBytesRcvd += bytesRcvd; System.out.print("."); // Do something else } System.out.println("Received: " + // convert to String per default charset new String(readBuf.array(), 0, totalBytesRcvd)); clntChan.close(); } }
上面的輪詢僅僅是演示用。
需要使用Selector類來避免忙等的輪詢??紤]一個即時的消息服務(wù)器,可能有上千個客戶端同時連接到了服務(wù)器,但任何時刻都只有非常少量的消息需要讀取和分發(fā)。這就需要一種方法阻塞等待,直到至少有一個信道可以進行I/O操作,并指出是哪個信道。NIO的選擇器就實現(xiàn)了這樣的功能。一個Selector實例可以同時檢查一組信道的I/O狀態(tài)。用專業(yè)術(shù)語來說,選擇器就是一個多路開關(guān)選擇器,因為一個選擇器能夠管理多個信道上的I/O操作。
要使用選擇器,需要創(chuàng)建一個Selector實例并將其注冊到想要監(jiān)控的信道上(注意,這要通過channel的方法實現(xiàn),而不是使用selector的方法)。最后,調(diào)用選擇器的select方法,該方法會阻塞等待,直到還有一個或更多的信道準(zhǔn)備好了I/O操作或等待超時。select方法返回可進行I/O操作的信道數(shù)量。
public class TCPServerSelector { private static final int BUFSIZE = 256; // Buffer size (bytes) private static final int TIMEOUT = 3000; // Wait timeout (milliseconds) public static void main(String[] args) throws IOException { if (args.length < 1) { // Test for correct # of args throw new IllegalArgumentException("Parameter(s):..."); } // Create a selector to multiplex listening sockets and connections Selector selector = Selector.open(); // Create listening socket channel for each port and register selector for (String arg : args) { ServerSocketChannel listnChannel = ServerSocketChannel.open(); listnChannel.socket().bind(new InetSocketAddress(Integer.parseInt(arg))); listnChannel.configureBlocking(false); // must be nonblocking to register // Register selector with channel. The returned key is ignored listnChannel.register(selector, SelectionKey.OP_ACCEPT); } // Create a handler that will implement the protocol TCPProtocol protocol = new EchoSelectorProtocol(BUFSIZE); while (true) { // Run forever, processing available I/O operations // Wait for some channel to be ready (or timeout) if (selector.select(TIMEOUT) == 0) { // returns # of ready chans System.out.print("."); continue; } // Get iterator on set of keys with I/O to process Iterator keyIter = selector.selectedKeys().iterator(); while (keyIter.hasNext()) { SelectionKey key = keyIter.next(); // Key is bit mask // Server socket channel has pending connection requests? if (key.isAcceptable()) { protocol.handleAccept(key); } // Client socket channel has pending data? if (key.isReadable()) { protocol.handleRead(key); } // Client socket channel is available for writing and // key is valid (i.e., channel not closed)? if (key.isValid() && key.isWritable()) { protocol.handleWrite(key); } keyIter.remove(); // remove from set of selected keys } } } }
由于select方法只是向selector所關(guān)聯(lián)的鍵集合中添加元素,因此,如果不移除每個處理過的鍵,它就會在下次調(diào)用select方法時仍然保留在集合中,而且可能會有無用的操作來調(diào)用它。
具體的處理方法
public class EchoSelectorProtocol implements TCPProtocol { private int bufSize; // Size of I/O buffer public EchoSelectorProtocol(int bufSize) { this.bufSize = bufSize; } public void handleAccept(SelectionKey key) throws IOException { SocketChannel clntChan = ((ServerSocketChannel) key.channel()).accept(); clntChan.configureBlocking(false); // Must be nonblocking to register // Register the selector with new channel for read and attach byte buffer clntChan.register(key.selector(), SelectionKey.OP_READ, ByteBuffer .allocate(bufSize)); } public void handleRead(SelectionKey key) throws IOException { // Client socket channel has pending data SocketChannel clntChan = (SocketChannel) key.channel(); ByteBuffer buf = (ByteBuffer) key.attachment(); long bytesRead = clntChan.read(buf); if (bytesRead == -1) { // Did the other end close? clntChan.close(); } else if (bytesRead > 0) { // Indicate via key that reading/writing are both of interest now. key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE); } } public void handleWrite(SelectionKey key) throws IOException { /* * Channel is available for writing, and key is valid (i.e., client channel * not closed). */ // Retrieve data read earlier ByteBuffer buf = (ByteBuffer) key.attachment(); buf.flip(); // Prepare buffer for writing SocketChannel clntChan = (SocketChannel) key.channel(); clntChan.write(buf); if (!buf.hasRemaining()) { // Buffer completely written? // Nothing left, so no longer interested in writes key.interestOps(SelectionKey.OP_READ); } buf.compact(); // Make room for more data to be read in } }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/65929.html
摘要:學(xué)習(xí)和掌握技術(shù)已經(jīng)不是一個攻城獅的加分技能,而是一個必備技能。是雙向的,不僅可以讀取數(shù)據(jù)還能保存數(shù)據(jù),程序不能直接讀寫通道,只與緩沖區(qū)交互為了讓大家不被高并發(fā)與大量連接處理問題所困擾,動力節(jié)點推出了高效處理模型應(yīng)用教程。 大家肯定了解Java IO, 但是對于NIO一般是陌生的,而現(xiàn)在使用到NIO的場景越來越多,很多技術(shù)框...
摘要:內(nèi)存溢出的情況就是從類加載器加載的時候開始出現(xiàn)的,內(nèi)存溢出分為兩大類和。以下舉出個內(nèi)存溢出的情況,并通過實例代碼的方式講解了是如何出現(xiàn)內(nèi)存溢出的。內(nèi)存溢出問題描述元空間的溢出,系統(tǒng)會拋出。這樣就會造成棧的內(nèi)存溢出。 導(dǎo)言: 對于java程序員來說,在虛擬機自動內(nèi)存管理機制的幫助下,不需要自己實現(xiàn)釋放內(nèi)存,不容易出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出的問題,由虛擬機管理內(nèi)存這一切看起來非常美好,但是一旦...
摘要:基礎(chǔ)知識基礎(chǔ)語法基礎(chǔ)知識編程第一步基礎(chǔ)知識基本數(shù)據(jù)類型基礎(chǔ)知識解釋器基礎(chǔ)知識注釋基礎(chǔ)知識運算符基礎(chǔ)知識數(shù)字基礎(chǔ)知識字符串基礎(chǔ)知識列表基礎(chǔ)知識元組基礎(chǔ)知識字典基礎(chǔ)知識條件控制基礎(chǔ)知識循環(huán)基礎(chǔ)知識迭代器與生成器基礎(chǔ)知識函數(shù)基礎(chǔ)知識數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)知 Python3基礎(chǔ)知識 | 基礎(chǔ)語法?Python3基礎(chǔ)知識 | 編程第一步?Python3基礎(chǔ)知識 | 基本數(shù)據(jù)類型Python3基礎(chǔ)知識 | ...
閱讀 2437·2021-10-09 09:59
閱讀 2192·2021-09-23 11:30
閱讀 2600·2019-08-30 15:56
閱讀 1156·2019-08-30 14:00
閱讀 2947·2019-08-29 12:37
閱讀 1265·2019-08-28 18:16
閱讀 1669·2019-08-27 10:56
閱讀 1033·2019-08-26 17:23