源码研究mycat之mysql通信协议篇之握手认证协议

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 selOps = null;

loop:

while(true) {

int n = selector.select();

selOps = selector.selectedKeys();

if(selOps == null || selOps.isEmpty()) {

continue;

}

try {

for(Iterator 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();

}

}

总结

以上是字节二面的一些问题,面完之后其实挺后悔的,没有提前把各个知识点都复习到位。现在重新好好复习手上的面试大全资料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等),现在起闭关修炼半个月,争取早日上岸!!!

下面给大家分享下我的面试大全资料

  • 第一份是我的后端JAVA面试大全

image.png

后端JAVA面试大全

  • 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

  • 第三份是Spring全家桶资料

字节二面拜倒在“数据库”脚下,闭关修炼半个月,我还有机会吗?

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理
tion 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();

}

}

总结

以上是字节二面的一些问题,面完之后其实挺后悔的,没有提前把各个知识点都复习到位。现在重新好好复习手上的面试大全资料(含JAVA、MySQL、算法、Redis、JVM、架构、中间件、RabbitMQ、设计模式、Spring等),现在起闭关修炼半个月,争取早日上岸!!!

下面给大家分享下我的面试大全资料

  • 第一份是我的后端JAVA面试大全

[外链图片转存中…(img-pKcYbxN9-1721193706948)]

后端JAVA面试大全

  • 第二份是MySQL+Redis学习笔记+算法+JVM+JAVA核心知识整理

[外链图片转存中…(img-xdtV4KXk-1721193706948)]

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

  • 第三份是Spring全家桶资料

[外链图片转存中…(img-LPlLrhyb-1721193706949)]

MySQL+Redis学习笔记算法+JVM+JAVA核心知识整理

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值