PostgreSQL7.0手册-开发者手册 -64. 前端/后端协议
内容
概述
协议
消息数据类型
消息格式
注意:由 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
未加密的口令.
--------------------------------------------------------------------------------