原創(chuàng):轉(zhuǎn)載請(qǐng)說(shuō)明出處
水平有限再加上源碼的復(fù)雜性,難免出現(xiàn)錯(cuò)誤,請(qǐng)共同研究予以糾正
本文參考源碼:
Net_serv.cc(主要參考)
MySQL.h.pp
Mysql_socket.h
Violite.h
Viosocket.c
Vio.c
參考書(shū)籍:
深入理解MYSQL核心技術(shù)
MYSQL核心內(nèi)幕
internals-en
MYSQL官方手冊(cè)
LINUX系統(tǒng)編程手冊(cè)
注意:
1、本文將主要解析非壓縮MYSQL NET包,而盡量不考慮壓縮的MYSQL NET包來(lái)減小難度
2、本文主要以TCP->IP->以太網(wǎng)為藍(lán)本進(jìn)行描述,不考慮其他協(xié)議如(UDP)
3、本文主要以Net_serv.cc的my_net_write來(lái)描述寫入socket階段,而沒(méi)有考慮net_write_command
實(shí)際上net_write_command函數(shù)是client傳遞命令包的主要函數(shù)入口,調(diào)用的下層函數(shù)一致
4、寫入階段可以達(dá)到net buffer滿寫入也可以調(diào)用net_flush()寫入,但是這里無(wú)力研究net_flush()只研究滿寫入的情況
一、基本概念
在這之前我們必須明白如下的一些基本概念,否則不利于閱讀
1、socket:是一種進(jìn)程間通信的方式,可以用于多態(tài)計(jì)算機(jī)和本地兩個(gè)進(jìn)程進(jìn)行通信,類似管道是雙向
通信的一種方式,在網(wǎng)絡(luò)上主要通過(guò)綁定IP和端口和識(shí)別唯一的網(wǎng)絡(luò)服務(wù)端,在本地通過(guò)綁
定一個(gè)本地文件進(jìn)行通信,它工作在LINUX 內(nèi)核態(tài)。
2、通信協(xié)議:協(xié)議也就是客戶端和服務(wù)端事先商量好的一種格式,如果格式不對(duì)則解包失敗,比如TCP
協(xié)議格式如下,MYSQL有自己的通信協(xié)議。
3、MYSQL協(xié)議:MYSQL作為大型數(shù)據(jù)庫(kù)系統(tǒng),他有著自己的協(xié)議,至少包含如下一些數(shù)據(jù)包。
1、握手階段
--服務(wù)端到客戶端 初始化握手包
--客戶端到服務(wù)端 客戶端認(rèn)證包
--服務(wù)端到客戶端 OK包、ERROR包
2、連接建立階段
--客戶端到服務(wù)端 命令(command)包
--服務(wù)端到客戶端 OK包、ERROR包、結(jié)果集包
其中結(jié)果集包包含:
1、包頭包
2、FILED屬性包
3、EOF包
4、行數(shù)據(jù)包
FILED屬性包:為列屬性每個(gè)列都會(huì)有一個(gè)
行數(shù)據(jù)包:為返回?cái)?shù)據(jù)每行一個(gè)包
如果一個(gè)SELECT 返回 2行3列數(shù)據(jù)
會(huì)包含3(列)+2(行)+1(包頭包)+2(EOF包)個(gè)數(shù)據(jù)包
由于MYSQL數(shù)據(jù)包的復(fù)雜性本文并不準(zhǔn)備解析MYSQL協(xié)議各種包,可以參考:
MYSQL核心內(nèi)幕
internals-en
下圖是展示了MYSQL 服務(wù)端和客戶端之間如何握手成功,并且進(jìn)行數(shù)據(jù)傳輸
我們約定它叫做MYSQL數(shù)據(jù)包
4、MYSQL NET包:它是實(shí)際的傳輸包的大小,大小最大為16M-1,這是在源碼中定義死了的,每個(gè)MYSQL NET包
包含一個(gè)包頭,至少包含4個(gè)字節(jié)(非壓縮包,如果壓縮包會(huì)多3個(gè)字節(jié)),如下:
3 bytes:(壓縮后)payload長(zhǎng)度
1 bytes:序號(hào)
(壓縮)3 bytes:壓縮前payload長(zhǎng)度
其中payload就是實(shí)際數(shù)據(jù)
比如這樣一個(gè)MYSQL NET包:
為什么有一個(gè)序號(hào)呢?因?yàn)闉榱吮WC每個(gè)命令發(fā)送的包是有序的,比如一個(gè)結(jié)果
集合包會(huì)包含多個(gè)包,而其中的行數(shù)據(jù)包(SERVER->CLIENT的時(shí)候每一行數(shù)據(jù)是一個(gè)MYSQL數(shù)據(jù)包)
包很可能大于16M-1,那么我們就需要將整個(gè)結(jié)果集包分為多個(gè)MYSQL NET包進(jìn)行傳輸,當(dāng)?shù)竭_(dá)
client的時(shí)候保證他順序。當(dāng)然并不是每個(gè)MYSQL NET包都很大,比如一些MYSQL數(shù)據(jù)包如OK包,就很
小,我們知道在以太網(wǎng)傳輸?shù)淖畲髱瑸镸TU 1500字節(jié),那么可能出現(xiàn)一個(gè)以太網(wǎng)幀包含多個(gè)MYSQL NET
包(如OK包),也可能一個(gè)MYSQL NET包在多個(gè)以太網(wǎng)幀中,同時(shí)可能出現(xiàn)一個(gè)MYSQL數(shù)據(jù)包在多個(gè)MYSQL
NET包中,但是最小一個(gè)MYSQL NET包至少包含一個(gè)MYSQL數(shù)據(jù)包(如OK包),當(dāng)然TCP
注意當(dāng)一個(gè)MYSQL數(shù)據(jù)包分為多個(gè)MYSQL NET包的時(shí)候其最后會(huì)緊跟一個(gè)長(zhǎng)度為0作為結(jié)束的標(biāo)識(shí),源碼中
/* End of big multi-packet. */
if (!pkt_len)
goto end;
我們約定它叫做MYSQL NET包
5、NET結(jié)構(gòu)體說(shuō)明
下面先來(lái)看幾個(gè)截圖說(shuō)明:
可以看到NET結(jié)構(gòu)中封裝了一個(gè)BUFFER,而這個(gè)BUFFER正是由參數(shù)net-buffer-length控制
其大小不能超過(guò)參數(shù)max-allowed-packet大小的這個(gè)buffer,本文約定將它叫做net buffer。
net-buffer-length 默認(rèn)16K最大為1M
max-allowed-packet 默認(rèn)4M最大1G
結(jié)構(gòu)體還封裝了2個(gè)unsigned int的變量write_timeout,read_timeout. 他們正是
net-wirte-timeout,net-read-timeout參數(shù)指定,用來(lái)表示在返回一個(gè)ETIMEDOUT錯(cuò)誤前能夠
KEEPLIVE最大的時(shí)間。
設(shè)置超時(shí)的底層調(diào)用很有可能是
ret= mysql_socket_setsockopt(vio->mysql_socket, SOL_SOCKET, optname,optval, sizeof(timeout));
之類的調(diào)用
另外結(jié)構(gòu)體還封裝了retry_count這是在遇到EINTR錯(cuò)誤的時(shí)候重試的次數(shù)由參數(shù)net-retry-count
控制,在后面將會(huì)講述
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平臺(tái)下MYSQL讀取和寫入scoket函數(shù)
位于Mysql_socket.h中
send(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
recv(mysql_socket.fd, buf, IF_WIN((int),) n, flags);
當(dāng)然如果是WIN_32平臺(tái)send和recv函數(shù)有底層封裝
8、包封裝流程
如下圖:
本文研究是應(yīng)用層MYSQL通過(guò)自己的協(xié)議進(jìn)行數(shù)據(jù)包封裝后如何進(jìn)行傳輸?shù)?br />
二、MYSQL數(shù)據(jù)包的寫入scoket階段
1、將可能大的MYSQL數(shù)據(jù)包進(jìn)行拆分
函數(shù)原型
my_bool my_net_write(NET *net, const uchar *packet, size_t len)
net:NET結(jié)構(gòu)體指針
packet:MYSQL數(shù)據(jù)包指針,MYSQL數(shù)據(jù)包由MYSQL協(xié)議棧準(zhǔn)備好
len:MYSQL數(shù)據(jù)包長(zhǎng)度
這個(gè)過(guò)程會(huì)將大的MYSQL數(shù)據(jù)包進(jìn)行拆分打包為多個(gè)MYSQL NET包,如果是小的MYSQL數(shù)據(jù)包(如OK包)就進(jìn)行
打包為MYSQL NET包調(diào)用net_write_buff下面我將我寫的中文注釋加上源碼部分一同放出如下:
點(diǎn)擊(此處)折疊或打開(kāi)
在新會(huì)等地區(qū),都構(gòu)建了全面的區(qū)域性戰(zhàn)略布局,加強(qiáng)發(fā)展的系統(tǒng)性、市場(chǎng)前瞻性、產(chǎn)品創(chuàng)新能力,以專注、極致的服務(wù)理念,為客戶提供網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站建設(shè) 網(wǎng)站設(shè)計(jì)制作定制網(wǎng)站建設(shè),公司網(wǎng)站建設(shè),企業(yè)網(wǎng)站建設(shè),成都品牌網(wǎng)站建設(shè),營(yíng)銷型網(wǎng)站,成都外貿(mào)網(wǎng)站建設(shè),新會(huì)網(wǎng)站建設(shè)費(fèi)用合理。
-
my_bool my_net_write(NET *net, const uchar *packet, size_t len) //將長(zhǎng)度為packet的數(shù)據(jù)寫入到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 協(xié)議包的長(zhǎng)度大于了最大mysq NET包 就分為多個(gè)MYSQL NET包
-
{
-
const ulong z_size = MAX_PACKET_LENGTH; // 16M-1 計(jì)為包的長(zhǎng)度
-
int3store(buff, z_size); //將長(zhǎng)度寫入到棧 buff中
-
buff[3]= (uchar) net->pkt_nr++; //將buffer中的 seq+1 當(dāng)然 pkt_nr 序列也+1
-
if (net_write_buff(net, buff, NET_HEADER_SIZE) || //寫入MYSQL NET包頭部
-
net_write_buff(net, packet, z_size)) //將長(zhǎng)度為z_size的進(jìn)行拆分的MYSQL 協(xié)議包一分部寫入到net buffer中
-
{
-
MYSQL_NET_WRITE_DONE(1);
-
return 1;
-
}
-
packet += z_size; //將packet的指針 加上z_size的大小 其實(shí)也就是16M-1
-
len-= z_size; //當(dāng)然len 也就相應(yīng)的減少z_size 其實(shí)也就是16M-1
-
}
-
//如果不是大的MYSQL 協(xié)議包或者是MYSQL協(xié)議包的最后一部分則執(zhí)行下面代碼
-
/* Write last packet */
-
int3store(buff,len); //將最后的長(zhǎng)度計(jì)入buffer 頭3字節(jié)
-
buff[3]= (uchar) net->pkt_nr++; //當(dāng)然序號(hào)繼續(xù)+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 協(xié)議包 的最后數(shù)據(jù)寫入到net buffer中
-
MYSQL_NET_WRITE_DONE(rc);
-
return rc;
-
}
2、寫入緩存階段
函數(shù)原型
static my_bool net_write_buff(NET *net, const uchar *packet, ulong len)
net:NET結(jié)構(gòu)體指針
packet:MYSQL數(shù)據(jù)包指針,注意這個(gè)指針和上面不同,由于my_net_write分包后這個(gè)指針
也會(huì)每次相應(yīng)的增加到上次寫入后的位置
len:如果是拆分的大包就是16M-1,如果是小包(如OK包)就是其相應(yīng)的長(zhǎng)度,還可能是MYSQL NET包頭長(zhǎng)度
這個(gè)過(guò)程分為如下情況:
--如果MYSQL NET包大于net buffer的剩余空間
--將MYSQL NET包一部分調(diào)用memcpy寫入到剩余空間,完成后調(diào)用net_write_packet來(lái)進(jìn)行一次傳輸,清空net buffer
--如果MYSQL NET包的剩余部分任然大于net buffer(net-buffer-length)則直接調(diào)用net_write_packet進(jìn)行傳輸
--如果MYSQL NET包能夠存儲(chǔ)在net buffer中
--直接調(diào)用memcpy寫入到net buffer即可
這里有幾個(gè)重點(diǎn)
one、MYSQL這樣處理實(shí)際上講大的MYSQL NET包和小的MYSQL NET進(jìn)行區(qū)分開(kāi),使用net buffer來(lái)減小傳輸?shù)拇螖?shù),提高
性能
two、這里也揭示了寫入階段不會(huì)出現(xiàn)超過(guò)net buffer大小的情況,這和read不同,在寫入階段net buffer只是一個(gè)提高
性能的緩存,如果大于他可以直接調(diào)用net_write_packet寫入,而read階段不同net buffer還承載了另外一個(gè)重要
的功能將多個(gè)MYSQL NET包合并為一個(gè)MYSQL 數(shù)據(jù)包的功能,所以net buffer的大小小于一個(gè)MYSQL數(shù)據(jù)包的大小會(huì)
直接導(dǎo)致報(bào)錯(cuò)如:Got a packet bigger than 'max_allowed_packet' bytes
three、net buffer的設(shè)置也就是net-buffer-length參數(shù)設(shè)置會(huì)直接影響到這里,同時(shí)這里并不會(huì)進(jìn)行擴(kuò)充到max_allowed_packet
的操作,擴(kuò)充到max_allowed_packet是在read 階段才會(huì)出現(xiàn),后面會(huì)描述
下面我將我寫的中文注釋加上源碼部分一同放出如下:
-
static my_bool
-
net_write_buff(NET *net, const uchar *packet, ulong len)
-
{
-
ulong left_length;
-
//下面計(jì)算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) //如果長(zhǎng)度大于剩余空間
-
{
-
if (net->write_pos != net->buff)
-
{
-
/* Fill up already used packet and write it */
-
memcpy(net->write_pos, packet, left_length); //這里使用指針packet后left_lengeh長(zhǎng)度來(lái)填滿整個(gè)net buffer
-
if (net_write_packet(net, net->buff,
-
(size_t) (net->write_pos - net->buff) + left_length))//寫滿后,然后調(diào)用net_write_packet寫到scoket
-
//(size_t) (net->write_pos - net->buff) + left_length 為整個(gè)buffer長(zhǎng)度
-
return 1;
-
net->write_pos= net->buff; //這里wirte_pos指針 應(yīng)該也是移動(dòng)了到了wirte_pos+left_lengeh
-
packet+= left_length; //packet 指針增加
-
len-= left_length; //長(zhǎng)度相應(yīng)減少
-
}
-
if (net->compress)//壓縮屬性先不考慮,實(shí)際是壓縮開(kāi)啟使用Zlib進(jìn)行壓縮位于Zlib/compress中
-
{
-
..................
-
}
-
if (len > net->max_packet) //如果填滿 net->max_packet 后剩余的數(shù)據(jù) 還是大于整個(gè)net buffer 大小,則跳過(guò)緩沖區(qū)直接寫scoket (重要)
-
//實(shí)際上這里len 最大為16M-1, 如果為16M-1的MYSQL NET包始終會(huì)使用直接寫入的方法,這點(diǎn)
-
//和read階段不同,read階段會(huì)有一個(gè)合并mysql net包為MYSQL協(xié)議包過(guò)程,net buffer有著額外
-
//的使命
-
return net_write_packet(net, packet, len); //直接調(diào)用net_write_packet寫入
-
/* Send out rest of the blocks as full sized blocks */
-
}
-
memcpy(net->write_pos, packet, len); //如果長(zhǎng)度小于 net buffer剩余的空間,只是寫入net buffer 即可
-
net->write_pos+= len; //這里wirte_pos指針也移動(dòng)相應(yīng)的長(zhǎng)度
-
return 0;
-
}
3、進(jìn)行壓縮階段
函數(shù)原型
my_bool net_write_packet(NET *net, const uchar *packet, size_t length)
return TRUE on error, FALSE on success.
net:NET結(jié)構(gòu)體指針
packet:這里的packet有2個(gè)可能的來(lái)源
--來(lái)自net buffer
--原始的MYSQL 數(shù)據(jù)包指針偏移后的位置如16M-1的大MYSQL NET包
lenth:寫入長(zhǎng)度
這一步實(shí)際上是進(jìn)行一個(gè)壓縮功能,并沒(méi)有進(jìn)行真正的傳輸,所以我們不進(jìn)行過(guò)多的討論
下面我將我寫的中文注釋加上源碼部分一同放出如下
-
my_bool
-
net_write_packet(NET *net, const uchar *packet, size_t length) //函數(shù)并沒(méi)有真正傳輸只是做了一層封裝將數(shù)據(jù)壓縮封裝在內(nèi)
-
//注意這里的數(shù)據(jù)可能來(lái)自net->buffer 可能來(lái)自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; //設(shè)置標(biāo)示表示開(kāi)始寫入
-
-
#ifdef HAVE_COMPRESS //參數(shù)是否開(kāi)啟
-
const bool do_compress= net->compress;
-
if (do_compress) //MYSQL自己決定是否開(kāi)啟壓縮
-
{
-
if ((packet= compress_packet(net, packet, &length)) == NULL) //壓縮數(shù)據(jù) 如果內(nèi)存不足報(bào)錯(cuò)
-
//{ "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" },
-
//壓縮完成后返回一個(gè)malloc的內(nèi)存空間(壓縮后數(shù)據(jù)的內(nèi)存首地址)給packet,這個(gè)時(shí)候packet已經(jīng)不是形參的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); //進(jìn)行真正的底層傳輸工作
-
-
#ifdef HAVE_COMPRESS//參數(shù)是否開(kāi)啟
-
if (do_compress)//mysql自己決定
-
my_free((void *) packet);//如前所述這里需要釋放壓縮后數(shù)據(jù)的內(nèi)存避免泄露
-
#endif
-
-
net->reading_or_writing= 0;//關(guān)閉標(biāo)示
-
-
DBUG_RETURN(res);
-
}
4、調(diào)用vio虛擬I/O接口進(jìn)行寫入階段
函數(shù)原型
static my_bool net_write_raw_loop(NET *net, const uchar *buf, size_t count)
net:NET結(jié)構(gòu)體指針
packet:這里的buffer有3個(gè)可能的來(lái)源
--來(lái)自net buffer
--原始的MYSQL 數(shù)據(jù)包指針偏移后的位置如16M-1的大MYSQL NET包
--經(jīng)過(guò)壓縮后的上面兩種包
lenth:寫入長(zhǎng)度
這個(gè)函數(shù)調(diào)用真正的底層vio_write虛擬IO接口函數(shù)進(jìn)行寫入,同時(shí)如果遇到EINTR錯(cuò)誤會(huì)進(jìn)行如下的操作:
--線程安全客戶端如果是EINTR總是重試
--非線程安全客戶端或者
服務(wù)器端如果是EINTR并且達(dá)到net->retry_count就跳出循環(huán)
服務(wù)端MYSQLD肯定是線程安全的但是為了服務(wù)端的性能不可能在EINTR錯(cuò)誤下面無(wú)限重試
非線程安全的客戶端可能全局區(qū)數(shù)據(jù)已經(jīng)混亂造成I/O錯(cuò)誤
此外如果數(shù)據(jù)沒(méi)有發(fā)送完成或者剩余了一部分會(huì)根據(jù)錯(cuò)誤碼判斷拋錯(cuò)
--ETIMEOUT錯(cuò)誤,如果是則報(bào)錯(cuò)Got timeout writing communication packets
--否則Got an error writing communication packets
注意這里的ETIMEOUT就是根據(jù)參數(shù)net-wirte-timeout設(shè)置的SOCKET超時(shí)設(shè)置
下面我將我寫的中文注釋加上源碼部分一同放出如下
-
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);//成功放回寫入字節(jié)數(shù)量 失敗返回-1 這里真正寫max_packet buffer包/mysql NET包>max_packet buffer的數(shù)據(jù)到socket
-
-
/* VIO_SOCKET_ERROR (-1) indicates an error. */
-
if (sentcnt == VIO_SOCKET_ERROR) //如果寫操作遇到錯(cuò)誤下面是異常處理 總體來(lái)說(shuō)就是暈倒的是EINTR就做重試,否則直接退出發(fā)送數(shù)據(jù)循環(huán)進(jìn)入異常處理if語(yǔ)句
-
{
-
/* A recoverable I/O error occurred? */
-
if (net_should_retry(net, &retry_count))
-
//1、線程安全客戶端如果是EINTR總是重試
-
//2、非線程安全客戶端或者服務(wù)器端如果是EINTR并且達(dá)到net->retry_count就跳出循環(huán)
-
//服務(wù)端MYSQLD肯定是線程安全的但是為了服務(wù)端的性能不可能在EINTR錯(cuò)誤下面無(wú)線重試
-
//非線程安全的客戶端可能全局區(qū)數(shù)據(jù)已經(jīng)混亂造成I/O錯(cuò)誤
-
continue;
-
else
-
break;
-
}
-
//下面是正常情況下
-
count-= sentcnt; //總數(shù)-發(fā)送的
-
buf+= sentcnt; //指針當(dāng)然也就相應(yīng)增加
-
update_statistics(thd_increment_bytes_sent(sentcnt));
-
}
-
-
/* On failure, propagate the error code. */
-
if (count) //如果count>0 也就是還有未發(fā)送的數(shù)據(jù)
-
{
-
/* Socket should be closed. */
-
net->error= 2;
-
-
/* Interrupted by a timeout? */
-
if (vio_was_timeout(net->vio)) //是否為ETIMEOUT錯(cuò)誤,如果是則報(bào)錯(cuò)Got timeout writing communication packets
-
net->last_errno= ER_NET_WRITE_INTERRUPTED;
-
else //否則報(bào)錯(cuò)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
-
}
到這里MYSQL層次對(duì)MYSQL數(shù)據(jù)包到MYSQL NET包的轉(zhuǎn)換和傳輸準(zhǔn)備已經(jīng)完成接下來(lái)就是通過(guò)
底層TCP/IP、以太網(wǎng)等協(xié)議進(jìn)行封裝然后通過(guò)socket傳輸了。下面一張圖對(duì)上面的說(shuō)明
進(jìn)行一個(gè)匯總,但是圖中有些細(xì)節(jié)并沒(méi)有表示出來(lái)還是最好通過(guò)源碼備注了解
三、MYSQL數(shù)據(jù)包的讀取scoket階段
1、合并多個(gè)MYSQL NET包為一個(gè)MYSQL 數(shù)據(jù)包
函數(shù)原型
ulong my_net_read(NET *net)
net:NET結(jié)構(gòu)體指針,一個(gè)MYSQL 數(shù)據(jù)包存儲(chǔ)在一個(gè)NET結(jié)構(gòu)體的buffer所指向的內(nèi)存
空間中
返回值為讀取到的實(shí)際一個(gè)MYSQL 數(shù)據(jù)包的長(zhǎng)度,不包MYSQL NET包的包頭字節(jié)數(shù)
這個(gè)函數(shù)調(diào)用net_read_packet來(lái)讀取一個(gè)MYSQL 數(shù)據(jù)包,如果為大的MYSQL 數(shù)據(jù)包完成解壓
合并操作源碼注釋中將大的MYSQL 數(shù)據(jù)包分為多個(gè)MYSQL NET包叫做packet of a multi-packet
下面我將我寫的中文注釋加上源碼部分一同放出如下,注意我忽略了解壓操作來(lái)降低學(xué)習(xí)的難度
-
ulong
-
my_net_read(NET *net) //
-
{
-
size_t len, complen;
-
-
MYSQL_NET_READ_START();
-
-
#ifdef HAVE_COMPRESS
-
if (!net->compress)//如果沒(méi)有壓縮
-
{
-
#endif
-
len= net_read_packet(net, &complen); //讀取一個(gè)MYSQL NET包返回實(shí)際長(zhǎng)度在len變量中,如果有壓縮
-
//壓縮前長(zhǎng)度保存在complen變量中 這個(gè)函數(shù)還有一個(gè)重要
-
//功能就是擴(kuò)張max_packet buffer的長(zhǎng)度直到max_packet_size
-
//限制,如果不能擴(kuò)張就報(bào)錯(cuò),這里也指出了一個(gè)現(xiàn)實(shí)每個(gè)MYSQL
-
//協(xié)議包必須放到一個(gè)max_packet buffer中,這也是很多packet
-
//buffer 不夠報(bào)錯(cuò)的根源
-
if (len == MAX_PACKET_LENGTH) //是否為一個(gè)滿包及大小為16M-1大小
-
{
-
/* First packet of a multi-packet. Concatenate the packets */
-
ulong save_pos = net->where_b;
-
size_t total_length= 0;
-
do //這里這個(gè)循環(huán)完成多個(gè)mysql net包合并為一個(gè)MYSQL 協(xié)議包的動(dòng)作
-
{
-
net->where_b += len; //讀取偏移量不斷增加
-
total_length += len; //總長(zhǎng)度不斷增加
-
len= net_read_packet(net, &complen); //讀取動(dòng)作
-
} while (len == MAX_PACKET_LENGTH);
-
if (len != packet_error) //packet_err被定義為 ~((unsigned long)(0))
-
len+= total_length; //這里要注意MYSQL協(xié)議包分為多個(gè)mysql net包后結(jié)束包的長(zhǎng)度是0,所以也不會(huì)增加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; //返回讀取的總長(zhǎng)度
-
#ifdef HAVE_COMPRESS
-
}
-
else //不考慮壓縮
-
{.....
2、獲得MYSQL NET包長(zhǎng)度階段
函數(shù)原型
static ulong net_read_packet(NET *net, size_t *complen)
net:NET結(jié)構(gòu)體指針,一個(gè)MYSQL 數(shù)據(jù)包存儲(chǔ)在一個(gè)NET結(jié)構(gòu)體的buffer所指向的內(nèi)存
空間中
complen:為輸出形參,輸出的是可能的壓縮前的數(shù)據(jù)長(zhǎng)度
返回值為實(shí)際讀取的MYSQL NET包的長(zhǎng)度大小( Read the packet data (payload))
失敗返回packet_error
本函數(shù)主要是為了獲得MYSQL NET包的長(zhǎng)度而封裝的一層函數(shù),net_read_packet_header為獲得MYSQL
NET包長(zhǎng)度函數(shù),并且本函數(shù)計(jì)算多個(gè)MYSQL NET包為一個(gè)MYSQL 數(shù)據(jù)包后需要的內(nèi)存空間是否夠用
如果不夠用分為如下操作
1、如果擴(kuò)充后NET BUFFER的大小不會(huì)超過(guò)參數(shù)max_packet_size設(shè)置的大小,則調(diào)用net_realloc()擴(kuò)充成功
2、如果擴(kuò)充后NET BUFFER的大小超過(guò)參數(shù)max_packet_size設(shè)置的大小,則調(diào)用net_realloc擴(kuò)充失敗報(bào)錯(cuò)
{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" }
這也是非常常見(jiàn)的一個(gè)錯(cuò)誤
當(dāng)然如果內(nèi)存不足都會(huì)引起如下錯(cuò)誤
{ "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" }
這里不對(duì)net_realloc函數(shù)和net_read_packet_header函數(shù)進(jìn)行詳細(xì)分析,如果有興趣自行研究
下面我將我寫的中文注釋加上源碼部分一同放出如下
-
static ulong net_read_packet(NET *net, size_t *complen)
-
{
-
size_t pkt_len, pkt_data_len;
-
-
*complen= 0;
-
-
net->reading_or_writing= 1; //將讀寫標(biāo)示設(shè)置為1,表示讀取開(kāi)始
-
-
/* Retrieve packet length and number. */
-
if (net_read_packet_header(net)) //讀取一個(gè)MYSQL net包的長(zhǎng)度和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包的長(zhǎng)度
-
-
/* End of big multi-packet. */
-
if (!pkt_len) //判斷是否為mysql數(shù)據(jù)包分包后的結(jié)束包
-
goto end;
-
-
pkt_data_len = max(pkt_len, *complen) + net->where_b; //獲得讀取此MYSQL NET包后需要的內(nèi)存空間,也就是整個(gè)NET BUFFER需要多大,需要判斷如果是
-
//是經(jīng)過(guò)壓縮的需要的空間是數(shù)據(jù)壓縮前的長(zhǎng)度
-
-
/* Expand packet buffer if necessary. */
-
if ((pkt_data_len >= net->max_packet) && net_realloc(net, pkt_data_len)) //這里實(shí)際的判斷net buffer是否夠用,如果不夠用調(diào)用realloc進(jìn)行內(nèi)存擴(kuò)充,
-
//在realloc中判斷是否超過(guò)max_packet_size的設(shè)置
-
goto error;
-
-
/* Read the packet data (payload). */
-
if (net_read_raw_loop(net, pkt_len)) //開(kāi)始進(jìn)行實(shí)際的讀取操作
-
goto error;
-
-
end:
-
net->reading_or_writing= 0; //將讀寫標(biāo)示設(shè)置為0,表示讀取結(jié)束
-
return pkt_len; //函數(shù)返回本次讀取
-
-
error: //出錯(cuò)返回值
-
net->reading_or_writing= 0;
-
return packet_error;
-
}
3、調(diào)用vio虛擬I/O接口進(jìn)行讀取階段
函數(shù)原型
static my_bool net_read_raw_loop(NET *net, size_t count)
net:NET結(jié)構(gòu)體指針,一個(gè)MYSQL 數(shù)據(jù)包存儲(chǔ)在一個(gè)NET結(jié)構(gòu)體的buffer所指向的內(nèi)存
空間中
count:本次讀取的MYSQL NET包有多大,如果是壓縮過(guò)的MYSQL NET包不是壓縮前的數(shù)據(jù)而是壓縮后的MYSQL NET包長(zhǎng)度
(@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); //如果寫操作遇到錯(cuò)誤下面是異常處理 總體來(lái)說(shuō)就是暈倒的是EINTR就做重試,否則直接退出發(fā)送數(shù)據(jù)循環(huán)進(jìn)入異常處理if語(yǔ)句
-
-
/* 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、非線程安全客戶端或者服務(wù)器端如果是EINTR并且達(dá)到net->retry_count就跳出循環(huán)
-
//服務(wù)端MYSQLD肯定是線程安全的但是為了服務(wù)端的性能不可能在EINTR錯(cuò)誤下面無(wú)線重試
-
//非線程安全的客戶端可能全局區(qū)數(shù)據(jù)已經(jīng)混亂造成I/O錯(cuò)誤
-
-
continue;
-
else
-
break;
-
}
-
/* Zero indicates end of file. */
-
else if (!recvcnt) //recv半連接狀態(tài)? 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 也就是沒(méi)有讀取到預(yù)期的數(shù)據(jù)
-
{
-
/* Socket should be closed. */
-
net->error= 2;
-
-
/* Interrupted by a timeout? */
-
if (!eof && vio_was_timeout(net->vio)) //是否為ETIMEOUT錯(cuò)誤,如果是則報(bào)錯(cuò)Got timeout reading communication packets
-
net->last_errno= ER_NET_READ_INTERRUPTED;
-
else
-
net->last_errno= ER_NET_READ_ERROR;//否則報(bào)錯(cuò)Got an error reading communication packets
-
網(wǎng)頁(yè)題目:MYSQLCLENT/SERVER數(shù)據(jù)包傳輸及netpacketbuffer作用解析
文章轉(zhuǎn)載:http://weahome.cn/article/jsespj.html