• Java之NIO传输数据


    NIO可谓陈词旧调,不值一提. 但之前都是泛泛而谈, 现在深入应用才知道秘诀所在. 对于SocketChannel有read()与write(),但由于"非阻塞IO"本质, 这二个方法的返回值提示其字符数目. 说白点, 就是你得有个措施解决可能一次不能完成的操作. 否则, 你在服务端的数据会莫名其妙地乱码, 莫名其妙地不见...
    还有另一个关键之处就是Buffer的应用, 重用Buffer的时候务必注意, position, limit的标点. 下面是实质源码:
    private void onAccept(SelectionKey key) {

      logger.debug("处理Accept事件");
      SocketChannel sc = null;
      try {
       ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
       sc = ssc.accept();
       /* 判断其是否可以连接 */
       String client = ((InetSocketAddress) sc.socket().getRemoteSocketAddress()).getAddress().getHostAddress();
       if (config.senders.containsKey(client)) {
        /* 前缀格式<系统>+<服务器IP>+ */
        String prefix = config.senders.getProperty(client);
        sc.configureBlocking(false);
        sc.register(selector, SelectionKey.OP_READ, new AttachObject(encoder.encode(prefix.toString(), config.charset)));
        logger.info(String.format("发送者%s连接成功, 记录前缀:%s", client, prefix));
       } else {
        logger.info(String.format("发送者%s连接拒绝", client));
        sc.close();
       }
      } catch (Exception e) {
       logger.error("处理Accept事件错误!", e);
       if (sc != null) {
        try {
         sc.close();
        } catch (IOException e1) {
         logger.error("关闭异常Socket错误!", e1);
        }
       }
      }
    }
    使用InetAddress.getHostAddress()才能获取实际意义上的IP.
    private void onRead(SelectionKey key) {

      /* 必须注意NIO可能无法一次接收完全部数据 */
      logger.debug("处理Read事件");
      int ret = 0;
      int size = 0;
      SocketChannel sc = null;
      try {
       sc = (SocketChannel) key.channel();
       AttachObject attach = (AttachObject) key.attachment();
       if (attach.idx < 4) {
        ret = sc.read(attach.sizeBuf);
        if (ret == -1) {
         logger.debug("客户端输入流已关闭!");
         sc.close();
         sc = null;
         return;
        } else {
         attach.idx += ret;
        }
       }

       if (attach.idx == 4) {
        attach.sizeBuf.flip();
        size = attach.sizeBuf.getInt();
        attach.tot = 4 + size;
        if (attach.dataBuf.capacity() < size) {
         attach.dataBuf = ByteBuffer.allocate(size);
        }else {
         attach.dataBuf.limit(size);/* 必须限制可读字节数,否则可能读多 */
        }
       }

       if (attach.idx >= 4 && attach.idx < attach.tot) {
        ret = sc.read(attach.dataBuf);
        if (ret == -1) {
         logger.debug("客户端输入流已关闭!");
         sc.close();
         sc = null;
         return;
        } else {
         attach.idx += ret;
        }
       }

       if (attach.idx == attach.tot) {
        attach.dataBuf.flip();
        cache.put((byte[]) attach.attach, attach.dataBuf.array(), 0, attach.dataBuf.limit());
        attach.reset();
       }

      } catch (Exception e) {
       logger.error("处理Read事件错误!", e);
       if (sc != null) {
        try {
         sc.close();
        } catch (IOException e1) {
         logger.error("关闭异常Socket错误!", e1);
        }
       }
      }
    }
    每个Key要有独享的Attachment来保存中间信息, 使用Buffer读取或写入字节务必注意其返回值. 必须在字节数完全读完才能去解码.
    public void run() {
       ByteBuffer sizeBuf = ByteBuffer.allocate(4);
       int idx = 0;
       int tot = 0;
       LinkedList<byte[]> batch = new LinkedList<byte[]>();
       try {
        SocketChannel sc = SocketChannel.open();
        sc.connect(new InetSocketAddress(outer.config.receiverHost, outer.config.receiverPort));
        outer.scList.add(sc);
        while (!Thread.currentThread().isInterrupted()) {
         batch.clear();
         if (outer.cache.get(batch, outer.config.senderBatchSize, true) > 0) {
          for (byte[] data : batch) {
           /* 必须注意,NIO有可能不会一次写完Buffer的字节 */
           idx = 0;
           tot = 4 + data.length;

           sizeBuf.clear();
           sizeBuf.putInt(data.length);
           sizeBuf.flip();
           do {
            idx += sc.write(sizeBuf);
           } while (idx < 4);

           ByteBuffer dataBuf = ByteBuffer.wrap(data);
           do {
            idx += sc.write(dataBuf);
           } while (idx < tot);
          }
         }
        }
       } catch (IOException e) {
        throw new RuntimeException(e);
       }
      }
    使用Buffer写字节数据也必须注意其返回值, 在未达到预期时, 使用循环继续.
    以上三个方法是Socket NIO的关键所在. 当你接收到的数据乱码的时候,你会想起这些...

  • 相关阅读:
    上班5个月总结
    使用余弦定理计算两篇文章的相似性
    9月10日 小结
    软件测试
    《增长黑客》笔记
    统计学术语
    数据分析师:数据分析工作常见七种错误及其规避技巧(转自经管之家)
    输入一个日期,计算这个日期与 2018-03-12差多少天;
    求输入数字的阶乘 及加和 #s=1!+2!+3!+…..+n!
    列表去重
  • 原文地址:https://www.cnblogs.com/zolo/p/5849309.html
Copyright © 2020-2023  润新知