所謂非同步輸入輸出機制,是指在進行輸入輸出處理時,不必等到輸入輸出處理完畢才返回。所以非同步同義語是非阻塞(None Blocking)。
網上有很多網友用很通俗的比喻 把同步和非同步講解的很透徹 轉過來
舉個例子:普通B/S模式(同步)AJAX技術(非同步)
同步:提交請求->等待伺服器處理->處理完畢返回 這個期間用戶端瀏覽器不能幹任何事
非同步: 請求通過事件觸發->伺服器處理(這是瀏覽器仍然可以作其他事情)->處理完畢
同步就是你叫我去吃飯,我聽到了就和你去吃飯;如果沒有聽到,你就不停的叫,直到我告訴你聽到了,才一起去吃飯。
非同步就是你叫我,然後自己去吃飯,我得到訊息後可能立即走,也可能等到下班才去吃飯。
所以,要我請你吃飯就用同步的方法,要請我吃飯就用非同步方法,這樣你可以省錢。
以通訊為例
同步:發送一個請求,等待返回,然後再發送下一個請求
非同步:發送一個請求,不等待返回,隨時可以再發送下一個請求
並發:同時發送多個請求
下面再轉一段關於java非同步應用的文章
用非同步輸入輸出資料流編寫Socket進程通訊程式
在Merlin中加入了用於實現非同步輸入輸出機制的應用程式介面包:java.nio(新的輸入輸出包,定義了很多基本類型緩衝(Buffer)),java.nio.channels(通道及選取器等,用於非同步輸入輸出),java.nio.charset(字元的編碼解碼)。通道(Channel)首先在選取器(Selector)中註冊自己感興趣的事件,當相應的事件發生時,選取器便通過選擇鍵(SelectionKey)通知登入的通道。然後通道將需要處理的資訊,通過緩衝(Buffer)打包,編碼/解碼,完成輸入輸出控制。
通道介紹:
這裡主要介紹ServerSocketChannel和SocketChannel.它們都是可選擇的(selectable)通道,分別可以工作在同步和非同步兩種方式下(注意,這裡的可選擇不是指可以選擇兩種工作方式,而是指可以有選擇的註冊自己感興趣的事件)。可以用channel.configureBlocking(Boolean )來設定其工作方式。與以前版本的API相比較,ServerSocketChannel就相當於ServerSocket
(ServerSocketChannel封裝了ServerSocket),而SocketChannel就相當於Socket(SocketChannel封裝了Socket)。當通道工作在同步方式時,編程方法與以前的基本相似,這裡主要介紹非同步工作方式。
所謂非同步輸入輸出機制,是指在進行輸入輸出處理時,不必等到輸入輸出處理完畢才返回。所以非同步同義語是非阻塞(None Blocking)。在伺服器端,ServerSocketChannel通過靜態函數open()返回一個執行個體serverChl。然後該通道調用serverChl.socket().bind()綁定到伺服器某連接埠,並調用register(Selector sel, SelectionKey.OP_ACCEPT)註冊OP_ACCEPT事件到一個選取器中(ServerSocketChannel只可以註冊OP_ACCEPT事件)。當有客戶請求串連時,選取器就會通知該通道有客戶串連請求,就可以進行相應的輸入輸出控制了;在用戶端,clientChl執行個體註冊自己感興趣的事件後(可以是OP_CONNECT,OP_READ,OP_WRITE的組合),調用clientChl.connect
(InetSocketAddress )串連伺服器然後進行相應處理。注意,這裡的串連是非同步,即會立即返回而繼續執行後面的代碼。
選取器和選擇鍵介紹:
選取器(Selector)的作用是:將通道感興趣的事件放入隊列中,而不是馬上提交給應用程式,等登入的通道自己來請求處理這些事件。換句話說,就是選取器將會隨時報告已經準備好了的通道,而且是按照先進先出的順序。那麼,選取器是通過什麼來報告的呢?選擇鍵(SelectionKey)。選擇鍵的作用就是表明哪個通道已經做好了準備,準備幹什麼。你也許馬上會想到,那一定是登入的通道感興趣的事件。不錯,例如對於伺服器端serverChl來說,可以調用key.isAcceptable()來通知serverChl有用戶端串連請求。相應的函數還有:SelectionKey.isReadable(),SelectionKey.isWritable()。一般的,在一個迴圈中輪詢感興趣的事件(具體可參照下面的代碼)。如果選取器中尚無通道登入事件發生,調用Selector.select()將阻塞,直到有事件發生為止。另外,可以調用selectNow()或者select(long
timeout)。前者立即返回,沒有事件時返回0值;後者等待timeout時間後返回。一個選取器最多可以同時被63個通道一起註冊使用。
應用執行個體:
下面是用非同步輸入輸出機制實現的客戶/伺服器執行個體程式�D�D程式清單1(限於篇幅,只給出了伺服器端實現,讀者可以參照著實現用戶端代碼):
- public class NBlockingServer {
- int port = 8000;
- int BUFFERSIZE = 1024;
- Selector selector = null;
- ServerSocketChannel serverChannel = null;
- HashMap clientChannelMap = null;//用來存放每一個客戶串連對應的通訊端和通道
- public NBlockingServer( int port ) {
- this.clientChannelMap = new HashMap();
- this.port = port;
- }
- public void initialize() throws IOException {
- //初始化,分別執行個體化一個選取器,一個伺服器端可選擇通道
- this.selector = Selector.open();
- this.serverChannel = ServerSocketChannel.open();
- this.serverChannel.configureBlocking(false);
- InetAddress localhost = InetAddress.getLocalHost();
- InetSocketAddress isa = new InetSocketAddress(localhost, this.port );
- this.serverChannel.socket().bind(isa);//將該通訊端綁定到伺服器某一可用連接埠
- }
- //結束時釋放資源
- public void finalize() throws IOException {
- this.serverChannel.close();
- this.selector.close();
- }
- //將讀入位元組緩衝的資訊解碼
- public String decode( ByteBuffer byteBuffer ) throws
- CharacterCodingException {
- Charset charset = Charset.forName( "ISO-8859-1" );
- CharsetDecoder decoder = charset.newDecoder();
- CharBuffer charBuffer = decoder.decode( byteBuffer );
- String result = charBuffer.toString();
- return result;
- }
- //監聽連接埠,當通道準備好時進行相應操作
- public void portListening() throws IOException, InterruptedException {
- //伺服器端通道註冊OP_ACCEPT事件
- SelectionKey acceptKey =this.serverChannel.register( this.selector,
- SelectionKey.OP_ACCEPT );
- //當有登入的事件發生時,select()傳回值將大於0
- while (acceptKey.selector().select() > 0 ) {
- System.out.println("event happened");
- //取得所有已經準備好的所有選擇鍵
- Set readyKeys = this.selector.selectedKeys();
- //使用迭代器對選擇鍵進行輪詢
- Iterator i = readyKeys.iterator();
- while (i.hasNext()) {
- SelectionKey key = (SelectionKey)i.next();
- i.remove();//刪除當前將要處理的選擇鍵
- if ( key.isAcceptable() ) {//如果是有用戶端串連請求
- System.out.println("more client connect in!");
- ServerSocketChannel nextReady =
- (ServerSocketChannel)key.channel();
- //擷取用戶端通訊端
- Socket s = nextReady.accept();
- //設定對應的通道為非同步方式並註冊感興趣事件
- s.getChannel().configureBlocking( false );
- SelectionKey readWriteKey =
- s.getChannel().register( this.selector,
- SelectionKey.OP_READ|SelectionKey.OP_WRITE );
- //將註冊的事件與該通訊端聯絡起來
- readWriteKey.attach( s );
- //將當前建立串連的用戶端通訊端及對應的通道存放在雜湊表//clientChannelMap中
- this.clientChannelMap.put( s, new
- ClientChInstance( s.getChannel() ) );
- }
- else if ( key.isReadable() ) {//如果是通道讀準備好事件
- System.out.println("Readable");
- //取得選擇鍵對應的通道和通訊端
- SelectableChannel nextReady =
- (SelectableChannel) key.channel();
- Socket socket = (Socket) key.attachment();
- //處理該事件,處理方法已封裝在類ClientChInstance中
- this.readFromChannel( socket.getChannel(),
- (ClientChInstance)
- this.clientChannelMap.get( socket ) );
- }
- else if ( key.isWritable() ) {//如果是通道寫準備好事件
- System.out.println("writeable");
- //取得通訊端後處理,方法同上
- Socket socket = (Socket) key.attachment();
- SocketChannel channel = (SocketChannel)
- socket.getChannel();
- this.writeToChannel( channel,"This is from server!");
- }
- }
- }
- }
- //對通道的寫操作
- public void writeToChannel( SocketChannel channel, String message )
- throws IOException {
- ByteBuffer buf = ByteBuffer.wrap( message.getBytes() );
- int nbytes = channel.write( buf );
- }
- //對通道的讀操作
- public void readFromChannel( SocketChannel channel, ClientChInstance clientInstance )
- throws IOException, InterruptedException {
- ByteBuffer byteBuffer = ByteBuffer.allocate( BUFFERSIZE );
- int nbytes = channel.read( byteBuffer );
- byteBuffer.flip();
- String result = this.decode( byteBuffer );
- //當用戶端發出”@exit”退出命令時,關閉其通道
- if ( result.indexOf( "@exit" ) >= 0 ) {
- channel.close();
- }
- else {
- clientInstance.append( result.toString() );
- //讀入一行完畢,執行相應操作
- if ( result.indexOf( "\n" ) >= 0 ){
- System.out.println("client input"+result);
- clientInstance.execute();
- }
- }
- }
- //該類封裝了怎樣對用戶端的通道進行操作,具體實現可以通過重載execute()方法
- public class ClientChInstance {
- SocketChannel channel;
- StringBuffer buffer=new StringBuffer();
- public ClientChInstance( SocketChannel channel ) {
- this.channel = channel;
- }
- public void execute() throws IOException {
- String message = "This is response after reading from channel!";
- writeToChannel( this.channel, message );
- buffer = new StringBuffer();
- }
- //當一行沒有結束時,將當前字竄置於緩衝尾
- public void append( String values ) {
- buffer.append( values );
- }
- }
- //主程式
- public static void main( String[] args ) {
- NBlockingServer nbServer = new NBlockingServer(8000);
- try {
- nbServer.initialize();
- } catch ( Exception e ) {
- e.printStackTrace();
- System.exit( -1 );
- }
- try {
- nbServer.portListening();
- }
- catch ( Exception e ) {
- e.printStackTrace();
- }
- }
- }
小結:
從以上程式段可以看出,伺服器端沒有引入多餘線程就完成了多客戶的客戶/伺服器模式。該程式中使用了回調模式(CALLBACK),細心的讀者應該早就看出來了。需要注意的是,請不要將原來的輸入輸出包與新加入的輸入輸出包混用,因為出於一些原因的考慮,這兩個包並不相容。即使用通道時請使用緩衝完成輸入輸出控制。該程式在Windows2000,J2SE1.4下,用telnet測試成功。
以上轉自http://space.itpub.net/17074730/viewspace-563262。。這的真心不錯。。