MYSQLCLENT/SERVER数据包传输及netpacketbuffer作用解析
原创:转载请说明出处
水平有限再加上源码的复杂性,难免出现错误,请共同研究予以纠正
本文参考源码:
Net_serv.cc(主要参考)
MySQL.h.pp
Mysql_socket.h
Violite.h
Viosocket.c
Vio.c
参考书籍:
深入理解MYSQL核心技术
MYSQL核心内幕
internals-en
MYSQL官方手册
LINUX系统编程手册
注意:
1、本文将主要解析非压缩MYSQL NET包,而尽量不考虑压缩的MYSQL NET包来减小难度
2、本文主要以TCP->IP->以太网为蓝本进行描述,不考虑其他协议如(UDP)
3、本文主要以Net_serv.cc的my_net_write来描述写入socket阶段,而没有考虑net_write_command
实际上net_write_command函数是client传递命令包的主要函数入口,调用的下层函数一致
4、写入阶段可以达到net buffer满写入也可以调用net_flush()写入,但是这里无力研究net_flush()只研究满写入的情况
一、基本概念
在这之前我们必须明白如下的一些基本概念,否则不利于阅读
1、socket:是一种进程间通信的方式,可以用于多态计算机和本地两个进程进行通信,类似管道是双向
通信的一种方式,在网络上主要通过绑定IP和端口和识别唯一的网络服务端,在本地通过绑
定一个本地文件进行通信,它工作在LINUX 内核态。
2、通信协议:协议也就是客户端和服务端事先商量好的一种格式,如果格式不对则解包失败,比如TCP
协议格式如下,MYSQL有自己的通信协议。
3、MYSQL协议:MYSQL作为大型数据库系统,他有着自己的协议,至少包含如下一些数据包。
1、握手阶段
--服务端到客户端 初始化握手包
--客户端到服务端 客户端认证包
--服务端到客户端 OK包、ERROR包
2、连接建立阶段
--客户端到服务端 命令(command)包
--服务端到客户端 OK包、ERROR包、结果集包
其中结果集包包含:
1、包头包
2、FILED属性包
3、EOF包
4、行数据包
FILED属性包:为列属性每个列都会有一个
行数据包:为返回数据每行一个包
如果一个SELECT 返回 2行3列数据
会包含3(列)+2(行)+1(包头包)+2(EOF包)个数据包
由于MYSQL数据包的复杂性本文并不准备解析MYSQL协议各种包,可以参考:
MYSQL核心内幕
internals-en
下图是展示了MYSQL 服务端和客户端之间如何握手成功,并且进行数据传输
我们约定它叫做MYSQL数据包
4、MYSQL NET包:它是实际的传输包的大小,大小最大为16M-1,这是在源码中定义死了的,每个MYSQL NET包
包含一个包头,至少包含4个字节(非压缩包,如果压缩包会多3个字节),如下:
3 bytes:(压缩后)payload长度
1 bytes:序号
(压缩)3 bytes:压缩前payload长度
其中payload就是实际数据
比如这样一个MYSQL NET包:
为什么有一个序号呢?因为为了保证每个命令发送的包是有序的,比如一个结果
集合包会包含多个包,而其中的行数据包(SERVER->CLIENT的时候每一行数据是一个MYSQL数据包)
包很可能大于16M-1,那么我们就需要将整个结果集包分为多个MYSQL NET包进行传输,当到达
client的时候保证他顺序。当然并不是每个MYSQL NET包都很大,比如一些MYSQL数据包如OK包,就很
小,我们知道在以太网传输的最大帧为MTU 1500字节,那么可能出现一个以太网帧包含多个MYSQL NET
包(如OK包),也可能一个MYSQL NET包在多个以太网帧中,同时可能出现一个MYSQL数据包在多个MYSQL
NET包中,但是最小一个MYSQL NET包至少包含一个MYSQL数据包(如OK包),当然TCP
注意当一个MYSQL数据包分为多个MYSQL NET包的时候其最后会紧跟一个长度为0作为结束的标识,源码中
/* End of big multi-packet. */
if (!pkt_len)
goto end;
我们约定它叫做MYSQL NET包
5、NET结构体说明
下面先来看几个截图说明:
可以看到NET结构中封装了一个BUFFER,而这个BUFFER正是由参数net-buffer-length控制
其大小不能超过参数max-allowed-packet大小的这个buffer,本文约定将它叫做net buffer。
net-buffer-length 默认16K最大为1M
max-allowed-packet 默认4M最大1G
结构体还封装了2个unsigned int的变量write_timeout,read_timeout. 他们正是
net-wirte-timeout,net-read-timeout参数指定,用来表示在返回一个ETIMEDOUT错误前能够
KEEPLIVE最大的时间。
设置超时的底层调用很有可能是
ret= mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, optname,optval, sizeof(timeout));
之类的调用
另外结构体还封装了retry_count这是在遇到EINTR错误的时候重试的次数由参数net-retry-count
控制,在后面将会讲述
6、LINUX ETIMEDOUT、EINTR、EWOULDBLOCK、EAGAIN
#define ETIMEDOUT 110 /* Connection timed out */
#define EINTR 4 /* Interrupted system call */
#define EAGAIN 11 /* Try again */
#define EWOULDBLOCK EAGAIN /* Operation would block *
7、LINUX平台下MYSQL读取和写入scoket函数
位于Mysql_socket.h中
send(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
recv(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
当然如果是WIN_32平台send和recv函数有底层封装
8、包封装流程
如下图:
本文研究是应用层MYSQL通过自己的协议进行数据包封装后如何进行传输的
二、MYSQL数据包的写入scoket阶段
1、将可能大的MYSQL数据包进行拆分
函数原型
my_bool my_net_write(NET *net, const uchar *packet, size_t len)
net:NET结构体指针
packet:MYSQL数据包指针,MYSQL数据包由MYSQL协议栈准备好
len:MYSQL数据包长度
这个过程会将大的MYSQL数据包进行拆分打包为多个MYSQL NET包,如果是小的MYSQL数据包(如OK包)就进行
打包为MYSQL NET包调用net_write_buff下面我将我写的中文注释加上源码部分一同放出如下:
2、写入缓存阶段
函数原型
static my_bool net_write_buff(NET *net, const uchar *packet, ulong len)
net:NET结构体指针
packet:MYSQL数据包指针,注意这个指针和上面不同,由于my_net_write分包后这个指针
也会每次相应的增加到上次写入后的位置
len:如果是拆分的大包就是16M-1,如果是小包(如OK包)就是其相应的长度,还可能是MYSQL NET包头长度
这个过程分为如下情况:
--如果MYSQL NET包大于net buffer的剩余空间
--将MYSQL NET包一部分调用memcpy写入到剩余空间,完成后调用net_write_packet来进行一次传输,清空net buffer
--如果MYSQL NET包的剩余部分任然大于net buffer(net-buffer-length)则直接调用net_write_packet进行传输
--如果MYSQL NET包能够存储在net buffer中
--直接调用memcpy写入到net buffer即可
这里有几个重点
one、MYSQL这样处理实际上讲大的MYSQL NET包和小的MYSQL NET进行区分开,使用net buffer来减小传输的次数,提高
性能
two、这里也揭示了写入阶段不会出现超过net buffer大小的情况,这和read不同,在写入阶段net buffer只是一个提高
性能的缓存,如果大于他可以直接调用net_write_packet写入,而read阶段不同net buffer还承载了另外一个重要
的功能将多个MYSQL NET包合并为一个MYSQL 数据包的功能,所以net buffer的大小小于一个MYSQL数据包的大小会
直接导致报错如:Got a packet bigger than 'max_allowed_packet' bytes
three、net buffer的设置也就是net-buffer-length参数设置会直接影响到这里,同时这里并不会进行扩充到max_allowed_packet
的操作,扩充到max_allowed_packet是在read 阶段才会出现,后面会描述
下面我将我写的中文注释加上源码部分一同放出如下:
3、进行压缩阶段
函数原型
my_bool net_write_packet(NET *net, const uchar *packet, size_t length)
return TRUE on error, FALSE on success.
net:NET结构体指针
packet:这里的packet有2个可能的来源
--来自net buffer
--原始的MYSQL 数据包指针偏移后的位置如16M-1的大MYSQL NET包
lenth:写入长度
这一步实际上是进行一个压缩功能,并没有进行真正的传输,所以我们不进行过多的讨论
下面我将我写的中文注释加上源码部分一同放出如下
4、调用vio虚拟I/O接口进行写入阶段
函数原型
static my_bool net_write_raw_loop(NET *net, const uchar *buf, size_t count)
net:NET结构体指针
packet:这里的buffer有3个可能的来源
--来自net buffer
--原始的MYSQL 数据包指针偏移后的位置如16M-1的大MYSQL NET包
--经过压缩后的上面两种包
lenth:写入长度
这个函数调用真正的底层vio_write虚拟IO接口函数进行写入,同时如果遇到EINTR错误会进行如下的操作:
--线程安全客户端如果是EINTR总是重试
--非线程安全客户端或者服务器端如果是EINTR并且达到net->retry_count就跳出循环
服务端MYSQLD肯定是线程安全的但是为了服务端的性能不可能在EINTR错误下面无限重试
非线程安全的客户端可能全局区数据已经混乱造成I/O错误
此外如果数据没有发送完成或者剩余了一部分会根据错误码判断抛错
--ETIMEOUT错误,如果是则报错Got timeout writing communication packets
--否则Got an error writing communication packets
注意这里的ETIMEOUT就是根据参数net-wirte-timeout设置的SOCKET超时设置
下面我将我写的中文注释加上源码部分一同放出如下
到这里MYSQL层次对MYSQL数据包到MYSQL NET包的转换和传输准备已经完成接下来就是通过
底层TCP/IP、以太网等协议进行封装然后通过socket传输了。下面一张图对上面的说明
进行一个汇总,但是图中有些细节并没有表示出来还是最好通过源码备注了解
三、MYSQL数据包的读取scoket阶段
1、合并多个MYSQL NET包为一个MYSQL 数据包
函数原型
ulong my_net_read(NET *net)
net:NET结构体指针,一个MYSQL 数据包存储在一个NET结构体的buffer所指向的内存
空间中
返回值为读取到的实际一个MYSQL 数据包的长度,不包MYSQL NET包的包头字节数
这个函数调用net_read_packet来读取一个MYSQL 数据包,如果为大的MYSQL 数据包完成解压
合并操作源码注释中将大的MYSQL 数据包分为多个MYSQL NET包叫做packet of a multi-packet
下面我将我写的中文注释加上源码部分一同放出如下,注意我忽略了解压操作来降低学习的难度
2、获得MYSQL NET包长度阶段
函数原型
static ulong net_read_packet(NET *net, size_t *complen)
net:NET结构体指针,一个MYSQL 数据包存储在一个NET结构体的buffer所指向的内存
空间中
complen:为输出形参,输出的是可能的压缩前的数据长度
返回值为实际读取的MYSQL NET包的长度大小( Read the packet data (payload))
失败返回packet_error
本函数主要是为了获得MYSQL NET包的长度而封装的一层函数,net_read_packet_header为获得MYSQL
NET包长度函数,并且本函数计算多个MYSQL NET包为一个MYSQL 数据包后需要的内存空间是否够用
如果不够用分为如下操作
1、如果扩充后NET BUFFER的大小不会超过参数max_packet_size设置的大小,则调用net_realloc()扩充成功
2、如果扩充后NET BUFFER的大小超过参数max_packet_size设置的大小,则调用net_realloc扩充失败报错
{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" }
这也是非常常见的一个错误
当然如果内存不足都会引起如下错误
{ "ER_OUT_OF_RESOURCES", 1041, "Out of memory; check if mysqld or some other process uses all
available memory; if not, you may have to use \'ulimit\' to allow mysqld to use more memory
or you can add more swap space" }
这里不对net_realloc函数和net_read_packet_header函数进行详细分析,如果有兴趣自行研究
下面我将我写的中文注释加上源码部分一同放出如下
3、调用vio虚拟I/O接口进行读取阶段
函数原型
static my_bool net_read_raw_loop(NET *net, size_t count)
net:NET结构体指针,一个MYSQL 数据包存储在一个NET结构体的buffer所指向的内存
空间中
count:本次读取的MYSQL NET包有多大,如果是压缩过的MYSQL NET包不是压缩前的数据而是压缩后的MYSQL NET包长度
(@return TRUE on error, FALSE on success.)
成功返回FALSE、失败返回TURE
水平有限再加上源码的复杂性,难免出现错误,请共同研究予以纠正
本文参考源码:
Net_serv.cc(主要参考)
MySQL.h.pp
Mysql_socket.h
Violite.h
Viosocket.c
Vio.c
参考书籍:
深入理解MYSQL核心技术
MYSQL核心内幕
internals-en
MYSQL官方手册
LINUX系统编程手册
注意:
1、本文将主要解析非压缩MYSQL NET包,而尽量不考虑压缩的MYSQL NET包来减小难度
2、本文主要以TCP->IP->以太网为蓝本进行描述,不考虑其他协议如(UDP)
3、本文主要以Net_serv.cc的my_net_write来描述写入socket阶段,而没有考虑net_write_command
实际上net_write_command函数是client传递命令包的主要函数入口,调用的下层函数一致
4、写入阶段可以达到net buffer满写入也可以调用net_flush()写入,但是这里无力研究net_flush()只研究满写入的情况
一、基本概念
在这之前我们必须明白如下的一些基本概念,否则不利于阅读
1、socket:是一种进程间通信的方式,可以用于多态计算机和本地两个进程进行通信,类似管道是双向
通信的一种方式,在网络上主要通过绑定IP和端口和识别唯一的网络服务端,在本地通过绑
定一个本地文件进行通信,它工作在LINUX 内核态。
2、通信协议:协议也就是客户端和服务端事先商量好的一种格式,如果格式不对则解包失败,比如TCP
协议格式如下,MYSQL有自己的通信协议。
3、MYSQL协议:MYSQL作为大型数据库系统,他有着自己的协议,至少包含如下一些数据包。
1、握手阶段
--服务端到客户端 初始化握手包
--客户端到服务端 客户端认证包
--服务端到客户端 OK包、ERROR包
2、连接建立阶段
--客户端到服务端 命令(command)包
--服务端到客户端 OK包、ERROR包、结果集包
其中结果集包包含:
1、包头包
2、FILED属性包
3、EOF包
4、行数据包
FILED属性包:为列属性每个列都会有一个
行数据包:为返回数据每行一个包
如果一个SELECT 返回 2行3列数据
会包含3(列)+2(行)+1(包头包)+2(EOF包)个数据包
由于MYSQL数据包的复杂性本文并不准备解析MYSQL协议各种包,可以参考:
MYSQL核心内幕
internals-en
下图是展示了MYSQL 服务端和客户端之间如何握手成功,并且进行数据传输
我们约定它叫做MYSQL数据包
4、MYSQL NET包:它是实际的传输包的大小,大小最大为16M-1,这是在源码中定义死了的,每个MYSQL NET包
包含一个包头,至少包含4个字节(非压缩包,如果压缩包会多3个字节),如下:
3 bytes:(压缩后)payload长度
1 bytes:序号
(压缩)3 bytes:压缩前payload长度
其中payload就是实际数据
比如这样一个MYSQL NET包:
为什么有一个序号呢?因为为了保证每个命令发送的包是有序的,比如一个结果
集合包会包含多个包,而其中的行数据包(SERVER->CLIENT的时候每一行数据是一个MYSQL数据包)
包很可能大于16M-1,那么我们就需要将整个结果集包分为多个MYSQL NET包进行传输,当到达
client的时候保证他顺序。当然并不是每个MYSQL NET包都很大,比如一些MYSQL数据包如OK包,就很
小,我们知道在以太网传输的最大帧为MTU 1500字节,那么可能出现一个以太网帧包含多个MYSQL NET
包(如OK包),也可能一个MYSQL NET包在多个以太网帧中,同时可能出现一个MYSQL数据包在多个MYSQL
NET包中,但是最小一个MYSQL NET包至少包含一个MYSQL数据包(如OK包),当然TCP
注意当一个MYSQL数据包分为多个MYSQL NET包的时候其最后会紧跟一个长度为0作为结束的标识,源码中
/* End of big multi-packet. */
if (!pkt_len)
goto end;
我们约定它叫做MYSQL NET包
5、NET结构体说明
下面先来看几个截图说明:
可以看到NET结构中封装了一个BUFFER,而这个BUFFER正是由参数net-buffer-length控制
其大小不能超过参数max-allowed-packet大小的这个buffer,本文约定将它叫做net buffer。
net-buffer-length 默认16K最大为1M
max-allowed-packet 默认4M最大1G
结构体还封装了2个unsigned int的变量write_timeout,read_timeout. 他们正是
net-wirte-timeout,net-read-timeout参数指定,用来表示在返回一个ETIMEDOUT错误前能够
KEEPLIVE最大的时间。
设置超时的底层调用很有可能是
ret= mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, optname,optval, sizeof(timeout));
之类的调用
另外结构体还封装了retry_count这是在遇到EINTR错误的时候重试的次数由参数net-retry-count
控制,在后面将会讲述
6、LINUX ETIMEDOUT、EINTR、EWOULDBLOCK、EAGAIN
#define ETIMEDOUT 110 /* Connection timed out */
#define EINTR 4 /* Interrupted system call */
#define EAGAIN 11 /* Try again */
#define EWOULDBLOCK EAGAIN /* Operation would block *
7、LINUX平台下MYSQL读取和写入scoket函数
位于Mysql_socket.h中
send(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
recv(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
当然如果是WIN_32平台send和recv函数有底层封装
8、包封装流程
如下图:
本文研究是应用层MYSQL通过自己的协议进行数据包封装后如何进行传输的
二、MYSQL数据包的写入scoket阶段
1、将可能大的MYSQL数据包进行拆分
函数原型
my_bool my_net_write(NET *net, const uchar *packet, size_t len)
net:NET结构体指针
packet:MYSQL数据包指针,MYSQL数据包由MYSQL协议栈准备好
len:MYSQL数据包长度
这个过程会将大的MYSQL数据包进行拆分打包为多个MYSQL NET包,如果是小的MYSQL数据包(如OK包)就进行
打包为MYSQL NET包调用net_write_buff下面我将我写的中文注释加上源码部分一同放出如下:
点击(此处)折叠或打开
在新会等地区,都构建了全面的区域性战略布局,加强发展的系统性、市场前瞻性、产品创新能力,以专注、极致的服务理念,为客户提供网站设计制作、成都网站建设 网站设计制作定制网站建设,公司网站建设,企业网站建设,成都品牌网站建设,营销型网站,成都外贸网站建设,新会网站建设费用合理。
-
my_bool my_net_write(NET *net, const uchar *packet, size_t len) //将长度为packet的数据写入到net->buffer
-
{
-
uchar buff[NET_HEADER_SIZE]; // lenth 3 seq 1 4bytes
-
int rc;
-
-
if (unlikely(!net->vio)) /* nowhere to write */
-
return 0;
-
-
MYSQL_NET_WRITE_START(len);
-
-
DBUG_EXECUTE_IF("simulate_net_write_failure", {
-
my_error(ER_NET_ERROR_ON_WRITE, MYF(0));
-
return 1;
-
};
-
);
-
-
/*
-
Big packets are handled by splitting them in packets of MAX_PACKET_LENGTH
-
length. The last packet is always a packet that is < MAX_PACKET_LENGTH.
-
(The last packet may even have a length of 0)
-
*/
-
while (len >= MAX_PACKET_LENGTH) //如果写入MYSQL 协议包的长度大于了最大mysq NET包 就分为多个MYSQL NET包
-
{
-
const ulong z_size = MAX_PACKET_LENGTH; // 16M-1 计为包的长度
-
int3store(buff, z_size); //将长度写入到栈 buff中
-
buff[3]= (uchar) net->pkt_nr++; //将buffer中的 seq+1 当然 pkt_nr 序列也+1
-
if (net_write_buff(net, buff, NET_HEADER_SIZE) || //写入MYSQL NET包头部
-
net_write_buff(net, packet, z_size)) //将长度为z_size的进行拆分的MYSQL 协议包一分部写入到net buffer中
-
{
-
MYSQL_NET_WRITE_DONE(1);
-
return 1;
-
}
-
packet += z_size; //将packet的指针 加上z_size的大小 其实也就是16M-1
-
len-= z_size; //当然len 也就相应的减少z_size 其实也就是16M-1
-
}
-
//如果不是大的MYSQL 协议包或者是MYSQL协议包的最后一部分则执行下面代码
-
/* Write last packet */
-
int3store(buff,len); //将最后的长度计入buffer 头3字节
-
buff[3]= (uchar) net->pkt_nr++; //当然序号继续+1
-
if (net_write_buff(net, buff, NET_HEADER_SIZE)) //写入MYSQL NET包头部
-
{
-
MYSQL_NET_WRITE_DONE(1);
-
return 1;
-
}
-
#ifndef DEBUG_DATA_PACKETS
-
DBUG_DUMP("packet_header", buff, NET_HEADER_SIZE);
-
#endif
-
rc= MY_TEST(net_write_buff(net,packet,len));//写入 MYSQL 协议包 的最后数据写入到net buffer中
-
MYSQL_NET_WRITE_DONE(rc);
-
return rc;
- }
函数原型
static my_bool net_write_buff(NET *net, const uchar *packet, ulong len)
net:NET结构体指针
packet:MYSQL数据包指针,注意这个指针和上面不同,由于my_net_write分包后这个指针
也会每次相应的增加到上次写入后的位置
len:如果是拆分的大包就是16M-1,如果是小包(如OK包)就是其相应的长度,还可能是MYSQL NET包头长度
这个过程分为如下情况:
--如果MYSQL NET包大于net buffer的剩余空间
--将MYSQL NET包一部分调用memcpy写入到剩余空间,完成后调用net_write_packet来进行一次传输,清空net buffer
--如果MYSQL NET包的剩余部分任然大于net buffer(net-buffer-length)则直接调用net_write_packet进行传输
--如果MYSQL NET包能够存储在net buffer中
--直接调用memcpy写入到net buffer即可
这里有几个重点
one、MYSQL这样处理实际上讲大的MYSQL NET包和小的MYSQL NET进行区分开,使用net buffer来减小传输的次数,提高
性能
two、这里也揭示了写入阶段不会出现超过net buffer大小的情况,这和read不同,在写入阶段net buffer只是一个提高
性能的缓存,如果大于他可以直接调用net_write_packet写入,而read阶段不同net buffer还承载了另外一个重要
的功能将多个MYSQL NET包合并为一个MYSQL 数据包的功能,所以net buffer的大小小于一个MYSQL数据包的大小会
直接导致报错如:Got a packet bigger than 'max_allowed_packet' bytes
three、net buffer的设置也就是net-buffer-length参数设置会直接影响到这里,同时这里并不会进行扩充到max_allowed_packet
的操作,扩充到max_allowed_packet是在read 阶段才会出现,后面会描述
下面我将我写的中文注释加上源码部分一同放出如下:
点击(此处)折叠或打开
-
static my_bool
-
net_write_buff(NET *net, const uchar *packet, ulong len)
-
{
-
ulong left_length;
-
//下面计算buffer->max_packet的剩余空间
-
if (net->compress && net->max_packet > MAX_PACKET_LENGTH)
-
left_length= (ulong) (MAX_PACKET_LENGTH - (net->write_pos - net->buff));
-
else
-
left_length= (ulong) (net->buff_end - net->write_pos);
-
-
#ifdef DEBUG_DATA_PACKETS
-
DBUG_DUMP("data", packet, len);
-
#endif
-
if (len > left_length) //如果长度大于剩余空间
-
{
-
if (net->write_pos != net->buff)
-
{
-
/* Fill up already used packet and write it */
-
memcpy(net->write_pos, packet, left_length); //这里使用指针packet后left_lengeh长度来填满整个net buffer
-
if (net_write_packet(net, net->buff,
-
(size_t) (net->write_pos - net->buff) + left_length))//写满后,然后调用net_write_packet写到scoket
-
//(size_t) (net->write_pos - net->buff) + left_length 为整个buffer长度
-
return 1;
-
net->write_pos= net->buff; //这里wirte_pos指针 应该也是移动了到了wirte_pos+left_lengeh
-
packet+= left_length; //packet 指针增加
-
len-= left_length; //长度相应减少
-
}
-
if (net->compress)//压缩属性先不考虑,实际是压缩开启使用Zlib进行压缩位于Zlib/compress中
-
{
- ..................
-
}
-
if (len > net->max_packet) //如果填满 net->max_packet 后剩余的数据 还是大于整个net buffer 大小,则跳过缓冲区直接写scoket (重要)
-
//实际上这里len 最大为16M-1, 如果为16M-1的MYSQL NET包始终会使用直接写入的方法,这点
-
//和read阶段不同,read阶段会有一个合并mysql net包为MYSQL协议包过程,net buffer有着额外
-
//的使命
-
return net_write_packet(net, packet, len); //直接调用net_write_packet写入
-
/* Send out rest of the blocks as full sized blocks */
-
}
-
memcpy(net->write_pos, packet, len); //如果长度小于 net buffer剩余的空间,只是写入net buffer 即可
-
net->write_pos+= len; //这里wirte_pos指针也移动相应的长度
-
return 0;
- }
函数原型
my_bool net_write_packet(NET *net, const uchar *packet, size_t length)
return TRUE on error, FALSE on success.
net:NET结构体指针
packet:这里的packet有2个可能的来源
--来自net buffer
--原始的MYSQL 数据包指针偏移后的位置如16M-1的大MYSQL NET包
lenth:写入长度
这一步实际上是进行一个压缩功能,并没有进行真正的传输,所以我们不进行过多的讨论
下面我将我写的中文注释加上源码部分一同放出如下
点击(此处)折叠或打开
-
my_bool
-
net_write_packet(NET *net, const uchar *packet, size_t length) //函数并没有真正传输只是做了一层封装将数据压缩封装在内
-
//注意这里的数据可能来自net->buffer 可能来自net_flush
-
{
-
my_bool res;
-
DBUG_ENTER("net_write_packet");
-
-
#if defined(MYSQL_SERVER) && defined(USE_QUERY_CACHE)
-
query_cache_insert((char*) packet, length, net->pkt_nr);
-
#endif
-
-
/* Socket can't be used */
-
if (net->error == 2)
-
DBUG_RETURN(TRUE);
-
-
net->reading_or_writing= 2; //设置标示表示开始写入
-
-
#ifdef HAVE_COMPRESS //参数是否开启
-
const bool do_compress= net->compress;
-
if (do_compress) //MYSQL自己决定是否开启压缩
-
{
-
if ((packet= compress_packet(net, packet, &length)) == NULL) //压缩数据 如果内存不足报错
-
//{ "ER_OUT_OF_RESOURCES", 1041, "Out of memory; check if mysqld or some other process uses all available memory; if not, you may have to use \'ulimit\' to allow mysqld to use more memory or you can add more swap space" },
-
//压缩完成后返回一个malloc的内存空间(压缩后数据的内存首地址)给packet,这个时候packet已经不是形参的packet了,需要释放
-
{
-
net->error= 2;
-
net->last_errno= ER_OUT_OF_RESOURCES;
-
/* In the server, allocation failure raises a error. */
-
net->reading_or_writing= 0;
-
DBUG_RETURN(TRUE);
-
}
-
}
-
#endif /* HAVE_COMPRESS */
-
-
#ifdef DEBUG_DATA_PACKETS
-
DBUG_DUMP("data", packet, length);
-
#endif
-
-
res= net_write_raw_loop(net, packet, length); //进行真正的底层传输工作
-
-
#ifdef HAVE_COMPRESS//参数是否开启
-
if (do_compress)//mysql自己决定
-
my_free((void *) packet);//如前所述这里需要释放压缩后数据的内存避免泄露
-
#endif
-
-
net->reading_or_writing= 0;//关闭标示
-
-
DBUG_RETURN(res);
- }
函数原型
static my_bool net_write_raw_loop(NET *net, const uchar *buf, size_t count)
net:NET结构体指针
packet:这里的buffer有3个可能的来源
--来自net buffer
--原始的MYSQL 数据包指针偏移后的位置如16M-1的大MYSQL NET包
--经过压缩后的上面两种包
lenth:写入长度
这个函数调用真正的底层vio_write虚拟IO接口函数进行写入,同时如果遇到EINTR错误会进行如下的操作:
--线程安全客户端如果是EINTR总是重试
--非线程安全客户端或者服务器端如果是EINTR并且达到net->retry_count就跳出循环
服务端MYSQLD肯定是线程安全的但是为了服务端的性能不可能在EINTR错误下面无限重试
非线程安全的客户端可能全局区数据已经混乱造成I/O错误
此外如果数据没有发送完成或者剩余了一部分会根据错误码判断抛错
--ETIMEOUT错误,如果是则报错Got timeout writing communication packets
--否则Got an error writing communication packets
注意这里的ETIMEOUT就是根据参数net-wirte-timeout设置的SOCKET超时设置
下面我将我写的中文注释加上源码部分一同放出如下
点击(此处)折叠或打开
-
static my_bool
-
net_write_raw_loop(NET *net, const uchar *buf, size_t count)
-
{
-
unsigned int retry_count= 0;
-
-
while (count)
-
{
-
size_t sentcnt= vio_write(net->vio, buf, count);//成功放回写入字节数量 失败返回-1 这里真正写max_packet buffer包/mysql NET包>max_packet buffer的数据到socket
-
-
/* VIO_SOCKET_ERROR (-1) indicates an error. */
-
if (sentcnt == VIO_SOCKET_ERROR) //如果写操作遇到错误下面是异常处理 总体来说就是晕倒的是EINTR就做重试,否则直接退出发送数据循环进入异常处理if语句
-
{
-
/* A recoverable I/O error occurred? */
-
if (net_should_retry(net, &retry_count))
-
//1、线程安全客户端如果是EINTR总是重试
-
//2、非线程安全客户端或者服务器端如果是EINTR并且达到net->retry_count就跳出循环
-
//服务端MYSQLD肯定是线程安全的但是为了服务端的性能不可能在EINTR错误下面无线重试
-
//非线程安全的客户端可能全局区数据已经混乱造成I/O错误
-
continue;
-
else
-
break;
-
}
-
//下面是正常情况下
-
count-= sentcnt; //总数-发送的
-
buf+= sentcnt; //指针当然也就相应增加
-
update_statistics(thd_increment_bytes_sent(sentcnt));
-
}
-
-
/* On failure, propagate the error code. */
-
if (count) //如果count>0 也就是还有未发送的数据
-
{
-
/* Socket should be closed. */
-
net->error= 2;
-
-
/* Interrupted by a timeout? */
-
if (vio_was_timeout(net->vio)) //是否为ETIMEOUT错误,如果是则报错Got timeout writing communication packets
-
net->last_errno= ER_NET_WRITE_INTERRUPTED;
-
else //否则报错Got an error writing communication packets
- net->last_errno= ER_NET_ERROR_ON_WRITE;
-
#ifdef MYSQL_SERVER
-
my_error(net->last_errno, MYF(0));
-
#endif
- }
底层TCP/IP、以太网等协议进行封装然后通过socket传输了。下面一张图对上面的说明
进行一个汇总,但是图中有些细节并没有表示出来还是最好通过源码备注了解
三、MYSQL数据包的读取scoket阶段
1、合并多个MYSQL NET包为一个MYSQL 数据包
函数原型
ulong my_net_read(NET *net)
net:NET结构体指针,一个MYSQL 数据包存储在一个NET结构体的buffer所指向的内存
空间中
返回值为读取到的实际一个MYSQL 数据包的长度,不包MYSQL NET包的包头字节数
这个函数调用net_read_packet来读取一个MYSQL 数据包,如果为大的MYSQL 数据包完成解压
合并操作源码注释中将大的MYSQL 数据包分为多个MYSQL NET包叫做packet of a multi-packet
下面我将我写的中文注释加上源码部分一同放出如下,注意我忽略了解压操作来降低学习的难度
点击(此处)折叠或打开
-
ulong
-
my_net_read(NET *net) //
-
{
-
size_t len, complen;
-
-
MYSQL_NET_READ_START();
-
-
#ifdef HAVE_COMPRESS
-
if (!net->compress)//如果没有压缩
-
{
-
#endif
-
len= net_read_packet(net, &complen); //读取一个MYSQL NET包返回实际长度在len变量中,如果有压缩
-
//压缩前长度保存在complen变量中 这个函数还有一个重要
-
//功能就是扩张max_packet buffer的长度直到max_packet_size
-
//限制,如果不能扩张就报错,这里也指出了一个现实每个MYSQL
-
//协议包必须放到一个max_packet buffer中,这也是很多packet
-
//buffer 不够报错的根源
-
if (len == MAX_PACKET_LENGTH) //是否为一个满包及大小为16M-1大小
-
{
-
/* First packet of a multi-packet. Concatenate the packets */
-
ulong save_pos = net->where_b;
-
size_t total_length= 0;
-
do //这里这个循环完成多个mysql net包合并为一个MYSQL 协议包的动作
-
{
-
net->where_b += len; //读取偏移量不断增加
-
total_length += len; //总长度不断增加
-
len= net_read_packet(net, &complen); //读取动作
-
} while (len == MAX_PACKET_LENGTH);
-
if (len != packet_error) //packet_err被定义为 ~((unsigned long)(0))
-
len+= total_length; //这里要注意MYSQL协议包分为多个mysql net包后结束包的长度是0,所以也不会增加len
-
net->where_b = save_pos;
-
}
-
net->read_pos = net->buff + net->where_b;
-
if (len != packet_error)
-
net->read_pos[len]=0; /* Safeguard for mysql_use_result */
-
MYSQL_NET_READ_DONE(0, len);
-
return len; //返回读取的总长度
-
#ifdef HAVE_COMPRESS
-
}
-
else //不考虑压缩
- {.....
2、获得MYSQL NET包长度阶段
函数原型
static ulong net_read_packet(NET *net, size_t *complen)
net:NET结构体指针,一个MYSQL 数据包存储在一个NET结构体的buffer所指向的内存
空间中
complen:为输出形参,输出的是可能的压缩前的数据长度
返回值为实际读取的MYSQL NET包的长度大小( Read the packet data (payload))
失败返回packet_error
本函数主要是为了获得MYSQL NET包的长度而封装的一层函数,net_read_packet_header为获得MYSQL
NET包长度函数,并且本函数计算多个MYSQL NET包为一个MYSQL 数据包后需要的内存空间是否够用
如果不够用分为如下操作
1、如果扩充后NET BUFFER的大小不会超过参数max_packet_size设置的大小,则调用net_realloc()扩充成功
2、如果扩充后NET BUFFER的大小超过参数max_packet_size设置的大小,则调用net_realloc扩充失败报错
{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" }
这也是非常常见的一个错误
当然如果内存不足都会引起如下错误
{ "ER_OUT_OF_RESOURCES", 1041, "Out of memory; check if mysqld or some other process uses all
available memory; if not, you may have to use \'ulimit\' to allow mysqld to use more memory
or you can add more swap space" }
这里不对net_realloc函数和net_read_packet_header函数进行详细分析,如果有兴趣自行研究
下面我将我写的中文注释加上源码部分一同放出如下
点击(此处)折叠或打开
-
static ulong net_read_packet(NET *net, size_t *complen)
-
{
-
size_t pkt_len, pkt_data_len;
-
-
*complen= 0;
-
-
net->reading_or_writing= 1; //将读写标示设置为1,表示读取开始
-
-
/* Retrieve packet length and number. */
-
if (net_read_packet_header(net)) //读取一个MYSQL net包的长度和MYSQL NET sequence
-
goto error;
-
-
net->compress_pkt_nr= net->pkt_nr;
-
-
#ifdef HAVE_COMPRESS
-
if (net->compress)//先不考虑压缩
-
{
- .......
-
}
-
#endif
-
-
/* The length of the packet that follows. */
-
pkt_len= uint3korr(net->buff+net->where_b);//获得本MYSQL NET包的长度
-
-
/* End of big multi-packet. */
-
if (!pkt_len) //判断是否为mysql数据包分包后的结束包
-
goto end;
-
-
pkt_data_len = max(pkt_len, *complen) + net->where_b; //获得读取此MYSQL NET包后需要的内存空间,也就是整个NET BUFFER需要多大,需要判断如果是
-
//是经过压缩的需要的空间是数据压缩前的长度
-
-
/* Expand packet buffer if necessary. */
-
if ((pkt_data_len >= net->max_packet) && net_realloc(net, pkt_data_len)) //这里实际的判断net buffer是否够用,如果不够用调用realloc进行内存扩充,
-
//在realloc中判断是否超过max_packet_size的设置
-
goto error;
-
-
/* Read the packet data (payload). */
-
if (net_read_raw_loop(net, pkt_len)) //开始进行实际的读取操作
-
goto error;
-
-
end:
-
net->reading_or_writing= 0; //将读写标示设置为0,表示读取结束
-
return pkt_len; //函数返回本次读取
-
-
error: //出错返回值
-
net->reading_or_writing= 0;
- return packet_error;
- }
3、调用vio虚拟I/O接口进行读取阶段
函数原型
static my_bool net_read_raw_loop(NET *net, size_t count)
net:NET结构体指针,一个MYSQL 数据包存储在一个NET结构体的buffer所指向的内存
空间中
count:本次读取的MYSQL NET包有多大,如果是压缩过的MYSQL NET包不是压缩前的数据而是压缩后的MYSQL NET包长度
(@return TRUE on error, FALSE on success.)
成功返回FALSE、失败返回TURE
点击(此处)折叠或打开
-
static my_bool net_read_raw_loop(NET *net, size_t count)
-
{
-
bool eof= false;
-
unsigned int retry_count= 0;
-
uchar *buf= net->buff + net->where_b;
-
-
while (count)
-
{
-
size_t recvcnt= vio_read(net->vio, buf, count); //如果写操作遇到错误下面是异常处理 总体来说就是晕倒的是EINTR就做重试,否则直接退出发送数据循环进入异常处理if语句
-
-
/* VIO_SOCKET_ERROR (-1) indicates an error. */
-
if (recvcnt == VIO_SOCKET_ERROR) //
-
{
-
/* A recoverable I/O error occurred? */
-
if (net_should_retry(net, &retry_count))
-
//1、线程安全客户端如果是EINTR总是重试
-
//2、非线程安全客户端或者服务器端如果是EINTR并且达到net->retry_count就跳出循环
-
//服务端MYSQLD肯定是线程安全的但是为了服务端的性能不可能在EINTR错误下面无线重试
-
//非线程安全的客户端可能全局区数据已经混乱造成I/O错误
-
-
continue;
-
else
-
break;
-
}
-
/* Zero indicates end of file. */
-
else if (!recvcnt) //recv半连接状态? LINUX man recv:The return values will be 0 when the peer has performed an orderly shutdown
-
{
-
eof= true;
-
break;
-
}
-
-
count-= recvcnt;
-
buf+= recvcnt;
-
update_statistics(thd_increment_bytes_received(recvcnt));
-
}
-
-
/* On failure, propagate the error code. */
-
if (count)//如果count>0 也就是没有读取到预期的数据
-
{
-
/* Socket should be closed. */
-
net->error= 2;
-
-
/* Interrupted by a timeout? */
-
if (!eof && vio_was_timeout(net->vio)) //是否为ETIMEOUT错误,如果是则报错Got timeout reading communication packets
-
net->last_errno= ER_NET_READ_INTERRUPTED;
-
else
-
net->last_errno= ER_NET_READ_ERROR;//否则报错Got an error reading communication packets
-
分享文章:MYSQLCLENT/SERVER数据包传输及netpacketbuffer作用解析
新闻来源:http://myzitong.com/article/jsespj.html