• 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

  • 相关阅读:
    JS中Text节点总结
    JS中Document节点总结
    HTML5 Geolocation位置信息定位总结
    HTML5form表单的相关知识总结
    HTML5文档的各个组成部分分类
    JS中Node节点总结
    vue.js指令总结
    javascript string对象方法总结
    php 接口文档自动生产方式
    python使用
  • 原文地址:https://www.cnblogs.com/wala-wo/p/5119216.html
Copyright © 2020-2023  润新知