背景:使用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添加进去