此文已由作者张镐薪授权网易云社区发布。

欢迎访问网易云社区,了解更多网易技术产品运营经验。

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()方法都调用了。这是因为这两个方法在正常工作情况下为了注册一个会先主动发一个握手包,另一个会先读取一个握手包。所以都会执行异步读方法。

免费体验云安全(易盾)内容安全、验证码等服务

更多网易技术、产品、运营经验分享请点击

相关文章:
【推荐】 Android输入法弹出时覆盖输入框问题
【推荐】 Android TV 开发(4)

数据库路由中间件MyCat - 源代码篇(6)的更多相关文章

  1. 数据库路由中间件MyCat - 源代码篇(1)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 进入了源代码篇,我们先从整体入手,之后拿一个简单流程前端连接建立与认证作为例子,理清代码思路和设计模式.然后 ...

  2. 数据库路由中间件MyCat - 源代码篇(13)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 4.配置模块 4.2 schema.xml 接上一篇,接下来载入每个schema的配置(也就是每个MyCat ...

  3. 数据库路由中间件MyCat - 源代码篇(7)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.4 FrontendConnection前端连接 构造方法: public Fronte ...

  4. 数据库路由中间件MyCat - 源代码篇(15)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. public static void handle(String stmt, ServerConnectio ...

  5. 数据库路由中间件MyCat - 源代码篇(17)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 调用processInsert(sc,schema,sqlType,origSQL,tableName,pr ...

  6. 数据库路由中间件MyCat - 源代码篇(14)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 对于表的dataNode对应关系,有个特殊配置即类似dataNode="distributed(d ...

  7. 数据库路由中间件MyCat - 源代码篇(4)

    此文已由作者易国强授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...

  8. 数据库路由中间件MyCat - 源代码篇(2)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 2. 前端连接建立与认证 Title:MySql连接建立以及认证过程client->MySql:1.T ...

  9. 数据库路由中间件MyCat - 源代码篇(16)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 5. 路由模块 真正取得RouteResultset的步骤:AbstractRouteStrategy的ro ...

  10. 数据库路由中间件MyCat - 源代码篇(10)

    此文已由作者张镐薪授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. 3. 连接模块 3.5 后端连接 3.5.2 后端连接获取与维护管理 还是那之前的流程, st=>st ...

随机推荐

  1. Django框架创建数据库表时setting文件配置_模型层

    若想将模型转为mysql数据库中的表,需要在settings中配置: 一. 确保配置文件中的INSTALLED_APPS中写入我们创建的app名称-->bms INSTALLED_APPS = ...

  2. matlab vs使用

    ~ matlab无论什么程序只输出 ans 1 注意matlab命名规则:1不能与matlab内部函数名字重合.2.文件名首字母不能是数字或下划线.3.文件名中不能有空格.4.文件名不能太长.5注意大 ...

  3. RaspBerry Pi3 ~ 内核编译

    RaspBerryPi3-内核编译 转载注明出处:http://www.cnblogs.com/einstein-2014731/p/5985128.html 在有道云笔记的同步分享:http://n ...

  4. Raspberry Pi3 ~ 使用eclipse进行远程调试

    为了开发方便需要在电脑上对树莓派进行远程Debug. l  在eclipse中安装交叉编译(参照开发环境搭建)    arm-linux-gnueabihf-gcc l  树莓派中检查是否安装了gdb ...

  5. c# &与&& 和 |与||的区别(转载)

    &:按位与,对两个条件都进行判断&&:逻辑与,只要一个条件满足,另外一个条件就不会执行 同理:|:按位或,对两个条件都进行判断||:逻辑或,只要一个条件满足,另外一个条件就不会 ...

  6. 《程序员代码面试指南》第八章 数组和矩阵问题 打印N 个数组整体最大的Top K

    题目 打印N 个数组整体最大的Top K java代码 package com.lizhouwei.chapter8; /** * @Description: 打印N 个数组整体最大的Top K * ...

  7. Sprin Boot2.0之整合Mybatis整合分页插件

    pageHelper PageHelper 是一款好用的开源免费的 Mybatis 第三方物理分页插件 物理分页 支持常见的 12 种数据库.Oracle,MySql,MariaDB,SQLite,D ...

  8. IDEA 设置代码行宽度

    1.在File->settings->Editor->Code Style 2.在File->settings->Editor->Code Style->XM ...

  9. 如何用js获取日期(转载)

    本文介绍了js获取日期的方法,可以获取前天.昨天.今天.明天.后天. 代码: <html> <head> <meta http-equiv="Content-T ...

  10. Java丨springMVC + Ajax 来进行分页

    javaweb 分页技术 实现的方式有很多种,但是小编在网上问了度娘也问了谷老师,得到的答案让小编我不是很满意,so,还是自己写吧! 在小编的博文中,小编不会上传源码,只会把重要的代码和思路供大家参考 ...