MySQL NET协议处于应用层之下、TCP/IP网络层之上。MySQL NET将应用层的数据分别打包。MySQL NET和TCP/IP的关系如下图:
从是否被压缩的角度来讲,MySQL NET的数据报分为两种格式-压缩和非压缩。在双方的握手阶段,由双方根据各自的性能和设置来决定服务端和客户端之间的会话采用哪种包进行通信。
按发送者身份来分,数据包又分为由客户端发来的命令包和服务端发送的结果包。其中服务端发送的消息格式包含数据包、数据流结束标志符、OK包和错误消息包。
所有的MySQL NET包可以归纳为:
握手阶段(当客户端开始连接时)
命令包(客户端对服务端的任一请求)
下面介绍几个专业术语
在网络发送的数据中,经常包含变长的字符串。如果没有特殊标志来说明的话,我们无从知晓字符串何时结束。
第一字节值 | 后续字节数 | 说明 |
0~250 | 0 | 这个值说明了Length Coded String后面有多少字节数据,这种方式限制了后续数据最多有250个字节 |
251 | 0 | 列值为Null,仅仅用于行数据包 |
252 | 2 | 后面两个字节的值,说明Length Coded String后面有多少字节数据 |
253 | 3 | 后面三个字节的值,说明Length Coded String后面有多少字节数据 |
254 | 8 | 后面八个字节的值,说明Length Coded String后面有多少字节数据 |
例如:对16进制字符串0x 02 61 62的解析如下:
02处于0~250之间,所以02表示了后面有2个字节的数据。“61 62”为内容部分“ab”。
所有的MySQL NET数据包都含有一个4字节长度的包头,具体的包头格式如下:
字节数 | 名称 | 描述 |
3 | Myd>SQL NET数据包长度 | 数据包长度记录了数据包头部之后的数据长度。根据2^24,我们可以得出一个包最大长度为16MB,如果超长,则数据包会被分割 |
1 | 数据包序列号 | 和大部分的网络包一样,TCP/IP无法保证收到的顺序,所以需要应用层定义顺序以保证数据按预期顺序收到。 |
当我们的服务器采用了compress方式压缩数据包后
mysql>SHOW VARIABLES LIKE "have_com%"
| have_compress | YES |
则数据包头部格式中就会出现三个额外的字节,这三个字节用于记录被压缩包的数据长度。MySQL使用开源社区的zlib来压缩数据包压缩包。
zlib将原数据包作为参数传给Zlib/compress.c中的compress函数:
int ZEXPORT compress(dest, destLen, source, sourceLen)
Bytef *dest;
uLongf *destLen;
const Bytef &source
uLong sourceLen;
{
return compress2(dest, destLen, source, sourceLen, z_DEFAULT_COMPRESSION);
}
函数的作用是最终得到的一个放在地址dest中、destLen长度的压缩包数据。如果存在压缩失败或者内存不足的情况,compress()函数返回Z_BUF_ERROR
或Z_MEM_ERROR
错误。此时通信两端只能按照非压缩格式来传输数据了。
客户端接收到服务器发来的初始化包之后,将向MySQL服务器发送认证请求,该包的格式如下表:
字节数 | 字段名 |
4 | 字客户端标志 |
4 | 包最大长度(Max_packet_size) |
1 | 客户端字符集ID |
23 | 填充字符(0x00) |
N | 用户名 |
1+N | 密码加密字段(scramble_buff) |
N | 数据库名(可选) |
这其中的密码字段和数据库名字段是可选的,但即使没有密码,密码字段长度也至少为1。
static int check_connection(THD *thd)
{
uint connect_errors = 0;
NET *net = &thd->net;
ulong pkt_len = 0;
char *end;
DBUG_PRINT(*info*, (*New connection received on %s", vio_description(net->vio)));
#ifdef SIGNAL_WITH_VIO_CLOSE
thd->sdt_active_vio(net->vio);
#endif
...
}
一旦握手认证阶段完成并通过后,客户端开始使用命令包向服务端发送各种命令。命令包的格式如下表:
字节数 | 字段名 |
1 | 命令 |
N | 命令参数 |
由第四章可知,THD类存放着命令类型的字段,用户可以通过show processlist命令进行查看。
Command列显示,一个线程正执行Sleep命令,即线程在非活动状态(sleep 0x00),另外一个线程则在执行Query(0x03)。Info列显示该命令的参数。
下面是使用MiniSniffer抓取的一个例包:
可以判断02位COM_INIT_DB,即MySQL客户端的use命令。这个包实际上是我们执行了“use test;”。
字节数 | 字段名 |
4 | 进程号字段 |
一旦MySQL服务器接收到客户端发送的命令后,服务器在后台直接处理该请求,并生成回答包。
初始化包是有服务器端在初始化握手阶段发往客户端的,其格式如下表所示:
字节数 | 字段名 |
1 | 协议版本号 |
n=strlen(server_version)+1 | 服务器信息 |
4 | 进程id |
8 | 密码验证 |
1 | 0x00填充位 |
2 | 服务器能力描述 |
1 | 服务器字符集 |
2 | 服务器状态 |
13 | 0x00填充位 |
13 | 密码验证2 |
对该格式我们做下述说明:
include/mysql_versiom.h
的PROTOCOL_VERSION
常量中定义,例如:10include/mysql_version.h
文件中找到。MYSQL_SERVER_VERSION常量中保留着服务器版本信息,例如“5.0.77-community-nt MYSQL Community Edition(GPL)”服务器状态:服务器状态标志,以SERVER_STATUS_
开头
握手初始化包在MySQL开发团队内部也称为“Greeting Packet(欢迎包)”。下面是我们通过MiniSniffer抓取的一个包:
在这个包中,服务器端能力描述2c 82转化为二进制 0010 1110 1000 0010。根据能力描述的定义可知晓该服务器支持CLIENT_MULTI_RESULTS、CLIENT_SSL、CLIENT_COMPRESS、CLIENT_CONNECT_WITH_DB和CLIENT_FOUND_ROWS。
服务端字符集ID号33可用下面的SQL语句校验:
mysql>SELECT CHARACTER_SET_NAME, COLLATION_NAME FROM INFORMATION_SCHEMA.COLLATIONS WHERE ID=33;
应对客户端的认证包或命令包的请求,服务器端都将发送一个回应包,回应包有时是认证请求的通过(OK)或拒绝(ERROR),要么是查询/命令(Command)语句的执行结果。每种包都定义了自己的特殊格式以供客户端区分。
每种包的最开头一个字节定义包类型(其中结果集包、属性包和行数据包将这个字节称为FIELD_COUNT):
结果包的类型 | 字段内容 | 例包 |
OK包 | 0x00 | |
ERROR包 | 0xff | |
结果集包 | 1-250 | Selet * from table1的包 |
属性包 | 1-250 | Select 1+1f返回的包 |
行数据包 | 1-250 | |
EOF包 | 0xfe |
当MySQL服务器成功执行了一个命令后,它将回复OK包。OK包常常是对以下各种客户端命令的回复:
字节数 | 字段说明 |
1 | FIELD_COUNT |
1-9 | 命令影响的行数 |
1-9 | 插入ID |
2 | 服务器状态 |
2 | 警告数量 |
N | 消息 |
相对于OK包,一旦服务器处理命令出错,或者用户的认证信息有问题,那么MySQL就会传递ERROR包,ERROR包的格式如下表所示:
字节数 | 字段说明 |
1 | 其值总为0xff |
2 | 错误号 |
1 | SQL状态标识符,总是“#” |
5 | SQL状态 |
N | 消息 |
#define ER_ERROR_FIRST 1000
#define ER_HASHCHK 1000
#define ER_NISAMCHK 1001
#define ER_NO 1002
#define ER_YES 1003
#define ER_CANT_CREATE_FILE 1004
#define ER_CANT_CREATE_TABLE 1005
#define ER_CANT_CREATE_DB 1006
#define ER_DB_CREATE_EXISTS 1007
#define ER_dB_DROP_EXISTS 1008
例:
mysql>drop database abcd;
ERROR 1008(HY000):Can't drop database "abcd"; database doesn't exist.
该错误是由于删除了一个并不存在的数据库引起的,错误号为1008,对应的ER_DB_DROP_EXISTS.
2. SQL状态标识符
总是“#”,用于区分该状态出自MySQL 4.1及之后的版本
3. SQL状态
每个错误号对应了一个错误状态。该对应任务由mysql_errno_to_sqlstate()
函数完成。SQL状态include/sql_state.h
中有所记录:
ER_DUP_KEY, *23000*, **,
ER_OUTOPREMORY, *HY001*, *S1001*,
ER_OUT_OF_SORTMEMORY, *HY001*, *S1001*,
ER_CON_COUNT_ERROR, *08004*, **,
ER_BAD_HOST_ERROR, *08S01*, **,
ER_HANDSHAKE_ERROR, *08S01*, **,
ER_DBACCESS_DENIED_ERROR, *42000*, **,
ER_ACCESS_DENIED_ERROR, *28000*, **,
ER_NO_DB_ERROR, *3D000*, **,
ER_UNKNOWN_COM_ERROR, *08S01*, **,
ER_BAD_NULL_ERROR, *23000*, **,
ER_BAD_DB_ERROR, *42000*, **,
ER_TABLE_EXISTS_ERROR, *42S01*, **,
ER_BAD_TABLE_ERROR, *42S02*, **,
ER_NON_UNIQ_ERROR, *23000*, **,
ER_SERVER_SHUTDOWN, *08S01*, **,
ER_BAD_FIELD_ERROR, *42S22*, *S0022*,
ER_WRONG_FIELD_WITH_GROUP, *42000*, *S1009*,
类似SELECT、SHOW、CHECK、REPAIR和EXPLAIN的请求服务端会返回一个结果集,一个结果集由一系列的包组成,其中有包头、查询返回的列包、查询返回的行包和EOF包,具体的结果集结构如下图所示:
结果集包头布记录了返回的结果集中包含多少列数据。属性包则为结果集列的描述。EOF标志某一种类的包(如属性包、行数据包)结束。
查询语句中如果得到的结果是n列、m行,则最后结果集包共由m+n+3个包组成,如下面这个查询:
mysql>select * from pet where owner='hiro';
结果返回如下表:
name | owner | species | sex | birth | death |
zhanghai | hiro | cat | m | 0000-00-00 | NULL |
MySQL共为此包发送了6+1+3个包,下面我们详细描述这些包的格式
字节数 | 字段名 |
1-9 | FIELD_COUN:区分包类型如Ok包、ERROR包等,这里的FIELD_COUNT记录了返回集的列数 |
1-9 | 附属字段:可选,执行show columns等命令时需要, |
字节数 | 字段名 |
N(带长度标识) | 行值 |
不管一行中有多少个列或属性,这些列将置于同一个行数据包中。表示时,各列之间没有任何空格。
4.EOF包
EOF是End of File的缩写。在MySQL的网络环境下表示某种类型的包传输已经结束,所以EOF包出现在列(属性)包、行数据包之后。
EOF的包格式如下,
字节数 | 字段说明 |
1 | FIELD_COUNT,总是等于0xfe |
2 | 警告数量:在传递完所有数据包时,显示所有警告(Warning)的数量 |
2 | 状态标识符:包含各类服务器的状态,如SERVER_STATUS_MORE_RESULTS |