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的关键所在. 当你接收到的数据乱码的时候,你会想起这些...

Java之NIO传输数据的更多相关文章

  1. JAVA bio nio aio

    [转自]http://qindongliang.iteye.com/blog/2018539 在高性能的IO体系设计中,有几个名词概念常常会使我们感到迷惑不解.具体如下: 序号 问题 1 什么是同步? ...

  2. java的nio之:java的nio系列教程之buffer的概念

    一:java的nio的buffer==>Java NIO中的Buffer用于和NIO通道Channel进行交互.==>数据是从通道channel读入缓冲区buffer,从缓冲区buffer ...

  3. java的nio之:java的nio系列教程之channel的概念

    一:java的nio的channel Java NIO的通道类似流,但又有些不同: ==>既可以从通道中读取数据,又可以写数据到通道.但流的读写通常是单向的. ==>通道可以异步地读写. ...

  4. java的nio之:java的nio系列教程之概述

    一:java的nio的核心组件?Java NIO 由以下几个核心部分组成: ==>Channels ==>Buffers ==>Selectors 虽然Java NIO 中除此之外还 ...

  5. java之NIO编程

    所谓行文如编程,随笔好比java文件,文章好比类,参考文献是import,那么目录就是方法定义. 本篇文章处在分析thrift的nonblocking server之前,因为后者要依赖该篇文章的知识. ...

  6. 输入和输出--java的NIO

    Java的NIO 实际开发中NIO使用到的并不多,我并不是说NIO使用情景不多,是说我自己接触的并不是很多,前面我在博客园和CSDN上转载了2篇别人写的文章,这里来大致总结下Java的NIO,大概了解 ...

  7. JAVA 探究NIO

    事情的开始 1.4版本开始,java提供了另一套IO系统,称为NIO,(New I/O的意思),NIO支持面向缓冲区的.基于通道的IO操作. 1.7版本的时候,java对NIO系统进行了极大的扩展,增 ...

  8. 理解Java的NIO

    同步与阻塞 同步和异步是针对应用程序和内核的交互而言的. 同步:执行一个操作之后,进程触发IO操作并等待(阻塞)或者轮询的去查看IO的操作(非阻塞)是否完成,等待结果,然后才继续执行后续的操作. 异步 ...

  9. Java通过NIO实现快速文件拷贝的代码

    将内容过程重要的内容片段做个记录,下面的内容段是关于Java通过NIO实现快速文件拷贝的内容. public static void fileCopy( File in, File out ) thr ...

随机推荐

  1. 免费的API接口

    有如下三个Json格式的查询天气预报接口: http://www.weather.com.cn/data/sk/101010100.html http://www.weather.com.cn/dat ...

  2. 应用OpenCV进行OCR字符识别

    opencv自带一个字符识别的例子,它的重点不是OCR字符识别,而主要是演示机器学习的应用.它应用的是UCI提供的字符数据(特征数据). DAMILES在网上发布了一个应用OpenCV进行OCR的例子 ...

  3. Windows Server 2012下安装Hyper-V虚拟机

    Windows Server 2012下安装Hyper-V虚拟机 Win server 2012系统中Hyper-V 性能进一步提高,广大爱好者都尝试体验它,可是有不少朋友无法正确安装虚拟机,尽管在网 ...

  4. setjmp 与 longjmp

    setjmp和longjmp是C语言独有的,只有将它们结合起来使用,才能达到程序控制流有效转移的目的,按照程序员的预先设计的意图,去实现对程序中可能出现的异常进行集中处理. 先来看一下这两个函数的定义 ...

  5. Windows server 2008 R2远程桌面终端连接数的破解

    Windows server 2008 R2远程桌面终端连接数的破解 日常工作中,经常需要远程连接到服务器上,然而在公司里,老总.同事都需要连接到服务器上,而默认的服务器系统同时连接的最大连接数只有2 ...

  6. Android IOS WebRTC 音视频开发总结(十五)-- 培训课程大纲

    最近在给公司做内部培训,主要是关于即时通讯和移动视频通话,包括android与android,ios与ios,android与ios,以及手机与PC. ------------------------ ...

  7. wifi热点共享 3G网络

    本文描述一些iptables的基础知识和使用方法.最后记录一个wifi共享3G上网,以及禁止wifi内某个IP通过3G联网的例子. 一. 在Linux系统中,防火墙,网址转换(NAT),数据包记录以及 ...

  8. POJ C++程序设计 编程题#1 编程作业—继承与派生

    编程题#1 来源: POJ (Coursera声明:在POJ上完成的习题将不会计入Coursera的最后成绩.) 注意: 总时间限制: 1000ms 内存限制: 65536kB 描述 写一个MyStr ...

  9. Objective-C中的self和super

    1.有过面向对象的人知道,self相当于this,super相当于调用父类的方法 2.self是类的隐藏的参数,指向当前调用方法的类,另一个隐藏参数是_cmd,代表当前类方法的selector. su ...

  10. 设置DataGridView 显示自己添加编辑的列名,不动态显示数据库本身

    设置DataGridView 显示自己添加编辑的列名,不动态显示数据库本身. 方法: (1)界面操作,把DataGridView控件拖放在窗体中,就看到DataGridView控件的右上角有个小三角, ...