﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-cooperzh-随笔分类-NIO</title><link>http://www.blogjava.net/cooperzh/category/50400.html</link><description /><language>zh-cn</language><lastBuildDate>Thu, 05 Jan 2012 22:15:20 GMT</lastBuildDate><pubDate>Thu, 05 Jan 2012 22:15:20 GMT</pubDate><ttl>60</ttl><item><title>《构建高性能的大型分布式Java应用》笔记 第一章 IO</title><link>http://www.blogjava.net/cooperzh/archive/2011/12/27/367323.html</link><dc:creator>cooperzh</dc:creator><author>cooperzh</author><pubDate>Tue, 27 Dec 2011 03:50:00 GMT</pubDate><guid>http://www.blogjava.net/cooperzh/archive/2011/12/27/367323.html</guid><description><![CDATA[IO三种方式：BIO，NIO，AIO<span style="font-size: 10pt; ">&nbsp;(异步读写asynchronous IO)</span><br /><br /><span style="font-size: 10pt; ">jdk1.6及之前都只实现BIO 和 NIO</span><br /><span style="font-size: 10pt; ">jdk1.7开始支持AIO，即NIO 2.0</span><br /><br /><br /><span style="font-size: 10pt; ">在BIO阻塞模式下server端:</span><br /><span style="font-size: 10pt; ">1 new ServerSocket(int port) 监听端口</span><br /><span style="font-size: 10pt; ">2 serverSocket.accept() 阻塞式等待客户端的连接，有连接才返回Socket对象</span><br /><span style="font-size: 10pt; ">3 socket.getINputStream() 获取客户端发过来的信息流</span><br /><span style="font-size: 10pt; ">4 socket.getOutputStream() 获取输出流对象，从而写入数据返回客户端</span><br /><br /><span style="font-size: 10pt; ">client端：</span><br /><span style="font-size: 10pt; ">1 newSocket（String host,int port) 建立与服务器端的连接，如果服务器没启动，报Connection refused异常</span><br /><span style="font-size: 10pt; ">2 socket.getInputStream() 读取服务器端返回的流</span><br /><span style="font-size: 10pt; ">3 socket.getOutputStream() 获取输出流，写入数据发送到服务器端</span><br /><br /><br /><span style="font-size: 10pt; ">在NIO模式下Server端：</span><br /><span style="font-size: 10pt; ">1 ServerSocketChannel.open() 获取serverScoketChannel实例</span><br /><span style="font-size: 10pt; ">2 serverScoketChannel.configueBlocking(false) 设置channel为非阻塞模式</span><br /><span style="font-size: 10pt; ">3 serverSocketChannel.socket() 获取serverSocket对象</span><br /><span style="font-size: 10pt; ">4 serverSocket.bind(port) 监听端口</span><br /><span style="font-size: 10pt; ">5 Selector.open() 打开Selector，获取selector实例</span><br /><span style="font-size: 10pt; ">6 serverSocketChannel.register(Selector,int) 向selector注册channel和感兴趣的事件</span><br /><span style="font-size: 10pt; ">7 while(true) 循环以保证正常情况下服务器端一直处于运行状态</span><br /><span style="font-size: 10pt; ">8 selector.select() 获取selector实例中需要处理的SelectionKey的数量</span><br /><span style="font-size: 10pt; ">9 for(SelectionKey key:selector.selectedKeys()) 遍历selector.selectedKeys,以对每个SelectionKey的事件进行处理</span><br /><span style="font-size: 10pt; ">10 key.isAcceptable() 判断SelectionKey的类型是否为客户端建立连接的类型</span><br /><span style="font-size: 10pt; ">11 key.channel() 当SelectionKey的类型是acceptabel时，获取绑定的ServerSocketChannel对象</span><br /><span style="font-size: 10pt; ">12 serverSocketChannel.accept() 接受客户端建立连接的请求，并返回SocketChannel对象</span><br /><span style="font-size: 10pt; ">13 socketChannel.regiseter(Selector,int) 向Selector注册感兴趣的事件类型，如read,write</span><br /><span style="font-size: 10pt; ">14 key.isReadable() 判断SelectionKey是否为readable，如是则意味着有消息流在等待处理</span><br /><span style="font-size: 10pt; ">15 socketChannel.read(ByteBuffer) 从SelectionKey中绑定的SocketChannel对象读取消息流</span><br /><span style="font-size: 10pt; ">16 socketChannel.write(ByteBuffer) 从SelectionKey中绑定的SocketChannel对象输出消息流</span><br /><br /><div><span style="font-family: Tahoma; font-size: 10pt; ">client端：</span></div><div><span style="font-size: 10pt; ">1 SocketChannel.open() 打开SocketChannel</span></div><div><span style="font-size: 10pt; ">2 SocketChannel.configureBlocking(false) 将SocketChannel配置为非阻塞模式</span></div><div><span style="font-size: 10pt; ">3 SocketChannel.connect(host,port) 连接到指定的目标地址</span></div><div><span style="font-size: 10pt; ">4 Selector.open() 打开Selector</span></div><div><span style="font-size: 10pt; ">5 SocketChannel.register(Selector,int) 向Selector注册感兴趣的事件,connected,read,write</span></div><div><span style="font-size: 10pt; ">6 while(true) 循环执行保证客户端一直处于运行状态</span></div><div><span style="font-size: 10pt; ">7 Selector.select() 从Selector中获取是否有可读的key信息</span></div><div><span style="font-size: 10pt; ">8 for(SelectionKey key:selector.selectedKeys()) 遍历selector中所有selectedKeys</span></div><div><span style="font-size: 10pt; ">9 SelectionKey.isConnectable() 判断是否为连接建立的类型</span></div><div><span style="font-size: 10pt; ">10 SelectionKey.channel() 获取绑定的SocketChannel</span></div><div><span style="font-size: 10pt; ">11 SocketChannel.finishConnect() 完成连接的建立（TCP/IP的三次握手）</span></div><div><span style="font-size: 10pt; ">12 SelectionKey.isReadable() 判断是否为可读类型</span></div><div><span style="font-size: 10pt; ">13 SelectionKey.channel() 获取绑定的SocketChannel</span></div><div><span style="font-size: 10pt; ">14 SocketChannel.read(ByteBuffer) 从SocketChannel中读取数到ByteBuffer中</span></div><div><span style="font-size: 10pt; ">15 SocketChannel.write(ByteBuffer) 向SocketChannel中写入ByteBuffer对象数据</span></div><div style="font-family: Tahoma; font-size: 11px; "></div><img src ="http://www.blogjava.net/cooperzh/aggbug/367323.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cooperzh/" target="_blank">cooperzh</a> 2011-12-27 11:50 <a href="http://www.blogjava.net/cooperzh/archive/2011/12/27/367323.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NIO trick and trap NIO的技巧与陷阱</title><link>http://www.blogjava.net/cooperzh/archive/2011/12/20/366884.html</link><dc:creator>cooperzh</dc:creator><author>cooperzh</author><pubDate>Tue, 20 Dec 2011 12:41:00 GMT</pubDate><guid>http://www.blogjava.net/cooperzh/archive/2011/12/20/366884.html</guid><description><![CDATA[<div><span style="font-size: 11px; ">出处：<a title="http://www.blogjava.net/killme2008/archive/2011/06/30/353422.html" href="http://www.blogjava.net/killme2008/archive/2011/06/30/353422.html">http://www.blogjava.net/killme2008/archive/2011/06/30/353422.html</a><br /><br />IO划分为两个阶段：</span></div><div><span style="font-size: 11px;">1 等待数据就绪</span></div><div><span style="font-size: 11px;">2 从内核缓冲区copy到进程缓冲区（从socket通过socketChannel复制到ByteBuffer）</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">non-direct ByteBuffer: HeapByteBuffer，创建开销小</span></div><div><span style="font-size: 11px;">direct ByteBuffer：通过操作系统native代码，创建开销大</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">基于block的传输通常比基于流的传输更高效</span></div><div><span style="font-size: 11px; "><br /></span></div><div><span style="font-size: 11px;">使用NIO做网络编程容易，但离散的事件驱动模型编程困难，而且陷阱重重</span></div><div><span style="font-size: 11px;"><br /></span></div><div style="background-color: #cccccc; "><span style="font-size: 11px; "><strong>Reactor模式：经典的NIO网络框架</strong></span></div><div><span style="font-size: 11px;">核心组件：</span></div><div><span style="font-size: 11px;">1 Synchronous Event Demultiplexer : Event loop + 事件分离</span></div><div><span style="font-size: 11px;">2 Dispatcher：事件派发，可以多线程</span></div><div><span style="font-size: 11px;">3 Request Handler：事件处理，业务代码</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">理想的NIO框架：</span></div><div><span style="font-size: 11px;">1 优雅地隔离IO代码和业务代码</span></div><div><span style="font-size: 11px;">2 易于扩展</span></div><div><span style="font-size: 11px;">3 易于配置，包括框架自身参数和协议参数</span></div><div><span style="font-size: 11px;">4 提供良好的codec框架，方便marshall/unmarshall</span></div><div><span style="font-size: 11px;">5 透明性，内置良好的日志记录和数据统计</span></div><div><span style="font-size: 11px;">6 高性能</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">NIO框架性能的关键因素</span></div><div><span style="font-size: 11px;">1 数据的copy</span></div><div><span style="font-size: 11px;">2 上下文切换(context switch)</span></div><div><span style="font-size: 11px;">3 内存管理</span></div><div><span style="font-size: 11px;">4 TCP选项，高级IO函数</span></div><div><span style="font-size: 11px;">5 框架设计</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">减少数据copy：</span></div><div><span style="font-size: 11px;">ByteBuffer的选择</span></div><div><span style="font-size: 11px;">View ByteBuffer</span></div><div><span style="font-size: 11px;">FileChannel.transferTo/transferFrom</span></div><div><span style="font-size: 11px;">FileChannel.map/MappedByteBuffer</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">ByteBuffer的选择：</span></div><div><span style="font-size: 11px;">不知道用哪种buffer时，用Non-Direct</span></div><div><span style="font-size: 11px;">没有参与IO操作，用Non-Direct</span></div><div><span style="font-size: 11px;">中小规模应用(&lt;1K并发连接)，用Non-Direct</span></div><div><span style="font-size: 11px;">长生命周期，较大的缓冲区，用Direct</span></div><div><span style="font-size: 11px;">测试证明Direct比Non-Direct更快，用Direct</span></div><div><span style="font-size: 11px;">进程间数据共享(JNI)，用Direct</span></div><div><span style="font-size: 11px;">一个Buffer发给多个Client，考虑使用view ByteBuffer共享数据，buffer.slice()</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">HeapByteBuffer缓存</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">使用ByteBuffer.slice()创建view ByteBuffer：</span></div><div><span style="font-size: 11px;">ByteBuffer buffer2 = buffer1.slice()；</span></div><div><span style="font-size: 11px;">则buffer2的内容和buffer1的从position到limit的数据内容完全共享</span></div><div><span style="font-size: 11px;">但是buffer2的position，limit是独立于buffer1的</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">传输文件的传统方式：</span></div><div><span style="font-size: 11px;">byte[] buf = new byte[8192];</span></div><div><span style="font-size: 11px;">while(in.read(buf)&gt;0){</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp; out.write(buf);</span></div><div><span style="font-size: 11px;">}</span></div><div><span style="font-size: 11px;">使用NIO后：</span></div><div><span style="font-size: 11px;">FileChannel in = ...</span></div><div><span style="font-size: 11px;">WriteableByteChannel out = ...</span></div><div><span style="font-size: 11px;">in.transferTo(0,fsize,out);</span></div><div><span style="font-size: 11px;">性能会有60%的提升</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">FileChannel.map</span></div><div><span style="font-size: 11px;">将文件映射为内存区域&#8212;&#8212;MappedByteBuffer</span></div><div><span style="font-size: 11px;">提供快速的文件随机读写能力</span></div><div><span style="font-size: 11px;">平台相关</span></div><div><span style="font-size: 11px;">适合大文件，只读型操作，如大文件的MD5校验等</span></div><div><span style="font-size: 11px;">没有unmap方法，什么时候被回收取决于GC</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">减少上下文切换</span></div><div><span style="font-size: 11px;">时间缓存</span></div><div><span style="font-size: 11px;">Selector.wakeup</span></div><div><span style="font-size: 11px;">提高IO读写效率</span></div><div><span style="font-size: 11px;">线程模型</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">时间缓存：</span></div><div><span style="font-size: 11px;">1网络服务器通常需要频繁获取系统时间：定时器，协议时间戳，缓存过期等</span></div><div><span style="font-size: 11px;">2 System.currentTimeMillis</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;a linux调用gettimeofday需要切换到内核态</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;b 普通机器上，1000万次调用需要12秒，平均一次1.3毫秒</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;c 大部分应用不需要特别高的精度</span></div><div><span style="font-size: 11px;">3 SystemTimer.currentTimeMillis（自己创建）</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;a 独立线程定期更新时间缓存</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;b currentTimeMillis直接返回缓存值</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;c 精度取决于定期间隔</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;d 1000万次调用降低到59毫秒</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">Selector.wakeup() 主要作用：</span></div><div><span style="font-size: 11px;">解除阻塞在Selector.select()上的线程，立即返回</span></div><div><span style="font-size: 11px;">两次成功的select()之间多次调用wakeup等价于一次调用</span></div><div><span style="font-size: 11px;">如果当前没有阻塞在select()上，则本次wakeup将作用在下次select()上</span></div><div><span style="font-size: 11px;">什么时候wakeup() ？</span></div><div><span style="font-size: 11px;">注册了新的Channel或者事件</span></div><div><span style="font-size: 11px;">Channel关闭，取消注册</span></div><div><span style="font-size: 11px;">优先级更高的事件触发（如定时器事件），希望及时处理</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">wakeup的原理：</span></div><div><span style="font-size: 11px;">1 linux上利用pipe调用创建一个管道</span></div><div><span style="font-size: 11px;">2 windows上是一个loopback的tcp连接，因为win32的管道无法加入select的fd set</span></div><div><span style="font-size: 11px;">3 将管道或者tcp连接加入selected fd set</span></div><div><span style="font-size: 11px;">4 wakeup向管道或者连接写入一个字节</span></div><div><span style="font-size: 11px;">5 阻塞的select()因为有IO时间就绪，立即返回</span></div><div><span style="font-size: 11px;">可见wakeup的调用开销不可忽视</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">减少wakeup调用：</span></div><div><span style="font-size: 11px;">1 仅在有需要时才调用。如往连接发送数据，通常是缓存在一个消息队列，当且仅当队列为空时注册write并wakeup</span></div><div><span style="font-size: 11px;">booleanneedsWakeup=false;</span></div><div><span style="font-size: 11px;">synchronized(queue){</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp; if(queue.isEmpty()) &nbsp;needsWakeup=true;</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp; queue.add(session);</span></div><div><span style="font-size: 11px;">}</span></div><div><span style="font-size: 11px;">if(needsWakeup){</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp; registerOPWrite();</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp; selector.wakeup();</span></div><div><span style="font-size: 11px;">}</span></div><div><span style="font-size: 11px;">2 记录调用状态，避免重复调用，例如Netty的优化</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">读到或者写入0个字节：</span></div><div><span style="font-size: 11px;">不代表连接关闭</span></div><div><span style="font-size: 11px;">高负载或者慢速网络下很常见的情况</span></div><div><span style="font-size: 11px;">通常的处理方法是返回并继续注册read/write，等待下次处理，缺点是系统调用开销和线程切换开销</span></div><div><span style="font-size: 11px;">其他解决办法：循环一定次数写入（如Mina）或者yield一定次数</span></div><div><span style="font-size: 11px;">启用临时选择器Temporary Selector在当前线程注册并poll，例如Girzzy中</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">在当前线程写入：</span></div><div><span style="font-size: 11px;">当发送缓冲队列为空的时候，可以直接往channel写数据，而不是放入缓冲队列，interest了write等待IO线程写入，可以提高发送效率</span></div><div><span style="font-size: 11px;">优点是可以减少系统调用和线程切换</span></div><div><span style="font-size: 11px;">缺点是当前线程中断会引起channel关闭</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">线程模型</span></div><div><span style="font-size: 11px;">selector的三个主要事件：read，write，accept，都可以运行在不同的线程上</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">通常Reactor实现为一个线程，内部维护一个selector</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">1 Boss Thread + worker Thread</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;boss thread处理accept，connect</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;worker thread处理read，write</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">Reactor线程数目：</span></div><div><span style="font-size: 11px;">1 Netty 1 + 2 * cpu</span></div><div><span style="font-size: 11px;">2 Mina 1 + cpu + 1</span></div><div><span style="font-size: 11px;">3 Grizzly 1 + 1</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">常见线程模型：</span></div><div><span style="font-size: 11px;">1 read和accept都运行在reactor线程上</span></div><div><span style="font-size: 11px;">2 accept运行在reactor线程上，read运行在单独线程</span></div><div><span style="font-size: 11px;">3 read和accept都运行在单独线程</span></div><div><span style="font-size: 11px;">4 read运行在reactor线程上，accept运行在单独线程</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">选择适当的线程模型：</span></div><div><span style="font-size: 11px;">类echo应用，unmashall和业务处理的开销非常低，选择模型1</span></div><div><span style="font-size: 11px;">模型2，模型3，模型4的accept处理开销很低</span></div><div><span style="font-size: 11px;">最佳选择：模型2。unmashall一般是cpu-bound，而业务逻辑代码一般比较耗时，不要在reactor线程处理</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">内存管理</span></div><div><span style="font-size: 11px;">1 java能做的事情非常有限</span></div><div><span style="font-size: 11px;">2 缓冲区的管理</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;a 池化。ThreadLocal cache，环形缓冲区</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;b 扩展。putString,getString等高级API，缓冲区自动扩展和伸缩，处理不定长度字节</span></div><div><span style="font-size: 11px;">&nbsp; &nbsp;c 字节顺序。跨语言通讯需要注意，默认字节顺序Big-Endian，java的IO库和class文件</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">数据结构的选择</span></div><div><span style="font-size: 11px;">1 使用简单的数据结构：链表，队列，数组，散列表</span></div><div><span style="font-size: 11px;">2 使用j.u.c框架引入的并发集合类，lock-free，spin lock</span></div><div><span style="font-size: 11px;">3 任何数据结构都要注意容量限制，OutOfMemoryError</span></div><div><span style="font-size: 11px;">4 适当选择数据结构的初始容量，降低GC带来的影响</span></div><div><span style="font-size: 11px;"><br /></span></div><div><span style="font-size: 11px;">定时器的实现<br />1 定时器在网络程序中频繁使用<br />&nbsp; &nbsp; a 周期事件的触发<br />&nbsp; &nbsp; b 异步超时的通知和移除<br />&nbsp; &nbsp; c 延迟事件的触发<br />2 三个时间复杂度<br /></span>&nbsp; &nbsp; a 插入定时器<br />&nbsp; &nbsp; b 删除定时器<br />&nbsp; &nbsp; c PerTickBookkeeping，一次tick内系统需要执行的操作<br /><span style="font-size: 11px; ">3 Tick的方式<br /></span>&nbsp; &nbsp; Selector.select(timeout)<br />&nbsp; &nbsp; Thread.sleep(timeout)<br /><br /><span style="font-size: 11px; ">定时器的实现：链表<br />将定时器组织成链表结构<br />插入定时器，加入链表尾部<br />删除定时器<br /></span><div>PerTickBookkeeping，遍历链表查找expire事件</div><span style="font-size: 11px; "><br /></span><div>定时器的实现：排序链表</div><span style="font-size: 11px; ">将定时器组织成有序链表结构，按照expire截止时间升序排序<br />插入定时器，找到合适的位置插入<br />删除定时器<br /></span><div>PerTickBookkeeping，直接从表头找起</div><span style="font-size: 11px; "><br /></span><div>定时器的实现：优先队列</div><span style="font-size: 11px; ">将定时器组织成优先队列，按照expire截止时间作为优先级，优先队列一般采用最小堆实现<br />插入定时器<br />删除定时器<br /></span><div>PerTickBookkeeping，直接取root判断</div><span style="font-size: 11px; "><br /></span><div>定时器的实现：Hash wheel timer</div><span style="font-size: 11px; ">将定时器组织成时间轮<br />指针按照一定周期旋转，一个tick跳动一个槽位<br />定时器根据延时时间和当前指针位置插入到特定槽位<br /></span><div>插入定时器<br />删除定时器</div><div>PerTickBookkeeping</div><div>槽位和tick决定了精度和延时<br /><br /><div>定时器的实现：Hierarchical Timing</div></div><span style="font-size: 11px; ">Hours Wheel，Minutes Wheel，Seconds Wheel<br /><br />连接IDLE的判断<br />1 连接处于IDLE状态：一段时间没有IO读写事件发生<br />2 实现方式：<br />&nbsp; &nbsp; a 每次IO读写都记录IO读和写的时间戳<br />&nbsp; &nbsp; b 定时扫描所有连接，判断当前时间和上一次读或写的时间差是否超过设定阀值，超过即认为连接处于IDLE状态，通知业务处理器<br /></span>&nbsp; &nbsp;c 定时的方式：基于select(timeout)或者定时器。Mina：select(timeout);Netty:HashWheelTimer<br /><span style="font-size: 11px; "><br />合理设置TCP/IP选项，有时会起到显著效果，需要根据应用类型、协议设计、网络环境、OS平台等因素做考量，以测试结果为准<br /><br />Socket缓冲区设置选项：SO_RCVBUF 和 SO_SNDBUF<br />Socket.setReceiveBufferSize/setSendBufferSize 仅仅是对底层平台的提示，是否有效取决于底层平台。因此get返回的不是真实的结果。<br />设置原则：<br />1 以太网上，4k通常是不够的，增加到16k，吞吐量增加了40%<br />2 Socket缓冲区大小至少应该是连接的MSS的三倍，MSS=MTU+40，一般</span>以太网卡的MTU=1500字节。<br />&nbsp;&nbsp;&nbsp;&nbsp;MSS：最大分段大小<br />&nbsp; &nbsp; MTU：最大传输单元<br /><span style="font-size: 11px; ">3 send buffer最好与对端的receive buffer尺寸一致<br />4 对于一次性发送大量数据的应用，增加缓冲区到48k、64k可能是唯一最有效的提高性能的方式。<br />&nbsp; &nbsp; 为了最大化性能，</span>send buffer至少要跟BDP(带宽延迟乘积)一样大。<span style="font-size: 11px; "><br /></span><span style="font-size: 11px; ">5 同样，对于大量接收数据的应用，提高接收缓冲区，能减少发送端的阻塞<br />6 如果应用既发送大量数据，又接收大量数据，则</span>send buffer和<span class="Apple-style-span" style="font-size: 11px; ">receive buffer应该同时增加<br /></span>7 如果设置的ServerSocket的<span class="Apple-style-span" style="font-size: 11px; ">receive buffer超过RFC1323定义的64k，那么必须在绑定端口前设置，以后accept产生的socket将继承这一设置<br /></span>8 无论缓冲区大小多少，你都应该尽可能地帮助TCP至少以那样大小的块写入<br /><span style="font-size: 11px; "><br /></span><div>BDP(带宽延迟乘积)</div><span style="font-size: 11px; ">为了优化TCP吞吐量，发送端应该发送足够的数据包以填满发送端和接收端之间的逻辑通道<br />BDP = 带宽 * RTT<br /><br />Nagle算法：SO_TCPNODELAY<br />通过将缓冲区内的小包自动相连组成大包，阻止发送大量小包阻塞网络，提高网络应用效率对于实时性要求较高的应用(telnet、网游)，需要关闭此算法<br />Socket.setTcpNoDelay(true) 关闭算法<br /><div style="display: inline-block; "><div>Socket.setTcpNoDelay(false)&nbsp;</div></div>打开算法，默认<br /><br />SO_LINGER选项，控制socket关闭后的行为<br />Socket.setSoLinger(boolean linger,int timeout)<br />1&nbsp;</span>linger=false，timeout=-1<br />当socket主动close，调用的线程会马上返回，不会阻塞，然后进入CLOSING状态，残留在缓冲区中的数据将继续发送给对端，并且与对端进行FIN-ACK协议交换，最后进入TIME_WAIT状态<br /><div><span style="font-size: 11px; ">2&nbsp;</span>linger=true，timeout&gt;0</div><span style="font-size: 11px; ">调用close的线程将阻塞，发生两种可能的情况：一是剩余的数据继续发送，进行关闭协议交换，二是超时过期，剩余数据将被删除，</span>进行FIN-ACK协议交换<span style="font-size: 11px; "><br /></span><div><span style="font-size: 11px; ">3&nbsp;</span>linger=true，timeout=0</div><span style="font-size: 11px; ">进行所谓&#8220;hard-close&#8221;，任何剩余的数据将被丢弃，并且FIN-ACK交换也不会发生，替代产生RST，让对端抛出&#8220;connection reset&#8221;的SocketException<br />4 慎重使用此选项，TIME_WAIT状态的价值：<br />&nbsp;&nbsp;&nbsp;&nbsp;可靠实现TCP连接终止<br />&nbsp;&nbsp;&nbsp;&nbsp;允许</span>老的分节在网络中流失，防止发给新的连接<br />&nbsp; &nbsp;持续时间=2*MSL（MSL为最大分节生命周期，一般为30秒到2分钟）<span style="font-size: 11px; "><br /></span><span style="font-size: 11px; "><br />SO_REUSEADDR：重用端口<br />Socket.setReuseAddress(boolean) 默认false<br />适用场景：<br />1 当一个使用本地地址和端口的socket1处于TIME_WAIT状态时，你启动的socket2要占用该地址和端口，就要用到此选项<br />2&nbsp;</span>SO_REUSEADDR允许同一端口上启动一个服务的多个实例（多个进程），但每个实例绑定的地址是不能相同的<br /><span style="font-size: 11px; ">3</span>&nbsp;SO_REUSEADDR允许完全相同的地址和端口的重复绑定。但这只用于UDP的多播，不适用TCP<br /><br /><span style="font-size: 11px; ">SO_REUSEPORT<br />listen做四元组，多进程同一地址同一端口做accept，适合大量短连接的web server<br />Freebsd独有<br /><br />其他选项：<br />Socket.setPerformancePreferences(connectionTime, latency, bandwidth) 设置连接时间、延迟、带宽的相对重要性<br />Socket.setKeepAlive(boolean) 这是TCP层的keep-alive概念，非HTTP协议的。用于TCP连接保活，默认间隔2小时，建议在应用层做心跳<br />Socket.sendUrgentData(data) 带外数据<br /></span><span style="font-size: 11px; "><br /><br />技巧：<br />1 读写公平<br />&nbsp; &nbsp; Mina限制一次写入的字节数不超过最大的读缓冲区的1.5倍<br />2 针对FileChannel.transferTo的bug<br /></span>&nbsp; &nbsp; Mina判断异常，如果是temporarily unavailable的IOException，则认为传输字节数为0<br /><span style="font-size: 11px; ">3 发送消息，通常是放入一个缓冲区队列注册write，等待IO线程去写<br />&nbsp; &nbsp; 线程切换，系统调用<br />&nbsp; &nbsp; 如果队列为空，直接在当前线程channel.write，隐患是当前线程的中断会引起连接关闭<br />4 事件处理优先级<br />&nbsp; &nbsp; ACE框架推荐：accept &gt; write &gt; read (推荐)<br />&nbsp; &nbsp; &nbsp;Mina 和 Netty：read &gt; write<br />5 处理事件注册的顺序<br /></span>&nbsp; &nbsp; 在select()之前<br />&nbsp; &nbsp; 在select()之后，处理wakeup竞争条件<br /><br />Java Socket实现在不同平台上的差异<br />由于各种OS平台的socket实现不尽相同，都会影响到socket的实现<br />需要考虑性能和健壮性<br />&nbsp; &nbsp;&nbsp;<span style="font-size: 11px; "><br />&nbsp;<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></span></div><div><span class="Apple-style-span" style="font-size: 11px; "><br /></span></div><div><span style="font-size: 11px;"><br /></span></div><div style="font-family: Tahoma; font-size: 11px; "></div><img src ="http://www.blogjava.net/cooperzh/aggbug/366884.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cooperzh/" target="_blank">cooperzh</a> 2011-12-20 20:41 <a href="http://www.blogjava.net/cooperzh/archive/2011/12/20/366884.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NIO Selector</title><link>http://www.blogjava.net/cooperzh/archive/2011/12/19/366789.html</link><dc:creator>cooperzh</dc:creator><author>cooperzh</author><pubDate>Mon, 19 Dec 2011 10:53:00 GMT</pubDate><guid>http://www.blogjava.net/cooperzh/archive/2011/12/19/366789.html</guid><description><![CDATA[单一的线程使用就绪选择来同时监控大量的通道<br /><br />处于就绪状态的通道就会等待Selector选择<br /><br />选择器提供了询问通道是否已经准备好执行每个IO操作的能力<br /><br />就绪选择的真正价值在于潜在的大量通道可以同时进行就绪状态的检查，Selector可以轻松决定选择哪个通道<br /><br />真正的就绪必须由操作系统来检查，操作系统处理IO请求并通知各个线程它们的数据已经已经准备好了，而选择器封装了这种抽象<br /><br />原因：java代码来进行就绪选择，甚至jvm来进行就绪检查代价都十分昂贵，并且不够原子性，甚至会破坏线程的正常进行<br /><br />实际上只有三个类用于执行就绪选择：<br />Selector：管理着一个被注册的通道集合信息和他们的就绪状态<br />SelectableChannel：被注册到选择器中，并且可以声明对哪种操作感兴趣<br />一个通道可以注册到多个选择器上<br />一个通道在一个选择器上只能注册一个操作集，该操作集不会叠加<br />SelectionKey：封装了特定通道与特定选择器的注册关系，被SelectableChannel.register()返回<br /><br /><div>SelectableChannel在注册到选择器上之前，先设置为非阻塞模式。<br />如果注册一个阻塞模式的SelectableChannel将会抛出IllegalBlockingModeException异常<br />另外，已经注册的SelectableChannel不能再修改阻塞模式，否则也会抛出IllegalBlockingModeException异常</div>注册一个已经关闭的SelectableChannel，将会抛出ClosedChannelException异常<br />将SelectableChannel注册到Selector上之后，Selector控制了通道的选择过程<br /><br />通道有四种操作：<br />SelectionKey.OP_ACCEPT,<br />SelectionKey.OP_CONNECT,<br />SelectionKey.OP_READ,<br />SelectionKey.OP_WRITE<br /><br />SocketChannel不支持accept操作<br /><br /><div>SelectionKey.cancel()方法可以终结通道和选择器的关系，然后SelectionKey会放在Selector.cancelledKeys() 的集合里<br />取消后SelectionKey会立即失效，但注册不会立即取消，select()调用结束后，cancelledKeys将会处理，相应的注销完成。<br /><br />关闭通道时，所有的相关的SelectionKey都会自动取消<br /><br /><div>Selector关闭时，所有注册的通道将会注销，所有相关的SelectionKey会立即失效<br /><br />一个SelectionKey实例中有两个byte掩码：<br />instrest集合：指示通道/选择器结合体关心的操作<br />ready集合：通道已经就绪的操作<br /><br /><div>instrest集合通过SelectionKey.interestOps()获取，通过SelectionKey.interestOps(int ops)来改变<br />当相关的Selector正在select()操作时改变SelectionKey的instrest集合，不影响正在进行的select()操作，只会在下次select()时体现<br /><br /><div>ready集合通过SelectionKey.readyOps()获取，是instrest集合的子集，是上次select()操作后就绪的操作<br /><br />最简单的状态测试方法是：<br /><div>SelectionKey.isAcceptable()&nbsp;&nbsp; 等价于&nbsp;((key.readyOps( ) &amp; SelectionKey.OP_ACCEPT) != 0<br /><div>SelectionKey.isConnectable()&nbsp;&nbsp; 等价于&nbsp;((key.readyOps( ) &amp; SelectionKey.OP_CONNECT) != 0<br /><div>SelectionKey.isReadable()&nbsp;&nbsp; 等价于&nbsp;((key.readyOps( ) &amp; SelectionKey.OP_READ) != 0<br /><div>SelectionKey.isWritable() &nbsp; 等价于&nbsp;((key.readyOps( ) &amp; SelectionKey.OP_WRITE) != 0<br /><br /><div>SelectionKey.readyOps()返回的ready集合指示个提示，不是绝对的保证，因为底层的通道在任何时候都可能会被改变<br /><br /><div>SelectionKey是线程安全的，但修改instrest集合的操作是通过Selector进行同步的<br />这导致SelectionKey.interestOps()的调用会阻塞不确定长的时间<br /><div>Selector使用的锁策略是依赖具体实现的<br /><br />在单线程管理多个通道时不会出现问题，而多个线程共享Selector时会遇到同步的问题，到那时就应该重新设计<br /><br />每个Selector都要同时维护三个SelectionKey集合：<br />已注册的键的集合Registered key set，不能直接修改，只能通过SelectionKey.interestOps()重置<br />已选择的键的集合Selected key set，其中每个SelectionKey都有一个内嵌的ready集合，可以直接移除其中的SelectionKey，但不能添加<br />已取消的键的集合Cancelled key set，是Selector的私有成员，无法直接访问<br /><br /><div>Selector的核心是select()，是对操作系统的本地调用(native call)的封装，但是它不仅仅是向操作系统代码传递参数，还对每个select操作应用不同的过程</div><div>select()的执行步骤：<br />1 检查Cancelled key set。如果非空，每个取消的key将从另外两个集合中移除，相关通道被注销，执行完毕，Cancelled key set为空<br />2&nbsp;检查Registered key set中每个键的instrest集合，检查中如果对instrest集合有改动，不会影响剩余的检查过程<br />&nbsp; &nbsp;底层操作系统将会进行查询，确定每个通道关心的操作的真实就绪状态，依赖特定的select调用。如果没有就绪的通道，则阻塞，直至超时<br />&nbsp; &nbsp;这个过程会使调用线程睡眠一段时间，直至通道的就绪状态被确定下来<br />&nbsp; &nbsp;对于那些操作系统指示至少已经准备好instrest集合中的一种操作的通道，将执行下面的一种操作：<br />&nbsp; &nbsp;a 如果通道的SelectionKey没有处于Selected key set中，那么SelectionKey的ready集合将会清空，表示当前通道已经准备好的操作的Byte掩码将被设置<br />&nbsp; &nbsp;b&nbsp;通道的SelectionKey已经处于Selected key set中，每个SelectionKey的ready集合更新为已经准备好的操作的Byte掩码(SelectionKey.OP_READ ，SelectionKey.OP_WRITE等)<br />3 步骤2会花费很长时间，特别是所激发的线程处于休眠状态时。步骤2结束时，步骤1会重新执行，以完成任意一个在选择进行的过程当中，键已经被取消的通道的注销<br />4&nbsp;select()返回的值是步骤2中被修改的键的数量，即上一个select()调用之后进入就绪状态的通道的数量，而不是Selected key set中通道的数量。之前的调用中已经就绪的，并且在本次调用中仍然就绪的通道不会被计入。返回值有可能为0<br /><br />使用Cancelled key set来延迟注销，是一种防止线程在取消键时阻塞，并防止与正在进行的选择操作冲突的好方法<br /><br />注销通道是一种代价很高的操作，清理已取消的键，并在选择操作之前或之后立即注销通道，可以消除它们可能正好在选择的过程中执行的潜在的问题。<br /><br /><div>Selector的select()有三种形式：<br /><div>1 Selector.select()&nbsp;<br />&nbsp; &nbsp;该调用在没有通道就绪时将无限阻塞，一旦有至少一个已注册的通道就绪，Selector的选择键将会立即更新，每个选择键的ready集合也会被更新<br />2&nbsp;Selector.select(long timeout)&nbsp;<br />&nbsp; &nbsp;在提供的超时时间内没有就绪通道，将会返回0。如果设置timeout=0，则等同于select()&nbsp;<br />3&nbsp;Selector.selectNow() 是完全非阻塞的，会立即返回值<br /><br /><div>Selector.wakeup() 提供了使线程从被阻塞的select()方法中优雅的退出的能力<br />同样有三种方式唤醒select()方法中睡眠的线程：<br />1&nbsp;Selector.wakeup() 将使Selector上第一个没有返回的select()操作立即返回，如果当前没有正在进行的select()操作，则下次select()操作会立即返回。<br />2&nbsp;Selector.close() 被调用时，所有在select()操作中阻塞的线程都将被唤醒，Selector相关通道被注销，相关SelectionKey将被取消<br />3 Thread.interrupt()被调用时，返回状态将被设置。如果唤醒之后的线程试图在通道上执行IO操作，通道会立即关闭，线程捕捉到一个异常<br /><br />如果想让一个睡眠线程在中断之后继续进行，需要执行一些步骤来清理中断状态<br /><br />这些操作不会改变单个相关通道，中断一个Selector和中断一个通道是不一样的。Selector只会检查通道的状态。<br /><br />当一个select() 操作时睡眠的线程发生了中断，对于通道状态而言，是没有歧义的<br /><br /><br />选择器把合理的管理SelectionKey，以确保它们表示的状态不会变得陈旧的任务交给了程序员<br /><br />当SelectionKey已经不在选择器的Selected key set中时，会发生什么<br /><br />当通道上至少一个感兴趣的操作就绪时，SelectionKey的ready集合会被清空，并且当前已经就绪的操作会被添加到ready集合里，该SelectionKey随后会被添加到Selected key set中<br /><br />清理一个SelectionKey的ready集合的方式是将这个key从Selected key set中移除<br /><br /><div>SelectionKey的就绪状态只有在Selector的select() 操作过程中才会修改，因为只有Selected key set中的SelectionKey才被认为是包含了合法的就绪信息的，这些信息在SelectionKey中长久存在，知道key从Selected key set中移除，以通知Selector你已经看到并对它进行了处理。当下一次通道的感兴趣的操作发生时，key将被重新设置以反映当时通道的状态，并再次被添加到Selected key set中<br /><br />这种框架提供了非常大的灵活性。<br /><br />常规做法是先调用select() 操作，再遍历selectKeys()返回的key的集合。在按顺序进行检查每个key的过程中，相关的通道也根据key的就绪集合进行处理。然后key从Selected key set中移除，检查下一个key。完成后通过调用select() 重复循环。</div>服务器端使用方法<br />1 创建ServerSocketChannel，注册到Selector中，感兴趣的操作为accept<br />2 轮询Selector的select()操作，从就绪key集合中遍历key的ready集合，有accept则调用ServerSocketChannel的accept()方法获取SocketChannel，<u>其中包含接收到的socket的句柄</u>。再将SocketChannel注册到Selector中感兴趣的操作为read<br />3 当下次select()操作中key的ready集合中有read时，开始做事<br /><br /><div>Selector是线程安全的，但key set不是。Selector.keys 和&nbsp;Selector.selectkeys() 返回的是Selector内部私有key set的引用。这个集合可能随时被改变。迭代器Iterator是快速失败的(fail-fast)，如果迭代的时候key set发生改变，抛出ConcurrentModificationException。所有如果希望在多个线程间共享Selector或SelectionKey，则要对此做好准备。当你修改一个key的时候，可能会破坏另一个线程的Iterator</div><br />如果在多个线程并发的访问一个Selector的key set时，需要合理地同步访问。在select()操作时，先在Selector上进行同步，再是Registered key set，最后是Selected key set。按照这样的顺序，Cancelled key set就会在第一步和第三步之间保持同步。<br /><br />在多线程的场景中，如果需要对任何一个key set进行更改，不管是直接更改还是其他操作带来的副作用，都需要以相同的顺序，在同一对象上进行同步。如果竞争的线程没有以同样的顺序请求锁，则会有死锁的隐患。<br /><br /><div>Selector的close()和select()操作都会有一直阻塞的可能，当在select()的过程当中，所有对close()的调用都会被阻塞，直到select()结束，或者执行select()的线程进入睡眠。当select()的线程进入睡眠时，close()的线程获得锁后会立即唤醒select()的线程，并关闭Selector<br /></div>如果不采取相同的顺序同步，则key set中key的信息不保证是有效的，相关通道也不保证是打开的<br /><br /><br />一个cpu的时候使用一个线程来管理所有通道，是一个合适的解决方案，但会浪费其他cpu的运行能力<br /><br />在大量通道上执行select()操作不会有太大开销，因为大多数工作都是操纵系统完成的<br /><br />方案1 管理多个Selector，并随机分配通道不是一个好方案<br />方案2 所有通道使用一个Selector，将就绪通道的服务委托给其他线程。相当于用一个线程监控通道的就绪状态，使用另外的工作线程池来处理接收到的数据。而线程池是可以调整的，或者动态调整。<br /><br />在方案2中，如果某些通道要求更高的响应速度，可以用两个选择器来解决。并且线程池可以细化为日志线程池、命令线程池、状态请求线程池等<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /></div></div></div></div></div></div></div></div></div></div></div></div></div></div></div><img src ="http://www.blogjava.net/cooperzh/aggbug/366789.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cooperzh/" target="_blank">cooperzh</a> 2011-12-19 18:53 <a href="http://www.blogjava.net/cooperzh/archive/2011/12/19/366789.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NIO Channel 管道</title><link>http://www.blogjava.net/cooperzh/archive/2011/12/19/366764.html</link><dc:creator>cooperzh</dc:creator><author>cooperzh</author><pubDate>Mon, 19 Dec 2011 07:57:00 GMT</pubDate><guid>http://www.blogjava.net/cooperzh/archive/2011/12/19/366764.html</guid><description><![CDATA[Unix中的管道pipe是用来连接一个进程的输出和另一个进程的输入<br /><br />java.nio中的管道只是在jvm进程内部传输数据，优势在于封装性<br /><br />这样就允许单个用户线程适用一个Selector从多个通道收集数据，并任意结合网络连接或本地工作线程适用<br /><br />pipe的另一个用处是测试，可以将某个测试类连接到管道的&#8220;写&#8221;端，并检查管道&#8220;读&#8221;端出来的数据<img src ="http://www.blogjava.net/cooperzh/aggbug/366764.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cooperzh/" target="_blank">cooperzh</a> 2011-12-19 15:57 <a href="http://www.blogjava.net/cooperzh/archive/2011/12/19/366764.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NIO channel Socket通道</title><link>http://www.blogjava.net/cooperzh/archive/2011/12/19/366752.html</link><dc:creator>cooperzh</dc:creator><author>cooperzh</author><pubDate>Mon, 19 Dec 2011 06:15:00 GMT</pubDate><guid>http://www.blogjava.net/cooperzh/archive/2011/12/19/366752.html</guid><description><![CDATA[Socket通道类：DatagramChannel,SocketChannel,ServerSocketChannel<br /><br /><div>DatagramChannel和SocketChannel实现了ReadableByteChannel 和&nbsp;WritableByteChannel接口，而ServerSocketChannel没实现<br /><div>ServerSocketChannel只负责监听传入的连接，创建新的SocketChannel，它自身从不传输数据</div><br />socket类有Socket,ServerSocket,DatagramSocket<br /><br />socket和socket通道之间的关系：<br />1&nbsp;socket位于java.net包下，socket通道位于java.nio<br />2 Socket通道被创建的时候都会创建一个对等的Socket实例<br />3 通道的socket()可以调用socket,socket的getChannel()可以调用通道<br />4&nbsp;Socket通道都有对应的Socket对象，但直接定义的Socket对象是没有对应的通道<br />5 Socket通道委派协议操作给对应的Socket对象<br /><br />要把一个Socket通道置为非阻塞模式，就要依靠所有Socket通道类的父类SelectableChannel<br /><br />ServerSocketChannel是一个基于通道的Socket监听器，因为增加了通道语意，所以可以在非阻塞模式下运行。<br /><br /><div>ServerSocketChannel没有bind方法，只能通过其对应的ServerSocket来实现</div><div>ServerSocketChannel和其对应的ServerSocket都有accept()方法：</div><div>ServerSocketChannel.accept() 返回SocketChannel对象，SocketChannel中包含对应的Socket</div><div>ServerSocket.accept() 阻塞并返回一个Socket对象</div><br />非阻塞模式下，没有连接传入时，ServerSocketChannel.accept()返回null<br /><br />SocketChannel使用最频繁<br /><br />Socket,SocketChannel类封装点对点，有序的网络连接，类似于TCP/IP网络连接<br />Socket是面向流的<br /><br /><div>SocketChannel扮演客户端发起一个监听服务器的连接，直到连接成功，才能收到数据，并且只从连接到的地址接收</div><br /><div>SocketChannel的connect()方法是阻塞式的，同其对应Scoket的connect()方法一样<br /><br />在非阻塞模式下调用SocketChannel的finishConnect()方法会返回的结果：<br />1 connect()方法没调用，产生NoConnectionPendingException异常<br />2 连接正在建立，尚未完成，立即返回false<br />3 非阻塞模式下调用connect()后，SocketChannel回到阻塞模式，立即返回false<br />4 连接已建立后，SocketChannel内部状态会更新为已连接状态，finishConnect()方法会返回true,SocketChannel就可以开始传输数据了<br /><br />DatagramChannel对应DatagramSocket对象，模拟包导向的无连接协议UDP/IP<br /><div>DatagramSocket</div>每个数据报Datagram都包含自己的地址<br /><br /><div>DatagramChannel可以发送Datagram到不同的地址，同样也可以接收不同地址的Datagram<br /><br />基于包的吞吐量比基于流的高很多，因为互联网基础设施就是包导向的<br /><br />TCP这样面向流的协议会产生巨大的开销，并且不能适用所有的情形<br /><br />选择DatagramSocket而不是流Socket的前提：<br />1 程序可以承受数据丢失和无序数据<br />2 发送后不需要知道对方是否已经接收，即fire and forget<br />3 数据吞吐量比可靠性更重要<br />4 同时发送数据给多个接收者<br />5 包导向更适合现在的任务</div></div></div><img src ="http://www.blogjava.net/cooperzh/aggbug/366752.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cooperzh/" target="_blank">cooperzh</a> 2011-12-19 14:15 <a href="http://www.blogjava.net/cooperzh/archive/2011/12/19/366752.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NIO channel</title><link>http://www.blogjava.net/cooperzh/archive/2011/12/19/366733.html</link><dc:creator>cooperzh</dc:creator><author>cooperzh</author><pubDate>Mon, 19 Dec 2011 03:26:00 GMT</pubDate><guid>http://www.blogjava.net/cooperzh/archive/2011/12/19/366733.html</guid><description><![CDATA[channel 用于在 ByteBuffer 和socket(或文件)之间传输数据<br /><br />channel的实现经常使用操作系统的本地代码<br /><br />implement InterruptibleChannel 后标示该通道可以被中断，大多数channel都是可以被中断的<br />面向字节的接口：ReadableByteChannel,WriteableByteChannel<br />ByteChannel接口继承了ReadableByteChannel和WriteableByteChannel接口<br /><br />IO广义上可以分为file IO 和 stream IO，对应file通道和socket通道<br />file通道的类：FileChanel<br />socket通道的类：SocketChannel,ServerSocketChannel,DatagramChanenel<br /><br />FileChannel不能直接创建，只能通过打开的RandomAccessFile,FileInputStream,FileOutputStream调用getChannel()获得<br /><div>socket通道可以直接调用其工厂方法获得实例<br /><br />只实现ReadableByteChannel或WriteableByteChannel的通道都是单向的，两个都实现就是双向的<br />实现ByteChannel的通道都是双向的<br /><br />所有socketChannel类都是双向的<br /><br />read() 将字节从通道读入缓冲区<br />write() 将字节从缓冲区写入通道<br /><br />讲byteBuffer的数据写入通道：<br />while(buffer.hasRemaining()){<br />&nbsp;&nbsp;&nbsp;&nbsp;dest.write(buffer);<br />}<br />因为write操作可能会因为其他线程的调用而阻塞<br /><br />缓冲区可以重复使用，而通道是一次性的，用完就关闭。通道关闭后，代表的与socket的连接会丢失<br /><br />close方法是阻塞式的，多次close没有坏处。close的实现取决于操作系统。<br /><br />实现InterruptibleChannel接口的类，如果线程在该通道上被阻塞，同时线程被中断，则通道将会关闭，阻塞线程会报异常：ClosedByInterruptException.<br /><br />另外，设置了interrupt status的线访问一个通道时，该通道将会立即被关闭，同时抛出ClosedByInterruptException异常<br />而线程的interrupt status是线程的interrupt()方法设置，并通过Thread.interrupted()清除。<br /><br />通道上的线程休眠，则通道会关闭<br /><br />当一个通道被关闭的时候，所有在此通道上休眠的线程都将被唤醒，并收到一个AsynchronousCloseException，接着通道被关闭。<br /><br />一个通道同时对多个byteBuffer的操作称为分散和聚合<br />Scatter分散体现在channel.read(ByteBuffer[] dsts) 和 channel.read(ByteBuffer[] dsts,int offset,int length);&nbsp;<br /><div>Gather分散体现在channel.write(ByteBuffer[] dsts) 和 channel.write(ByteBuffer[] dsts,int offset,int length);</div><div>offset 和 length指的是ByteBuffer[]中第几个ByteBuffer</div><br /><br /><br /></div><img src ="http://www.blogjava.net/cooperzh/aggbug/366733.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cooperzh/" target="_blank">cooperzh</a> 2011-12-19 11:26 <a href="http://www.blogjava.net/cooperzh/archive/2011/12/19/366733.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>NIO buffer</title><link>http://www.blogjava.net/cooperzh/archive/2011/12/18/366679.html</link><dc:creator>cooperzh</dc:creator><author>cooperzh</author><pubDate>Sun, 18 Dec 2011 13:10:00 GMT</pubDate><guid>http://www.blogjava.net/cooperzh/archive/2011/12/18/366679.html</guid><description><![CDATA[ByteBuffer buffer = ByteBuffer.allocate(int);<br /><div>ByteBuffer buffer = ByteBuffer.allocateDirect(int);</div><span style="color: red; ">allocate返回的其实是</span><span style="color: red; ">ByteBuffer的子类HeapByteBuffer;</span><br /><div><span style="color: red; ">allocateDirect</span><span style="color: red; ">返回的其实是MappedByteBuffer的子类DirectByteBuffer;</span></div><br /><span style="color: red; ">CharBuffer.allocate(int cap); 创建的是char[]数组，即cap是char的数量，而不是byte的数量（1个char长度为2个byte）</span><br /><br />buffer的四个属性<br />0 &lt;= mark &lt;= position &lt;= limit &lt;= capacity<br /><br />buffer支持级联调用：<br />buffer.position(2).mark().position(4);<br />因为position,mark方法返回的都是buffer的引用<br /><br />position决定了read和write的起始位置，小于position位置的char都忽略<br /><br /><span style="color: red; ">buffer.get() 和 buffer.put(byte) 都是相对方法，都会导致position++</span><br /><div><span style="color: red; ">buffer.get(int) 和 buffer.put(int,byte) 都是绝对方法，不会导致position变化</span></div><br /><span style="color: red; ">buffer.clear() 只是将position置为0，limit设置为capacity，并不真正清空数据<br /><br /></span><span style="color: red; ">buffer.position(int) 当 newPosition &lt; mark 时，丢弃mark(设置mark=-1)<br /><br /></span><span style="color: red; ">buffer.mark() 将&nbsp;</span><span style="color: red; ">position</span><span style="color: red; ">赋值给&nbsp;</span><span style="color: red; ">mark ;</span><br /><span style="color: red; ">buffer.reset() 将&nbsp;</span><span style="color: red; ">mark 赋值给</span><span style="color: red; ">&nbsp;position;</span><br /><br />buffer.remaining() 表示position到limit的数量<br /><br />buffer的比较是比较position到limit之间的内容<br /><br />读取buffer的内容，是从position开始读取的，并不是从0位<br /><br /><span style="color: red; ">反转buffer：buffer.flip();</span><br /><span style="color: red; ">1 limit = position;</span><br /><span style="color: red; ">2 position = 0;</span><br /><span style="color: red; ">3 mark = -1;</span><br /><span style="color: red; ">flip只能翻转1次，翻转两次将会导致limit也等于0；通常用于read状态切换到write状态，或者write状态切换到read状态</span><br /><font color="#ff0000"><br />buffer.put(buffer) 会报IllegalArgumentException异常，因为position的移动相矛盾<br /><br />buffer.slice() 用position和limit之间的内容新生成一个buffer，该buffer的capacity为limit-position的值<br /></font><br />charBuffer.wrap(char[], int start, int end) 将会设置positon=start，并将char[]直接传递给charBuffer成为内部char array<br /><br />从buffer读取数据到数组，必须指定数据的长度：<br />1 buffer.remaining() &lt; arr.length 读取buffer的position到limit的所有值<br />2&nbsp;buffer.remaining() &gt; arr.length 读取buffer中arr.length长度的值，并立即处理，然后再读取buffer中剩余的值<br /><br />从数组读取数据到buffer：<br /><div>1 buffer.remaining() &gt; arr.length 读取数组的所有值<br />2&nbsp;buffer.remaining() &lt; arr.length 抛出BufferOverflowException</div><br />buffer中多字节的数据类型存储的字节顺序没有标准，目前有<br />1 大端字节顺序(BIG_ENDIAN) ，高位值在低地址(靠左侧)，motorola,sun,prow pc的处理器<br />2 小端字节顺序 (LITTLE_ENDIAN)，高位值在高地址(靠右侧)，intel处理器<br />这是由操作系统决定的，可以用ByteOrder.nativeOrder()的返回值：ByteOrder.BIG_ENDIAN 或&nbsp;ByteOrder.LITTLE_ENDIAN来确定<br />默认都是BIG_ENDIAN ，这是jvm决定的，所以jvm运行在intel处理器上会有性能隐患<br />只有ButeBuffer可以修改字节顺序，通过order()方法，除非显式修改，否则创建后永不改变<br />其他类型的buffer可以转换为ByteBuffer处理<br /><br />直接缓冲区是IO操作的最好选择。但并非在一开始就一定要创建。<br />直接缓冲区是调用操作系统的代码分配的，绕开了jvm的堆栈。创建和销毁的代价都比较大。<br />只有byteBuffer.allocateDirect()才能创建直接缓冲区<br />allocate() 和 wrap() 的包装方法都创建的是非直接缓冲区(即jvm堆栈内)<br /><br />ByteBuffer允许通过创建视图来讲byte映射为其他原始数据类型，使用asLongBuffer()等方法<br /><br />ByteBuffer byteBuffer = ByteBuffer.allocate(7).order(ByteOrder.BIG_ENDIAN);<br />CharBuffer charBuffer = byteBuffer.asCharBuffer();<br />charBuffer只是一个view，只是为了方便操作，本质上数据依旧是存放在byteBuffer中的<br /><br />ByteBuffer的getInt等方法是获取position到limit中的byte并转换为对应的int等数据类型<br />注意，不同的字节顺序，取出的数值是不同的<br /><br /><span style="color: red; ">java中的byte都是有符号的<br /></span>获取及存放无符号整数需要自己写一个工具类：<br />如byte型：<br />buffer.put( (byte) (value &amp; 0xff) ); 保存value的低8位的值<br />(short)(buffer.get() &amp; 0xff) 取<br />short型：<br />buffer.putShort((short)(value &amp; 0xffff));<br />(int)(buffer.getShort() &amp; 0xffff);<br />int型:<br />buffer.putInt((int)(value &amp; 0xffffffffL));<br />(long)(buffer.getInt() &amp; 0xffffffffL);<br /><br /><br /><br /><br /><br /><img src ="http://www.blogjava.net/cooperzh/aggbug/366679.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/cooperzh/" target="_blank">cooperzh</a> 2011-12-18 21:10 <a href="http://www.blogjava.net/cooperzh/archive/2011/12/18/366679.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>