真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

MYSQLCLENT/SERVER數(shù)據(jù)包傳輸及netpacketbuffer作用解析

原創(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é)議。
                    MYSQL CLENT/SERVER數(shù)據(jù)包傳輸及net packet buffer作用解析


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 CLENT/SERVER數(shù)據(jù)包傳輸及net packet buffer作用解析
 
                     我們約定它叫做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包:
               MYSQL CLENT/SERVER數(shù)據(jù)包傳輸及net packet buffer作用解析
               
               為什么有一個(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ō)明:
MYSQL CLENT/SERVER數(shù)據(jù)包傳輸及net packet buffer作用解析
MYSQL CLENT/SERVER數(shù)據(jù)包傳輸及net packet buffer作用解析MYSQL CLENT/SERVER數(shù)據(jù)包傳輸及net packet buffer作用解析MYSQL CLENT/SERVER數(shù)據(jù)包傳輸及net packet buffer作用解析


   可以看到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、包封裝流程
如下圖:
MYSQL CLENT/SERVER數(shù)據(jù)包傳輸及net packet buffer作用解析

本文研究是應(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)用合理。

  1. my_bool my_net_write(NET *net, const uchar *packet, size_t len) //將長(zhǎng)度為packet的數(shù)據(jù)寫入到net->buffer
  2. {
  3.   uchar buff[NET_HEADER_SIZE]; // lenth 3 seq 1 4bytes
  4.   int rc;

  5.   if (unlikely(!net->vio)) /* nowhere to write */
  6.     return 0;

  7.   MYSQL_NET_WRITE_START(len);

  8.   DBUG_EXECUTE_IF("simulate_net_write_failure", {
  9.                   my_error(ER_NET_ERROR_ON_WRITE, MYF(0));
  10.                   return 1;
  11.                   };
  12.                  );

  13.   /*
  14.     Big packets are handled by splitting them in packets of MAX_PACKET_LENGTH
  15.     length. The last packet is always a packet that is < MAX_PACKET_LENGTH.
  16.     (The last packet may even have a length of 0)
  17.   */
  18.   while (len >= MAX_PACKET_LENGTH) //如果寫入MYSQL 協(xié)議包的長(zhǎng)度大于了最大mysq NET包 就分為多個(gè)MYSQL NET包
  19.   {
  20.     const ulong z_size = MAX_PACKET_LENGTH; // 16M-1 計(jì)為包的長(zhǎng)度
  21.     int3store(buff, z_size); //將長(zhǎng)度寫入到棧 buff中
  22.     buff[3]= (uchar) net->pkt_nr++; //將buffer中的 seq+1 當(dāng)然 pkt_nr 序列也+1
  23.     if (net_write_buff(net, buff, NET_HEADER_SIZE) || //寫入MYSQL NET包頭部
  24.         net_write_buff(net, packet, z_size)) //將長(zhǎng)度為z_size的進(jìn)行拆分的MYSQL 協(xié)議包一分部寫入到net buffer中
  25.     {
  26.       MYSQL_NET_WRITE_DONE(1);
  27.       return 1;
  28.     }
  29.     packet += z_size; //將packet的指針 加上z_size的大小 其實(shí)也就是16M-1
  30.     len-= z_size; //當(dāng)然len 也就相應(yīng)的減少z_size 其實(shí)也就是16M-1
  31.   }
  32.   //如果不是大的MYSQL 協(xié)議包或者是MYSQL協(xié)議包的最后一部分則執(zhí)行下面代碼
  33.   /* Write last packet */
  34.   int3store(buff,len); //將最后的長(zhǎng)度計(jì)入buffer 頭3字節(jié)
  35.   buff[3]= (uchar) net->pkt_nr++; //當(dāng)然序號(hào)繼續(xù)+1
  36.   if (net_write_buff(net, buff, NET_HEADER_SIZE)) //寫入MYSQL NET包頭部
  37.   {
  38.     MYSQL_NET_WRITE_DONE(1);
  39.     return 1;
  40.   }
  41. #ifndef DEBUG_DATA_PACKETS
  42.   DBUG_DUMP("packet_header", buff, NET_HEADER_SIZE);
  43. #endif
  44.   rc= MY_TEST(net_write_buff(net,packet,len));//寫入 MYSQL 協(xié)議包 的最后數(shù)據(jù)寫入到net buffer中
  45.   MYSQL_NET_WRITE_DONE(rc);
  46.   return rc;
  47. }
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ì)描述
下面我將我寫的中文注釋加上源碼部分一同放出如下:

點(diǎn)擊(此處)折疊或打開(kāi)

  1. static my_bool
  2. net_write_buff(NET *net, const uchar *packet, ulong len)
  3. {
  4.   ulong left_length;
  5.   //下面計(jì)算buffer->max_packet的剩余空間
  6.   if (net->compress && net->max_packet > MAX_PACKET_LENGTH)
  7.     left_length= (ulong) (MAX_PACKET_LENGTH - (net->write_pos - net->buff));
  8.   else
  9.     left_length= (ulong) (net->buff_end - net->write_pos);

  10. #ifdef DEBUG_DATA_PACKETS
  11.   DBUG_DUMP("data", packet, len);
  12. #endif
  13.   if (len > left_length) //如果長(zhǎng)度大于剩余空間
  14.   {
  15.     if (net->write_pos != net->buff)
  16.     {
  17.       /* Fill up already used packet and write it */
  18.       memcpy(net->write_pos, packet, left_length); //這里使用指針packet后left_lengeh長(zhǎng)度來(lái)填滿整個(gè)net buffer
  19.       if (net_write_packet(net, net->buff,
  20.                            (size_t) (net->write_pos - net->buff) + left_length))//寫滿后,然后調(diào)用net_write_packet寫到scoket
  21.                            //(size_t) (net->write_pos - net->buff) + left_length 為整個(gè)buffer長(zhǎng)度
  22.         return 1;
  23.       net->write_pos= net->buff; //這里wirte_pos指針 應(yīng)該也是移動(dòng)了到了wirte_pos+left_lengeh
  24.       packet+= left_length; //packet 指針增加
  25.       len-= left_length; //長(zhǎng)度相應(yīng)減少
  26.     }
  27.     if (net->compress)//壓縮屬性先不考慮,實(shí)際是壓縮開(kāi)啟使用Zlib進(jìn)行壓縮位于Zlib/compress中
  28.     {
  29.      ..................
  30.     }
  31.     if (len > net->max_packet) //如果填滿 net->max_packet 后剩余的數(shù)據(jù) 還是大于整個(gè)net buffer 大小,則跳過(guò)緩沖區(qū)直接寫scoket (重要)
  32.                                 //實(shí)際上這里len 最大為16M-1, 如果為16M-1的MYSQL NET包始終會(huì)使用直接寫入的方法,這點(diǎn)
  33.                                 //和read階段不同,read階段會(huì)有一個(gè)合并mysql net包為MYSQL協(xié)議包過(guò)程,net buffer有著額外
  34.                                 //的使命
  35.       return net_write_packet(net, packet, len); //直接調(diào)用net_write_packet寫入
  36.     /* Send out rest of the blocks as full sized blocks */
  37.   }
  38.   memcpy(net->write_pos, packet, len); //如果長(zhǎng)度小于 net buffer剩余的空間,只是寫入net buffer 即可
  39.   net->write_pos+= len; //這里wirte_pos指針也移動(dòng)相應(yīng)的長(zhǎng)度
  40.   return 0;
  41. }
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ò)多的討論
下面我將我寫的中文注釋加上源碼部分一同放出如下

點(diǎn)擊(此處)折疊或打開(kāi)

  1. my_bool
  2. net_write_packet(NET *net, const uchar *packet, size_t length) //函數(shù)并沒(méi)有真正傳輸只是做了一層封裝將數(shù)據(jù)壓縮封裝在內(nèi)
  3. //注意這里的數(shù)據(jù)可能來(lái)自net->buffer 可能來(lái)自net_flush
  4. {
  5.   my_bool res;
  6.   DBUG_ENTER("net_write_packet");

  7. #if defined(MYSQL_SERVER) && defined(USE_QUERY_CACHE)
  8.   query_cache_insert((char*) packet, length, net->pkt_nr);
  9. #endif

  10.   /* Socket can't be used */
  11.   if (net->error == 2)
  12.     DBUG_RETURN(TRUE);

  13.   net->reading_or_writing= 2; //設(shè)置標(biāo)示表示開(kāi)始寫入

  14. #ifdef HAVE_COMPRESS //參數(shù)是否開(kāi)啟
  15.   const bool do_compress= net->compress;
  16.   if (do_compress) //MYSQL自己決定是否開(kāi)啟壓縮
  17.   {
  18.     if ((packet= compress_packet(net, packet, &length)) == NULL) //壓縮數(shù)據(jù) 如果內(nèi)存不足報(bào)錯(cuò)
  19.      //{ "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" },
  20.      //壓縮完成后返回一個(gè)malloc的內(nèi)存空間(壓縮后數(shù)據(jù)的內(nèi)存首地址)給packet,這個(gè)時(shí)候packet已經(jīng)不是形參的packet了,需要釋放
  21.     {
  22.       net->error= 2;
  23.       net->last_errno= ER_OUT_OF_RESOURCES;
  24.       /* In the server, allocation failure raises a error. */
  25.       net->reading_or_writing= 0;
  26.       DBUG_RETURN(TRUE);
  27.     }
  28.   }
  29. #endif /* HAVE_COMPRESS */

  30. #ifdef DEBUG_DATA_PACKETS
  31.   DBUG_DUMP("data", packet, length);
  32. #endif

  33.   res= net_write_raw_loop(net, packet, length); //進(jìn)行真正的底層傳輸工作

  34. #ifdef HAVE_COMPRESS//參數(shù)是否開(kāi)啟
  35.   if (do_compress)//mysql自己決定
  36.     my_free((void *) packet);//如前所述這里需要釋放壓縮后數(shù)據(jù)的內(nèi)存避免泄露
  37. #endif

  38.   net->reading_or_writing= 0;//關(guān)閉標(biāo)示

  39.   DBUG_RETURN(res);
  40. }
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è)置
下面我將我寫的中文注釋加上源碼部分一同放出如下

點(diǎn)擊(此處)折疊或打開(kāi)

  1. static my_bool
  2. net_write_raw_loop(NET *net, const uchar *buf, size_t count)
  3. {
  4.   unsigned int retry_count= 0;

  5.   while (count)
  6.   {
  7.     size_t sentcnt= vio_write(net->vio, buf, count);//成功放回寫入字節(jié)數(shù)量 失敗返回-1 這里真正寫max_packet buffer包/mysql NET包>max_packet buffer的數(shù)據(jù)到socket

  8.     /* VIO_SOCKET_ERROR (-1) indicates an error. */
  9.     if (sentcnt == VIO_SOCKET_ERROR) //如果寫操作遇到錯(cuò)誤下面是異常處理 總體來(lái)說(shuō)就是暈倒的是EINTR就做重試,否則直接退出發(fā)送數(shù)據(jù)循環(huán)進(jìn)入異常處理if語(yǔ)句
  10.     {
  11.       /* A recoverable I/O error occurred? */
  12.       if (net_should_retry(net, &retry_count))
  13.      //1、線程安全客戶端如果是EINTR總是重試
  14.          //2、非線程安全客戶端或者服務(wù)器端如果是EINTR并且達(dá)到net->retry_count就跳出循環(huán)
  15.          //服務(wù)端MYSQLD肯定是線程安全的但是為了服務(wù)端的性能不可能在EINTR錯(cuò)誤下面無(wú)線重試
  16.          //非線程安全的客戶端可能全局區(qū)數(shù)據(jù)已經(jīng)混亂造成I/O錯(cuò)誤
  17.         continue;
  18.       else
  19.         break;
  20.     }
  21.     //下面是正常情況下
  22.     count-= sentcnt; //總數(shù)-發(fā)送的
  23.     buf+= sentcnt; //指針當(dāng)然也就相應(yīng)增加
  24.     update_statistics(thd_increment_bytes_sent(sentcnt));
  25.   }

  26.   /* On failure, propagate the error code. */
  27.   if (count) //如果count>0 也就是還有未發(fā)送的數(shù)據(jù)
  28.   {
  29.     /* Socket should be closed. */
  30.     net->error= 2;

  31.     /* Interrupted by a timeout? */
  32.     if (vio_was_timeout(net->vio)) //是否為ETIMEOUT錯(cuò)誤,如果是則報(bào)錯(cuò)Got timeout writing communication packets
  33.       net->last_errno= ER_NET_WRITE_INTERRUPTED;
  34.     else //否則報(bào)錯(cuò)Got an error writing communication packets
  35.       net->last_errno= ER_NET_ERROR_ON_WRITE;
  36. #ifdef MYSQL_SERVER
  37.     my_error(net->last_errno, MYF(0));
  38. #endif
  39.   }
到這里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 CLENT/SERVER數(shù)據(jù)包傳輸及net packet buffer作用解析
三、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í)的難度

點(diǎn)擊(此處)折疊或打開(kāi)

  1. ulong
  2. my_net_read(NET *net) //
  3. {
  4.   size_t len, complen;

  5.   MYSQL_NET_READ_START();

  6. #ifdef HAVE_COMPRESS
  7.   if (!net->compress)//如果沒(méi)有壓縮
  8.   {
  9. #endif
  10.     len= net_read_packet(net, &complen); //讀取一個(gè)MYSQL NET包返回實(shí)際長(zhǎng)度在len變量中,如果有壓縮
  11.                                          //壓縮前長(zhǎng)度保存在complen變量中 這個(gè)函數(shù)還有一個(gè)重要
  12.                                          //功能就是擴(kuò)張max_packet buffer的長(zhǎng)度直到max_packet_size
  13.                                          //限制,如果不能擴(kuò)張就報(bào)錯(cuò),這里也指出了一個(gè)現(xiàn)實(shí)每個(gè)MYSQL
  14.                                          //協(xié)議包必須放到一個(gè)max_packet buffer中,這也是很多packet
  15.                                          //buffer 不夠報(bào)錯(cuò)的根源
  16.     if (len == MAX_PACKET_LENGTH) //是否為一個(gè)滿包及大小為16M-1大小
  17.     {
  18.       /* First packet of a multi-packet. Concatenate the packets */
  19.       ulong save_pos = net->where_b;
  20.       size_t total_length= 0;
  21.       do //這里這個(gè)循環(huán)完成多個(gè)mysql net包合并為一個(gè)MYSQL 協(xié)議包的動(dòng)作
  22.       {
  23.         net->where_b += len; //讀取偏移量不斷增加
  24.         total_length += len; //總長(zhǎng)度不斷增加
  25.         len= net_read_packet(net, &complen); //讀取動(dòng)作
  26.       } while (len == MAX_PACKET_LENGTH);
  27.       if (len != packet_error) //packet_err被定義為 ~((unsigned long)(0))
  28.         len+= total_length; //這里要注意MYSQL協(xié)議包分為多個(gè)mysql net包后結(jié)束包的長(zhǎng)度是0,所以也不會(huì)增加len
  29.       net->where_b = save_pos;
  30.     }
  31.     net->read_pos = net->buff + net->where_b;
  32.     if (len != packet_error)
  33.       net->read_pos[len]=0;        /* Safeguard for mysql_use_result */
  34.     MYSQL_NET_READ_DONE(0, len);
  35.     return len; //返回讀取的總長(zhǎng)度
  36. #ifdef HAVE_COMPRESS
  37.   }
  38.   else //不考慮壓縮
  39.   {.....



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ì)分析,如果有興趣自行研究
下面我將我寫的中文注釋加上源碼部分一同放出如下

點(diǎn)擊(此處)折疊或打開(kāi)

  1. static ulong net_read_packet(NET *net, size_t *complen)
  2. {
  3.   size_t pkt_len, pkt_data_len;

  4.   *complen= 0;

  5.   net->reading_or_writing= 1; //將讀寫標(biāo)示設(shè)置為1,表示讀取開(kāi)始

  6.   /* Retrieve packet length and number. */
  7.   if (net_read_packet_header(net)) //讀取一個(gè)MYSQL net包的長(zhǎng)度和MYSQL NET sequence
  8.     goto error;

  9.   net->compress_pkt_nr= net->pkt_nr;

  10. #ifdef HAVE_COMPRESS
  11.   if (net->compress)//先不考慮壓縮
  12.   {
  13.    .......
  14.   }
  15. #endif

  16.   /* The length of the packet that follows. */
  17.   pkt_len= uint3korr(net->buff+net->where_b);//獲得本MYSQL NET包的長(zhǎng)度

  18.   /* End of big multi-packet. */
  19.   if (!pkt_len) //判斷是否為mysql數(shù)據(jù)包分包后的結(jié)束包
  20.     goto end;

  21.   pkt_data_len = max(pkt_len, *complen) + net->where_b; //獲得讀取此MYSQL NET包后需要的內(nèi)存空間,也就是整個(gè)NET BUFFER需要多大,需要判斷如果是
  22.                                                         //是經(jīng)過(guò)壓縮的需要的空間是數(shù)據(jù)壓縮前的長(zhǎng)度

  23.   /* Expand packet buffer if necessary. */
  24.   if ((pkt_data_len >= net->max_packet) && net_realloc(net, pkt_data_len)) //這里實(shí)際的判斷net buffer是否夠用,如果不夠用調(diào)用realloc進(jìn)行內(nèi)存擴(kuò)充,
  25.                                                                            //在realloc中判斷是否超過(guò)max_packet_size的設(shè)置
  26.     goto error;

  27.   /* Read the packet data (payload). */
  28.   if (net_read_raw_loop(net, pkt_len)) //開(kāi)始進(jìn)行實(shí)際的讀取操作
  29.     goto error;

  30. end:
  31.   net->reading_or_writing= 0; //將讀寫標(biāo)示設(shè)置為0,表示讀取結(jié)束
  32.   return pkt_len; //函數(shù)返回本次讀取

  33. error: //出錯(cuò)返回值
  34.   net->reading_or_writing= 0;
  35.   return packet_error;
  36. }

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

點(diǎn)擊(此處)折疊或打開(kāi)

  1. static my_bool net_read_raw_loop(NET *net, size_t count)
  2. {
  3.   bool eof= false;
  4.   unsigned int retry_count= 0;
  5.   uchar *buf= net->buff + net->where_b;

  6.   while (count)
  7.   {
  8.     size_t recvcnt= vio_read(net->vio, buf, count); //如果寫操作遇到錯(cuò)誤下面是異常處理 總體來(lái)說(shuō)就是暈倒的是EINTR就做重試,否則直接退出發(fā)送數(shù)據(jù)循環(huán)進(jìn)入異常處理if語(yǔ)句

  9.     /* VIO_SOCKET_ERROR (-1) indicates an error. */
  10.     if (recvcnt == VIO_SOCKET_ERROR) //
  11.     {
  12.       /* A recoverable I/O error occurred? */
  13.       if (net_should_retry(net, &retry_count))
  14.      //1、線程安全客戶端如果是EINTR總是重試
  15.      //2、非線程安全客戶端或者服務(wù)器端如果是EINTR并且達(dá)到net->retry_count就跳出循環(huán)
  16.      //服務(wù)端MYSQLD肯定是線程安全的但是為了服務(wù)端的性能不可能在EINTR錯(cuò)誤下面無(wú)線重試
  17.      //非線程安全的客戶端可能全局區(qū)數(shù)據(jù)已經(jīng)混亂造成I/O錯(cuò)誤
  18.          
  19.         continue;
  20.       else
  21.         break;
  22.     }
  23.     /* Zero indicates end of file. */
  24.     else if (!recvcnt) //recv半連接狀態(tài)? LINUX man recv:The return values will be 0 when the peer has performed an orderly shutdown
  25.     {
  26.       eof= true;
  27.       break;
  28.     }

  29.     count-= recvcnt;
  30.     buf+= recvcnt;
  31.     update_statistics(thd_increment_bytes_received(recvcnt));
  32.   }

  33.   /* On failure, propagate the error code. */
  34.   if (count)//如果count>0 也就是沒(méi)有讀取到預(yù)期的數(shù)據(jù)
  35.   {
  36.     /* Socket should be closed. */
  37.     net->error= 2;

  38.     /* Interrupted by a timeout? */
  39.     if (!eof && vio_was_timeout(net->vio)) //是否為ETIMEOUT錯(cuò)誤,如果是則報(bào)錯(cuò)Got timeout reading communication packets
  40.       net->last_errno= ER_NET_READ_INTERRUPTED;
  41.     else
  42.       net->last_errno= ER_NET_READ_ERROR;//否則報(bào)錯(cuò)Got an error reading communication packets

  43. 網(wǎng)頁(yè)題目:MYSQLCLENT/SERVER數(shù)據(jù)包傳輸及netpacketbuffer作用解析
    文章轉(zhuǎn)載:http://weahome.cn/article/jsespj.html

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部