Java Nio 笔记

         网上的很多关于NIO的资料是不正确的,nio 支持阻塞和非阻塞模式

  • 关于读写状态切换

             在读写状态切换的情况下是不能使用regedit 方法注册,而应该使用以下方式进行

       selectionKey.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE); 
  • setsoTimeout

             只是说明允许连接阻塞时间,而不是连接持续时间。

  • select(timeOut)

             表示选择器阻塞时间,超过该事件关闭全部的channal

  • serverSocketChannel.socket().setReuseAddress(true);

              该设置应在绑定端口之前,否则该设置无效
              如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用 端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息, 抛出“Address already in   use: JVM_Bind”。
              如果你的服务程序停止后想立即重启,不等60秒,而新套接字依旧 使用同一端口,此时 SO_REUSEADDR 选项非常有用。

  • ConcurrentHashMap 与 HashMap 区别

              ConcurrentHashMap会自动加锁,避免遍历时,map内容发生变化
              *hashmap遍历时,发生插入,remove时会抛出异常

  • tcp通讯协议的名词解释

             1、send-Q 表示网路发送队列

                   对方没有收到的数据或者说没有Ack的,还是本地缓冲区.如果发送队列Send-Q不能很快的清零,可能是有应用向外发送数据包过快,或者是对方接收数据包不够快。

                   这两个值通常应该为0,如果不为0可能是有问题的。packets在两个队列里都不应该有堆积状态。可接受短暂的非0情况
             2、recv-q 非由用户进程连接到此socket的复制的总字节数
             3、send-q 非由远程主机传送过来的acknowledged总字节数

  • nio通讯实例
/**
 * 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);
    } 
}
  • 参考资料

              http://my.oschina.net/javagg/blog/3361

              http://blog.csdn.net/weibing_huang/article/details/7368635
              http://blog.csdn.net/rootsuper/article/details/8537498
              http://blog.csdn.net/rootsuper/article/details/8537682
              http://blog.csdn.net/rootsuper/article/details/8542236

原文地址:https://www.cnblogs.com/wala-wo/p/5119216.html