此文已由作者张镐薪授权网易云社区发布。
欢迎访问网易云社区,了解更多网易技术产品运营经验。
3. 连接模块
3.3 AbstractConnection:
3.3.2 NIOHandler
NIOHandler实际上就是对于业务处理方法的封装,对于不同的连接有不同的处理方法,也就是不同的NIOHandler
public interface NIOHandler { void handle(byte[] data); }
它的实现以及子类会在之后的对应的处理模块细讲。
3.3.3 NIOSocketWR
实现对于AbstractConnection(实际就是对里面封装的channel)进行异步读写,将从channel中读取到的放到AbstractConnection的readBuffer中,将writeBuffer和写队列中的数据写入到channel中。可以这么说,AbstractConnection的方法只对它里面的buffer进行操作,而buffer与channel之间的交互,是通过NIOSocketWR的方法完成的。 下面是它的方法以及对应的说明:
public void register(Selector selector) throws IOException { try { processKey = channel.register(selector, SelectionKey.OP_READ, con); } finally { if (con.isClosed.get()) { clearSelectionKey(); } } } private void clearSelectionKey() { try { SelectionKey key = this.processKey; if (key != null && key.isValid()) { key.attach(null); key.cancel(); } } catch (Exception e) { AbstractConnection.LOGGER.warn("clear selector keys err:" + e); } }
调用关系:这个方法就是之前讲的AbstractionConnection与RW线程绑定,AbstractionConnection封装的channel需要在RW线程的selector上注册读事件以监听读事件。
public void doNextWriteCheck() { //检查是否正在写,看CAS更新writing值是否成功 if (!writing.compareAndSet(false, true)) { return; } try { //利用缓存队列和写缓冲记录保证写的可靠性,返回true则为全部写入成功 boolean noMoreData = write0(); //因为只有一个线程可以成功CAS更新writing值,所以这里不用再CAS writing.set(false); //如果全部写入成功而且写入队列为空(有可能在写入过程中又有新的Bytebuffer加入到队列),则取消注册写事件 //否则,继续注册写事件 if (noMoreData && con.writeQueue.isEmpty()) { if ((processKey.isValid() && (processKey.interestOps() & SelectionKey.OP_WRITE) != 0)) { disableWrite(); } } else { if ((processKey.isValid() && (processKey.interestOps() & SelectionKey.OP_WRITE) == 0)) { enableWrite(false); } } } catch (IOException e) { if (AbstractConnection.LOGGER.isDebugEnabled()) { AbstractConnection.LOGGER.debug("caught err:", e); } con.close("err:" + e); } } private boolean write0() throws IOException { int written = 0; ByteBuffer buffer = con.writeBuffer; if (buffer != null) { //只要写缓冲记录中还有数据就不停写入,但如果写入字节为0,证明网络繁忙,则退出 while (buffer.hasRemaining()) { written = channel.write(buffer); if (written > 0) { con.netOutBytes += written; con.processor.addNetOutBytes(written); con.lastWriteTime = TimeUtil.currentTimeMillis(); } else { break; } } //如果写缓冲中还有数据证明网络繁忙,计数并退出,否则清空缓冲 if (buffer.hasRemaining()) { con.writeAttempts++; return false; } else { con.writeBuffer = null; con.recycle(buffer); } } //读取缓存队列并写channel while ((buffer = con.writeQueue.poll()) != null) { if (buffer.limit() == 0) { con.recycle(buffer); con.close("quit send"); return true; } buffer.flip(); while (buffer.hasRemaining()) { written = channel.write(buffer); if (written > 0) { con.lastWriteTime = TimeUtil.currentTimeMillis(); con.netOutBytes += written; con.processor.addNetOutBytes(written); con.lastWriteTime = TimeUtil.currentTimeMillis(); } else { break; } } //如果写缓冲中还有数据证明网络繁忙,计数,记录下这次未写完的数据到写缓冲记录并退出,否则回收缓冲 if (buffer.hasRemaining()) { con.writeBuffer = buffer; con.writeAttempts++; return false; } else { con.recycle(buffer); } } return true; } private void disableWrite() { try { SelectionKey key = this.processKey; key.interestOps(key.interestOps() & OP_NOT_WRITE); } catch (Exception e) { AbstractConnection.LOGGER.warn("can't disable write " + e + " con " + con); } } private void enableWrite(boolean wakeup) { boolean needWakeup = false; try { SelectionKey key = this.processKey; key.interestOps(key.interestOps() | SelectionKey.OP_WRITE); needWakeup = true; } catch (Exception e) { AbstractConnection.LOGGER.warn("can't enable write " + e); } if (needWakeup && wakeup) { processKey.selector().wakeup(); } }
这个doNextWriteCheck方法之前也讲过,看调用关系:第一个调用关系没意义,WriteEventCheckRunner这个类从没被调用过。 第二个调用很。。。就是将这个方法简单封装,估计是为了好修改,之后会提两种写策略对比。 第三个调用是主要调用,所有往AbstractionConnection中写入都会调用Abstraction.write(ByteBuffer),这个方法先把要写的放入缓存队列,之后调用上面这个doNextWriteCheck方法。 第四个和第五个都是定时检查任务,为了检查是否有AbstractionConnection的写缓存没有写完的情况
@Override public void asynRead() throws IOException { ByteBuffer theBuffer = con.readBuffer; //如果buffer为空,证明被回收或者是第一次读,新分配一个buffer给AbstractConnection作为readBuffer if (theBuffer == null) { theBuffer = con.processor.getBufferPool().allocate(); con.readBuffer = theBuffer; } //从channel中读取数据,并且保存到对应AbstractConnection的readBuffer中,readBuffer处于write mode,返回读取了多少字节 int got = channel.read(theBuffer); //调用处理读取到的数据的方法 con.onReadData(got); }
这个方法之前也讲过,异步将channel中的数据读取到readBuffer中,之后调用对应AbstractConnection的处理方法。 调用关系:按理说,应该只有在RW线程检测到读事件之后,才会调用这个异步读方法。但是在FrontendConnection的register()方法和BackendAIOConnection的register()方法都调用了。这是因为这两个方法在正常工作情况下为了注册一个会先主动发一个握手包,另一个会先读取一个握手包。所以都会执行异步读方法。
更多网易技术、产品、运营经验分享请点击。
相关文章:
【推荐】 谈谈Java异常处理这件事儿