[java]NIO服务器(ServerSocketChannel)开发的两种实现方式

内容还是NIO 而并不是NIO.2 算是对所学的一些总结.

在NIO中 开发TCP程序时会将SocketChannel的OP_READ注册到一个Selector上 selector进行轮训 这是与以往的Socket编程完全不同的新(现在看来已经不能算新啦)的东西.

ServerSocketChannel和原先的ServerSocket相比 增加了一个非堵塞的accept方式(configureBlocking(false)) 和传统的ServerSocket相比 设置成非堵塞方式accept方法会立即返回(没有连接到来就返回null) 所以和SocketChannel一样 他也需要注册到一个Selector上 注册的方法选为OP_ACCEPT.

现在来看看 ServerSocketChannel设置为堵塞(默认)和非堵塞的实现上的区别 以下的图当做草图来用...因为本人不会画uml...画出来多半也是错的 错的地方欢迎打脸..:

非堵塞情况下:

 
 这种情况比较简单  ServerSocketChannel在运行前设置成非堵塞模式 然后注册到实际进行任务处理的Dispather线程的Selector中就可以了

代码如下:

public void start() throws IOException{
    ServerSocketChannel ssc=ServerSocketChannel.open();
    // 设置端口绑定
    ssc.socket().bind(new InetSocketAddress(port));
    // 设置地址复用
    ssc.socket().setReuseAddress(true);
    ssc.configureBlocking(false); 
    Dispatcher dispatcher=new NIODispatcher();
    dispatcher.register(ssc, SelectionKey.OP_ACCEPT);
    new Thread(dispatcher).start();
  }

 对应的dispatcher中的Selector的轮询操作要麻烦一点 因为要判断是Accept的操作 还是Read的操作

 Dispatcher中的主要代码如下:

@Override
  public void run() {
    while (true) {
      try {
        dispatch();
      } catch (IOException e) {
        System.out.println(e);
        logger.error("NIODispatcher run()", e);
      }
    }
  }

  private void dispatch() throws IOException {
    selector.select();
    for (Iterator<SelectionKey> itor = selector.selectedKeys().iterator(); itor
        .hasNext();) {
      SelectionKey sk = itor.next();
      itor.remove();
      if (sk.isAcceptable()) {
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) sk
            .channel();
        SocketChannel sc = serverSocketChannel.accept();
        sc.configureBlocking(false);

        // 立即注册一个 OP_READ 的SelectionKey, 接收客户端的消息
        SelectionKey key = sc.register(selector, SelectionKey.OP_READ);
        // 如果socket没有连接打开或者sk无效那么下一个
      } else if (sk.isReadable()) {
        SocketChannel sc = (SocketChannel) sk.channel();
        ChannelHelper ch = new ChannelHelper(sc, false);
        try {
          int count = ch.read();
          // 小于零 意味着要么就是传过来的没有信息 要么就是有异常了
          if (count < 0) {
            sk.cancel();
            sc.close();
            continue;
          }
          // 等于零的情况 继续
          if (count <= 0) {
            sk.cancel();
            sc.close();
            continue;
          }
        } catch (Exception e) {
          logger.error("NIODiapatcher dispatch()", e);
          sk.cancel();
          sc.close();
          continue;
        }
        RequestHandler handler = new RequestHandler(ch, sk);
        pool.execute(handler);
      }
    }
  }

  public void register(ServerSocketChannel ssc, int ops) {
    try {
      ssc.register(selector, ops);
    } catch (ClosedChannelException e) {
      logger.error("NIODispatcher register", e);
    }
  }

以上是非堵塞的实现方式

堵塞的话要多一个接受者的线程来处理到来的连接 并把连接注册到Dispatcher线程的Selector中

堵塞的情况:

图示:

[java]NIO服务器(ServerSocketChannel)开发的两种实现方式_第1张图片 
 额 图示上都没画箭头 箭头方向是NIO到acceptor和dispatcher  acceptor到dispatcher dispatcher负责处理任务(更确切的说是负责任务的派遣)

代码如下:

NIOServer中 不用设置阻塞模式 默认为阻塞:

public void start() throws IOException{
    ServerSocketChannel ssc=ServerSocketChannel.open();
    // 设置端口绑定
    ssc.socket().bind(new InetSocketAddress(port));
    // 设置地址复用
    ssc.socket().setReuseAddress(true);
    
    Dispatcher dispatcher=new NIODispatcher();
    
    Acceptor acceptor=new NIOAcceptor(ssc, dispatcher);
    
    new Thread(acceptor).start();
    new Thread(dispatcher).start();
  }

 这里可以看到多了一个Acceptor线程

这个线程的内容很简单 就是负责把得到的连接注册到dispatcher上:

@Override
  public void run() {
    while(true){
      try {
        SocketChannel sc=ssc.accept();
        sc.configureBlocking(false);
        dispatcher.register(sc, SelectionKey.OP_READ);
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

 最后是dispatcher的代码:

public class NIODispatcher implements Dispatcher {

  private static final LogUtil logger = new LogUtil(NIODispatcher.class);

  private ReentrantLock lock = new ReentrantLock();

  private Selector selector = null;

  private ExecutorService pool=null;
  
  public NIODispatcher() throws IOException {
    selector = Selector.open();
    pool=Executors.newCachedThreadPool();
  }

  @Override
  public void run() {
    while (true) {
      try {
        dispatch();
      } catch (IOException e) {
        System.out.println(e);
        logger.error("NIODispatcher run()", e);
      }
    }
  }

  private void dispatch() throws IOException {
    System.out.println("---select操作----");
    selector.select();
    for (Iterator<SelectionKey> itor = selector.selectedKeys().iterator(); itor
        .hasNext();) {
      SelectionKey sk = itor.next();
      itor.remove();
      SocketChannel sc = (SocketChannel) sk.channel();
      // 如果socket没有连接打开或者sk无效那么下一个
      System.out.println("---"+sc.getLocalAddress()+" ---");
      if (sc.socket().isClosed()|| !sk.isValid() || !sc.isOpen()) {
        System.out.println("连接无效");
        sk.cancel();
        sc.close();
        continue;
      }

      ChannelHelper ch = new ChannelHelper(sc, false);
      try {
        int count = ch.read();
        //小于等于零 意味着要么就是传过来的没有信息 要么就是有异常了
        // 而在读取中做了处理 如果返回是0就是没有信息了 可以安心关闭连接
        if (count <= 0) {
          sk.cancel();
          sc.close();
          continue;
        }
      } catch (Exception e) {
        logger.error("NIODiapatcher dispatch()", e);
        sk.cancel();
        sc.close();
        continue;
      }
      RequestHandler handler=new RequestHandler(ch, sk);
      pool.execute(handler);

    }
    lock.lock();
    lock.unlock();
  }

  @Override
  public void register(SocketChannel sc, int ops) {
    try {
      lock.lock();
      selector.wakeup();
      sc.register(selector, ops);
    } catch (Exception ex) {
      logger.error("NIODispatcher register", ex);
    } finally {
      lock.unlock();
    }
  }

}

 注意注册的时候要先让Selector wakeup一下 这是为了让在select()方法中堵塞的seletor能去做其他的事情(这里就是SocketChannel注册咯)

更多关于wakeup的解释看: http://www.iteye.com/topic/650195

你可能感兴趣的:([java]NIO服务器(ServerSocketChannel)开发的两种实现方式)