天天看點

源碼研究mycat之mysql通信協定篇之握手認證協定1、mysql 通信協定使用小端序列進行傳輸。2、Mysql 握手驗證協定3、模拟Mysql Client 實作Mysql握手認證互動過程

1、mysql 通信協定使用小端序列進行傳輸。

大端序列與小端序列:

  • 小端法(Little-Endian)就是低位位元組排放在記憶體的低位址端即該值的起始位址,高位位元組排放在記憶體的高位址端。 
  • 大端法(Big-Endian)就是高位位元組排放在記憶體的低位址端即該值的起始位址,低位位元組排放在記憶體的高位址端。

通俗的講,小端法,接收方先接收到整數的低位部分。大端法,接收方先接收到正式的高位部分。

比如我們通過網絡發送 0x12345678 這個整數,在80X86平台中,它是以小端法存放的,在發送前需要使用系統提供的htonl将其轉換成大端法存放,如圖2所示。

源碼研究mycat之mysql通信協定篇之握手認證協定1、mysql 通信協定使用小端序列進行傳輸。2、Mysql 握手驗證協定3、模拟Mysql Client 實作Mysql握手認證互動過程

小端序列存儲如下:               

源碼研究mycat之mysql通信協定篇之握手認證協定1、mysql 通信協定使用小端序列進行傳輸。2、Mysql 握手驗證協定3、模拟Mysql Client 實作Mysql握手認證互動過程

大端序列存儲如下:

源碼研究mycat之mysql通信協定篇之握手認證協定1、mysql 通信協定使用小端序列進行傳輸。2、Mysql 握手驗證協定3、模拟Mysql Client 實作Mysql握手認證互動過程

注意:大端序列、小端序列是針對數值型來說的,比如short、int、long等類型。

2、Mysql 握手驗證協定

用戶端首先發起 TCP 連接配接,連接配接服務端,TCP 經過三次握手協定之後,建立可靠的傳輸通道。

  1. 成功建立TCP之後,首先由Mysql伺服器發送一個握手包,包括協定版本号、伺服器版本号,伺服器授權認證資訊、伺服器權能辨別等等。
  2. 用戶端收到握手包後向服務端發送登入驗證封包,主要包括使用者名,資料庫名,密碼(密文)
  3. 伺服器向用戶端發送認證結果封包(OK Package或 Error Package)或其他響應結果。

2.1 封包結構

mysql通信封包分為 消息頭與消息體兩部分。

其中消息頭固定4個位元組,前個位元組為消息體的長度,1個處理序列号。

2.2 Mysql 基本類型

2.2.1 整數值

Mysql封包中整數值分别有 1、2、3、4、8位元組長度,使用小端序列傳輸。(接收方先接收到 整數的低位部分)。

2.2.2 字元串(以Null結尾 0x00)(Null-Terminated String)

字元串長度不固定,當遇到'NULL'(0x00) 字元時結束。

2.2.3 二進制資料(長度編碼)(Length Coded Binary)

資料長度不固定,位元組數由第一個位元組決定。

第一個位元組值 後續位元組數 長度值說明
0-250 第一個位元組值即為資料的真實長度
251 空資料,資料的真實長度為零
252 2 後續額外2個位元組辨別了資料的真實長度
253 3 後續額外3個位元組辨別了資料的真實長度
254 8 後續額外8個位元組辨別了資料的真實長度

2.2.4 字元串(長度編碼)(Length Coded String)

字元串長度不固定,無'NULL'(0x00)的介紹符,編碼方式與上面的Length Code Binary。

2.3 協定描述mysql通信協定描述

Type Description
int<1> 1 byte Protocol::FixedLengthInteger
int<2> 2 byte Protocol::FixedLengthInteger
int<3> 3 byte Protocol::FixedLengthInteger
int<4> 4 byte Protocol::FixedLengthInteger
int<6> 6 byte Protocol::FixedLengthInteger
int<8> 8 byte Protocol::FixedLengthInteger
int<lenenc> Protocol::LengthEncodedInteger
string<lenenc> Protocol::LengthEncodedString
string<fix> Protocol::FixedLengthString
string<var> Protocol::VariableLengthString:
string<EOF> Protocol::RestOfPacketString
string<NUL> Protocol::NulTerminatedString

3、模拟Mysql Client 實作Mysql握手認證互動過程

參考資料:

  • mysql協定分析中文版 http://www.cnblogs.com/davygeek/p/5647175.html
  • mysql官方文檔:https://dev.mysql.com/doc/internals/en/client-server-protocol.html

Mysql握手認證協定抓包:

源碼研究mycat之mysql通信協定篇之握手認證協定1、mysql 通信協定使用小端序列進行傳輸。2、Mysql 握手驗證協定3、模拟Mysql Client 實作Mysql握手認證互動過程

 握手包

源碼研究mycat之mysql通信協定篇之握手認證協定1、mysql 通信協定使用小端序列進行傳輸。2、Mysql 握手驗證協定3、模拟Mysql Client 實作Mysql握手認證互動過程

認證包

源碼研究mycat之mysql通信協定篇之握手認證協定1、mysql 通信協定使用小端序列進行傳輸。2、Mysql 握手驗證協定3、模拟Mysql Client 實作Mysql握手認證互動過程

響應包

源碼位址:https://git.oschina.net/zhcsoft/StudyDemo.git   包路徑:persistent.prestige.console.mysql

源碼核心類如下:

3.1 握手包Handshake10Packet 

package persistent.prestige.console.mysql.protocol;

/*
 * 伺服器--》用戶端  握手資料包
 * mysql4.1之後
 * 
 * mysql封包頭格式固定為4位元組:3位元組資料封包長度,1位元組序号
 * 
 * 封包體
 *    1                 協定版本号
 *    string[NUL]       伺服器版本資訊,(null-Termimated String)
 *    4                 伺服器線程ID
 *    string[8]         随機挑戰數(auth-plugin-data-part-1)
 *    1                 填充值0x00 filler
 *    2                 伺服器權能标志(capability flags)(lower 2 bytes)
 *    if more data in the packet:
 *    1                 character set
 *    2                 status flags
 *    2                 capability flags (upper 2 bytes)
      if capabilities & CLIENT_PLUGIN_AUTH {
 *    1                 length of auth-plugin-data
      } else {
      1                 [00] filler
      }
 *    string[10]     reserved (all [00])
      if capabilities & CLIENT_SECURE_CONNECTION {
         string[$len]   auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8))
      if capabilities & CLIENT_PLUGIN_AUTH {
          string[NUL]    auth-plugin name
      }
 *                      
 */
@SuppressWarnings("serial")
public class Handshake10Packet extends Packet {

    // 協定版本号
    private byte protocolVersion;
    private byte[] serverVersion;
    private int connectionId;
    private byte[] authPluginDataPart1;
    private int serverCapability;
    private byte characterSet;
    private int serverStatus;
    private byte[] authPluginDataPart2;
    private byte[] authPluginName;

    private Handshake10Packet() {}


    /**
     * 根據msg解析出該包
     * @param msg
     * @return
     */
    public static final Handshake10Packet newInstance(MysqlMessage msg) {
        int recvBufferCapacity = msg.remaining();

        int dataLen = 0;
        if(recvBufferCapacity > 3) {
            dataLen = msg.getPacketLength();
        }

        if(recvBufferCapacity < HEAD_LENGTH + dataLen) { //不是一個完整的包名
            return null;
        }

        Handshake10Packet packet = new Handshake10Packet();
        msg.skipReadBytes(4);
        packet.setProtocolVersion(msg.get());
        packet.setServerVersion(msg.readNullTerminatedString());
        packet.setConnectionId(msg.getInt());
        byte[] authPluginDataPart1 = new byte[8];
        msg.get(authPluginDataPart1);
        packet.setAuthPluginDataPart1(authPluginDataPart1);

        msg.skipReadBytes(1);
        //兩個位元組的 capability flags
        packet.setServerCapability(msg.getUB2());//低位兩位元組的伺服器權能辨別符

        if(!msg.hasRemaining()) { //還有可讀位元組,繼續解析
            return packet;
        }

        packet.setCharacterSet(msg.get()); //伺服器編碼
        packet.setServerStatus(msg.getUB2());
        int high = msg.getUB2();//伺服器權能标志,高16位置
        int serverCapability = packet.getServerCapability() | ( high << 16 );
        packet.setServerCapability(serverCapability);

        int authPluginDataLen = 0;
        if( (serverCapability & CLIENT_PLUGIN_AUTH) != 0) {
            authPluginDataLen = msg.get();
        } else {
            msg.skipReadBytes(1);
        }

        msg.skipReadBytes(10);//10個填充字元

        if((serverCapability & CLIENT_SECURE_CONNECTION) != 0) {
            authPluginDataLen = Math.max( 13 , authPluginDataLen - 8);
            byte[] authPluginDataPart2 = new byte[authPluginDataLen];
            msg.get(authPluginDataPart2);
            packet.setAuthPluginDataPart2(authPluginDataPart2);
        }

        if( (serverCapability & CLIENT_PLUGIN_AUTH) != 0) {
            packet.setAuthPluginName( msg.readNullTerminatedString() );
        }

        return packet;
    }







    @Override
    public int hashCode() {
        // TODO Auto-generated method stub
        return super.hashCode();
    }

    @Override
    public String toString() {
        StringBuilder str = new StringBuilder(100);
        str.append("協定版本号:").append(protocolVersion).append("\n")
            .append("伺服器版本資訊:").append(new String(serverVersion)).append("\n")
            .append("伺服器連接配接線程ID:").append(connectionId).append("\n")
            .append("capabilityFlag:").append( Integer.toHexString(serverCapability)).append("\n")
            .append("serverCharact:").append(characterSet).append("\n")
            .append("statusFlags:").append(Integer.toHexString(serverStatus)).append("\n");

        if(authPluginDataPart1 != null && authPluginDataPart1.length > 0 ) {
            str.append("authPluginDataPart1:").append(new String(authPluginDataPart1)).append("\n");
        }

        if(authPluginDataPart2 != null && authPluginDataPart2.length > 0 ) {
            str.append("authPluginDataPart2:").append(new String(authPluginDataPart2)).append("\n");
        }

        if(authPluginName != null && authPluginName.length > 0 ) {
            str.append("authPluginName:").append(new String(authPluginName)).append("\n");
        }




//            .append("authPluginName:").append(new String(authPluginName));
        return str.toString();
    }

    public byte getProtocolVersion() {
        return protocolVersion;
    }

    public void setProtocolVersion(byte protocolVersion) {
        this.protocolVersion = protocolVersion;
    }

    public byte[] getServerVersion() {
        return serverVersion;
    }

    public void setServerVersion(byte[] serverVersion) {
        this.serverVersion = serverVersion;
    }

    public int getConnectionId() {
        return connectionId;
    }

    public void setConnectionId(int connectionId) {
        this.connectionId = connectionId;
    }

    public byte[] getAuthPluginDataPart1() {
        return authPluginDataPart1;
    }

    public void setAuthPluginDataPart1(byte[] authPluginDataPart1) {
        this.authPluginDataPart1 = authPluginDataPart1;
    }

    public int getServerCapability() {
        return serverCapability;
    }

    public void setServerCapability(int serverCapability) {
        this.serverCapability = serverCapability;
    }

    public byte getCharacterSet() {
        return characterSet;
    }

    public void setCharacterSet(byte characterSet) {
        this.characterSet = characterSet;
    }


    public int getServerStatus() {
        return serverStatus;
    }

    public void setServerStatus(int serverStatus) {
        this.serverStatus = serverStatus;
    }

    public byte[] getAuthPluginDataPart2() {
        return authPluginDataPart2;
    }

    public void setAuthPluginDataPart2(byte[] authPluginDataPart2) {
        this.authPluginDataPart2 = authPluginDataPart2;
    }

    public byte[] getAuthPluginName() {
        return authPluginName;
    }

    public void setAuthPluginName(byte[] authPluginName) {
        this.authPluginName = authPluginName;
    }

}
           

4.2 認證包 

package persistent.prestige.console.mysql.protocol;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SocketChannel;

import persistent.prestige.console.mysql.MysqlClient;
import persistent.prestige.console.mysql.utils.SecurityUtil;
import persistent.prestige.console.mysql.utils.SeqUtils;

/**
 * 驗證資料包
 * 用戶端在收到服務端的握手協定後,需要向伺服器驗證權限(使用者名進行登入)
 * @author dingwei2
 * 
 * Protocol::HandshakeResponse41:
 * 4              capability flags, CLIENT_PROTOCOL_41 always set
   4              max-packet size
   1              character set
string[23]       reserved (all [0])
string[NUL]      username
  if capabilities & CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA {
    lenenc-int     length of auth-response
    string[n]      auth-response
  } else if capabilities & CLIENT_SECURE_CONNECTION {
  1              length of auth-response
string[n]      auth-response
  } else {
string[NUL]    auth-response
  }
  if capabilities & CLIENT_CONNECT_WITH_DB {
string[NUL]    database
  }
  if capabilities & CLIENT_PLUGIN_AUTH {
string[NUL]    auth plugin name
  }
  if capabilities & CLIENT_CONNECT_ATTRS {
lenenc-int     length of all key-values
lenenc-str     key
lenenc-str     value
   if-more data in 'length of all key-values', more keys and value pairs
  }
 *
 */
public class Auth41Packet extends Packet {

    private Handshake10Packet handshake10Packet;

    private int capabilityFlags;
    private int maxPacketSize = 1 << 31 - 1;
    private byte characterSet;
    private final static byte[] reserved = new byte[23];
    private byte[] username;

    private byte authResponseLen;
    private byte[] authResponse;
    private byte[] database = MysqlClient.DB.getBytes();
    private byte[] authPluginName;

    static {

        for (int i = 0; i < 23; i++) {
            reserved[i] = FILLER;
        }
    }

    private Auth41Packet(Handshake10Packet handshake10Packet) {
        this.handshake10Packet = handshake10Packet;
    }

    public static final Auth41Packet newInstance(Handshake10Packet handshake10Packet) {
        Auth41Packet packet = new Auth41Packet(handshake10Packet);
        packet.setCapabilityFlags(getCapabilities());
        // packet.setCapabilityFlags(handshake10Packet.getServerCapability());
        packet.setCharacterSet(handshake10Packet.getCharacterSet());
        packet.setUsername(MysqlClient.USERNAME.getBytes());

        try {
            if (handshake10Packet.getAuthPluginDataPart2() == null) {
                packet.setAuthResponse(SecurityUtil.scramble411_2(MysqlClient.PWD.getBytes("UTF-8"),
                        handshake10Packet.getAuthPluginDataPart1()));
            } else {
                final byte[] auth1 = handshake10Packet.getAuthPluginDataPart1();
                final byte[] auth2 = handshake10Packet.getAuthPluginDataPart2();

                byte[] seed = new byte[auth1.length + auth2.length - 1];
                System.arraycopy(auth1, 0, seed, 0, auth1.length);
                System.arraycopy(auth2, 0, seed, auth1.length, auth2.length - 1);

                // 關于seed 為什麼隻取auth2的 長度-1,是因為
                // Due to Bug#59453 the auth-plugin-name is missing the
                // terminating NUL-char
                // in versions prior to 5.5.10 and 5.6.2.;
                // 由于本示例代碼的目的是為了學習mysql通信協定,是以這裡就不做版本方面的相容了。直接取auth2
                // 0-length-1個位元組參與密碼的加密

                byte[] authResponse = SecurityUtil.scramble411_2(MysqlClient.PWD.getBytes(), seed);
                packet.setAuthResponse(authResponse);
                packet.setAuthResponseLen((byte) authResponse.length);
            }

        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        packet.setAuthPluginName(handshake10Packet.getAuthPluginName());

        return packet;

    }

    public int getPacketLength() {
        // 32 +
        int len = 32;
        if (username != null) {
            len += username.length + 1;// 1位元組填充
        }

        if (authResponseLen > 0) {
            len += 1 + authResponseLen; // 1位元組填充
        }

        if (database != null) {
            len += database.length + 1; // 1位元組填充
        }

        if (authPluginName != null) {
            len += authPluginName.length + 1; // 1位元組填充
        }

        return len;
    }

    /**
     * 
     * @param channel
     * @return
     */
    public int write(SelectableChannel channel) throws IOException {
        int packetLen = this.getPacketLength() + HEAD_LENGTH;
        byte seq = SeqUtils.getSeq(channel);

        ByteBuffer buf = ByteBuffer.allocate(packetLen);

        MysqlMessage msg = new MysqlMessage(buf);
        msg.putUB3(packetLen - HEAD_LENGTH);
        msg.put(seq); // 標頭 3位元組長度 + 1 位元組序列号
        msg.putInt(this.getCapabilityFlags());
        msg.putInt(this.maxPacketSize);
        msg.put(this.getCharacterSet());
        msg.putBytes(reserved);
        msg.putBytes(this.username);
        msg.put(FILLER);
        msg.putBytes(this.authResponse);
        msg.put(FILLER);
        msg.putBytes(this.database);
        msg.put(FILLER);
        msg.putBytes(authPluginName);
        msg.put(FILLER);
        msg.flip();
        SocketChannel c = (SocketChannel) channel;
        return c.write(msg.nioBuffer());
    }

    public static final int getCapabilities() {
        return CLIENT_LONG_PASSWORD | CLIENT_FOUND_ROWS | CLIENT_CONNECT_WITH_DB |
        // CLIENT_COMPRESS ,壓縮協定,為了簡單,暫不開啟
                CLIENT_LOCAL_FILES | CLIENT_IGNORE_SPACE | CLIENT_PROTOCOL_41 | CLIENT_INTERACTIVE
                | CLIENT_IGNORE_SIGPIPE | CLIENT_TRANSACTIONS |
                // CLIENT_SECURE_CONNECTION |
                // CLIENT_MULTI_STATEMENTS |
                CLIENT_MULTI_RESULTS | CLIENT_PS_MULTI_RESULTS | CLIENT_PLUGIN_AUTH;
        // CLIENT_CONNECT_ATTRS |
        // CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS |
        // CLIENT_SESSION_TRACK |
        // CLIENT_DEPRECATE_EOF;
    }

    public Handshake10Packet getHandshake10Packet() {
        return handshake10Packet;
    }

    public void setHandshake10Packet(Handshake10Packet handshake10Packet) {
        this.handshake10Packet = handshake10Packet;
    }

    public int getCapabilityFlags() {
        return capabilityFlags;
    }

    public void setCapabilityFlags(int capabilityFlags) {
        this.capabilityFlags = capabilityFlags;
    }

    public int getMaxPacketSize() {
        return maxPacketSize;
    }

    public void setMaxPacketSize(int maxPacketSize) {
        this.maxPacketSize = maxPacketSize;
    }

    public byte getCharacterSet() {
        return characterSet;
    }

    public void setCharacterSet(byte characterSet) {
        this.characterSet = characterSet;
    }

    public byte[] getUsername() {
        return username;
    }

    public void setUsername(byte[] username) {
        this.username = username;
    }

    public static byte[] getReserved() {
        return reserved;
    }

    public byte getAuthResponseLen() {
        return authResponseLen;
    }

    public void setAuthResponseLen(byte authResponseLen) {
        this.authResponseLen = authResponseLen;
    }

    public byte[] getAuthResponse() {
        return authResponse;
    }

    public void setAuthResponse(byte[] authResponse) {
        this.authResponse = authResponse;
    }

    public byte[] getDatabase() {
        return database;
    }

    public void setDatabase(byte[] database) {
        this.database = database;
    }

    public byte[] getAuthPluginName() {
        return authPluginName;
    }

    public void setAuthPluginName(byte[] authPluginName) {
        this.authPluginName = authPluginName;
    }

}
           

4.3 mysql client

package persistent.prestige.console.mysql;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import persistent.prestige.console.mysql.connection.Connection;
import persistent.prestige.console.mysql.connection.ConnectionFactory;
import persistent.prestige.console.mysql.protocol.Auth41Packet;
import persistent.prestige.console.mysql.protocol.ErrorPacket;
import persistent.prestige.console.mysql.protocol.Handshake10Packet;
import persistent.prestige.console.mysql.protocol.MysqlMessage;
import persistent.prestige.console.mysql.protocol.OkPacket;

public class MysqlClient {

    public static final String MYSQL_HOST = "10.2.35.23";
    public static final int MYSQL_PORT = 3306;


    public static final String DB = "demo";
    public static final String USERNAME = "peter";
    public static final String PWD = "peter";


    private Map<SocketChannel, Connection> connMap = new HashMap<SocketChannel, Connection>();


    public static void main(String[] args) {
        MysqlClient client = new MysqlClient();
        (new Thread(client.new Client())).start();
    }

    private class Client implements Runnable {

        @Override
        public void run() {
            // TODO Auto-generated method stub
            Selector selector = null;
            SocketChannel scc = null;
            try {
                selector = Selector.open();
                scc = SocketChannel.open();
                scc.configureBlocking(false);
                scc.register(selector, SelectionKey.OP_CONNECT);

                scc.connect(new InetSocketAddress(MYSQL_HOST, MYSQL_PORT)); 

                Set<SelectionKey> selOps = null;

                loop:
                while(true) {
                    int n = selector.select();
                    selOps = selector.selectedKeys();
                    if(selOps == null || selOps.isEmpty()) {
                        continue;
                    }

                    try {
                        for(Iterator<SelectionKey> it = selOps.iterator(); it.hasNext(); ) {
                            SelectionKey key = it.next();
                            if(!key.isValid()) {
                                key.cancel();
                            }

                            if(key.isReadable()) { //可讀
                                System.out.println("讀事件觸發");
                                SocketChannel sc = (SocketChannel)key.channel();
                                ByteBuffer recvBuffer = ByteBuffer.allocate(1024); // 固定1024位元組用來接收資料
                                int r = 0;
                                int remaining = recvBuffer.remaining();
                                int localRead = 0;
                                while( (r = sc.read(recvBuffer) ) > 0 ) {   //一次性讀完通道資料
                                    remaining -= r;
                                    localRead += r;
                                    if(r > 0 && remaining == 0 ) { //接收緩存區不足,擴容一倍
                                        ByteBuffer tempBuf = ByteBuffer.allocate( recvBuffer.capacity() << 1  );
                                        recvBuffer.flip();//變成讀模式
                                        tempBuf.put(recvBuffer);
                                        remaining = recvBuffer.remaining();
                                        recvBuffer = tempBuf;
                                    }
                                }


                                System.out.println("可讀資料:" + localRead);
                                if(r == -1 && localRead < 1) { //鍊路關閉了
                                    System.out.println("收到位元組為-1,服務端關閉連接配接");
                                    break loop;
                                }

                                Connection conn = getConn(sc);

                                if(!conn.isHandshake()) { //未驗證,發送握手協定包
                                    //開始解析服務端發送過來的握手協定
                                    recvBuffer.flip();//變成可讀模式
                                    Handshake10Packet handshkakePacket = Handshake10Packet.newInstance( new MysqlMessage(recvBuffer));
                                    System.out.println(handshkakePacket);
                                    if(handshkakePacket != null && !recvBuffer.hasRemaining()) { //如果解析出完整的包,并且recvBuffer
                                        //取消讀事件
                                        clearOpRead(key);   //這裡不考慮隻接收到一半的資料包,繼續下一次包解析,本示例主要關注的點mysql通信協定
                                    }

                                    //注冊寫事件
                                    key.attach(handshkakePacket);
                                } else if (!conn.isAuth()) { // 未成功授權,嘗試解析服務端包
                                    //開始解析伺服器授權響應封包
                                    recvBuffer.flip();//變成可讀模式
                                    MysqlMessage msg = new MysqlMessage(recvBuffer);

                                    int packetLen = msg.getPacketLength();
                                    byte packetSeq = msg.getPacketSeq();

                                    short pType = msg.getPTypeByFrom1Byte();

                                    System.out.println("資料包類型:" + pType);

                                    if(pType == 0) { //OK資料包  //此處不考慮其他情況
                                        OkPacket ok = OkPacket.newInstance(msg, packetSeq, packetLen);
                                        System.err.println(ok); 

                                        conn.setAuth(true);
                                        System.out.println("成功通過驗證");
                                        //接下來,取消讀事件,開始發送指令給服務端,-----測試mysql的請求指令。
                                        clearOpRead(key);

                                        //目前暫時退出用戶端
                                        break loop;

                                    } else if(pType == 0xFF) { // error 包
                                        System.out.println("錯誤包");
                                        ErrorPacket errorPacket = ErrorPacket.newInstance(msg, packetSeq, packetLen);
                                        System.out.println(errorPacket);

                                        //然後退出 用戶端
                                        break loop;

                                    } else { 
                                        System.out.println("收到暫不支援的包,将退出");
                                        break loop;
                                    }


                                } else { //其他響應包

                                }


                                addOpWrite(key);

                            } else if(key.isWritable()) { // 可寫
                                System.out.println("寫事件觸發");

                                SocketChannel sc = (SocketChannel)key.channel();
                                Connection conn = getConn(sc);

                                Object attachment = key.attachment();
                                if( attachment instanceof Handshake10Packet ) {
                                    Handshake10Packet handshkakePacket = (Handshake10Packet) attachment;
                                    Auth41Packet handshakeResponse = Auth41Packet.newInstance(handshkakePacket);
                                    int wc = handshakeResponse.write(key.channel());
                                    System.out.println("寫入通道資料:" + wc + "位元組");
                                    clearOpWrite(key);

                                    conn.setHandshake(true);
                                }


                                addOpRead(key);

                            } else if(key.isConnectable()) {
                                if(scc.isConnectionPending()) {
                                    scc.finishConnect();
                                    System.out.println("完成tcp連接配接");
                                }
                                scc.register(selector, SelectionKey.OP_READ);
                            }

                            it.remove();

                        }
                    } catch (Throwable e) {
                        e.printStackTrace();
                    } 




                } 




            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();

            } finally {
                if(scc != null) {
                    try {
                        scc.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                if(selector != null) {
                    try {
                        selector.close();
                    } catch (IOException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }

                System.out.println("鍊路關閉");
            }
        }

    }

    public Connection getConn(SocketChannel sc) {
        if(connMap.containsKey(sc)) {
            return connMap.get(sc);
        }

        Connection conn = ConnectionFactory.getConnection(sc);
        connMap.put(sc, conn);
        return conn;
    }

    public static void addOpRead(SelectionKey key) {
        key.interestOps(  key.interestOps() | SelectionKey.OP_READ  );
    }

    public static void clearOpRead(SelectionKey key) {
        key.interestOps(  key.interestOps() & ~SelectionKey.OP_READ  );
    }

    public static void clearOpWrite(SelectionKey key) {
        key.interestOps(  key.interestOps() & ~SelectionKey.OP_WRITE  );
    }

    public static void addOpWrite(SelectionKey key) {
        key.interestOps(  key.interestOps() | SelectionKey.OP_WRITE  );
    }

}
           

遇到的坑

1、服務端傳回的握手包的認證資訊中,如果伺服器版本是5.5.10-5.6.2,如果服務版本是上述版本的話,在認證包對密碼加密的時候,隻需要擷取字段中(1-12個位元組,第13個位元組忽略),不然總是提示使用者名密碼錯誤。

Due to Bug#59453 the auth-plugin-name is missing the terminating NUL-char in versions prior to 5.5.10 and 5.6.2.

源碼分析mycat之通信協定篇,本文首先簡單介紹解析Mysql通信協定的基礎知識接着重點講解如何利用java nio 模拟mysql用戶端實作與服務端的握手認證封包互動過程。

歡迎加筆者微信号(dingwpmz),加群探讨,筆者優質專欄目錄:

1、源碼分析RocketMQ專欄(40篇+)

2、源碼分析Sentinel專欄(12篇+)

3、源碼分析Dubbo專欄(28篇+)

4、源碼分析Mybatis專欄

5、源碼分析Netty專欄(18篇+)

6、源碼分析JUC專欄

7、源碼分析Elasticjob專欄

8、Elasticsearch專欄

9、源碼分析Mycat專欄

繼續閱讀