zoukankan      html  css  js  c++  java
  • 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的关键所在. 当你接收到的数据乱码的时候,你会想起这些...

  • 相关阅读:
    CF-478C
    HDU-2074-叠筐
    HDU-2037-今年暑假不AC
    POJ-2785-4 Values whose Sum is 0
    HDU-1160-FatMouse's Speed
    HDU-1297-Children’s Queue
    Redis客户端管理工具的安装及使用
    Redis客户端管理工具,状态监控工具
    memcached可视化客户端工具
    javascript回调函数
  • 原文地址:https://www.cnblogs.com/zolo/p/5849309.html
Copyright © 2011-2022 走看看