【網絡編程】AIO編程

JDK1.7升級了NIO類庫,升級後的NIO類庫被稱爲NIO2.0。同時,Java正式提供了異步文件IO操作,同時提供給了與UNIX網絡編程事件驅動IO對應的AIO。

NIO2.0引入了新的異步通道的概念,提供了異步文件通道和異步套接字通道的實現。異步通道以兩種方式獲取操作結果。

  • 通過java.util.concurrent.Future來來獲取異步操作的結果;

  • 在執行異步操作的時候傳入一個java.nio.channels。

NIO2.0的異步套接字是真正的異步非阻塞IO,對應UNIX網絡編程中的事件驅動IO,即AIO。它不需要通過多路複用選擇器Selector對註冊的通道進行輪詢操作即可實現異步讀寫,簡化了NIO的編程模型。服務器代碼:

package com.test.aio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;

/**
 * AIO編程服務器端
 * @author 程就人生
 * @Date
 */
public class HelloServer {

  public static void main( String[] args ){
    int port = 8080;
    AsyncHelloServerHandler helloServer = new  AsyncHelloServerHandler(port);
    new Thread(helloServer,"AIO").start();
  }
}

/**
 * AIO編程handler處理類
 * @author 程就人生
 * @Date
 */
class AsyncHelloServerHandler implements Runnable{
  
  private int port;
  
  CountDownLatch latch;
  
  AsynchronousServerSocketChannel asynchronousServerSocketChannel;

  public AsyncHelloServerHandler(int port) {
    this.port = port;
    try {
      // 創建一個異步的服務端通道
      asynchronousServerSocketChannel = AsynchronousServerSocketChannel.open();
      // 綁定監聽端口
      asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
      System.out.println("AIO編程,服務器端已啓動,啓動端口號爲:" + port);
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void run() {
    // 完成一組正在執行的操作之前,允許當前的線程一直阻塞
    latch = new CountDownLatch(1);
    // 接收客戶端的連接
    doAccept();
    try{
      latch.await();
    }catch(InterruptedException e){
      e.printStackTrace();
    }
  }

  private void doAccept() {
    asynchronousServerSocketChannel.accept(this, new AcceptCompletionHandler());
  }  
}
/**
 * 接收新的客戶端連接
 * @author 程就人生
 * @Date
 */
class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncHelloServerHandler>{

  public void completed(AsynchronousSocketChannel result, AsyncHelloServerHandler attachment) {
    attachment.asynchronousServerSocketChannel.accept(attachment, this);
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    result.read(buffer, buffer, new ReadCompletionHandler(result));
  }

  public void failed(Throwable exc, AsyncHelloServerHandler attachment) {
    attachment.latch.countDown();
  }
  
}
/**
 * 接收通知回調處理的handler
 * @author 程就人生
 * @Date
 */
class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer>{
  
  private AsynchronousSocketChannel channel;  

  public ReadCompletionHandler(AsynchronousSocketChannel channel) {
    this.channel = channel;
  }

  public void completed(Integer result, ByteBuffer attachment) {
    attachment.flip();
    byte[] body = new byte[attachment.remaining()];
    attachment.get(body);
    try {
      String req = new String(body, "utf-8");
      System.out.println("服務器端收到的:" + req);
      // 應答
      doWrite();
    } catch (UnsupportedEncodingException e) {
      e.printStackTrace();
    }
  }

  private void doWrite() {
    byte[] bytes = "服務器端的反饋消息".getBytes();
    // 發送緩衝區
    ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
    writeBuffer.put(bytes);
    writeBuffer.flip();
    channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>(){

      public void completed(Integer result, ByteBuffer buffer) {
        // 如果沒有發送完,繼續發送
        if(buffer.hasRemaining()){
          channel.write(buffer, buffer, this);
        }
      }

      public void failed(Throwable exc, ByteBuffer buffer) {
        try {
          channel.close();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    });
  }

  public void failed(Throwable exc, ByteBuffer attachment) {
    try {
      this.channel.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }  
}

在AsyncHelloServerHandler 的構造方法中,首先創建一個異步的服務器端通道AsynchronousServerSocketChannel,然後調用bind方法綁定監聽端口。

在線程run方法中,初始化CountDownLatch對象,完成一組正在執行的操作之前,允許當前的線程一直阻塞,防止服務器端執行完退出,實際項目中不需要。

使用doAccept方法接收客戶端的連接,因爲是異步操作,在這裏傳遞一個CompletionHandler類型的handler實例接收accept操作成功的通知消息。在AcceptCompletionHandler 類中可以接收新加入的客戶端連接。在77行預分配1KB的緩衝區。78行調用read進行異步讀操作。

在118行先進行flip操作,爲後續從緩衝區讀取數據做準備。根據緩衝區的可讀字節數創建byte數據組,通過new String對字節數組進行編碼,並打印出來。最後通過doWrite回寫客戶端。

客戶端代碼:

package com.test.aio;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.CountDownLatch;

/**
 * AIO編程客戶端
 * @author 程就人生
 * @Date
 */
public class HelloClient {

  public static void main( String[] args ){
    int port = 8080;
    new Thread(new AsyncHelloHandler("127.0.0.1",port)).start();
  }
}

class AsyncHelloHandler implements CompletionHandler<Void, AsyncHelloHandler>, Runnable{
  
  private String host;
  
  private int port;
  
  private AsynchronousSocketChannel client;
  
  private CountDownLatch latch;

  public AsyncHelloHandler(String host, int port) {
    this.host = host;
    this.port = port;
    try {
      client = AsynchronousSocketChannel.open();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void run() {
    latch = new CountDownLatch(1);
    client.connect(new InetSocketAddress(host, port), this, this);
    try {
      latch.await();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    try {
      client.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  public void completed(Void result, AsyncHelloHandler attachment) {
    byte[] req = "客戶端的消息".getBytes();
    ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
    writeBuffer.put(req);
    writeBuffer.flip();
    client.write(writeBuffer,writeBuffer, new CompletionHandler<Integer, ByteBuffer>(){

      public void completed(Integer result, ByteBuffer buffer) {
        if(buffer.hasRemaining()){
          client.write(buffer, buffer, this);
        }else{
          ByteBuffer readBuffer = ByteBuffer.allocate(1024);
          client.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>(){

            public void completed(Integer result, ByteBuffer buffer) {
              buffer.flip();
              byte[] bytes = new byte[buffer.remaining()];
              buffer.get(bytes);
              try {
                String body = new String(bytes, "utf-8");
                System.out.println("客戶端讀取到的:" + body);
                latch.countDown();
                System.out.println("客戶端操作完畢,關閉連接");
              } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
              }
            }

            public void failed(Throwable exc, ByteBuffer buffer) {
              try {
                client.close();
                latch.countDown();
              } catch (IOException e) {
                e.printStackTrace();
              }
            }
            
          });
        }
      }

      public void failed(Throwable exc, ByteBuffer attachment) {
        try {
          client.close();
          latch.countDown();
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
      
    });
  }

  public void failed(Throwable exc, AsyncHelloHandler attachment) {
    try {
      client.close();
      latch.countDown();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }  
}

在20行,客戶端通過一個獨立的線程創建異步客戶端處理handler。在AsyncHelloHandler中,使用了大量匿名內部類。第38行,通過AsynchronousSocketChannel的open方法創建了一個新的AsynchronousSocketChannel對象。

在第45行,創建CountDownLatch進行等待,防止異步操咋沒有執行完線程就退出了。在第46行,通過connect方法發起異步操作。

在第73行,completed異步連接成功之後的方法回調,創建請求消息體,對bytebuffer進行flip,併發給服務器端。發送給服務器端後,接收服務器端的反饋,並進行打印,最後調用latch的countDown()方法,關閉客戶端連接。

服務器端執行結果:

客戶端執行結果:

以上便是來自java.nio包的非阻塞異步服務器端、客戶端編碼的簡單演示。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章