日期:2014-05-19  浏览次数:20689 次

请教两个NIO的问题
最近看了下nio,有2个问题一直没想明白
希望能有人指点下

第一个是关于SelectionKey的OP_WRITE这个键

首先看下SocketChannel的write(ByteBuffer dst)这个方法的异常
NotYetConnectedException - 如果尚未连接此通道 
ClosedChannelException - 如果此通道已关闭 
AsynchronousCloseException - 如果正在进行写入操作时另一个线程关闭了此通道 
ClosedByInterruptException - 如果正在进行写入操作时另一个线程中断了当前线程,因此关闭了该通道

并将当前线程的状态设置为中断 
IOException - 如果发生其他 I/O 错误

因为SocketChannel是全双工的,再结合上面的异常
可以说在连接创建以后到断开之前,这段区间
SocketChannel都是可写的

那么如果有客户端连接进来,
注册了OP_WRITE这个键
那么下面这种代码

while(selector.select() > 0){
....
}



while(true){}

不是等价的吗

我想请教下实际中,OP_WRITE这个是怎么用的?
我的设想是:
先不注册这个键,
当你要写数据的时候,
先把数据放到buffer,设置到attachment
然后再注册OP_WRITE事件,
等selector执行,
key.isWritable()的时候就把buffer取出来写到socket
然后把这个键取消掉
是这么用的吗?


第二个问题是
read(ByteBuffer dst)这个方法,
我看一般都是配合remaining()来用的,
但是如果我在网络延迟高的情况下写转过码的byte数组
不是会出现中断的情况吗

比如用gbk转码"hello world",写入的是"[104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]"
但是可能网络延迟高,我收到的只有"104, 101, 108, 108, 111"
剩下的部分还在传输,这个时候我拿着收到这部分去转码就会问题
我想请教的就是,有没有办法知道这个接受完成了?
这两天写了个简单的工具,我的办法是先把bytes的length写过来
读的时候先读这个长度,然后没有读够这个长度的bytes就一直阻塞,
不知道有没有其他的解决方案?
或者是设置某个特别的值表示一次写的结束之类的?
------最佳解决方案--------------------
问题一,你可能忽略了selector.select()的特性:
   This method performs a blocking selection operation. It returns only after at least one channel is selected, this selector's wakeup method is invoked, or the current thread is interrupted, whichever comes first.
也就是它是阻塞式的,如果没有新的事件到达,就持续阻塞,而不是傻乎乎的狂循环浪费CPU。

关于OP_WRITE,其实如果你不考虑写给对方的输出流可能会因为对方没有及时读取而满了,导致你这边写阻塞的话,是可以不用考虑OP_WRITE;但由此就可能会在写的时候发生阻塞而丧失部分NIO的意义。


问题二,显然会,尤其是你发送的数据量越大,就约有可能出现部分传输,比如你发一个贼大的图片。需要自己设法解决,没有十分简易的解决方案。
比如可以另外使用一条专用的读线程来进行阻塞式读取;也可以考虑更复杂的自行组装。


这里有个不错的介绍:
https://www.ibm.com/developerworks/cn/java/l-niosvr/
------其他解决方案--------------------
再顶一下,没人回12点结贴算了
------其他解决方案--------------------
引用:
问题一,你可能忽略了selector.select()的特性:
   This method performs a blocking selection operation. It returns only after at least one channel is selected, this selector's wakeup method is invoked, ……

selector的阻塞我是知道的,
问题是只要有socketchannel连接进来,
并且注册了OP_WRITE这个键以后
每次select都会有key().isWritable()是true
也许我说得不清楚,用代码表示下

while (selector.select() > 0) {
Set readyKeys = selector.selectedKeys();
Iterator it = readyKeys.iterator();
while (it.hasNext()) {
SelectionKey key =  (SelectionKey) it.next();
it.remove();
if (key.isWritable()) {
System.out.println(11);
}
}
}


比如这段,只要存在没有断开的连接,
就疯狂的打印11
因为那个channel确实是一直都是可写的
一个cpu的占用也在80%的样子
基本上跟死循环是一样的
那我就认为OP_WRITE这个键应该不是这么用的..
我是想问实际中OP_WRITE会怎么使用?还是根本就不用