内容还是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中
堵塞的情况:
图示:
额 图示上都没画箭头 箭头方向是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