Java Socket NIO示例總結


Java NIO是非阻塞IO的實現,基於事件驅動,非常適用於服務器需要維持大量連接,但是數據交換量不大的情況,例如一些即時通信的服務等等,它主要有三個部分組成:

  • Channels
  • Buffers
  • Selectors

Channel有兩種ServerSocketChannel 和 SocketChannel,ServerSocketChannel可以監聽新加入的Socket連接,SocketChannel用於讀和寫操作。NIO總是把緩沖區的數據寫入通道,或者把通道里的數據讀出到緩沖區。

Buffer本質上是一塊用於讀寫的內存,只是被包裝成了buffer對象,你可以通過allocateDirect()或者allocate()申請內存空間(allocate分配方式產生的內存開銷是在JVM中的,而allocateDirect的分配方式產生的開銷在JVM之外,以就是系統級的內存分配,使用allocateDirect尤其注意內存溢出問題),Buffer尤其需要理解三個概念,

capacity、position、limit,capacity是固定大小,position是當前讀寫位置,limit是一個類似於門限的值,用於控制讀寫的最大的位置。Buffer的常用方法有clear、compact、flip等等,還有比如Buffer的靜態方法wrap等等,這些需要根據capacity、position、limit的值進行理解,上面ifeve上的文章就很詳細了,我就不再累述了。

Selector用於檢測通道,我們通過它才知道哪個通道發生了哪個事件,所以如果需要用selector的話就需要首先進行register,然后遍歷SelectionKey對事件進行處理。它一共有SelectionKey.OP_CONNECT、SelectionKey.OP_ACCEPT、SelectionKey.OP_READ、SelectionKey.OP_WRITE四種事件類型。

我在這里只是粗略的總結,關於NIO的概念 http://ifeve.com/java-nio-all/ 可以看這里。

http://blog.csdn.net/ns_code/article/details/15545057 蘭亭風雨的這個demo不錯,我直接照搬過來了。

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;


public class NIOServer {
    
    private static int BUFF_SIZE=1024;
    private static int TIME_OUT = 2000;
    public static void main(String[] args) throws IOException {
        
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel=ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(10083));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        
        TCPProtocol protocol = new EchoSelectorProtocol(BUFF_SIZE); 
        
        while (true) {
            if(selector.select(TIME_OUT)==0){
                //在等待信道准備的同時,也可以異步地執行其他任務,  這里打印*
                System.out.print("*");  
                continue;  
            }
            Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();
            while (keyIter.hasNext()) {
                SelectionKey key = keyIter.next(); 
                //如果服務端信道感興趣的I/O操作為accept  
                if (key.isAcceptable()){  
                    protocol.handleAccept(key);  
                }  
                //如果客戶端信道感興趣的I/O操作為read  
                if (key.isReadable()){  
                    protocol.handleRead(key);  
                }  
                //如果該鍵值有效,並且其對應的客戶端信道感興趣的I/O操作為write  
                if (key.isValid() && key.isWritable()) {  
                    protocol.handleWrite(key);  
                }
                
                //這里需要手動從鍵集中移除當前的key  
                keyIter.remove(); 
            }
            
        }
    }
}
import java.io.IOException;
import java.nio.channels.SelectionKey;


public interface TCPProtocol {

    void handleAccept(SelectionKey key) throws IOException;

    void handleRead(SelectionKey key) throws IOException;

    void handleWrite(SelectionKey key) throws IOException;

}
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;


public class EchoSelectorProtocol implements TCPProtocol {
    
    private int bufSize; // 緩沖區的長度  
    public EchoSelectorProtocol(int bufSize){  
    this.bufSize = bufSize;  
    }

    @Override
    public void handleAccept(SelectionKey key) throws IOException {
        System.out.println("Accept");
        SocketChannel socketChannel = ((ServerSocketChannel)key.channel()).accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufSize));
        
    }

    @Override
    public void handleRead(SelectionKey key) throws IOException {
        SocketChannel clntChan = (SocketChannel) key.channel();  
        //獲取該信道所關聯的附件,這里為緩沖區  
        ByteBuffer buf = (ByteBuffer) key.attachment();
        buf.clear();
        long bytesRead = clntChan.read(buf);
        //如果read()方法返回-1,說明客戶端關閉了連接,那么客戶端已經接收到了與自己發送字節數相等的數據,可以安全地關閉  
        if (bytesRead == -1){   
            clntChan.close();  
        }else if(bytesRead > 0){
            //如果緩沖區總讀入了數據,則將該信道感興趣的操作設置為為可讀可寫  
            key.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);  
        }  
        
    }

    @Override
    public void handleWrite(SelectionKey key) throws IOException {
        // TODO Auto-generated method stub
        ByteBuffer buffer=(ByteBuffer) key.attachment();
        buffer.flip();
        SocketChannel clntChan = (SocketChannel) key.channel();  
        //將數據寫入到信道中  
        clntChan.write(buffer);  
        if (!buffer.hasRemaining()){
            
        //如果緩沖區中的數據已經全部寫入了信道,則將該信道感興趣的操作設置為可讀  
          key.interestOps(SelectionKey.OP_READ);  
        } 
        //為讀入更多的數據騰出空間  
        buffer.compact();   
        
    }

} 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;


public class NIOClient {
    
    public static void main(String[] args) throws IOException {
        SocketChannel clntChan = SocketChannel.open();
        clntChan.configureBlocking(false);
        if (!clntChan.connect(new InetSocketAddress("localhost", 10083))){  
            //不斷地輪詢連接狀態,直到完成連接  
            while (!clntChan.finishConnect()){  
                //在等待連接的時間里,可以執行其他任務,以充分發揮非阻塞IO的異步特性  
                //這里為了演示該方法的使用,只是一直打印"."  
                System.out.print(".");    
            }  
         }
        
        //為了與后面打印的"."區別開來,這里輸出換行符  
        System.out.print("\n");  
        //分別實例化用來讀寫的緩沖區  
        
        ByteBuffer writeBuf = ByteBuffer.wrap("send send send".getBytes());
        ByteBuffer readBuf = ByteBuffer.allocate("send".getBytes().length-1);
        
        while (writeBuf.hasRemaining()) {
            //如果用來向通道中寫數據的緩沖區中還有剩余的字節,則繼續將數據寫入信道  
                clntChan.write(writeBuf);  
            
        }
        StringBuffer stringBuffer=new StringBuffer(); 
        //如果read()接收到-1,表明服務端關閉,拋出異常  
            while ((clntChan.read(readBuf)) >0){
                readBuf.flip();
                stringBuffer.append(new String(readBuf.array(),0,readBuf.limit()));
                readBuf.clear();
            }  
        
      //打印出接收到的數據  
        System.out.println("Client Received: " +  stringBuffer.toString());  
        //關閉信道  
        clntChan.close();  
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM