导读:
作者:曾永伟,知数堂10期学员,多年JAVA物流行业开发管理经验和PHP/Python跨境电商开发管理经验,对数据库系统情有独钟,善于运用SQL编程简化业务逻辑,去年开始正式从业MySQL DBA, 专注于DB系统自动化运维、MySQL云上实践。
本文为python-mysql-binlogserver系列的第二篇(T2通信篇),之后会陆续发布此系列的其他文章,请大家点击在看或者收藏,自行练习。
一、概述
从前一篇文章我们已经了解了:
-
二进制在计算机中的应用
-
字符集与二进制的关系及其在Python中处理
-
用struct来处理二进制的转换问题
-
Socket通信的基本编程方法
在本节中,我们将结合这些内容进一步来了解如何使Python和MySQL进行交互。
二、MySQL通信协议基础
在Python中,使用socket.secv接收数据或send发送数据的都是二进制流对象Bytes,我们需要结合MySQL通信协议来逐字节解析其具体的含义。
MySQL基础通信单位Packet,它由header + payload组成,header由3个字节的payload长度(最大16M字节数)和1个字节的流水号组成,在读取一个Packet时,通常先读4个字节,解析出payload的长度和payload的序号,再根据payload的长度把余下的正文读取出来。
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 3306))
'''
# 先读Header
header = s.recv(4)
length = struct.unpack('<I', header[:3] + b'\x00')[0]
sequenceId = struct.unpack('<B', header[:-1])[0]
# 再读余下的正文,完成一个Packet的完整读取
body = s.recv(length)
解析Greeting包
当Client连接上MySQL Server后,MySQL会主动发送一个greeting包,把自己的状态和随机密文发送给Client, 等待Client响应帐户和密码等信息,验证失败发送ERR包并主动断开连接,验证成功后发送OK包,保持连接,等待Client发送其它"指令"。
接下来我先看一下Greeting包正文的官方说明:
https://dev.mysql.com/doc/internals/en/connection-phase-packets.html
1 [0a] protocol version
string[NUL] server version
4 connection id
string[8] auth-plugin-data-part-1
1 [00] filler
2 capability flags (lower 2 bytes)
1 character set
2 status flags
2 capability flags (upper 2 bytes)
1 length of auth-plugin-data
string[10] reserved (all [00])
string[$len] auth-plugin-data-part-2 ($len=MAX(13, length of auth-plugin-data - 8))
string[NUL] auth-plugin name
为了更加直接的观察和理解,我这里去掉了对低版本的兼容格式,让其显得更加整洁。
我们来尝试用Python解析第一个Greeting包:
import socket
import struct
from pprint import pprint
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.56.101", 3306))
greeting = {}
# Header 3+1
greeting["length"] = struct.unpack('<I', s.recv(3) + b'\x00')[0]
greeting["sequenceId"] = struct.unpack('<B', s.recv(1))[0]
# 正文开始
greeting["protocolVersion"] = s.recv(1)
# serverVersion是string[NUL]类型,所以一直循环读取到\x00节束
greeting["serverVersion"] = ""
while True:
_byte = s.recv(1)
if _byte == b'\x00':
break
greeting["serverVersion"] += chr(int(_byte.hex(), 16))
# 余下部分请自行参照上方文档进行一一对照
greeting["connectionId"] = struct.unpack('<I', s.recv(4))[0]
greeting["challenge1"] = s.recv(8).decode("utf8")
_filler = s.recv(1)
greeting["capabilityFlags"] = s.recv(2)
greeting["characterSet"] = s.recv(1)
greeting["statusFlags"] = s.recv(2)
greeting["capabilityFlag"] = s.recv(2)
greeting["authPluginDataLength"] = struct.unpack('<B', s.recv(1))[0]
_reserved = s.recv(10)
greeting["challenge2"] = s.recv(max(13, greeting["authPluginDataLength"] - 8)).decode("utf8")
greeting["authPluginName"] = ""
while True:
_byte = s.recv(1)
if _byte == b'\x00':
break
greeting["authPluginName"] += chr(int(_byte.hex(), 16))
pprin