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面试大全
后端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核心知识整理