之前接触过一个项目,是tcp/ip对门禁上位机的数据监听,开始使用的是传统的Bio方式,但是由于ip数量过多,经常会出现socket连接No buffer space available的问题,导致接口大面积调用(webservice,httpclient)失败的问题,重启服务器后又恢复了正常。报错如下Caused by: java.net.SocketException: No buffer space available (maximum connections reached?): connect。后来改进使用队列的方式,发现还是坚持不了多长时间,又会出现同样的报错。后来想到用NIO,原因是nio的机制是同步非阻塞,适用于连接数目多且连接比较短(轻操作)的架构。
先来对BIO、NIO、AIO做比较:
BIO(IO)
同步阻塞,传统io方式。
适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中。
NIO
同步非阻塞,jdk4开始支持。
适用于连接数目多且连接比较短(轻操作)的架构,比如聊天服务器。
AIO
异步非阻塞,jdk7开始支持。
适用于连接数目多且连接比较长(重操作)的架构。
形象的理解NIO和AIO:如果把内核比作点外卖,NIO就是你要自己时不时查下外卖是否已经到了你楼下,然后自己去取快递;AIO就是外卖员送餐上门了。
项目代码如下:
public boolean processData() throws Exception { logger.info("processData....Start..."); initQueue(); logger.info("开始selector绑定"); Thread t = new Thread(new Runnable() { @Override public void run() { Selector selector = null; try { selector = Selector.open(); for (String serverIP : ipSet) { SocketChannel sChannel = createSocketChannel(serverIP, 8000); //将通道注册到一个选择器上(非阻塞模式与选择器搭配会工作的更好) validOps:返回一个操作集,标识此通道所支持的操作。 //将通道管理器与通道绑定,并为该通道注册SelectionKey.validOps事件 SelectionKey key = sChannel.register(selector, sChannel.validOps()); System.out.println("CreateSocketChannel ip:" + serverIP + " SelectionKey:" + key); //将SocketChannel向Selector注册事件句柄存入map selKeyMap.put(key, serverIP); } logger.info("processData....End..."); // 等待事件的循环 while (true) { logger.info("loading..."); try { // 等待 阻塞到至少有一个通道在注册的事件上就绪了 selector.select(); } catch (IOException e) { logger.error("select异常 "); e.printStackTrace(); } // 所有事件列表 遍历selectionKey来访问就绪的通道 Iterator<SelectionKey> it = selector.selectedKeys().iterator(); // 处理每一个事件 while (it.hasNext()) { // 得到关键字 SelectionKey selKey = it.next(); // 删除已经处理的关键字 try { // 处理事件 processSelectionKey(selKey); } catch (IOException e) { // 处理异常 // selKey.cancel(); logger.error("处理异常,key:" + selKeyMap.get(selKey)); e.printStackTrace(); } //删除已选key,防止重复处理 it.remove(); } // 线程睡眠1秒钟 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } catch (IOException e) { e.printStackTrace(); } finally { try { if (selector != null) { selector.close(); } // if (ssc != null) { // ssc.close(); // } } catch (IOException e) { e.printStackTrace(); } } } }); t.setDaemon(true); t.start(); // 跟门禁控制器的心跳 Timer heartBeatTimer = new Timer(); TimerTask heartBeatTimerTask = new TimerTask() { @Override public void run() { logger.info("heartBeatTimerTask timer"); logger.info("selKeyMap size is " + selKeyMap.size()); for (SelectionKey key : selKeyMap.keySet()) { try { SocketChannel channel = (SocketChannel) key.channel(); heartBeat.clear(); byte value = (byte)0x7e; heartBeat.put(value); heartBeat.put(value); heartBeat.flip(); if (key.isValid() && key.isWritable()) { channel.write(heartBeat); } } catch (Exception e) { logger.info(e.getMessage()); e.printStackTrace(); } } } }; // 每隔10秒发一次心跳数据 heartBeatTimer.schedule(heartBeatTimerTask, 10000, 10000); return true; }