1、mysql 通信協定使用小端序列進行傳輸。
大端序列與小端序列:
- 小端法(Little-Endian)就是低位位元組排放在記憶體的低位址端即該值的起始位址,高位位元組排放在記憶體的高位址端。
- 大端法(Big-Endian)就是高位位元組排放在記憶體的低位址端即該值的起始位址,低位位元組排放在記憶體的高位址端。
通俗的講,小端法,接收方先接收到整數的低位部分。大端法,接收方先接收到正式的高位部分。
比如我們通過網絡發送 0x12345678 這個整數,在80X86平台中,它是以小端法存放的,在發送前需要使用系統提供的htonl将其轉換成大端法存放,如圖2所示。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN0LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CXuVzVhtmVyoFcSNzYspESjZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39zN5EDMygDN0EzNwQDM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
小端序列存儲如下:
大端序列存儲如下:
注意:大端序列、小端序列是針對數值型來說的,比如short、int、long等類型。
2、Mysql 握手驗證協定
用戶端首先發起 TCP 連接配接,連接配接服務端,TCP 經過三次握手協定之後,建立可靠的傳輸通道。
- 成功建立TCP之後,首先由Mysql伺服器發送一個握手包,包括協定版本号、伺服器版本号,伺服器授權認證資訊、伺服器權能辨別等等。
- 用戶端收到握手包後向服務端發送登入驗證封包,主要包括使用者名,資料庫名,密碼(密文)
- 伺服器向用戶端發送認證結果封包(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握手認證協定抓包:
握手包
認證包
響應包
源碼位址: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專欄