PostgreSQL7.0手冊-開發者手冊 -64. 前端/后端協議
2019-09-08 23:34:06
供稿:網友
第六十四章. 前端/后端協議
內容
概述
協議
消息數據類型
消息格式
注意:由 Phil Thompson 寫作.協議 2.0 的更新由 Tom Lane 寫作.
Postgres 使用一種以消息為基礎的協議用于在前端和后端之間通訊.該協議是在 TCP/IP 和 Unix 套接字上實現的.Postgres v6.3 往協議里面引入了版本號.這么做的同時仍然允許早期的前端與新的后端進行聯接,但是本文檔沒有介紹那些早期版本的協議.
這份文檔描述了版本 2.0 的協議,在 Postgres v6.4 和以后的版本中實現.
在這個協議的基礎上建立的更高級特性(例如,libpq 是如何在建立聯接以后傳遞某種環境變量的)在其他地方描述.
概述
The three major components are the frontend (running on the client) and the postmaster and backend (running on the server). The postmaster and backend have different roles but may be implemented by the same executable.
三個主要的部分是前端(在客戶端運行)postmaster 和后端(在服務器端運行).postmaster 和后端有著不同的角色但是可以用同樣的可執行文件實現.
一個前端向 postmaster 發送一個啟動包.包里面包括用戶名和該用戶希望聯接的數據庫.postmaster 則使用這些信息和 pg_hba.conf(5) 文件里的信息決定她還需要前端發送什么樣的進一步認證信息(如果需要的話)并且把這些回應給相應的前端.
該前端則發送任何所要求的認證信息.一旦 postmaster 認為有效,那么它回應給前端并且把聯接轉交給一個后端.該后端則發送信息給前端表明啟動成功(正常狀態)或失敗(例如,非法數據庫名).
隨后的通訊是在前端和后端之間交換的查詢和結果包.postmaster 不再參與正常的查詢/結果通訊.(不過,當前端希望取消目前在其后端上執行的查詢時,postmaster 也要參與.詳細信息見下文.)
當前端希望斷開聯接時,它給后端發送一個合適的包并且在不等待后端回應的情況下關閉聯接.
包是當做數據流發送的.包的第一個字節決定了包的其余部分的類型.例外是從前端發送給 postmaster 的包,是由包長和包本身組成.這種區別是歷史原因造成的.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
協議
本節描述信息流.根據聯接的狀態不同有四種類型的流:啟動(startup),查詢(query),函數調用(function call)和結束(termination).還有用于通知響應和命令取消的特殊信息,這些特殊信息可能在啟動階段過后的任何時間發生.
啟動
啟動分成認證階段和后端啟動階段.
開始時,前端發送一個 StartupPacket (啟動包).postmaster 利用這個信息和 pg_hba.conf(5) 文件的內容決定這個前端必須使用那種認證方式.然后 postmaster 用下面信息之一響應:
ErrorResponse
然后 postmaster 馬上關閉聯接.
AuthenticationOk
然后 postmaster 轉交給后端.postmaster 不再參與以后的通訊.
AuthenticationKerberosV4
然后前端必須與 postmaster 進行一次 Kerberos V4 認證對話(在這里沒有描述).如果對話成功,postmaster 響應一個 AuthenticationOk (認證成功)信息,否則它響應一個 ErrorResponse (錯誤響應).
AuthenticationKerberosV5
然后前端必須與 postmaster 進行一次 Kerberos V5 認證對話(在這里沒有描述).如果對話成功,postmaster 響應一個 AuthenticationOk (認證成功)信息,否則它響應一個 ErrorResponse (錯誤響應).
AuthenticationUnencryptedPassword
然后前端必須發送一個 UnencryptedPasswordPacket (未加密口令)包.如果這是正確的口令,postmaster 用一個 AuthenticationOk 包響應,否則它響應一個 ErrorResponse 包.
AuthenticationEncryptedPassword
然后前端必須發送一個 EncryptedPasswordPacket (加密口令)包.如果這是正確口令,postmaster 用一個AuthenticationOk 響應,否則它用一個 ErrorResponse 響應.
如果前端不支持 postmaster 要求的認證方式,那么它應該馬上關閉聯接.
在發送完 AuthenticationOk 包之后,postmaster 試圖運行一個后端進程.因為這個過程可能失敗,或者后端可能在啟動過程中失敗,前端必須等待后端確認成功啟動.這時前端不應該發送任何信息.在這個階段從后端來的信息可能是:
BackendKeyData
這個消息是在后端成功啟動后才發出的.這個消息提供了密鑰(secret-key)數據,前端如果想要在稍后發出取消的請求,則必須保存這個數據.前端不應該響應這個信息,但是應該繼續偵聽等待 ReadyForQuery 消息.
ReadyForQuery
后端啟動成功,前端現在可以發出查詢或者函數調用消息.
ErrorResponse
后端啟動失敗.在發送完這個消息之后聯接被關閉.
NoticeResponse
發出了一個警告信息.前端應該顯示這個信息,并且繼續等待 ReadyForQuery 或 ErrorResponse.
后端在每個查詢循環后都會發出一個相同的 ReadyForQuery 消息.前端可以認為 ReadyForQuery 是一個查詢循環的開始(而 BackendKeyData 表明啟動階段的成功完成),或者認為 ReadyForQuery 是啟動階段和每個隨后查詢循環的結束,這些取決于前端的編碼需要.
查詢
一個查詢循環是由前端發送一條 Query 消息給后端初始化的.后端根據查詢命令字串的內容發送一條或者更多條響應消息給前端,并且最后是一條 ReadyForQuery 響應信息.ReadyForQuery 通知前端它可以安全地發送新查詢或者函數調用給后端了.
從后端來的可能的消息是:
CompletedResponse
一個正常結束的 SQL 命令.
CopyInResponse
后端已經準備好從前端拷貝數據到一個關系里面去.然后前端應該發送一條 CopyDataRows 消息.然后后端用一個帶有標記 "COPY" 的 CompletedResponse 消息響應.
CopyOutResponse
后端已經準備好從一個關系拷貝數據到前端里面去.然后它會發送一條 CopyDataRows 消息.最后后端發送一個帶有標記 "COPY" 的 CompletedResponse 消息.
CursorResponse
查詢可以是一條 insert(l),delete(l),update(l),fetch(l) 或一個 select(l) 命令.如果事務被終止,那么后端發送一條帶有標記 "*ABORT STATE*" 的 CompletedResponse 消息.否則發送下面的響應.
對一條 insert(l) 命令,后端發送一條帶有 "INSERT oidrows" 標記的 CompletedResponse 消息,這里的 rows 是插入的行數,而 oid 在插入的函數 rows 為 1 時是插入行的對象標識(OID),否則 oid 為 0.
對一條 delete(l) 命令,后端發送一條帶有 "DELETE rows" 標記的 CompletedResponse 消息,這里 rows 是刪除的行數.
對一條 update(l) 命令,后端發送一條帶有 "UPDATE rows" 標記的 CompletedResponse 消息,這里 rows 是更新的行數.
對于一條 fetch(l) 或 select(l) 命令,后端發送一條 RowDescription 消息.然后跟著一條 AsciiRow 或 BinaryRow 消息 (取決于是否聲明了一個二進制游標)給返回給前端的每一行.最后,后端發送一條帶有標記 "SELECT" 的 CompletedResponse 消息.
EmptyQueryResponse
識別了一個空的查詢字串.( 對這個情況的特殊識別是歷史原因.)
ErrorResponse
發生了一個錯誤.
ReadyForQuery
查詢字串的處理完成.發送一個分隔消息來標識這個是因為查詢字串可能包含多個 SQL 命令.(CompletedResponse 只是標記一條 SQL 命令處理完畢,而不是整個字串.)ReadyForQuery 總會被發送,不管是處理成功結束還是產生錯誤.
NoticeResponse
發送了一個與查詢有關的警告信息.注意信息是附加在其他響應上的,也就是說, 后端將繼續處理該命令.
前端在等待其他類型的消息時必須準備接收 ErrorResponse 和 NoticeResponse 消息.
實際上,當前端沒有等待任何消息時 NoticeResponse 消息也有可能到達,那就是,后端正常空閑.(尤其是后端可以被其 postmaster 命令終止運行.在那種情況下,后端將在關閉聯接之前發送一條 NoticeResponse 消息.)我們建議前端在發出任何命令之前檢查這樣的異步通知消息.
同樣,如果前端發出了任何 listen(l) 命令,那么它必須在任何時候準備接收 NotificationResponse 消息;見下文.
函數調用
一個函數調用循環是由前端向后端發送一條 FunctionCall 消息初始化的.然后后端根據函數調用的結果發送一條或者更多響應消息,并且在最后是一條 ReadyForQuery 響應消息.ReadyForQuery 通知前端它可以安全地發送一條新的查詢或者函數調用了.
從后端來的可能的響應信息是:
ErrorResponse
發生了一個錯誤.
FunctionResultResponse
函數調用被執行并且返回一個結果.
FunctionVoidResponse
函數調用被執行并且沒有返回一個結果.
ReadyForQuery
函數調用處理完成.ReadyForQuery 將總是被發送,不管是成功完成處理還是發生一個錯誤.
NoticeResponse
發出了一條有關該函數調用的警告信息.通知是附加在其他響應上的,也就是說,后端將繼續處理命令.
前端在等待其他類型的消息時必須準備接收 ErrorResponse 和 NoticeResponse 消息.同樣,如果前端發出了任何 listen(l) 命令,那么它必須在任何時候準備接收 NotificationResponse 消息;見下文.
通知響應
如果前端發出了一條 listen(l) 命令,那么每當為同樣名稱的通知執行一條 notify(l) 命令,后端都會發送一條 NotificationResponse 消息 (不要與 NoticeResponse 弄混了!).
除了在另外一個后端的消息里以外,在協議里的任何地方(啟動后)都允許通知響應.因此,前端在等待任何消息的時候都必須準備識別 NotificationResponse 消息.實際上,前端甚至在不能進行查詢的時候都要能夠處理 NotificationResponse 消息.
NotificationResponse
一條 notify(l) 命令為前面執行的 listen(l) 命令的名稱被執行.通知可以在任何時候發送.
有一點值得我們指出來,就是在 listen 和 notify 里面的名稱不必與 SQL 數據庫里的關系(表)的名稱有任何關系.通知名只是一個獨立選出來的條件名.
取消正在處理的請求
在一條查詢正在處理的時候,前端可以通過發送合適的消息給 postmaster 取消該查詢的處理.這樣的取消不直接發送給后端是因為實現的有效性:我們不希望后端在處理查詢的過程中不停地檢查前端來的輸入.取消請求應該相對而言比較少見,所以我們把取消做得稍微笨拙一些,以便不影響正常狀況的性能.
要發送一條取消請求,前端打開一個與 postmaster 的新的聯接并且發送一條 CancelRequest 消息,而不是通常在新聯接中經常發送的 StartupPacket 消息.postmaster 將處理這個請求然后關閉聯接.出于安全原因,對取消請求消息不做直接的響應.
除非 CancelRequest 消息包含與聯接啟動過程中傳遞給前端的相同的鍵數據(PID 和 安全鍵字),否則它將被忽略.如果該請求與當前運行著的后端匹配了 PID 和安全鍵字,postmaster 將給后端發送信號以退出當前查詢.
取消信號可能有也可能沒有做用 -- 例如,如果它在后端完成查詢的處理后到達,那么它就沒有做用.如果取消起作用了,它將導致當前命令帶著一個錯誤信息被提前退出.
這么做的結果是對安全和有效性通盤考慮的結果,前端沒有直接的方法獲知一個取消請求是否成功.它必須繼續等待后端對查詢響應.執行取消僅僅是增加了當前查詢快些結束的可能性,以及增加了當前查詢會帶著一條錯誤信息失敗而不是成功執行的可能性.
因為取消請求是發送給 postmaster 而不是通過通常的前端/后端通訊鏈接,所以取消請求可能是任意過程執行的,而不僅僅是要取消查詢的前端.這樣可能對創建多進程應用有某種靈活性的好處.但是同時這樣也帶來了安全風險,因為這樣任何一個非認證用戶都可能試圖取消查詢.這個安全風險通過要求在取消請求中提供一個動態生成的安全鍵字排除.
終止
通常的和優雅的終止過程是前端發送一條 Terminate (終止)消息并且立刻關閉聯接.一旦收到消息,后端馬上關閉聯接并且退出.
一個欠優雅的的退出可能因為任何一端的軟件失效(例如.內核傾倒)產生.如果前端或后端看到一個意外的聯接關閉,它應該清理并退出.前端可以選擇通過 postmaster 重新建立一個新的聯接 -- 如果它不想終止自己的處理的話.
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
消息數據類型
本節描述消息里用到的基本數據類型.
Intn(i)
一個網絡字節序(譯注:高位->高地址,底位->底地址)的 n 位整數.如果聲明了 i ,它就是字面(literal )值.如..Int16,Int32(42).
LimStringn(s)
一個被當成一個 '/0' 結尾的字串的 n 字節的字符數組.如果空間不夠,'/0' 被忽略.如果聲明了 s ,那么它是字面值.例如.LimString32,LimString64("user").
String(s)
一個傳統沒有長度限制的的 C 的 '/0' 結尾的字符串。如果聲明了 s ,那么它是字面值.例如 String,String("user")。
注意:后端返回的字串的可能長度沒有預定義的限制。所以前端必須使用良好的編碼策略,使用某種可擴展的緩沖區以便能接受任何能放進內存里的東西。如果那樣做不可行,則讀取全長的字串然后拋棄不能放進你的定長緩沖區的結尾字符。
Byten(c)
精確的 n 字節.如果聲明了 c 它是字面值.例如.Byte,Byte1('/n').
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
消息格式
本節描述各種消息的詳細格式.每種消息都可以由一個前端 (F),一個 posmaster/后端(B)或者兩者(F & B)發送.
AsciiRow (B)
Byte1('D')
標識消息是一個 ASCII 數據行.(一個前面的 RowDescription 消息定義該行里面的字段數和它們的數據類型.)
Byten
一個每個位對應(數據)行里一個字段的位圖.第一字段對應第一字節的位7 (MSB,最高位),第二字段對應第一字節位6,第八字段對應第一字節位 0 (LSB,最低位),第九字段對應第二字節的位7,等等.如果對應域的值不為 NULL,對應位設置為1,如果字段的個數不為8的倍數,位圖最后一個字節余下的位就沒有用.
然后,每個非空的字段值(non-NULL),有下面規則:
Int32
聲明字段值的尺寸,包括尺寸本身.
Byten
聲明以 ASCII 字符表示的字段值本身.n 是上面的尺寸減 4.在字段數據里面沒有結尾的 '/0';如果前端希望有個結束符,它必須自己追加一個.
AuthenticationOk (B)
Byte1('R')
標識該消息是一條認證請求.
Int32(0)
聲明該認證是成功的.
AuthenticationKerberosV4 (B)
Byte1('R')
標識該消息是一條認證請求.
Int32(1)
聲明需要 Kerberos V4 認證.
AuthenticationKerberosV5 (B)
Byte1('R')
標識該消息是一條認證請求.
Int32(2)
聲明需要 Kerberos V5 認證.
AuthenticationUnencryptedPassword (B)
Byte1('R')
標識該消息是一條認證請求.
Int32(3)
聲明需要一個未加密的口令.
AuthenticationEncryptedPassword (B)
Byte1('R')
標識該消息是一條認證請求.
Int32(4)
聲明需要一個加密的口令.
Byte2
加密口令使用的'種子'(salt).
BackendKeyData (B)
Byte1('K')
標識該消息是一個取消鍵字數據.如果前端希望能夠在稍后發出 CancelRequest 消息,那么它必須保存這個值.
Int32
后端的進程號(PID).
Int32
此后端的密鑰(secret key ).
BinaryRow (B)
Byte1('B')
標識消息為為二進制(數據)行.(一個前面的 RowDescription 消息定義數據行里的字段數和它們的數據類型.)
Byten
一個每個位對應(數據)行里一個字段的位圖.第一字段對應第一字節的位7 (MSB,最高位),第二字段對應第一字節位6,第八字段對應第一字節位 0 (LSB,最低位),第九字段對應第二字節的位7,等等.如果對應域的值不為 NULL,對應位設置為1,如果字段的個數不為8的倍數,位圖最后一個字節余下的位就沒有用.
然后,每個非空的字段值(non-NULL),有下面規則:
Int32
聲明字段值的尺寸,包括尺寸本身.
Byten
以二進制格式聲明字段本身的值.n 就是上面的尺寸.
CancelRequest (F)
Int32(16)
以字節計的包尺寸.
Int32(80877102)
取消請求代碼.選這個值是為了在高16位包含 "1234",低16位包含 "5678".(要避免混亂,這個代碼必須與協議版本號不同.)
Int32
目標后端的進程號(PID).
Int32
目標后端的密鑰(secret key ).
CompletedResponse (B)
Byte1('C')
標識此消息是一個完成響應.
String
命令標記.它通常是(但并不總是)一個單字,標識完成了哪條 SQL 命令.
CopyDataRows (B & F)
這是一個行的流,這里每行都是一個 Byte1('/n') 結尾的.后面順序跟著 Byte1('//'),Byte1('.'),Byte1('/n').
CopyInResponse (B)
Byte1('G')
標識這條消息是一條 Start Copy In (開始拷貝進入)響應消息.前端現在必須發送一條 CopyDataRows.
CopyOutResponse (B)
Byte1('H')
標識這條消息是一條 Start Copy Out (開始拷貝進出)響應消息.這條消息后面將跟著一條 CopyDataRows 消息.
CursorResponse (B)
Byte1('P')
標識這條消息是一條游標響應消息.
String
游標的名稱.如果游標是隱含的,這個地方會是"空白" ("blank").
EmptyQueryResponse (B)
Byte1('I')
標識這條消息是對一個空查詢字串的響應.
String("")
沒有用.
EncryptedPasswordPacket (F)
Int32
以字節記的包的尺寸.
String
加密了(使用 crypt())的口令.
ErrorResponse (B)
Byte1('E')
標識消息是一條錯誤.
String
錯誤消息本身.
FunctionCall (F)
Byte1('F')
標識消息是一個函數調用.
String("")
未使用.
Int32
聲明待調用的函數的對象標識(OID).
Int32
聲明提供給函數的參數個數.
然后,每個參數用下面格式聲明:
Int32
聲明參數值的尺寸,除去尺寸本身的長度.
Byten
聲明以二進制格式表示的字段值本身. n 是上面的尺寸.
FunctionResultResponse (B)
Byte1('V')
標識這條消息是函數調用結果.
Byte1('G')
聲明返回了一個非空結果.
Int32
聲明結果值的尺寸,除去尺寸本身長度.
Byten
聲明二進制格式表示的結果值本身.n 是上面的尺寸.
Byte1('0')
未用.(嚴格的說,FunctionResultResponse 和 FunctionVoidResponse 都是一樣的東西,但是消息里有一些可選的部分.)
FunctionVoidResponse (B)
Byte1('V')
標識這條消息是一個函數調用結果.
Byte1('0')
聲明返回了一個空結果.
NoticeResponse (B)
Byte1('N')
標識消息是一個通知.
String
通知消息本身.
NotificationResponse (B)
Byte1('A')
標識消息是一個通知響應.
Int32
發出通知的后端進程的進程號(PID).
String
生成通知的條件名.
Query (F)
Byte1('Q')
標識此消息是一條查詢.
String
查詢字串本身.
ReadyForQuery (B)
Byte1('Z')
標識消息類型.當后端準備好進入一個新的查詢循環時,它發送一條 ReadyForQuery.
RowDescription (B)
Byte1('T')
標識消息是一個行描述消息.
Int16
聲明一行里的字段數(可以是零).
然后每個字段,用下面格式表示:
String
聲明字段名稱.
Int32
聲明字段的對象標識(OID).
Int16
聲明類型尺寸.
Int32
聲明類型修改器.
StartupPacket (F)
Int32(296)
以字節計的包的尺寸.
Int32
協議版本號.最高16位是主版本號.低16位是次版本號.
LimString64
數據庫名,缺省時是用戶名.
LimString32
用戶名.
LimString64
由 postmaster 傳遞給后端的任何附加的命令行參數.
LimString64
未使用.
LimString64
后端用于調試信息輸出的可選 tty (控制臺).
Terminate (F)
Byte1('X')
標識這條消息是終止消息.
UnencryptedPasswordPacket (F)
Int32
以字節計的包尺寸.
String
未加密的口令.
--------------------------------------------------------------------------------