项目场景:
背景:使用nio写服务器端接收文件的上传
问题描述
服务器端使用selector.select()来获取被关心的状态事件,使用SelectionKey的isReadable判断事件是否为可读事件,由于select()是水平触发(这里见下面的解释),所以在通道没有处理完毕之前会一直被触发。为了让触发在接受处理后就被关闭,就需要移除掉这个key的可读事件
就有了下面的代码:
@Override
protected void readData(final SelectionKey key) throws IOException {
//移除掉这个key的可读事件,已经在线程池里面处理
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));//主要注意这句话
exec.execute(new Runnable() {
@Override
public void run() {
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024);
FileChannel fileChannel = fileMap.get(key);
buffer.clear();
SocketChannel socketChannel = (SocketChannel) key.channel();
int num = 0;
try {
while ((num = socketChannel.read(buffer)) > 0) {
buffer.flip();
// 写入文件
fileChannel.write(buffer);
buffer.clear();
}
} catch (IOException e) {
key.cancel();
e.printStackTrace();
return;
}
// 调用close为-1 到达末尾
if (num == -1) {
try {
fileChannel.close();
System.out.println("上传完毕");
buffer.put((socketChannel.getRemoteAddress() + "上传成功").getBytes());
buffer.clear();
socketChannel.write(buffer);
} catch (IOException e) {
e.printStackTrace();
}
// 只有调用cancel才会真正从已选择的键的集合里面移除,否则下次select的时候又能得到
// 一端close掉了,其对端仍然是可读的,读取得到EOF,返回-1
key.cancel();
return;
}
// Channel的read方法可能返回0,返回0并不一定代表读取完了。
// 工作线程结束对通道的读取,需要再次更新键的ready集合,将感兴趣的集合重新放在里面
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
// 调用wakeup,使得选择器上的第一个还没有返回的选择操作立即返回即重新select
key.selector().wakeup();
}
});
}
原因分析:
分析水平触发和边缘触发
(1) 水平触发是当就绪的fd未被用户进程处理,下一次select()查询依旧会返回,这是select和poll的触发方式。
(2) 边缘触发是无论就绪的fd是否被处理,下一次不再返回。理论上性能更高,但是实现相当复杂,并且任何意外的丢失事件都会造成请求处理错误。epoll默认使用水平触发,通过相应选项可以使用边缘触发。
下面来分析这句话:
key.interestOps(key.interestOps() & (~SelectionKey.OP_READ));
关键点是这个符号:~先理解这个:
- 正数的原码 = 反码 = 补码
- 负数的反码 = 原码符号位不变,其它位全取反,负数的补码 = 反码 + 1。
上面话的意思是:
key.interestOps(1按照位与-2)
key.interestOps(0) //代表取消上面的四个监听,代表不监听任何东西
1、首先~表示非运算符,就是将该数的所有二进制位全取反。但又由于计算机中是以补码的形式存储的,所以0 1010全取反是1 0101(只是补码形式,还需要转成原码)。
.
2、此时得到的1 0101只是补码,我们需要将它先转为反码,反码 = 补码-1,得到反码为1 0100。
.
3、我们得到反码后,将它转为原码,原码 = 反码符号位不变,其它位全取反,得到最终的原码为1 1011,转化为十进制就是-11。
解决方案:
总结:
公式:
(~x) = -(x + 1)
“&~xx” 代表取消xx ”|xx“ 代表将xx添加进去