MySQL客户端与服务器协议

关于总观:

     1、监听tcp协议之上
     2、握手验证先行
     3、Command in one packet,Responses may in several.

包(packet)格式
     压缩与非压缩,取决于握手阶段,客户端与服务器协商
     抛开压缩项,包分为两种:客户端请求与服务器回复

关于服务器回包:
     数据包、数据流结束包、OK包、错误信息包
     所有包都有4字节header.
     3字节:包体长度
     1字节:包序列

关于压缩

压缩包会多一个3字节的域,用于表示紧跟其后的压缩包大小;而非压缩包直接在header之后写入Body。
压缩使用zlib算法,调用compress()函数。
即便如此,当压缩后长度没有缩小或者因为内存lack等原因压缩失败,包体同样不压缩。会在压缩包多出来的字节域填入0表示(?),导致浪费了3字节空间,显而易见,如果使用了包比较小或者压缩率不高,关掉压缩选项,会使交互更快。

关于长度限制
长度域为3字节,因为body长度限制在16MB.
V3.23以前,无法传输更大的包
V4.0之后对协议进行了改进,fix了这个限制
代码中,宏MAX_PACKET_LENGTH(sql/net_serv.cc)表明了包的大小限制。
如果超过这个限制,需要将包split成为多个MAX_PACKET_LENGTH加上尾数包的形式。
如果包长度刚好没有存在尾数包的必要,但长度为0的尾包依然会发,表明数据流已经结束。


抓包可以看到,多个MySQL协议包可能会打包在一个TCP/IP包中,也有可能一个MySQL包出现在多个TCP/IP包中。
why?服务器/客户端使用my_net_write()(sql/net_serv.cc)发送包数据,该函数将包数据放入缓存中。当缓存到达容量设定后,内容会被flush,系统调用write()会将数据输出。连串的内容如果不能一次发送的话,系统级别上会根据操作系统的协议数据容量限制,切分数据包发送。

关于握手
握手交互之前,服务器会检查客户端host是否处于白名单。
host检查后,握手才开始:
1、服务器先发greeting包,包含标准4字节头,包序号为0
     body:
     1字节协议包含号 + 变长(空字节结束的服务器版本串) + 4字节MySQL内部分配处理线程ID + 9字节随机串(新版本长度有所改动) + 2字节服务器配置位 + 1字节字符集编码 + 2字节服务器状态标志位 + 13字节保留 + 13字节随机串(新版本改动,保存剩下随机串)

2、客户端回复凭证包(格式4.0以前与4.1之后有所改动)
     4.0以前版本
     2字节客户端配置位 + 3个字节的包最大限制 + 变长(凭证串:用户名, 8字节XOR encrypted密码,db名)
     4.1之后版本
     4字节客户端配置位 + 4字节包最大限制 + 1字节字符集编码 + 变长(用户名,20字节SHA1加密密码,db)

这个阶段如果客户端与服务器都支持SSL传输,客户端会发送不带凭证串的初始部分包至服务器,服务器会转到SSL层与客户端进行后续交互,如果从效率上考虑,不需要重新发送,但为了保证服务器处理代理逻辑稳定,客户端还是重新发送整个包

3、服务器收到凭证包,可能有三种处理逻辑:
     1) 检查通过,发送OK包
     2) 检查不通过,发送错误信息包
     3) 4.0与4.1的不同导致,mysql.user表存储的是旧类型的密码hash值,新版本的协议无法处理,服务器会回送一个1字节长包含值254的包至客户端,客户端收到后发送body包含旧格式的密码string,服务器回复OK包或者错误信息包

支持能力协议掩码

1、握手阶段协商而定
2、包含4byte跟2byte两种类型。4.1+的版本支持两种类型,旧版本仅仅支持2byte
3、无论什么版本,服务器端仅用2byte长度表示本身支持能力。支持任何版本客户端
4、客户端检测服务器的版本,根据服务器的支持版本决定发送4byte或者2byte的mask bit

掩码定义在include/mysql_com.h, 如:
     0x0020 表示是否具备压缩能力
     0x0800 SSL能力
     

关于客户请求包
验证通过后,客户端开始发送请求包,body格式:
1字节请求类型 + 请求参数(非压缩类型包长度:包头长度-1;压缩类型包长度:压缩body长度-1)

请求类型:server_command(include/mysql_com.h)
请求处理逻辑:dispatch_command()(sql/sql_parse.cc)

如果需要加入一个新的command类型,确保向后兼容旧客户端。将新请求类型加入在COM_END类型之前。确保枚举值不与旧客户端冲突,从这个先后顺序上面可以看到请求的发展历程。

关于服务器回包
服务器收到请求包后,通过一个或者多个回复包答复客户端。
会存在几种回复类型包,提几种回复类型包之前,讲讲数据域表示法

数据域表示法

该表示方式是服务器回复包中比较关键的组成部分,由长度加上实际的数据组成。
数据域的长度表示有点特殊。net_store_length()(sql/pack.cc)
>如果实际数据小于251,用1byte字段表示长度
>如果大于等于251,并且两个字节可以容纳长度值,前面会有另外多一个字节数据,填入252,然后两个字节填入实际长度
>如果2byte不够存,但4byte可以,前面多余的1byte填入253,后续跟着4字节的实际长度
>如果实际长度超过4byte,多余的1字节填入254,并在后续的8byte填入实际长度值 
>251表示该数据域为空,该字段为空

大部分时间字段都是小的,不浪费空间。如果长度大到需要多一个字节来表示长度的话,也不在乎一个字节了。

长度位之后,紧跟着便是实际的数据值,数据值转化成为string类型表示

4.1版本之前,调用net_store_data()函数,该函数因参数不同存在几种不同的重载形式。
实现于sql/net_pkg.cc
4.1+版本,使用Protocol::store(),对于函数net_store_data()的封装,实现在sql/protocol.cc

4.1版本有个特殊情况,当准备语句返回的数据不是字符串时,数据会通过原始二进制格式返回。同样实际数据之前存在一个长度指示符。

几种回复类型包:


OK回复包

用于表示服务器已正确处理请求,下面几种请求会导致一个OK回包:
1、ping包
2、不返回结果集查询请求包,如insert/update/alter table
3、refresh包
4、slave注册包
OK包类型适合于不返回结果集的请求包。尽管如此,包中还是包含了一些特定的状态信息,如受影响记录数、产生对应的自增主键值或者状态字符串。
具体格式:
     1字节标志(值0表示没有字段域) + 影响记录数 + 自增ID(0表示没有使用) + 2字节服务器状态(定义在include/mysql_com.h,4.0之前的协议,该值非0值才有用;4.1之后,该值都有用) + 2字节的warnings数(4.1之后版本才有使用) + 状态字符串(数据域表示法:长度+数据)
通过调用send_ok()函数发送OK包,定义于sql/protocol.h(cc)

错误回复包
请求包处理发现错误时,服务器发送错误包,具体格式:
1字节标志(255,客户端发现第一个字节是255的包都认为是错误包) + 2字节错误码 + 2字节("#"+连接状态) + 变长的错误信息字符串
通过调用send_error(),定义于sql/protocol.cc

EOF回复包
EOF包用于几种交互信息情况:
1、结果集中表明字段域信息结束
2、结果集中表明行数据结束
3、COM_SHUTDOWN请求的确认
4、COM_SET_OPTION和COM_DEBUG的正确处理答复
5、旧验证方式的请求结束

格式:
     1字节(十进制254) + 2字节警告数 + 2字节服务器状态掩码位
由于以254开始,跟之前提到的数据域中认为254开头的后续有8个字节的字段长度值这种表示方式存在冲突,所有这里对于EOF包中254值后7字节限制。区分这两种情况。
send_eof()函数定义于sql/protocol.cc


结果集回复包
诸如select、show、check、repair、explain此等请求,绝大部分的请求都会导致一个结果集数据返回。简单的状态报告请求不需要结果集返回的情况还是少数。

结果集由一系列包构成,首先,服务器通过Protocol::send_fields()(sql/protocol.cc)发送字段域信息。
这个阶段会产生以下序列包:
1、各字段长度指示符构成的包。
2、各字段描述信息,一个包包含一个字段域的描述信息。
3、EOF包。
以上三步骤完成了字段域的描述,2步中关系到各字段的描述方式,
基本都是采用数据域的表示法(长度+数据),依次保存了数据名+表名+列名+原列名+字符集编码+字段长度+字段枚举值(include/mysql_com.h)+字段掩码+精确度+可选默认值

字段掩码表示:主键、可空、唯一键、BLOBorTEXT、是否0填充、是否枚举值、自增属性、时间戳、SET、游标等

发送完字段描述系列包之后,服务器接着推送具体数据,采用每包一行形式,包内采用标准的数据域表示法。

常规查询请求(COM_QUERY),字段数据转化为string类型;如果使用准备语句(COM_PREPARE),字段数据采用原生标准格式发送。

最后,发送完全部行数据后,发送EOF包表示结束。


  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值