解读 MySQL Client/Server Protocol: Connection & Replication
MySQL 客户端与服务器之间的通信基于特定的 TCP 协议,本文将会详解其中的 Connection 和 Replication 部分,这两个部分分别对应的是客户端与服务器建立连接、完成认证鉴权,以及客户端注册成为一个 slave 并获取 master 的 binlog 日志。
Connetcion Phase
MySQL 客户端想要与服务器进行通信,第一步就是需要成功建立连接,整个过程如下图所示:
connection-phase
1.client 发起一个 TCP 连接。2.server 响应一个 Initial Handshake Packet
(初始化握手包),内容会包含一个默认的认证方式。3.这一步是可选的,双方建立 SSL 加密连接。4.client 回应 Handshake Response Packet
,内容需要包括用户名和按照指定方式进行加密后的密码数据。5.server 响应 OK_Packet
确认认证成功,或者 ERR_Packet
表示认证失败并关闭连接。
Packet
一个 Packet 其实就是一个 TCP 包,所有包都有一个最基本的结构:
packet
如上图所示,所有包都可以看作由 header 和 body 两部分构成:第一部分 header 总共有 4 个字节,3 个字节用来标识 body 即 payload 的大小,1 个字节记录 sequence ID;第二部分 body 就是 payload 实际的负载数据。
由于 payload length 只有 3 个字节来记录,所以一个 packet 的 payload 的大小不能超过 2^24 = 16 MB ,示例:
packet-example
Packet :
•当数据不超过 16 MB 时,准确来说是 payload 的大小不超过 2^24-1 Byte(三个字节所能表示的最大整数 0xFFFFFF),发送一个 packet 就够了。•当数据大小超过了 16 MB 时,就需要把数据切分成多个 packet 传输。•当数据 payload 的刚好是 2^24-1 Byte 时,一个包虽然足够了,但是为了表示数据传输完毕,仍然会多传一个 payload 为空的 packet 。
Sequence ID:包的序列号,从 0 开始递增。在一个完整的会话过程中,每个包的序列号依次加一,当开始一个新的会话时,序列号重新从 0 开始。例如:在建立连接的阶段,server 发送 Initial Handshake Packet( Sequence ID 为 0 ),client 回应 Handshake Response Packet( Sequence ID 为 1 ),server 再响应 OK_Packet 或者 ERR_Packet( Sequence ID 为 2 ),然后建立连接的阶段就结束了,再有后续的命令数据,包的 Sequence ID 就重新从 0 开始;在命令阶段(client 向 server 发送增删改查这些都属于命令阶段),一个命令的请求和响应就可以看作一个完整的会话过程,比如 client 先向 server 发送了一个查询请求,然后 server 对这个查询请求进行了响应,那么这一次会话就结束了,下一个命令就是新的会话,Sequence ID 也就重新从 0 开始递增。
Initial Handshake Packet
建立连接时,当客户端发起一个 TCP 连接后,MySQL 服务端就会回应一个 Initial Handshake Packet
,这个初始化握手包的数据格式如下图所示:
handshakeV10
这个图从上往下依次是:
•1 个字节的整数,表示 handshake protocol 的版本,现在都是 10 。•以 NUL(即一个字节 0x00)结尾的字符串,表示 MySQL 服务器的版本,例如 5.7.18-log
。•4 个字节的整数,表示线程 id,也是这个连接的 id。•8 个字节的字符串,auth-plugin-data-part-1
后续密码加密需要用到的随机数的前 8 位。•1 个字节的填充位。•2 个字节的整数,capability_flags_1
即 Capabilities Flags
的低位 2 位字节。•1 个字节的整数,表示服务器默认的字符编码格式,比如 utf8_general_ci
。•2 个字节的整数,服务器的状态标识。•2 个字节的整数,capability_flags_2
即 Capabilities Flags
的高位 2 位字节。•1 个字节的整数,如果服务器具有 CLIENT_PLUGIN_AUTH 的能力(其实就是能够进行客户端身份验证,基本都支持),那么传递的是 auth_plugin_data_len
即加密随机数的长度,否则传递的是 0x00 。•10 个字节的填充位,全部是 0x00 。•由 auth_plugin_data_len
指定长度的字符串,auth-plugin-data-part-2
加密随机数的后 13 位。•如果服务器具有 CLIENT_PLUGIN_AUTH 的能力(其实就是能够进行客户端身份验证,基本都支持),那么传递的是 auth_plugin_name
即用户认证方式的名称。
对于 MySQL 5.x 版本,默认的用户身份认证方式叫做 mysql_native_password
(对应上面的 auth_plugin_name
),这种认证方式的算法是:
SHA1( password ) XOR SHA1( "20-bytes random data from server" <concat> SHA1( SHA1( password ) ) )
其中加密所需的 20 个字节的随机数就是 auth-plugin-data-part-1
( 8 位数)和 auth-plugin-data-part-2
( 13 位中的前 12 位数)组成。
注意:MySQL 使用的小端字节序。
看到这,你可能还对 Capabilities Flags
感到很困惑。
Capabilities Flags
Capabilities Flags
其实就是一个功能标志,用来表明服务端和客户端支持并希望使用哪些功能。为什么需要这个功能标志?因为首先 MySQL 有众多版本,每个版本可能支持的功能有区别,所以服务端需要表明它支持哪些功能;其次,对服务端来说,连接它的客户端可以是各种各样的,这些客户端希望使用哪些功能也是需要表明的。
Capabilities Flags
一般是 4 个字节的整数:
capability
如上图所示,每个功能都独占一个 bit 位。
Capabilities Flags
通常都是多个功能的组合表示,例如要表示 CLIENT_PROTOCOL_41
、CLIENT_PLUGIN_AUTH
、CLIENT_SECURE_CONNECTION
这三个功能,那么就把他们对应的 0x00000200
、0x00080000
、0x00008000
进行比特位或运算就能得到最终的值 0x00088200
也就是最终的 Capabilities Flags
。
根据 Capabilities Flags
判断是否支持某个功能,例如 Capabilities Flags
的值是 0x00088200
,要判断它是否支持 CLIENT_SECURE_CONNECTION
的功能,则直接进行比特位与运算即可,即 Capabilities Flags
& CLIENT_SECURE_CONNECTION
== CLIENT_SECURE_CONNECTION
。