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

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

2. 前端连接建立与认证

Title:MySql连接建立以及认证过程client->MySql:1.TCP连接请求 
MySql->client:2.接受TCP连接client->MySql:3.TCP连接建立MySql->client:4.握手包HandshakePacketclient->MySql:5.认证包AuthPacketMySql->client:6.如果验证成功,则返回OkPacketclient->MySql:7.默认会发送查询版本信息的包MySql->client:8.返回结果包

2.3 (5~6)认证包AuthPacket,如果验证成功,则返回OkPacket

继续执行FrontendConnection的register()方法:

// 异步读取并处理,这个与RW线程中的asynRead()相同,之后客户端收到握手包返回AuthPacket就是从这里开始看。
         this.asynRead();

FrontendConnection.asynRead()方法直接调用this.socketWR.asynRead();如之前所述,一个Connection对应一个socketWR。在这里是一个FrontendConnection对应一个NIOSocketWR。NIOSocketWR是操作类,里面的方法实现异步读写。 NIOSocketWR.asynRead():

public void asynRead() throws IOException {
        ByteBuffer theBuffer = con.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);
    }

读取完数据到缓存readBuffer后,调用处理readBuffer方法: AbstractConnection.onReadData(int got):

public void onReadData(int got) throws IOException {        //如果连接已经关闭,则不处理
        if (isClosed.get()) {            return;
        }
        ByteBuffer buffer = this.readBuffer;
        lastReadTime = TimeUtil.currentTimeMillis();        //读取到的字节小于0,表示流关闭,如果等于0,代表TCP连接关闭了
        if (got < 0) {            this.close("stream closed");           
         return;
        } else if (got == 0) {           
         if (!this.channel.isOpen()) {             
            this.close("socket closed");             
               return;
            }
        }
        netInBytes += got;
        processor.addNetInBytes(got);        // 循环处理字节信息
        //readBuffer一直处于write mode,position记录最后的写入位置
        int offset = readBufferOffset, length = 0, position = buffer.position();     
           for (; ; ) {            //获取包头的包长度信息
            length = getPacketLength(buffer, offset);           
             if (length == -1) {               
              if (!buffer.hasRemaining()) {
                    buffer = checkReadBuffer(buffer, offset, position);
                }                break;
            }            //如果postion小于包起始位置加上包长度,证明readBuffer不够大,需要扩容
            if (position >= offset + length) {
                buffer.position(offset);               
                 byte[] data = new byte[length];                //读取一个完整的包
                buffer.get(data, 0, length);                //处理包,每种AbstractConnection的处理函数不同
                handle(data);                //记录下读取到哪里了
                offset += length;                //如果最后写入位置等于最后读取位置,则证明所有的处理完了,可以清空缓存和offset
                //否则,记录下最新的offset
                //由于readBufferOffset只会单线程(绑定的RW线程)修改,但是会有多个线程访问(定时线程池的清理任务),所以设为volatile,不用CAS
                if (position == offset) {                  
                  if (readBufferOffset != 0) {
                        readBufferOffset = 0;
                    }
                    buffer.clear();                  
                      break;
                } else {
                    readBufferOffset = offset;
                    buffer.position(position);             
                           continue;
                }
            } else {              
              if (!buffer.hasRemaining()) {
                    buffer = checkReadBuffer(buffer, offset, position);
                }                break;
            }
        }
    }private ByteBuffer checkReadBuffer(ByteBuffer buffer, int offset,                           
                int position) {        
                if (offset == 0) {       
                     if (buffer.capacity() >= maxPacketSize) {    
                throw new IllegalArgumentException(                   
                     "Packet size over the limit.");
            }            int size = buffer.capacity() << 1;
            size = (size > maxPacketSize) ? maxPacketSize : size;
            ByteBuffer newBuffer = processor.getBufferPool().allocate(size);
            buffer.position(offset);
            newBuffer.put(buffer);
            readBuffer = newBuffer;
            recycle(buffer);         
               return newBuffer;
        } else {
            buffer.position(offset);
            buffer.compact();
            readBufferOffset = 0;        
                return buffer;
        }
    }

可以看出,处理缓存需要考虑到容量,扩容和位置记录等方面。 这里,readBuffer一直处于写模式。MyCat通过position(),还有get(data, 0, length)来读取数据。readBufferOffset用来记录每次读取的offset,需要设置为volatile。由于readBufferOffset只会单线程(绑定的RW线程)修改,但是会有多个线程访问(定时线程池的清理空闲连接的任务),所以设为volatile,不用CAS。这是一个经典的用volatile代替CAS实现多线程安全访问的场景。 MyCat的缓存管理思路很好,之后我会仔细讲。 读取完整包之后,交给handler处理。每种AbstractConnection的handler不同,FrontendConnection的handler为FrontendAuthenticator

this.handler = new FrontendAuthenticator(this);

我们思考下,FrontendConnection会接收什么请求呢?有两种,认证请求和SQL命令请求。只有认证成功后,才会接受SQL命令请求。FrontendAuthenticator只负责认证请求,在认证成功后,将对应AbstractConnection的handler设为处理SQL请求的FrontendCommandHandler即可. 一切正常的话,这里读取到的应为客户端发出的AuthPacket: AuthPacket:

  • packet length(3 bytes)

  • packet number (1)

  • client flags (4)

  • max packet (4)

  • charset(1)

  • username (null terminated string)

  • password (length code Binary)

  • database (null terminated string)

public void handle(byte[] data) {        // check quit packet
        if (data.length == QuitPacket.QUIT.length && data[4] == MySQLPacket.COM_QUIT) {
            source.close("quit packet");          
              return;
        }         AuthPacket auth = new AuthPacket();
        auth.read(data);        // check user
        if (!checkUser(auth.user, source.getHost())) {
            failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "' with host '" + source.getHost()+ "'");          
              return;
        }        // check password
        if (!checkPassword(auth.password, auth.user)) {
            failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "', because password is error ");            return;
        }        // check degrade
        if ( isDegrade( auth.user ) ) {
             failure(ErrorCode.ER_ACCESS_DENIED_ERROR, "Access denied for user '" + auth.user + "', because service be degraded ");             return;
        }        // check schema
        switch (checkSchema(auth.database, auth.user)) {       
         case ErrorCode.ER_BAD_DB_ERROR:
            failure(ErrorCode.ER_BAD_DB_ERROR, "Unknown database '" + auth.database + "'");   
                     break;        case ErrorCode.ER_DBACCESS_DENIED_ERROR:
            String s = "Access denied for user '" + auth.user + "' to database '" + auth.database + "'";
            failure(ErrorCode.ER_DBACCESS_DENIED_ERROR, s);          
              break;       
               default:            //认证成功,设置好用户数据库和权限等,将handler设置为FrontendCommandHandler
            success(auth);
        }
    }

认证成功后,会发送OkPacket,对应FrontendConnection的handler变成了FrontendCommandHandler,可以接受SQL请求了。 发送OkPacket的过程与HandshakePacket相同,这里不再赘述。

source.write(source.writeToBuffer(AUTH_OK, buffer));

OkPacket结构:

  • packet length(3 bytes)

  • packet number (1)

  • 0x00 (1,包体首字节为0)

  • affect rows (length code Binary)

  • insert id (length code Binary)

  • server status (2)

  • warning status (2)

  • message (length code Binary)

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

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

相关文章:
【推荐】 线上日志集中化可视化管理:ELK
【推荐】 Spring Boot 学习系列(02)—使用热部署,提升开发效

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

  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. new 和 make 均是用于分配内存

    the-way-to-go_ZH_CN/06.5.md at master · Unknwon/the-way-to-go_ZH_CN https://github.com/Unknwon/the-w ...

  2. 题解 P1001 【A+B Problem】

    #include<iostream> using namespace std; #define I int a,b; #define AK cin>>a>>b; # ...

  3. 《ASP.NET4从入门到精通》学习笔记2

    版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/dongdongdongJL/article/details/37610807   <ASP.N ...

  4. .htaccess技巧: URL重写(Rewrite)与重定向(Redirect) (转)

    目录 Table of Contents 一.准备开始:mod_rewrite 二.利用.htaccess实现URL重写(rewrite)与URL重定向(redirect) 将.htm页面映射到.ph ...

  5. Java基础教程:对象比较排序

    Java基础教程:对象比较排序 转载请标明出处:http://blog.csdn.net/wangtaocsdn/article/details/71500500 有时候需要对对象列表或数组进行排序, ...

  6. HDU - 1213 How Many Tables 【并查集】

    题目链接 http://acm.hdu.edu.cn/showproblem.php?pid=1213 题意 给出N个人 M对关系 找出共有几对连通块 思路 并查集 AC代码 #include < ...

  7. perl之创建临时文件夹遇到同名文件该咋办

    当你在目录下进行一系列操作时,若要创建许多文件或者修改文件,可能会遇到许多麻烦的事.所以呢,新建一个文件夹,然后在这个文件夹下新建文件或者修改文件.假设,你的代码要在一个目录下新建一个文件夹,名为Tm ...

  8. Django的基础操作总结

    1:准备开始 建立一个新的project: django-admin.py startproject XXXXXX(名称) 建立一个新的App:python manage.py startapp XX ...

  9. android自定义控件(四) View中的方法

    onFinishInflate() 当View中所有的子控件 均被映射成xml后触发 onMeasure(int, int) 确定所有子元素的大小 onLayout(boolean, int, int ...

  10. java自定义类型 作为HashMap中的Key值 (Pair<V,K>为例)

    由于是自定义类型,所以HashMap中的equals()方法和hashCode()方法都需要自定义覆盖. 不然内容相同的对象对应的hashCode会不同,无法发挥算法的正常功能,覆盖equals方法, ...