標籤:conf continue strong 判斷 final attach 不同的 received ati
不知道該咋說(? •_•)?
ServerSocketChannel和SocketChannel,它們對應原來的ServerSocket和Socket。
Buffer、Channel和Selector
Buffer就是所要送的貨物,Channel就是送貨員(或者開往某個地區的配貨車),Selector就是中轉站的分揀員。
NioSocket使用中首先要建立ServerSocketChannel,然後註冊Selector,接下來就可以用Selecto接收請求並處理了。
ServerSocketChannel可以使用自己的靜態Factory 方法open建立。每個ServerSocketChannel對應一個ServerSocket,可以調用其socket方法來擷取,一般使用擷取到的ServerSocket來綁定連接埠。ServerSocketChannel可以通過configureBlocking方法設定是否採用阻塞模式,如果要採用非阻塞模式可以用configureBlocking(false)來設定,設定了非阻塞模式之後就可以調用register方法註冊Selector來使用了(阻塞模式不可以使用Selector)。
Selector可以通過其靜態Factory 方法open建立,建立後通過Channel的register方法註冊到ServerSocketChannel或者SocketChannel上,註冊完之後Selector就可以通過select方法來等待請求,select方法有一個long類型的參數,代表最長等待時間,如果在這段時間裡接收到了相應操作的請求則返回可以處理的請求的數量,否則在逾時後返回0,程式繼續往下走,如果傳入的參數為0或者調用無參數的重載方法,select方法會採用阻塞模式直到有相應操作的請求出現。當接收到請求後Selector調用selectedKeys方法返回SelectionKey的集合。
SelectionKey儲存了處理當前請求的Channel和Selector,並且提供了不同的操作類型。Channel在註冊Selector的時候可以通過register的第二個參數選擇特定的操作,這裡的操作就是在SelectionKey中定義的,一共有4種:·SelectionKey.OP_ACCEPT 接受請求操作·SelectionKey.OP_CONNECT 串連操作·SelectionKey.OP_READ 讀操作·SelectionKey.OP_WRITE 寫操作只有在register方法中註冊了相應的操作Selector才會關心相應類型操作的請求。Channel和Selector並沒有誰屬於誰的關係,就好像一個分揀員可以為多個地區分揀貨物而每個地區也可以有多個分揀員來分揀一樣,它們就好像資料庫裡的多對多的關係,不過Selector這個分揀員分揀得更細,它可以按不同的類型來分揀,分揀後的結果儲存在Selec-tionKey中,可以分別通過SelectionKey的channel方法和selector方法來擷取對應的Channel和Selector,而且還可以通過isAcceptable、isConnectable、isReadable和isWritable方法來判斷是什麼類型的操作。 NioSocket中服務端的處理過程可以分為5步:1)建立ServerSocketChannel並設定相應參數。2)建立Selector並註冊到ServerSocketChannel上。3)調用Selector的select方法等待請求。4)Selector接收到請求後使用selectedKeys返回SelectionKey集合。5)使用SelectionKey擷取到Channel、Selector和操作類型並進行具體操作。
public class NIOServer { public static void main(String[] args) throws Exception { //建立ServerSocketChannel,監聽8080連接埠 ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress(8080));//使用擷取到的ServerSocket來綁定連接埠 //設定為非阻塞模式,阻塞模式不可以使用Selector ssc.configureBlocking(false); //為ssc註冊選取器 Selector selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); //建立處理器 Handler handler = new Handler(1024); while(true){ //等待請求,每次等待阻塞3秒,超過3s後線程繼續向下運行,如果傳入0或者不傳參數將一直阻塞 if(selector.select(3000)==0){ System.out.println("等待請求逾時......"); continue; } System.out.println("處理請求......"); //擷取待處理的SelectionKey Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator(); while(keyIterator.hasNext()){ SelectionKey key = keyIterator.next(); try{//接收到串連請求時 //如果是接受請求操作 if(key.isAcceptable()){ handler.handleAccept(key); } //如果是讀資料操作 if(key.isReadable()){ handler.handleRead(key); } //還有isConnectable,isWritable }catch (IOException ex){ //處理完後,從待處理的SelectionKey迭代器中移除當前所使用的key keyIterator.remove(); continue; } keyIterator.remove(); } } } private static class Handler{ private int bufferSize = 1024; private String localCharset = "UTF-8"; public Handler(){} public Handler(int bufferSize){ this(bufferSize,null); } public Handler(String localCharset){ this(-1,localCharset); } public Handler(int bufferSize, String localCharset) { if (bufferSize > 0) this.bufferSize = bufferSize; if (localCharset != null) this.localCharset = localCharset; } public void handleAccept(SelectionKey key) throws IOException{ SocketChannel SC = ((ServerSocketChannel)key.channel()).accept(); SC.configureBlocking(false); SC.register(key.selector(),SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize)); } public void handleRead(SelectionKey key) throws IOException{ //擷取channel SocketChannel sc = (SocketChannel)key.channel(); //擷取buffer並重設 ByteBuffer buffer = (ByteBuffer)key.attachment(); buffer.clear(); //沒有讀到內容則關閉 if(sc.read(buffer) == -1){ sc.close(); }else{ //將buffer轉換為讀狀態 buffer.flip(); //將buffer中接收到的值按localCharset格式編碼後儲存到receivedString String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString(); System.out.println("received from cient: "+receivedString); //返回資料給用戶端 String sendString = "received data: "+receivedString; buffer = ByteBuffer.wrap(sendString.getBytes(localCharset)); sc.write(buffer); //關閉Socket sc.close(); } } }}
上面的處理過程都做了注釋,main方法啟動監聽,當監聽到請求時根據SelectionKey的狀態交給內部類Handler進行處理,Handler可以通過重載的構造方法設定編碼格式和每次讀取資料的最大值。Handler處理過程中用到了Buffer,Buffer是java.nio包中的一個類,專門用於儲存資料,Buffer裡有4個屬性非常重要,它們分別是:·capacity:容量,也就是Buffer最多可以儲存元素的數量,在建立時設定,使用過程中不可以改變; ·limit:可以使用的上限,開始建立時limit和capacity的值相同,如果給limit設定一個值之後,limit就成了最大可以訪問的值,其值不可以超過capacity。比如,一個Buffer的容量capacity為100,表示最多可以儲存100個資料,但是現在只往裡面寫了20個資料然後要讀取,在讀取的時候limit就會設定為20; ·position:當前所操作元素所在的索引位置,position從0開始,隨著get和put方法自動更新; ·mark:用來暫時儲存position的值,position儲存到mark後就可以修改並進行相關的操作,操作完後可以通過reset方法將mark的值恢複到position。比如,Buffer中一共儲存了20個資料,position的位置是10,現在想讀取15到20之間的資料,這時就可以調用Buffer#mark()將當前的position儲存到mark中,然後調用Buffer#position(15)將position指向第15個元素,這時就可以讀取了,讀取完之後調用Buffer#reset()就可以將position恢複到10。mark預設值為-1,而且其值必須小於position的值,如果調用Buffer#position(int newPosition)時傳入的newPosition比mark小則會將mark設為-1。 這4個屬性的大小關係是:mark<=position<=limit<=capacity。理解了這4個屬性,Buffer就容易理解了。 我們這裡的NioServer用到clear和flip方法,clear的作用是重新初始化limit、position和mark三個屬性,讓limit=capacity、position=0、mark=-1。flip方法的作用是這樣的:在儲存資料時儲存一個資料position加1,儲存完了之後如果想讀出來就需要將最好position的位置設定給limit,然後將position設定為0,這樣就可以讀取所儲存的資料了,flip方法就是做這個用的,這兩個方法的代碼如下:
// java.nio.Buffer public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; } public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
NioSocket的用法