自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

關(guān)于MySQL的網(wǎng)絡(luò)協(xié)議分析

數(shù)據(jù)庫 MySQL
本篇文章讓我們來看看最基礎(chǔ)的MySQL網(wǎng)絡(luò)協(xié)議分析,主要講述了MySQL的連接方式,通信過程及協(xié)議,以及傳輸包的基本格式和相關(guān)傳輸包的類型。

MySQL對大家來說,都應(yīng)該很熟悉了,從大學(xué)里的課程到實際工作中數(shù)據(jù)的存儲查詢,很多時候都需要用到數(shù)據(jù)庫,很多人也寫過與數(shù)據(jù)庫交互的程序,在Java中你可能一開始會使用原生mysql-connector-java來進(jìn)行操作,后來你會接觸到Hibernate,Mybatis等ORM框架,其實它們底層也是基于mysql-connector-java,但很多時候我們并不清楚程序是怎么跟數(shù)據(jù)庫具體交互的,比如執(zhí)行一個SQL查詢,程序是如何從MySQL中獲取數(shù)據(jù)的呢?今天就讓我們來看看最基礎(chǔ)的MySQL網(wǎng)絡(luò)協(xié)議分析。

引言

閱讀本文之前你需要對網(wǎng)絡(luò)協(xié)議需要有基本的了解,比如兩臺機子之間的數(shù)據(jù)是如何通信的,硬件層可以暫時不需了解,但網(wǎng)絡(luò)層和傳輸層的協(xié)議要有一定的理解,比如IP數(shù)據(jù)包,TCP/IP協(xié)議,UDP協(xié)議等相關(guān)概念,有了這些基礎(chǔ),有利于你閱讀本文。

背景

在歷史悠久的時代,數(shù)據(jù)庫只作為單機存儲,也不怎么需要與程序進(jìn)行交互的時候的首,它的網(wǎng)絡(luò)通信并不是那么重要,但隨著時代的發(fā)展,數(shù)據(jù)庫不再只是單純的作為一個數(shù)據(jù)的倉庫了,它需要提供與外界的交互,比如遠(yuǎn)程連接,程序操作數(shù)據(jù)庫等,這時候一份規(guī)范的網(wǎng)絡(luò)通信的協(xié)議就非常重要了,比如它是如何校驗權(quán)限,如何解析SQL語句,如何返回執(zhí)行結(jié)果都需要用到相應(yīng)的協(xié)議,很多時候我們并不需要接觸這些內(nèi)容,因為它太底層了,我們直接使用把它們封裝好的第三方包就可以了,為什么還要去學(xué)習(xí)它的網(wǎng)絡(luò)協(xié)議呢?確實對于一開始學(xué)習(xí)編程的人來說,這有點操之過急,反而有時候會適得其反,但當(dāng)你對這一方面有了一定的了解之后,你便會迫不及待得想去探索更深層的奧秘,去了解并學(xué)習(xí)我們平常用的第三方類庫是怎么去實現(xiàn),明白它的底層原理,甚至對一些莫名其妙的bug也不會再害怕。

MySQL連接方式

分析協(xié)議,我們首先要了解如何與數(shù)據(jù)庫連接,說到MySQL連接方式,大家突然可能有點懵,其實它一直伴隨著我們,比如我們第一次裝數(shù)據(jù)庫完成后執(zhí)行的第一次登錄,比如你沒有設(shè)置密碼: 

  1. mysql -uroot 

這是最基本的一種數(shù)據(jù)庫連接方式,那么MySQL連接方式到底有幾種呢?到MySQL5.7為止,總共有五種,分別是TCP/IP,TLS/SSL,Unix Sockets,Shared Memory,Named pipes,下面我們就來看看這五種的區(qū)別:

方式 默認(rèn)開啟 支持系統(tǒng) 只支持本機 如何開啟 參數(shù)配置
TCP/IP 所有系統(tǒng) --skip-networking=yes/no. --port
--bind-address
TLS/SSL 所有系統(tǒng)(基于TCP/IP)之上 --ssl=yes/no. --ssl-* options
Unix Sockets 類Unix系統(tǒng) 設(shè)置--socket=<empty> 來關(guān)閉. --socket=socket path
Shared Memory Windows系統(tǒng) --shared-memory=on/off. --shared-memory-base-name=<name>
Named pipes Windows系統(tǒng) --enable-named-pipe=on/off. --socket=<name>

從上表中我們可以清晰看出每種連接方式的區(qū)別,接下里我會具體說明幾種連接是怎么操作的,由于我的機子是Mac OS系統(tǒng),這里只模擬非Windows系統(tǒng)下的三種方式,因為這三種方式都是默認(rèn)開啟的,我們不需要進(jìn)行任何配置:

1.Unix Sockets: 

  1. mysql -uroot  

若你在本機使用這種方式連接MySQL數(shù)據(jù)庫的話,它默認(rèn)會使用Unix Sockets。

2.TCP/IP: 

  1. mysql --protocol=tcp -uroot 
  2.  
  3. mysql -P3306 -h127.0.0.1 -uroot  

連接的時候我們指定連接協(xié)議,或者指定相應(yīng)的IP及端口,我們的連接方式就變成了TCP/IP方式。

3.TLS/SSL: 

  1. mysql --protocol=tcp -uroot --ssl=on 
  2.  
  3. mysql -P3306 -h127.0.0.1 -uroot --ssl=on  

上表說過,TLS/SSL是基于TCP/IP的,所以我們只需再指定打開ssl配置即可。 

然后我們可以通過以下語句來查詢目前數(shù)據(jù)庫的連接情況: 

  1. SELECT DISTINCT connection_type from performance_schema.threads where connection_type is not null  

那么我們?nèi)绾芜x擇連接方式呢?個人總結(jié)了以下幾個原則: 

  • 若是你能確定程序和數(shù)據(jù)庫在同一臺機子(類Unix系統(tǒng))上,推薦使用Unix Sockets,因為它效率更高;
  • 若數(shù)據(jù)庫分布在不同的機子上,且能確保連接安全或者安全性要求不是那么高,推薦使用TCP/IP,反之使用TLS/SSL; 

MySQL數(shù)據(jù)包 

通信中最重要的就是數(shù)據(jù),那么程序是如何和MySQL Server進(jìn)行通信,并交互數(shù)據(jù)的呢?比如如何驗證賬戶,發(fā)送查詢語句,返回執(zhí)行結(jié)果等,我先畫一個流程圖來模擬一下整個過程,幫助大家理解: 

整個過程相對來說還是比較清晰的,我們對連接請求和斷開請求不需要過分關(guān)心,只需要了解這一點就可以了,重要的是其他幾點,那么在這幾步中,數(shù)據(jù)是怎么進(jìn)行交互的呢? 

其實主要就是兩步,Client將執(zhí)行命令編碼成Server要求的格式傳輸給Server端執(zhí)行,Server端將執(zhí)行結(jié)果傳輸給Client端,Client端再根據(jù)相應(yīng)的數(shù)據(jù)包格式解析獲得所需的數(shù)據(jù)。

1.基本數(shù)據(jù)類型 

雖然網(wǎng)絡(luò)中的數(shù)據(jù)是用字節(jié)傳輸?shù)?,但它背后的?shù)據(jù)源都是有類型的數(shù)據(jù),MySQL協(xié)議也有基本的數(shù)據(jù)類型,好比Java中的8種基本數(shù)據(jù)類型,但MySQL協(xié)議中簡單的多,它只有兩種基本數(shù)據(jù)類型,分別為Integer(整型),String(字符串),下面我們就來看看這兩種類型。

Integer(整型) 

首先Integer在MySQL協(xié)議中有兩種編碼方式,分別為FixedLengthInteger和LengthEncodedInteger,其中前者用于存儲無符號定長整數(shù),實際中使用的不多,這里著重講一下后者。 

使用LengthEncodedInteger編碼的整數(shù)可能會使用1, 3, 4, 或者9 個字節(jié),具體使用字節(jié)取決于數(shù)值的大小,下表是不同的數(shù)據(jù)長度的整數(shù)所使用的字節(jié)數(shù):

最小值(包含) 最大值(不包含) 存儲方式
0 251 1個字節(jié)
251 2^16 3個字節(jié)(0xFC + 2個字節(jié)具體數(shù)據(jù))
2^16 2^24 4個字節(jié)(0xFD + 3個字節(jié)具體數(shù)據(jù))
2^24 2^64 9個字節(jié)(0xFE + 8個字節(jié)具體數(shù)據(jù))

 舉個簡單的例子,比如1024的編碼為: 

  1. 0xFC 0x00 0x04  

其中0x代表16進(jìn)制,實際數(shù)據(jù)傳輸中并沒有該標(biāo)識,第一位代表這是一個251~2^16之間的數(shù)值,所以后面兩位為數(shù)值具體的值,這里使用的是小端字節(jié)序,MySQL默認(rèn)使用的也是這種編碼次序,所以這里1024是0x00 0x04,字節(jié)序相關(guān)知識可以參考:理解字節(jié)序,到這里大家應(yīng)該對這種編碼格式有了一定的了解了,下面我們就來看看String。

String(字符串) 

String的編碼格式相對Integer來說會復(fù)雜一點,主要有以下幾種: 

  • FixedLengthString(定長方式):需先知道String的長度,MySQL中的一個例子就是ERR_Packet包(后續(xù)會講到)就使用了這種編碼方式,因為它的長度固定,用5個字節(jié)存儲所有數(shù)據(jù)。
  • NullTerminatedString(Null結(jié)尾方式): 字符串以遇到Null作為結(jié)束標(biāo)志,相應(yīng)的字節(jié)為00。
  • VariableLengthString(動態(tài)計算字符串長度方式): 字符串的長度取決于其他變量計算而定,比如一個字符串由Integer + Value組成,我們通過計算Integer的值來獲取Value的具體的長度。
  • LengthEncodedString(指定字符串長度方式): 與VariableLengthString原理相似,是它的一種特殊情況,具體例子就是我上條舉的這個例子。
  • RestOfPacketString(包末端字符串方式):一個包末端的字符串,可根據(jù)包的總長度金和當(dāng)前位置得到字符串的長度,實際中并不常用。 

總的來說String的編碼格式種類相對比較多,不同方式之間的區(qū)別也比較大,若要深刻理解還需從實際的例子里去學(xué)習(xí),后續(xù)文章中我會寫幾個demo帶大家一起去探索。

2.基本數(shù)據(jù)包格式 

數(shù)據(jù)包格式也主要分為兩種,一種是Server端向Client端發(fā)送的數(shù)據(jù)包格式,另一種則是Client向Server端發(fā)送的數(shù)據(jù)包。

Server to Client 

Server向Client發(fā)送的數(shù)據(jù)包有兩個原則: 

  • 每個數(shù)據(jù)包大小不能超過2^24字節(jié)(16MB);
  • 每個數(shù)據(jù)包前都需要加上數(shù)據(jù)包信息; 

每個包的基本格式:

Type Name Description
int<3> payload_length(包數(shù)據(jù)長度) 具體數(shù)據(jù)包的內(nèi)容長度,從出去頭部四個字節(jié)后開始的內(nèi)容
int<1> sequence_id(包序列id) 每個包的序列id,總數(shù)據(jù)內(nèi)容大于16MB時需要用,從0開始,依次增加,新的命令執(zhí)行會重載為0
string payload(具體數(shù)據(jù)) 包中除去頭部后的具體數(shù)據(jù)內(nèi)容

 舉個列子:

例子 解釋

 

  1. 01 00 00 00 01| <li>payload_length: 1</li> <li>sequence_id: 0x00</li><li>payload: 0x01</li>  

若是數(shù)據(jù)內(nèi)容大于或者等于2^24-1個字節(jié),將會拆分發(fā)送,舉個例子,比如發(fā)送16 777 215 (2^24-1) 字節(jié)的內(nèi)容,則會按一下這種方式發(fā)送 

  1. ff ff ff 00 ... 
  2. 00 00 00 01  

第一個數(shù)據(jù)包滿載,第二個數(shù)據(jù)包是一個空數(shù)據(jù)包(一種臨界情況)。

Client to Server 

Client向Server端發(fā)送的格式相對來說就簡單一點了

Type Name Description
int<1> 執(zhí)行命令 執(zhí)行的操作,比如切換數(shù)據(jù)庫,查詢表等操作
string 參數(shù) 命令相應(yīng)的參數(shù)

 命令列表(摘抄自胡桃夾子的博客):

類型值 命令 功能
0x00 COM_SLEEP (內(nèi)部線程狀態(tài))
0x01 COM_QUIT 關(guān)閉連接
0x02 COM_INIT_DB 切換數(shù)據(jù)庫
0x03 COM_QUERY SQL查詢請求
0x04 COM_FIELD_LIST 獲取數(shù)據(jù)表字段信息
0x05 COM_CREATE_DB 創(chuàng)建數(shù)據(jù)庫
0x06 COM_DROP_DB 刪除數(shù)據(jù)庫
0x07 COM_REFRESH 清除緩存
0x08 COM_SHUTDOWN 停止服務(wù)器
0x09 COM_STATISTICS 獲取服務(wù)器統(tǒng)計信息
0x0A COM_PROCESS_INFO 獲取當(dāng)前連接的列表
0x0B COM_CONNECT (內(nèi)部線程狀態(tài))
0x0C COM_PROCESS_KILL 中斷某個連接
0x0D COM_DEBUG 保存服務(wù)器調(diào)試信息
0x0E COM_PING 測試連通性
0x0F COM_TIME (內(nèi)部線程狀態(tài))
0x10 COM_DELAYED_INSERT (內(nèi)部線程狀態(tài))
0x11 COM_CHANGE_USER 重新登陸(不斷連接)
0x12 COM_BINLOG_DUMP 獲取二進(jìn)制日志信息
0x13 COM_TABLE_DUMP 獲取數(shù)據(jù)表結(jié)構(gòu)信息
0x14 COM_CONNECT_OUT (內(nèi)部線程狀態(tài))
0x15 COM_REGISTER_SLAVE 從服務(wù)器向主服務(wù)器進(jìn)行注冊
0x16 COM_STMT_PREPARE 預(yù)處理SQL語句
0x17 COM_STMT_EXECUTE 執(zhí)行預(yù)處理語句
0x18 COM_STMT_SEND_LONG_DATA 發(fā)送BLOB類型的數(shù)據(jù)
0x19 COM_STMT_CLOSE 銷毀預(yù)處理語句
0x1A COM_STMT_RESET 清除預(yù)處理語句參數(shù)緩存
0x1B COM_SET_OPTION 設(shè)置語句選項
0x1C COM_STMT_FETCH 獲取預(yù)處理語句的執(zhí)行結(jié)果

這里距一個常見的的例子,比如切換數(shù)據(jù)庫: 

  1. use godpan  

相應(yīng)的報文格式則為: 

  1. 0x02 0x67 0x6f 0x64 0x70 0x61 0x6e  

其中0x02代表切換數(shù)據(jù)庫命令,后面的字節(jié)則為godpan的16進(jìn)制表達(dá)。

數(shù)據(jù)包類型 

有了以上的基礎(chǔ),我們基本知道的與MySQL通信之間的方式以及數(shù)據(jù)格式,那么與其通信間到底有哪幾種數(shù)據(jù)包呢?接下去的內(nèi)容是建立在MySQL4.1版本以后,之前版本的數(shù)據(jù)包類型這里不再論述。 

這里主要分為兩個階段,第一個階段是數(shù)據(jù)庫賬戶認(rèn)證階段,第二個階段則是執(zhí)行具體命令階段,我們先來看看前者。

數(shù)據(jù)庫賬戶認(rèn)證階段 

這個階段就是我們平常所說的登錄,主要步驟如下: 

    1.Client與Server進(jìn)行連接

    2.Server向Client發(fā)送Handshake packet

    3.Client與Server發(fā)送Auth packet

    4.Server向Client發(fā)送OK packet或者ERR packet 

這里我們來看一看上面的Handshake packet和Auth packet,OK packet和ERR packet放在另一個階段寫。

Handshake packet 

Handshake packet是由Server向Client發(fā)送的初始化包,因為所有從Server向Client端發(fā)送的包都是一樣的格式,所以前面的四個字節(jié)是包頭,前三位代表Handshake packet具體內(nèi)容的數(shù)據(jù),另外包序列號為0,很顯然這個包內(nèi)容小于16MB,下面是Handshake packet具體內(nèi)容的格式:

相對包內(nèi)容的位置 長度(字節(jié)) 名稱 描述
0 1 協(xié)議版本 協(xié)議版本的版本號,通常為10(0x0A)
1 len = strlen (server_version) + 1 數(shù)據(jù)庫版本 使用前面的NullTerminatedString格式編碼,長度為數(shù)據(jù)庫版本字符串的長度加上標(biāo)示結(jié)束的的一個字節(jié)
len + 1 4 線程ID 此次連接MySQL Server啟動的線程ID
len + 5 8 + 1(0x00表示結(jié)束) 挑戰(zhàn)隨機數(shù)(第一部分) 用于后續(xù)賬戶密碼驗證
len + 14 2 協(xié)議協(xié)商 用于與客戶端協(xié)商通訊方式
len + 16 1 編碼格式 標(biāo)識數(shù)據(jù)庫目前的編碼方式
len + 17 2 服務(wù)器狀態(tài) 用于表示服務(wù)器狀態(tài),比如是否是事務(wù)模式或者自動提交模式
len + 19 13 保留字節(jié) 未來可能會用到,預(yù)留字節(jié)
len + 32 12 + 1(0x00表示結(jié)束) 挑戰(zhàn)隨機數(shù)(第二部分) 用于后續(xù)賬戶密碼驗證

 上表就是整個Handshake packet的這個包結(jié)構(gòu),屬性的含義以及規(guī)范都有相應(yīng)的說明,下面是我本機解析的某次連接數(shù)據(jù)庫的Handshake packet包,僅供參考: 

  1. {protocolVersion=10, serverVersion='5.7.13', threadId=4055, scramble=[49, 97, 80, 3, 35, 118, 45, 15, 5, 118, 9, 11, 124, 93, 93, 5, 31, 47, 111, 109, 0, 0, 0, 0, 0], serverCapabilities=65535, serverLanguage=33, serverStatus=2} 

Auth packet 

Auth packet是由Client向Server發(fā)送的認(rèn)證包,用于驗證數(shù)據(jù)庫賬戶登錄,相應(yīng)內(nèi)容的格式:

相對包內(nèi)容的位置 長度(字節(jié)) 名稱 描述
0 4 協(xié)議協(xié)商 用于與服務(wù)端協(xié)商通訊方式
4 4 消息最長長度 客戶端可以發(fā)送或接收的最長長度,0表示不做任何限制
8 1 字符編碼 客服端字符編碼方式
9 23 保留字節(jié) 未來可能會用到,預(yù)留字節(jié),用0代替

 

  1. 32 |不定| 認(rèn)證字符串 | 主要有三部分內(nèi)容<br> <li>用戶名:NullTerminatedString格式編碼</li><li>加密后的密碼:LengthEncodedString格式編碼</li><li>數(shù)據(jù)庫名稱(可選):NullTerminatedString格式編碼</li> 

這部分內(nèi)容是由客戶端自己生成,所以說如果我們?nèi)绻獙懸粋€程序連接數(shù)據(jù)庫,那么這個包就得按照這個格式,不然服務(wù)端將會無法識別。

命令執(zhí)行階段 

在我們正確連接數(shù)據(jù)庫后,我們就要執(zhí)行相應(yīng)的命令了,比如切換數(shù)據(jù)庫,執(zhí)行CRUD操作等,這個階段主要分為兩步,Client發(fā)送命令(上文已經(jīng)給出,下面不再討論),Server端接收命令執(zhí)行相應(yīng)的操作,我們主要關(guān)心Server端向我們發(fā)送數(shù)據(jù)包,可分為4類和一個最基礎(chǔ)的報文結(jié)構(gòu)Data Field: 

  • Data Field:包數(shù)據(jù)的一個基礎(chǔ)結(jié)構(gòu);
  • OK包(包括PREPARE_OK):Server端發(fā)送正確處理信息的包,包頭標(biāo)識為0x00;
  • Error包: Server端發(fā)送錯誤信息的包,包頭標(biāo)識為0xFF;
  • EOF包:用于Server向Client發(fā)送結(jié)束包,包頭標(biāo)識為0xFE;
  • Result Set包:用于Server向Client發(fā)送的查詢結(jié)果包; 

Data Field 

Data Field是Server回應(yīng)包里的一個核心,主要是數(shù)據(jù)的一種編碼結(jié)構(gòu),跟我之前講的LengthEncodedInteger和LengthEncodedString很類似,也主要分為三個部分

最小數(shù)據(jù)長度(包含) 最大數(shù)據(jù)長度(不包含) 數(shù)據(jù)長度 格式
1 251 1個字節(jié) 1字節(jié) + 具體數(shù)據(jù)
251 2^16 2個字節(jié) 0xFC + 2個字節(jié)數(shù)據(jù)長度 + 具體數(shù)據(jù)
2^16 2^24 4個字節(jié) 0xFD + 4個字節(jié)數(shù)據(jù)長度 + 具體數(shù)據(jù)
2^24 2^64 8個字節(jié) 0xFE + 8個字節(jié)數(shù)據(jù)長度 + 具體數(shù)據(jù)
NULL NULL 0個字節(jié) 0xFB

要注意的一點是如果出現(xiàn)0xFB(251)開頭說明這個數(shù)據(jù)對應(yīng)的是MySQL中的NULL。

OK 包 

普通的OK包(PREPARE_OK包后面會講到)會在以下幾種情況下產(chǎn)生,由Server發(fā)送給相應(yīng)的接收方: 

  • COM_PING: 連接或者測試數(shù)據(jù)庫
  • COM_QUERY: 不需要查詢結(jié)果集的操作,比如INSERT, UPDATE, or ALTER TABLE
  • COM_REFRESH: 數(shù)據(jù)刷新
  • COM_REGISTER_SLAVE: 注冊從服務(wù)器 

OK 包的主要結(jié)構(gòu):

相對包內(nèi)容的位置 長度(字節(jié)) 名稱 描述
0 1 包頭標(biāo)識 0x00 代表這是一個OK 包
1 rows_len 影響行數(shù) 相應(yīng)操作影響的行數(shù),比如一個Update操作的記錄是5條,那么這個值就為5
1 + rows_len id_len 自增id 插入一條記錄時,如果是自增id的話,返回的id值
1 + rows_len + id_len 2 服務(wù)器狀態(tài) 用于表示服務(wù)器狀態(tài),比如是否是事務(wù)模式或者自動提交模式
3 + rows_len + id_len 2 警告數(shù) 上次命令引起的警告數(shù)
5 + rows_len + id_len msg_len 額外信息 此次操作的一些額外信息

下面是我本機解析的某次正確連接數(shù)據(jù)庫后的OK packet包,僅供參考: 

  1. OK{affectedRows=0, insertId=0, serverStatus=2, message='....' 

Error 包 

顧名思義Error 包就是當(dāng)出現(xiàn)錯誤的時候返回的信息,比如賬戶驗證不通過,查詢命令不合法,非空字段未指定值等相關(guān)操作,Server端都會向Client端發(fā)送Error 包。 

Error 包的主要結(jié)構(gòu):

相對包內(nèi)容的位置 長度(字節(jié)) 名稱 描述
0 1 包頭標(biāo)識 0xFF 代表這是一個Error 包
1 2 錯誤代碼 該錯誤的相應(yīng)錯誤代碼
3 1 標(biāo)識位 SQL執(zhí)行狀態(tài)標(biāo)識位,用'#'進(jìn)行標(biāo)識
4 5 執(zhí)行狀態(tài) SQL的具體執(zhí)行狀態(tài)
9 msg_len 錯誤信息 具體的錯誤信息

 比如我們現(xiàn)在已經(jīng)連接了數(shù)據(jù)庫,執(zhí)行 

 

  1. use test_database;  

但是我們數(shù)據(jù)庫中并沒有test_database這個數(shù)據(jù)庫,我們將會得到相應(yīng)的錯誤信息,下面是我本機解析的Error packet包,僅供參考: 

  1. Error{errno=1046, sqlState='3D000', message='No database selected' 

EOF Packet 

EOF Packet是用于標(biāo)識某個階段數(shù)據(jù)結(jié)束的標(biāo)志包,會在一下幾種情況中產(chǎn)生: 

  • 結(jié)果集中字段信息結(jié)束的時候;
  • 結(jié)果集中列信息結(jié)束的時候;
  • 服務(wù)器確認(rèn)停止服務(wù)的時候;
  • 客戶端發(fā)送COM_SET_OPTION and COM_DEBUG命令后,服務(wù)器回應(yīng)的時候;
  • 服務(wù)器請求使用MySQL4.1版本之前的認(rèn)證方式的時候; 

EOF 包的主要結(jié)構(gòu):

相對包內(nèi)容的位置 長度(字節(jié)) 名稱 描述
0 1 包頭標(biāo)識 0xFE 代表這是一個EOF 包
1 2 警告數(shù) 上次命令引起的警告數(shù)
3 2 服務(wù)器狀態(tài)

 這里要注意的一點,我們上面分析了Data Field的結(jié)構(gòu),發(fā)現(xiàn)它是用0xFE作為長度需要8個字節(jié)編碼值得標(biāo)識頭,所以我們在判斷一個包是否是EOF 包的時候,需要下面兩個條件: 

  • 標(biāo)識頭(第一個字節(jié))為0xFE;
  • 包的總長度小于9個字節(jié); 

Result Set包 

Result Set包產(chǎn)生于我們每次數(shù)據(jù)庫執(zhí)行需要返回結(jié)果集的時候,Server端發(fā)送給我們的包,比如平常的SELECT,SHOW等命令,Result Set包相對比較復(fù)雜,主要包含以下五個方面:

內(nèi)容 含義
Result Set Header 返回數(shù)據(jù)的列數(shù)量
Field 返回數(shù)據(jù)的列信息(多個)
EOF 列結(jié)束
Row Data 行數(shù)據(jù)(多個)
EOF 數(shù)據(jù)結(jié)束

我們逐個來分析,首先我們來看Result Set Header。

Result Set Header 

Result Set Header表示返回數(shù)據(jù)的列數(shù)量以及一些額外的信息,其主要結(jié)構(gòu)為:

長度 含義
1-9字節(jié) 數(shù)據(jù)的列數(shù)量(LengthEncodedInteger編碼格式)
1-9字節(jié) 額外信息(LengthEncodedInteger編碼格式)

Field 

Field表示Result Set中數(shù)據(jù)列的具體信息,可出現(xiàn)多次,具體次數(shù)取決于Result Set Header中數(shù)據(jù)的列數(shù)量,它的主要結(jié)構(gòu)為:

長度 含義
4 通常為ASCIIz字符串def

 

  1.     | 數(shù)據(jù)庫名稱(Data Field)
  2.     | 假如查詢指定了表別名,就是表別名(Data Field)
  3.     | 原始的表名(Data Field)
  4.     | 假如查詢指定了列別名,就是列別名(Data Field)
  5.     | 原始的列名(Data Field) 

1 | 標(biāo)識位,通常為12,表示接下去的12個字節(jié)是具體的field內(nèi)容

2 | field的編碼

4 | field的長度

1 | field的類型

2 | field的標(biāo)識

2 | field值的的小數(shù)點精度

2 | 預(yù)留字節(jié)

 

    | 可選元素,如果存在,則表示該field的默認(rèn)值 

其中field的類型與標(biāo)識具體定義和對應(yīng)變量含義可參考這篇文章:MySQL協(xié)議分析

EOF 包 

這里的EOF包是標(biāo)識這列信息的結(jié)束,具體結(jié)構(gòu)信息參考上面的EOF包解釋。

Row Data 

Row Data含著的是我們需要獲取的數(shù)據(jù),一個Result Set包里面包含著多個Row Data結(jié)構(gòu)(得到的數(shù)據(jù)可能多行),每個Row Data中包含著多個字段值,它們之間沒有間隔,比如我們現(xiàn)在查詢到的數(shù)據(jù)為(id: 1, name: godpan) 那么Row Data內(nèi)容為(1,godpan),這兩個值是連在一起的,對應(yīng)的值都用LengthEncodedString編碼。

EOF 包 

等待Row Data發(fā)送完之后,Server最后會向Client端發(fā)送一個EOF包,標(biāo)識所有的行數(shù)據(jù)已經(jīng)發(fā)送完畢。

PREPARE_OK包 

PREPARE_OK包產(chǎn)生在Client端向Server發(fā)送預(yù)處理SQL語句,Server進(jìn)行正確回應(yīng)的時候,大家寫寫Java的時候肯定用過PreparedStatement,這里PreparedStatement的功能就是進(jìn)行SQL的預(yù)處理,預(yù)處理的優(yōu)點比較多,比如效率高,防SQL注入等,有興趣的同學(xué)可以自己去學(xué)習(xí)下。下面是PREPARE_OK包的結(jié)構(gòu):

長度 含義
1 0x00(標(biāo)識是一個OK包)
4 statement_handler_id(預(yù)處理語句id)
2 number of columns in result set(結(jié)果集中列的數(shù)量)
2 number of parameters in query(查詢語句中參數(shù)的數(shù)量)
1 0x00 (填充值)
2 警告數(shù)

比如我現(xiàn)在執(zhí)執(zhí)行下面的語句: 

  1. PreparedStatement ps = connection.prepareStatement("SELECT * FROM `godpan_fans` where id=?"); 
  2.  
  3. ps.setInteger(1, 1); 
  4.  
  5. ps.executeQuery();  

得到下面的PREPARE_OK包,僅供參考: 

  1. PSOK{statementId=1, columns=5, parameters=1}  

如果上面的columns大于0,以及parameters大于0,則將有額外的兩個包傳輸,分別是columns的信息以及parameters的信息,對應(yīng)信息結(jié)構(gòu):

內(nèi)容 含義
Field columns信息(多個)
EOF columns信息結(jié)束
Field parameters(多個)
EOF parameters結(jié)束

到此整個PREPARE_OK包發(fā)送完畢。

Row Data Binary 

這個包跟上面提到的Row Data包有什么差別呢?主要有兩點: 

  • 用不同的方式定義NULL;
  • 數(shù)據(jù)編碼不再單純的使用LengthEncodedString,而是根據(jù)數(shù)據(jù)類型的不同進(jìn)行相應(yīng)的編碼; 

后面我會分別解釋這兩點,我們先來看看它的結(jié)構(gòu):

相對包內(nèi)容的位置 長度(字節(jié)) 名稱 描述
0 1 包頭標(biāo)識 0x00
1 (col_count+7+2)/8 Null Bit Map 前兩位為預(yù)留字節(jié),主要用于區(qū)別與其他的幾種包(OK,ERROR,EOF),在MySQL 5之后這兩個字節(jié)都為0X00,其中col_count為列的數(shù)量
(col_count+7+2)/8 + 1 n column values 具體的列值,重復(fù)多次,根據(jù)值類型編碼

現(xiàn)在我們來看一下它的兩個特點,首先我們來看它是如何來定義NULL的,首先我們看到他的結(jié)構(gòu)中有一個Null Bit Map,除去兩個標(biāo)識位,真正用于標(biāo)識數(shù)據(jù)信息的就是(col_count+7)/8位字節(jié),這里我先給出結(jié)論,后面再給大家具體分析:

參數(shù)個數(shù) 長度(字節(jié)) 具體值范圍 描述
1-8 1 -1, 2^n組合 1 = 2^0表示第一個參數(shù)為NULL,3 = 2^0 + 2^1表示第一個和第二參數(shù)為NULL...

上面給出了標(biāo)識NULL的基本算法,原則是哪個參數(shù)(次序為n)為NULL,則Null Bit Map相應(yīng)的值加上2^n,8個參數(shù)為一個周期,以此類推。 

接著我們來看一下第二點,是如何用具體值類型來對相應(yīng)的值進(jìn)行編碼的,這里主要分為三類,基本數(shù)據(jù)類型,時間類型,字符串類型; 

  • 基本數(shù)據(jù)類型:比如TINYINT使用一個字節(jié)編碼,F(xiàn)LOAT使用四個字節(jié),DOUBLE使用8個字節(jié)等;
  • 時間類型:使用類似LengthEncodedString的編碼方式編碼,具體可參考MySQL_PROTOCOL;
  • 字符串類:不屬于上面兩類的都屬于字符串類型,使用普通的LengthEncodedString; 

Execute包 

Execute包顧名思義是一個執(zhí)行包,它是由Client端發(fā)送到Server端的,但它和普通的命令又有點不同,它主要是用來執(zhí)行預(yù)處理語句,并會攜帶相應(yīng)參數(shù),具體結(jié)構(gòu)如下:

長度 含義
1 COM_EXECUTE(標(biāo)識是一個Execute包)
4 預(yù)處理語句id
1 游標(biāo)類型
4 預(yù)留字節(jié)
0 接下去的內(nèi)容只有在有參數(shù)的情況下
(param_count+7)/8 null_bit_map(描述參數(shù)中NULL的情況)
1 參數(shù)綁定情況
n*2 參數(shù)類型(依次存儲)
n 參數(shù)具體值(非NULL)(依次存儲,使用Row Data Binary方式編碼)

Execute包從Client端發(fā)送到Server端后可能會得到以下幾個結(jié)果: 

  • OK包
  • ERROR包
  • Result Set包(可能多個) 

我們需要根據(jù)包的不同類型來進(jìn)行不同的處理。

總結(jié) 

本篇文章主要講述了MySQL的連接方式,通信過程及協(xié)議,以及傳輸包的基本格式和相關(guān)傳輸包的類型,內(nèi)容相對來說,比較多也比較復(fù)雜,我也是將近三周才寫完,但總體按照我自學(xué)的思路走,不會太繞,有些點可能需要細(xì)心思考下,寫的有誤的地方也希望大家能指正,希望對大家有所幫助,后面可能會寫幾個實例和大家一起學(xué)習(xí)。 

責(zé)任編輯:龐桂玉 來源: segmentfault
相關(guān)推薦

2024-10-22 16:54:14

2010-09-09 16:28:19

2010-09-03 14:10:10

網(wǎng)絡(luò)協(xié)議分析軟件

2010-06-12 14:48:22

2010-06-09 10:28:20

2010-08-09 16:42:39

無線網(wǎng)狀網(wǎng)

2010-04-14 16:42:05

AdHoc無線網(wǎng)絡(luò)路由

2009-11-11 14:41:22

2010-09-10 12:52:28

網(wǎng)絡(luò)協(xié)議分析軟件安裝

2010-06-24 15:55:21

MAC協(xié)議

2010-09-10 12:34:54

2022-09-19 11:03:27

物聯(lián)網(wǎng)物聯(lián)網(wǎng)協(xié)議

2010-09-09 15:30:45

ethereal網(wǎng)絡(luò)協(xié)議分析軟件

2010-09-17 15:41:46

網(wǎng)絡(luò)協(xié)議分析軟件

2011-03-01 14:23:39

2010-06-10 13:18:31

RIP協(xié)議

2010-07-30 16:00:36

ICMP協(xié)議

2010-07-30 14:31:20

RIP協(xié)議

2010-06-24 14:29:58

GRE協(xié)議

2010-06-25 15:56:03

NetBEUI協(xié)議
點贊
收藏

51CTO技術(shù)棧公眾號