网上的很多关于NIO的资料是不正确的,nio 支持阻塞和非阻塞模式
在读写状态切换的情况下是不能使用regedit 方法注册,而应该使用以下方式进行
selectionKey.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
只是说明允许连接阻塞时间,而不是连接持续时间。
表示选择器阻塞时间,超过该事件关闭全部的channal
该设置应在绑定端口之前,否则该设置无效
如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用 端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息, 抛出“Address already in use: JVM_Bind”。
如果你的服务程序停止后想立即重启,不等60秒,而新套接字依旧 使用同一端口,此时 SO_REUSEADDR 选项非常有用。
ConcurrentHashMap会自动加锁,避免遍历时,map内容发生变化
*hashmap遍历时,发生插入,remove时会抛出异常
1、send-Q 表示网路发送队列
对方没有收到的数据或者说没有Ack的,还是本地缓冲区.如果发送队列Send-Q不能很快的清零,可能是有应用向外发送数据包过快,或者是对方接收数据包不够快。
这两个值通常应该为0,如果不为0可能是有问题的。packets在两个队列里都不应该有堆积状态。可接受短暂的非0情况
2、recv-q 非由用户进程连接到此socket的复制的总字节数
3、send-q 非由远程主机传送过来的acknowledged总字节数
/** * 1、socket通讯 * 2、通过BlockingQueue实现多线程共享队列,先进先出,队列满了排队等待 * */ public class CommunicationServer implements Runnable{ private final static Logger log = LoggerFactory.getLogger(CommunicationServer.class); private int DEFAULT_SIZE = 1024; private boolean isStartListen = false; private String message = ""; private String serverKey = null; private String serverIp = null; private String serverPort = null; private InetAddress clientIp = null; private int clientPort = 0; /*事件选择器*/ private Selector selector = null; private ServerSocketChannel serverSocketChannel; private BlockingQueue<Attence> attenceQueue; public CommunicationServer(String serverKey, String serverIP, String port, BlockingQueue<Attence> attenceQueue) { try{ /*在linux下InetAddress.getLocalHost().getHostAddress()获取到的是127.0.0.1,只能本机监听访问,因此不能使用该代码*/ /*0.0.0.0表示全网监听端口*/ this.serverIp = serverIP; }catch(Exception e){ e.printStackTrace(); log.error("获取本机Ip地址失败"); this.serverIp = serverIP; } this.serverPort = port; this.serverKey = serverKey; this.attenceQueue = attenceQueue; } @Override public void run() { /*启动监听服务器的监听服务*/ startAttenceServer(); listen(); message = "serverKey:"+ serverKey +",serverIp:"+ serverIp +", serverPort:"+ serverPort +",正常关闭数据"; /*停止接收数据服务线程*/ stopAttenceServer(message); log.info("服务器线程执行完毕停止工作"); } /*启动监听服务器的监听服务*/ private void startAttenceServer(){ message = ""; /*监听服务器端口*/ if(Common.isInteger(serverPort)){ try{ //创建一个新的selector selector = Selector.open(); // 创建一个新的serverSocketChannel serverSocketChannel = ServerSocketChannel.open(); /* * 该设置应在绑定端口之前,否则该设置无效 * 如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用 端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息, 抛出“Address already in use: JVM_Bind”。 * 如果你的服务程序停止后想立即重启,不等60秒,而新套接字依旧 使用同一端口,此时 SO_REUSEADDR 选项非常有用。 * */ serverSocketChannel.socket().setReuseAddress(true); // 设置为非堵塞模式,异步处理 serverSocketChannel.configureBlocking(false); // 绑定到端口 serverSocketChannel.socket().bind(new InetSocketAddress("0.0.0.0", Integer.valueOf(serverPort))); // 在选择器里面注册关注这个服务器套接字通道的accept事件 // ServerSocketChannel只有OP_ACCEPT可用,OP_CONNECT,OP_READ,OP_WRITE用于SocketChannel serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT); isStartListen = true; message = "Server:服务已经启动,serverKey:"+ serverKey +",监听IP为"+ serverIp +",监听端口:"+ serverPort; log.info(message); }catch(Exception e){ message = "启动服务失败:serverKey:"+ serverKey +",监听serverIp:"+ serverIp +",监听端口serverPort:"+ serverPort +" 原因:"+ e.getMessage(); /*尝试关闭socket通讯*/ stopAttenceServer(message); log.error(message); } }else{ message = "Server:端口错误"; log.info(message); } /*标记服务器是否启动成功*/ Server server = new Server(); server.setServerKey(serverKey); server.setIsStart(isStartListen ? "1" : "0"); server.setIsCanStart(isStartListen ? "1" : "0"); server.setReason(message); /*将服务器启动状态记录到数据库*/ ServerService serverService = SpringApplicationContextHolder.getBean(ServerService.class); serverService.updateServerStatus(server); } /*启动监听*/ public void listen() { while (isStartListen) { try { if(selector != null){ if(selector.select() == 0){ continue; } }else{ continue; } Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> iterator = selectedKeys.iterator(); while (iterator.hasNext()) { SelectionKey selectionKey = (SelectionKey) iterator.next(); iterator.remove(); handleKey(selectionKey); } selectedKeys.clear(); } catch (ClosedChannelException e) { e.printStackTrace(); log.info(e.getMessage()); } catch (Exception e) { e.printStackTrace(); log.info(e.getMessage()); } try{ Thread.sleep(50); }catch (InterruptedException ie) { ie.printStackTrace(); log.info("sleep错误:"+ie.getMessage()); } } } /*事件处理*/ private void handleKey(SelectionKey selectionKey){ try{ if(selectionKey.isValid()){ if(selectionKey.isAcceptable() ){ /*新的连接来临*/ /*得到和Selectionkey关联的Channel*/ ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel(); /*得到与客户端的套接字通道*/ SocketChannel socketChannel = serverSocketChannel.accept(); clientIp = socketChannel.socket().getInetAddress(); clientPort = socketChannel.socket().getPort(); log.info("serverKey:"+ serverKey +",监听serverIp:"+ serverIp +",监听端口serverPort:"+ serverPort + "接收客户端设备的新连接,来自于:"+ clientIp +":"+ clientPort); //设置socketChannel为非阻塞的socketChannel socketChannel.configureBlocking(false); //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读|写的权限。 //同样将于客户端的通道在selector上注册,OP_READ对应可读事件(对方有写入数据),可以通过key获取关联的选择器 //socketChannel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE); socketChannel.register(selector, SelectionKey.OP_READ); /*往客户端会写数据*/ sendDataToClient(socketChannel, clientIp, clientPort); }else if(selectionKey.isReadable() ){ SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); /*读取socket数据*/ byte[] socketData = readData(socketChannel, selectionKey); /*解析数据*/ if(socketData != null && socketData.length > 0){ clientIp = socketChannel.socket().getInetAddress(); clientPort = socketChannel.socket().getPort(); String hexData = AttenceUtil.bytesToHexString(socketData); parseData(hexData, socketChannel.getLocalAddress(), String.valueOf(clientIp),String.valueOf(clientPort)); }else{ log.info("serverKey:"+ serverKey +",监听serverIp:"+ serverIp +",监听端口serverPort:"+ serverPort + ";socketData为空"); } if(selectionKey != null && selectionKey.isValid()){ selectionKey.interestOps(SelectionKey.OP_READ); } /*立即回应写内容*/ try{ String responseString = "response"; ByteBuffer buffer = Common.encode(responseString); long bytes = socketChannel.write(buffer); log.info("readable读取后立即返回写入字节数:"+ bytes); }catch(Exception e){ String msg = "客户端设备"+ clientIp + ":"+ clientPort +"数据读取失败,移除连接"; log.info(msg); try{ socketChannel.socket().close(); socketChannel.close(); }catch(Exception e1){ e1.printStackTrace(); msg = "尝试关闭客户端设备"+ clientIp +":"+ clientPort +"连接失败:"+ e1.getMessage(); log.info(msg); } } }else if(selectionKey.isWritable()){ /*通过回写方式验证是否连接正常*/ selectionKey.interestOps(SelectionKey.OP_READ); log.info(clientIp + ":"+ clientPort +"进入写状态"); } } }catch(IOException e){ e.printStackTrace(); try { if(selectionKey != null){ selectionKey.cancel(); selectionKey.channel().close(); } } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); log.info(e1.getMessage()); } log.info(e.getMessage()); }catch(Exception e){ e.printStackTrace(); try { if(selectionKey != null){ selectionKey.cancel(); selectionKey.channel().close(); } } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); log.info(e1.getMessage()); } log.info(e.getMessage()); } } /*数据读取*/ private byte[] readData(SocketChannel socketChannel, SelectionKey selectionKey) { //创建一个用来读取socketChannel的readbuffer ByteBuffer readbuffer = ByteBuffer.allocate(DEFAULT_SIZE); readbuffer.clear(); try { //用readbuffer来读取socketChannel的数据 int nbytes = socketChannel.read(readbuffer); /*如果read()方法返回-1,则表示底层连接已经关闭,此时需要关闭信道。 关闭信道时,将从选择器的各种集合中移除与该信道关联的键。 */ if(nbytes == -1){ socketChannel.socket().close(); socketChannel.close(); return null; } //在readbuffer读取过数据之后,将readbuffer的位置设为0 readbuffer.flip(); byte[]data = new byte[readbuffer.limit()]; readbuffer.get(data, 0, readbuffer.limit()); return data; } catch(ClosedChannelException e){ e.printStackTrace(); log.info(e.getMessage()); } catch (IOException e) { e.printStackTrace(); clientIp = socketChannel.socket().getInetAddress(); clientPort = socketChannel.socket().getPort(); message = "serverKey:"+ serverKey +",serverIP:"+ serverIp +",serverPort:"+ serverPort +"。clientIp:"+ clientIp +",clientPort:"+ clientPort +",考勤设备网络连接异常中断,可能被断电:"+ e.getMessage(); log.info(message); try{ socketChannel.socket().close(); socketChannel.close(); selectionKey.cancel(); }catch(Exception e0){ e0.printStackTrace(); log.info("serverKey:"+ serverKey +",serverIP:"+ serverIp +",serverPort:"+ serverPort +",clientPort:"+ clientPort +",。selectionKey.cancel()时,错误:"+e0.getMessage()); } } return null; } /*数据解析*/ private void parseData(String hexData, SocketAddress socketAddress, String clientIP, String clientPort){ if (hexData != null){ log.info("来自"+ clientIP +":"+ clientPort +"原始数据:"+hexData); } } /*停止服务*/ public void stopAttenceServer(String closeMsg) { try { Thread.sleep(1000); } catch (Exception e) { e.printStackTrace(); } /*停止监听*/ this.isStartListen = false; /*关闭selector*/ if(selector != null && selector.isOpen()){ try { selector.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); log.info(e.getMessage()); } } /*关闭serverSocketChannel*/ if(serverSocketChannel != null && serverSocketChannel.isOpen() ){ try { serverSocketChannel.close(); } catch (IOException e) { e.printStackTrace(); log.info(e.getMessage()); } } closeMsg = this.serverIp + ":" + this.serverPort + "服务连接关闭:"+closeMsg; log.info(closeMsg); /*标记该服务端服务停止运行*/ Server server = new Server(); server.setServerKey(serverKey); server.setIsStart("0"); server.setIsCanStart("1"); server.setReason(closeMsg); /*将服务停止运行消息写入数据库*/ ServerService serverService = SpringApplicationContextHolder.getBean(ServerService.class); serverService.updateServerStatus(server); } /*往客户端回写数据*/ public void sendDataToClient(SocketChannel socketChannel, InetAddress clientIp, int clientPort){ String data = ""; ByteBuffer byteBuffer = Common.encode(data); try { socketChannel.write(byteBuffer); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); try { socketChannel.socket().close(); socketChannel.close(); } catch (IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); message = e1.getMessage(); log.info(message); } message = e.getMessage(); log.info(message); } log.info(message); } }