日期:
2007-05-09
翻译:
gashero <harry.python@gmail.com>
原文:
http://forge.mysql.com/wiki/MySQL_Internals_ClientServer_Protocol
目录
- 1 许可协议
- 2 组织
- 3 元素
- 4 包头
- 5 包类型
- 6 握手初始化包
- 7 客户端认证包
- 8 密码功能
- 9 命令包
- 10 命令包参数
- 11 返回结果包的类型
- 12 OK包
- 13 错误包
- 14 结果集包头
- 15 字段包
- 16 EOF包
- 17 行数据包
- 18 行数据包:二进制(实验性描述)
- 19 准备语句初始化包(实验性描述)
- 20 准备语句初始化包的OK包(实验性描述)
- 21 参数包(实验性描述)
- 22 长数据包(实验性描述)
- 23 执行包(实验性描述)
- 24 压缩
1 许可协议
MySQL的协议是专有的。
MySQL协议是MySQL DBMS的一部分。受到GPL协议保护,可以在MySQL网站上查看GPL协议的副本,产品下载中也有。
因为使用GPL协议,任何使用它连接到MySQL服务器,或者模拟MySQL服务器,或者在客户端和服务器之间提供中转协议,或者任何相似的目的,都在GPL范围之内。因此,如果你使用本文档的内容编写程序,你必须使你的程序在GPL许可之内。联系MySQL AB,如果你需要商议更多细节。
2 组织
本节讲解MySQL 5.0的C/S逻辑通信方式。
这是逻辑包描述,所以只会有很少的非逻辑描述,如物理结构,传输,缓存和压缩。如果你对这些感兴趣,可以在MySQL的文档版本库mysqldoc的internals目录的 net_doc.txt 文件查看 “MySQL Client – Server Protocol Document”。
这些文档在编写时是针对5.0版本的。大部分例子是基于4.1版本,当然,从4.1到5.0的修改很小。
具有代表性的包包括:
- “字节数和名称”。这是用于快速了解包中各个字段的大小和标志、顺序的方法。”字节”字段包含以字节为单位的长度。”名称”字段包含MySQL代码中使用的名字。因为4.0和4.1的认证方式有很大的不同,所以文中会展示2种格式。
- 每个字段的描述。包括文本注释和关于使用的重要内容。
- (如果必要)取值范围。文档中的命名一塌糊涂,你会经常发现一件事物的多个不同叫法。
- (如果必要)在MySQL代码中引用的头文件。例如 sql/http://protocol.cc net_store_length() 意味着”在sql子目录的http://protocol.cc文件,函数为net_store_length”。
例如,所有的例子都有如下3个字段:
字段名
十六进制描述
ascii描述,如果包含字符数据
所有返回数据都为特别的目的格式化成了十六进制。
最后一段是关于准备语句的,其中的文档可能并不是完全正确的,而且不包含例子。
3 元素
NULL终止字符串:用于一些变长字符串。值 ‘0’ (也可以写作0x00) 标志着字符串的结束。
长度编码为二进制长度编码,计算二进制编码的长度需要先检查值的第一个字节:
首字节值
随后字节数
描述
0-250
0
=首字节的值
251
0
列值为NULL,仅用于行数据包
252
2
随后的值为16bit字
253
4
随后的值为32bit字
254
8
随后的值为64bit字
如上介绍二进制长度编码,是包括首字节的,可能范围从1个到9个字节。相关的MySQL代码为 sql/http://protocol.cc net_store_length() 。
所有的数字都至少有一个必须的字节,所有的数字都是无符号的。
带长度字符串:变长字符串。用于替换NULL终止字符串,就是那种可能以 ‘0’ 结尾的超长的字符串。带长度字符串的第一个部分是二进制长度编码;第二部分是实际数据。一个例子是一个很短的十六进制3字节: 02 61 62 ,代表着 “长度=2,内容=’ab'”。
4 包头
字节数
名字
3
包长度
1
包序列号
包长度 :以字节为单位的包长度。包含一些重要的字节,最大允许为2**24=16MB长的数据包。
包序列号 :用于包排序的序列号。客户端查询的第一个包会生成一个序列号。如果开始了另外一个SQL语句,则会生成另外一个包序列号。
下面的包描述不会包含包头。可以认为它一直在那里。但是在逻辑上可以认为是它在包之前,而不是包含在包里面。
包序列号是一个从0开始的数字,其中握手初始化包的序列号为0,客户端认证包的ID为1,之后的顺次增加。
其他叫法:包长度也可以叫做”packetsize”。包序列号也叫做”Packet no”。
涉及到的MySQL代码: include/my_global.h int3store() sql/http://net_serv.cc my_net_write(), net_flush(), net_write_command(), my_net_read() 。
5 包类型
一个常见的会话过程:
握手(当客户端连接时):
服务器->客户端: 握手初始化包
客户端->服务器: 客户端认证包
服务器->客户端:OK包或Error包
命令(客户端希望服务器的动作):
客户端->服务器:命令包
服务器->客户端:OK包或Error包
本章剩余章节将会介绍每一种包的结构。
其他叫法:握手也叫做”Client login”或者”login procedure”或”connecting”。
6 握手初始化包
从服务器到客户端的初始化握手:
字节数
名称
1
协议版本 protocol_version
n(NULL终止字符串)
服务器版本 server_version
4
线程ID thread_id
8
杂乱缓存 scramble_buff
1
分隔符,总是0x00
2
服务器功能选项 server_capabilities
1
服务器语言 server_language
2
服务器状态 server_status
13
分隔符,总是0x00
13
剩余的杂乱缓存(4.1)
协议版本:服务器从 /include/mysql_version.h 中的 PROTOCOL_VERSION 获取,例如10。
服务器版本:服务器从 /include/mysql_version.h 中的 MYSQL_SERVER_VERSION 获取,例如 4.1.1-alpha 。我的是 4.1.20-log 。
线程号:服务器上用于处理这个连接的线程ID。
杂乱缓存:供密码机制使用,包含第二部分的13字节。参考 密码功能 。
服务器功能选项:CLIENT_XXX选项,这在写入时是非常重要的,其值来自 include/mysql_com.h
标志
数字
描述
CLIENT_LONG_PASSWORD
1
新版安全密码
CLIENT_FOUND_ROWS
2
发现了受影响的行
CLIENT_LONG_FLAG
4
获取所有列标志
CLIENT_CONNECT_WITH_DB
8
在连接时指定数据库
CLIENT_NO_SCHEMA
16
不允许database.table.column
CLIENT_COMPRESS
32
启用协议压缩
CLIENT_ODBC
64
ODBC客户端
CLIENT_LOCAL_FILES
128
可以使用LOAD DATA LOCAL
CLIENT_IGNORE_SPACE
256
忽略'(‘之前的空格
CLIENT_PROTOCOL_41
512
新的4.1协议
CLIENT_INTERACTIVE
1024
这是一个交互客户端
CLIENT_SSL
2048
握手之后转换到SSL
CLIENT_IGNORE_SIGPIPE
4096
忽略sigpipe,认证管道?
CLIENT_TRANSACTIONS
8192
客户端需要使用事务
CLIENT_RESERVED
16384
4.1版本协议的久标志
CLIENT_SECURE_CONNECTION
32768
新的4.1版本认证
CLIENT_MULTI_STATEMENTS
65536
启用/禁用multi-stmt支持
CLIENT_MULTI_RESULTS
131072
启用/禁用multi-results支持
服务器语言:当前服务器所用字符集数字。
服务器状态:SERVER_STATUS_xxx标志,例如:SERVER_STATUS_AUTOCOMMIT
其他叫法:握手初始化包也叫做”greeting package”。协议版本也叫做”Prot. version”。服务器版本也叫做”Server Version String”。线程号也叫做”Thread Number”。当前服务器字符集好吗也叫做”charset_no”。杂乱缓存也叫做”crypt seed”。服务器状态也叫做”SERVER_STATUS_xxx flag”或”Server status variables”。
一个握手初始化包的例子:
字段
十六进制
ASCII
协议版本
0a
.
服务器版本
34 2e 31 2e 31 2d 71 6c 70 68 61 2d 64 65 62 75 67 00
4.1.1-al pha-debu g.
线程号
01 00 00 00
….
杂乱缓存
3a 23 3d 4b 43 4a 2e 43
……..
(分隔符)
00
.
服务器功能选项
2c 82
..
服务器语言
08
.
服务器状态
02 00
..
(分隔符)
00 00 00 00 00 00 00 00 00 00 00 00 00
……..
例子当中的服务器告诉客户端拥有的功能包括CLIENT_MULTI_RESULTS, CLIENT_SSL, CLIENT_COMPRESS, CLIENT_CONNECT_WITH_DB, CLIENT_FOUND_ROWS。
包括我现在使用的4.1.20-log服务器,其协议版本都是0x10。
在握手包中的包头的ID必须为0,不可以是其他的,当然也不可以是随机的。
7 客户端认证包
在初始化握手之后从客户端发往服务器。
4.0版本:
字节数
名称
2
客户端标志 client_flags
3
最大包长度 max_packet_size
n(NULL终止字符串)
用户名 user
8
杂乱缓存 scramble_buff
1
分隔符,总是0x00
4.1版本:
字节数
名称
4
客户端标志 client_flags
4
最大包长度 max_packet_size
1
字符集编号 charset_number
23
分隔符,总是0x00
n(NULL终止字符串)
用户名 user
21(二进制长度编码)
杂乱缓存 1+20字节
1
分隔符,总是0x00
n(NULL终止字符串)
数据库名 databasename
客户端标志:CLIENT_xxx选项,列表,取值范围同握手初始化包中的服务器功能描述。对于某些位(bit),服务器传递了”哪些是可以打开的”,然后客户端留下一些bit是打开并增加一些其他的,然后传回服务器。一个很重要的选项是是否打开协议压缩。
最大包长度:客户端能接受的最大包长度。
字符集编号:在与握手初始化包中server_language相同的字段位置。
用户名:用于认证的用户名
杂乱缓存:使用服务器传回的杂乱缓存加密之后的密码。查看 密码功能 。
数据库名:最初使用的数据库。
杂乱缓存和数据库名字段是可选的,不一定必须有。
其他叫法:客户端认证包,有时也叫做”Client auth response”,或”client auth packet”。杂乱缓存有时也叫做”crypted password”。
相关的MySQL代码:客户端是 libmysql/libmysql.c::mysql_real_connect() 。服务器端是 sql/sql_parse.cc::check_connections() 。
一个客户端认证包的例子:
字段
十六进制
ASCII
客户端标志
85 a6 03 00
….
最大包长度
00 00 00 01
….
字符集编号
08
.
分隔符
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
…….. …….. …….
用户名
70 67 75 6c 75 74 7a 61 6e 00
pgulutzan.
客户端认证包的包头ID必须为1。而且数据库名之前并没有0x00分隔符,而是在其后有个可能作为NULL结束的0x00。
8 密码功能
服务器初始化包和客户端验证包都拥有一个8字节的字段。其值用来密码认证。工作方式如下:
Note
密码认证机制
服务器发送一个随机字符串到客户端,放在杂乱缓存里。
客户端使用随用户名的密码加密杂乱缓存。这个过程发生在 sql/password.c:scramble() 函数。客户端发送加密后的杂乱缓存到服务器。服务器使用mysql数据库里面的mysql.user.Password来加密原来的原始随机字符串。
然后服务器会比较加密后的字符串和从客户端传来的杂乱缓存。如果相同,密码就是正确的。
相关的MySQL代码: libmysql/password.c ,文件开头的注释。
9 命令包
从客户端发往服务器的,希望服务器作的事情。
字节数
名称
1
命令 command
n
参数 arg
命令:最常见的值是 03 COM_QUERY,因为 INSERT UPDATE DELETE SELECT 等等都拥有自己的值。可选的值如下,来自 /include/mysql_com.h 的 enum_server_command :
数字
名称
对应的客户端功能
0x00
COM_SLEEP
缺省的,例如 SHOW PROCESSLIST
0x01
COM_QUIT
mysql_close
0x02
COM_INIT_DB
mysql_select_db
0x03
COM_QUERY
mysql_real_query
0x04
COM_FIELD_LIST
mysql_list_fields
0x05
COM_CREATE_DB
mysql_create_db
0x06
COM_DROP_DB
mysql_drop_db
0x07
COM_REFRESH
mysql_refresh
0x08
COM_SHUTDOWN
0x09
COM_STATISTICS
mysql_stat
0x0a
COM_PROCESS_INFO
mysql_list_processes
0x0b
COM_CONNECT
在认证握手时
0x0c
COM_PROCESS_KILL
mysql_kill
0x0d
COM_DEBUG
0x0e
COM_PING
mysql_ping
0x0f
COM_TIME
(special value for slow logs?)
0x10
COM_DELAYED_INSERT
0x11
COM_CHANGE_USER
mysql_change_user
0x12
COM_BINLOG_DUMP
(用于slave服务器 mysqlbinlog)
0x13
COM_TABLE_DROP
(用于slave服务器获取master表格)
0x14
COM_CONNECT_OUT
(用于slave服务器log连接到master)
0x15
COM_REGISTER_SLAVE
(向master报告slave的位置)
0x16
COM_STMT_PREPARE
查看Prepare包的描述
0x17
COM_STMT_EXECUTE
查看Execute包的描述
0x18
COM_STMT_SEND_LONG_DATA
查看Long Data包描述
0x19
COM_STMT_CLOSE
新加入的,用于关闭语句
0x1a
COM_STMT_RESET
0x1b
COM_SET_OPTION
0x1c
COM_STMT_FETCH
参数:供用户输入的命令文本,在客户端不进行处理(除了去掉最后面的”;”)。这个字段并不是NULL结束字符串;但是字符串的长度可以依靠包长度计算出来,MySQL客户端在收到时会添加’0’到末尾。
相关的MySQL代码: sql-common/client.c cli_advanced_command(), mysql_send_query(). , libmysql/libmysql.c mysql_real_query(), simple_command(), net_field_length() 。
一个命令包的例子:
字段
十六进制
ASCII
命令
02
.
参数
74 65 73 74
test
在例子中,命令字段数值02表示COM_INIT_DB。等同于客户端输入 “use test;” 。
10 命令包参数
@waiting…
11 返回结果包的类型
一个结果包表示从服务器发到已认证的客户端的命令包。如果要区别不同类型的结果包,客户端需要查看包的第一个字节,我们在包的描述中把这个字节叫做”field_count”,当然,有很多种叫法。
结果包类型
首字节的16进制值(field_count)
OK Packet
00
Error Packet
ff
Result Set Packet
1-250(二进制码长度的首字节)
Field Packet
1-250(“”)
Row Data Packet
1-250(“”)
EOF Packet
fe
12 OK包
从服务器发往客户端的相应,在既没有错误,也没有结果集时。
4.0版本:
字节数
名称
1(二进制编码长度)
field_count,总是为0
1-9(二进制编码长度)
affected_rows
1-9(二进制编码长度)
insert_id
2
服务器状态
n(二进制编码长度)
信息
4.1版本:
字节数
名称
1(二进制编码长度)
field_count,总是为0
1-9(二进制编码长度)
affected_rows
1-9(二进制编码长度)
insert_id
2
服务器状态, server_status
2
警告数量, warning_count
n(二进制编码长度)
信息, message
field_count:总是为0
affected_rows:用于INSERT/UPDATE/DELETE的影响行数量。
insert_id:这将是”最后插入ID”,代表自动增量列的最后一个插入ID。
服务器状态:客户端可以依靠此检查命令是否在一个事务中。
警告数量:警告数量。
信息:例如在一个多行插入之后,信息就是:
Records: 3 Duplicates: 0 Warnings: 0
信息字段是可选的。
其他叫法:OK包同样被叫做”okay packet”或”ok packet”或”OK-Packet”。field_count也叫做”number of rows”或”marker for ok packet”。信息也叫做”Messagetext”。OK包和结果集包常统称为”Result packets”(结果包)。
相关的MySQL代码:客户端 sql/client.c mysql_read_query_result() ,服务器 sql/http://protocol.cc send_ok() 。
一个OK包的例子:
字段
十六进制
ASCII
field_count
00
.
affected_rows
01
.
insert_id
00
.
server_status
02 00
..
warning_count
00 00
..
在例子中,可选的信息字段是没有的,客户端可以依靠包长度检测到这个情况。这个包一般用于服务器在INSERT一行,并且没有auto_increment字段时的返回。
13 错误包
服务器发往客户端作为命令错误时的响应。
4.0版本:
字节数
名称
1
field_count,总为0xff
2
错误号, errno
n
信息
4.1版本:
字节数
名称
1
field_count,总为0xff
2
错误号, errno
1
SQL状态标志,总为’#’
5
SQL状态
n
信息
field_count:总是0xff。
错误号,可选值在手册中,或者参考MySQL代码 /include/mysqld_error.h 。对4.1来说,最大允许数字是1302,一般从1000开始的。
SQL状态标志:总是’#’,用于区别4.1版本信息。
SQL状态:服务器把错误码翻译成SQL状态(sqlstate),通过一个叫做 mysql_errno_to_sqlstate() 的函数。其可用值参考手册,或者在MySQL代码 /include/sql_state.h 。
信息:错误信息,字符串,没有长度标志,也没有NULL结束,但是其长度可以通过包头的来确定。取出的整个串都是信息字符串。
其他叫法:field_count也叫做”Status Code”或”Error Packet marker”。错误码也叫做”Error Number”或”Error Code”。
相关的MySQL代码:客户端 client.c net_safe_read() ,服务器 sql/http://protocol.cc send_error() 。
一个错误包的例子:
字段
十六进制
ASCII
field_count
ff
.
errno
1b 04
SQL状态标志
23
#
SQL状态
34 32 53 30 32
42S02
信息
55 63 6b 6e 6f 77 6e 20 74 61 62 6c 6c 65 20 27 71 27
Unknown table ‘ q’
14 结果集包头
在命令后从服务器发往客户端,如果没有错误和结果集,那就是查询会返回结果集。
结果集包头是一系列包的第一个,后面的包就是结果集了。结果集的发送顺序如下:
包
描述
结果集包头
列数量
字段包
列描述
EOF包
标志字段包结束的
行数据包
记录内容
结束包
标志行数据包结束
结果集包头结构
字节数
名称
1-9(二进制编码长度)
field_count
1-9(二进制编码长度)
扩展信息 extra
field_count:参考”结果集包类型”来区别首字节和OK包的field_count,或者其他类型。
扩展信息:例如SHOW COLUMNS使用这个来发送表格中的行数量。这个字段是可选的,并且在普通的结果集中肯定不会出现。
其他叫法:结果集包也叫做”a result packet for a command returning rows”或”a field description packet”。
相关MySQL代码:客户端 libmysql/libmysql.c 的 mysql_store_result() 从结果集读取内容到内存, mysql_use_result() 读取一行一行的读取结果集。也推荐看看 my_net_write() 描述了本地数据的装入方式。
一个结果集包头的例子:
字段
十六进制
ASCII
field_count
03
.
这个例子的包,可以参考 “SELECT * FROM t7” 表示表格t7拥有3个列。
15 字段包
从服务器发到客户端,作为结果集包的一部分。每个列一个包。如果结果集包头的field_count字段数字为3,则字段包会出现3次。
4.0版本:
字节数
名称
n(二进制编码字符串)
表名 table
n(二进制编码字符串)
列名 name
4(二进制编码长度)
长度 length
2(二进制编码长度)
类型 type
2(二进制编码长度)
标志 flags
1
小数位 decimals
n(二进制编码长度)
缺省值 default
4.1版本:
字节数
名称
n(二进制编码字符串)
目录 catalog
n(二进制编码字符串)
数据库 db
n(二进制编码字符串)
表格 table
n(二进制编码字符串)
原始表名 org_table
n(二进制编码字符串)
列名 name
n(二进制编码字符串)
原始列名 org_name
1
分隔符,一个例子 0x0c
2
字符集编号 charsetnr
4
长度 length
1
类型 type
2
标志 flags
1
小数位 decimals
2
分隔符,总是0x00
n(二进制编码长度)
缺省值 default
在实践中,标识符不要查过250个字节,因为字符串的编码方式使然。
目录:对4.1、5.0、5.1来说就是”def”。
数据库:数据库标识符,也就是schema名称。
表格:表格标识符,在AS子句之后的部分。
原始表名:原始表格标识符,在AS子句之前的部分。
列名:列名标识符,AS子句之后的部分。
原始列名:原始列名标识符,在AS子句之前的部分。
字符集编号:字符集编号。
长度:列的长度。
16 EOF包
略
17 行数据包
略
18 行数据包:二进制(实验性描述)
略
19 准备语句初始化包(实验性描述)
略
20 准备语句初始化包的OK包(实验性描述)
略
21 参数包(实验性描述)
略
22 长数据包(实验性描述)
略
23 执行包(实验性描述)
略
24 压缩
本章并没有讨论压缩,但是你知道他的存在。
压缩是一个或多个逻辑包。其packet_number字段在每个包头用于跟踪。相对于压缩包的是”raw”原始包。
压缩仅在客户端和服务器同时支持zlib压缩时可用,由客户端请求压缩。
压缩包的包头结果为:包长(3字节),包序列号(1字节),解压后包长度(3字节)。解压后长度是原来尚未压缩时的包长度,如果为0,则没有压缩。
当使用协议压缩时(客户端请求,并服务器接受),服务器和客户端发送的数据都是压缩的。但是当压缩后比原数据还大时就不会压缩。所以,会出现部分包压缩了,但是也有部分没有压缩。