4.2. Protocol #
本节描述消息流。有四种不同的流,取决于连接的状态:启动、查询、函数调用和终止。还有针对通知响应和命令取消的特殊规定,它们可以在启动阶段之后的任何时间发生。
4.2.1. 启动
最初,前端发送一个 StartupPacket。服务器使用这些信息以及 pg_hba.conf
文件的内容来确定 what authentication method the frontend must
use. 然后服务器以下列消息之一响应:
- ErrorResponse
然后服务器立即关闭连接。
- AuthenticationOk
认证交换完成。
- AuthenticationKerberosV4
然后前端必须与服务器进行 Kerberos V4 认证对话(此处不描述,属于 Kerberos 规范的一部分)。如果成功,服务器以 AuthenticationOk 响应,否则以 ErrorResponse 响应。
- AuthenticationKerberosV5
然后前端必须与服务器进行 Kerberos V5 认证对话(此处不描述,属于 Kerberos 规范的一部分)。如果成功,服务器以 AuthenticationOk 响应,否则以 ErrorResponse 响应。
- AuthenticationCleartextPassword
然后前端必须发送一个包含明文形式密码的 PasswordPacket。如果密码正确,服务器以 AuthenticationOk 响应,否则以 ErrorResponse 响应。
- AuthenticationCryptPassword
然后前端必须发送一个 PasswordPacket,其中包含用 crypt(3) 加密的密码,使用 AuthenticationCryptPassword 包中指定的 2 字符盐。如果密码正确,服务器以 AuthenticationOk 响应,否则以 ErrorResponse 响应。
- AuthenticationMD5Password
然后前端必须发送一个 PasswordPacket,其中包含用 MD5 加密的密码,使用 AuthenticationMD5Password 包中指定的 4 字符盐。如果密码正确,服务器以 AuthenticationOk 响应,否则以 ErrorResponse 响应。
- AuthenticationSCMCredential
此方法只可能在支持 SCM 凭据消息的平台上用于本地 Unix 域连接。前端必须发出一条 SCM 凭据消息,然后发送单个数据字节。(该数据字节的内容无关紧要;它只是用来确保服务器等待足够长的时间以接收凭据消息。)如果凭据可接受,服务器以 AuthenticationOk 响应,否则以 ErrorResponse 响应。
如果前端不支持服务器要求的认证方式,那么它应该马上关闭连接。
收到 AuthenticationOk 之后,前端应当等待服务器的进一步消息。后端在这个阶段可能发送的消息有:
- BackendKeyData
该消息提供密钥数据。如果前端希望稍后发送取消请求,就必须保存这些数据。前端不应响应该消息,而应继续等待 ReadyForQuery 消息。
- ReadyForQuery
启动完成。前端现在可以发出查询或函数调用消息。
- ErrorResponse
启动失败,在发送完这个消息之后连接被关闭。
- NoticeResponse
已发出一条警告消息。前端应显示该消息,但仍应继续等待 ReadyForQuery 或 ErrorResponse。
ReadyForQuery 消息与后端在每个查询周期之后将发出的消息相同。取决于前端的编码需要,既可以把 ReadyForQuery 视为一个查询周期的开始(然后 BackendKeyData 表示启动阶段的成功结束),也可以把 ReadyForQuery 视为启动阶段和每个后续查询周期的结束。
4.2.2. Query
查询周期由前端向 backend 发送 Query 消息发起。然后后端根据查询命令串的内容发送一个或多个响应消息,最后发送 ReadyForQuery 响应消息。ReadyForQuery informs the frontend that it may safely send a new query or function call.
后端可能发送的响应消息有:
- CompletedResponse
一条 SQL 命令正常完成。
- CopyInResponse
后端准备好把数据从前端复制到表。前端然后应发送 CopyDataRows 消息。后端随后将以带 COPY 标签的 CompletedResponse 消息响应。
- CopyOutResponse
后端准备好把数据从表复制到前端。然后它发送 CopyDataRows 消息,再发送带 COPY 标签的 CompletedResponse 消息。
- CursorResponse
对 SELECT、FETCH、INSERT、UPDATE 或 DELETE 查询的响应的开始。在 FETCH 的情况下,消息中包含所取游标的名称。否则该消息总是提到“空白”游标。
- RowDescription
指示将要对 SELECT 或 FETCH 查询返回行。消息内容描述行的布局。之后对于返回给前端的每一行,都会跟随一条 AsciiRow 或 BinaryRow 消息(取决于是否指定了二进制游标)。
- EmptyQueryResponse
识别出了一条空查询字符串。
- ErrorResponse
发生了一个错误。
- ReadyForQuery
查询串的处理完成。发送单独的消息来指示这一点,因为查询串可能包含多条 SQL 命令。(CompletedResponse marks the end of processing one SQL command, not the whole string.) ReadyForQuery will always be sent, whether processing terminates successfully or with an error.
- NoticeResponse
与查询相关的警告消息已发出。通知是其他响应的补充,即后端将继续处理命令。
对 SELECT 或 FETCH 查询的响应通常由 CursorResponse、RowDescription、零个或多个 AsciiRow 或 BinaryRow 消息以及最后的 CompletedResponse 组成。INSERT、UPDATE 和 DELETE 查询产生 CursorResponse 后跟 CompletedResponse。向前端或从前端 COPY 调用如上所述的特殊协议。所有其他查询类型通常只产生 CompletedResponse 消息。
由于查询字符串可能包含若干条查询(以分号分隔),因此在后端完成整个查询字符串的处理之前,可能会出现多个这样的响应序列。只有在整个字符串处理完毕且后端已准备好接受新的查询字符串时,才会发出 ReadyForQuery 消息。
如果收到完全为空(除空白外无内容)的查询串,响应为 EmptyQueryResponse 后跟 ReadyForQuery。(The need to specially distinguish this case is historical.)
一旦发生错误,就会发出一条 ErrorResponse 消息,后面跟着 ReadyForQuery。ErrorResponse 会中止该查询字符串中后续所有处理(即使其中还包含其他查询)。请注意,这种情况可能发生在处理单条查询所生成的消息序列中途。
每当期待任何其他类型的消息时,前端必须准备好接受 ErrorResponse 和 NoticeResponse 消息。
实际上,即使前端不期待任何类型的消息(即后端名义上空闲)时,NoticeResponse 也可能到达。(特别是,后端可能被其父进程命令终止。在这种情况下它会在关闭连接之前发送一条 NoticeResponse。)建议前端在发出任何新命令之前检查此类异步通知。
此外,如果前端发出任何 LISTEN 命令,那么它必须准备好在任何时间接受 NotificationResponse 消息;见下文。
建议以状态机的方式编写前端,使其能够在任何合理的时机接收相应类型的消息,而不把消息确切顺序的假设写死在代码中。
4.2.3. 函数调用
函数调用周期由前端向后端发送一条 FunctionCall 消息来启动。后端随后根据函数调用的结果发送一条或多条响应消息,最后发送一条 ReadyForQuery 响应消息。ReadyForQuery 告知前端,可以安全地发送新的查询或函数调用。
后端可能发送的响应消息有:
- ErrorResponse
发生了一个错误。
- FunctionResultResponse
函数调用已执行并返回了结果。
- FunctionVoidResponse
函数调用已执行但没有返回结果。
- ReadyForQuery
函数调用处理完成。ReadyForQuery将总是被发送,不管是成功完成处理还是发生一个错误。
- NoticeResponse
发出了一条有关该函数调用的警告信息。通知是附加在其他响应上的,也就是说,后端将继续处理该命令。
每当期待任何其他类型的消息时,前端必须准备好接受 ErrorResponse 和 NoticeResponse 消息。Also, if it issues any LISTEN
commands then it must be prepared to accept NotificationResponse
messages at any time; see below.
4.2.4. 通知响应
如果前端发出LISTEN命令,那么每当针对同一通知名执行NOTIFY命令时,后端都会发送一条 NotificationResponse 消息(不要与 NoticeResponse 混淆)。
通知响应在协议的任何位置(启动之后)都是允许的,除非在另一个后端消息之内。Thus, the frontend must be prepared to recognize a NotificationResponse message whenever it is expecting any message. Indeed, it should be able to handle NotificationResponse messages even when it is not engaged in a query.
- NotificationResponse
A
NOTIFYcommand has been executed for a name for which a previousLISTENcommand was executed. Notifications may be sent at any time.
可能值得指出,listen 和 notify 命令中使用的名称不必与 SQL 数据库中关系(表)的名称有任何关系。通知名称只是任意选择的条件名称。
4.2.5. 取消进行中的请求
在一条查询正在处理的时候,前端可以请求取消该查询。这种取消请求不是直接通过打开的连接发送给后端的,这么做是因为实现的效率:我们不希望后端在处理查询的过程中不停地检查前端来的输入。取消请求应该相对而言比较少见,所以我们把取消做得稍微笨拙一些,以便不影响正常状况的性能。
要发出取消请求,前端打开一个到服务器的新连接并发送 CancelRequest 消息,而不是通常通过新连接发送的 StartupPacket 消息。服务器将处理此请求然后关闭连接。出于安全原因,不对取消请求消息作直接回复。
除非CancelRequest消息包含在连接启动过程中传递给前端的相同的密钥数据(PID 和密钥),否则它将被忽略。如果该请求匹配当前运行着的后端的PID和密钥,则中止当前查询的处理(目前的实现里采用的方法是向正在处理该查询的后端进程发送一个特殊的信号)。
取消信号可能有效也可能无效——例如,如果它到达时后端已经处理完查询,那么它就不会有效果。如果取消有效,则会导致当前命令提前终止并附带一条错误消息。
这么做是对安全性和效率通盘考虑的结果,前端没有直接的方法获知一个取消请求是否成功。它必须继续等待后端对查询的响应。发出一个取消仅仅是增加了当前查询快些结束的可能性,同时也增加了当前查询会伴随着一条错误消息失败而不是成功执行的可能性。
由于取消请求是通过一条新的连接发送给服务器,而不是通过常规的前端/后端通信链路发送,因此发出取消请求的可以是任意进程,而不一定非要是要取消查询的那个前端。这为构建多进程应用提供了额外的灵活性,同时也带来了安全风险,因为未授权用户可能会尝试取消查询。通过要求在取消请求中提供动态生成的密钥,可以缓解这一安全风险。
4.2.6. Termination
正常的优雅终止过程是前端发送 Terminate 消息并立即关闭连接。收到该消息后,后端立即关闭连接并终止。
不优雅的终止可能由于任一端的软件故障(即核心转储)而发生。如果前端或后端看到连接意外关闭,应当清理并终止。如果前端不想终止自身,可以选择重新联系服务器来启动一个新的后端。
无论是正常还是异常终止,任何打开的事务都会被回滚而不是提交。但应注意,如果前端在一个查询正在处理时断开连接,后端很可能在注意到断开之前完成该查询。如果该查询在任何事务块(BEGIN ... COMMIT 序列)之外,那么其结果可能在断开被识别之前提交。
4.2.7. SSL 会话加密
近期的 PostgreSQL 版本允许用 SSL 加密前端/后端通信。这在攻击者可能捕获会话流量的环境中提供了通信安全性。
要发起 SSL 加密连接,前端最初发送的是 SSLRequest 消息而不是 StartupPacket。服务器随后以一个包含 Y 或 N 的单字节响应,分别表示它愿意或不愿意执行 SSL。如果前端对响应不满意,可以在此点关闭连接。要在 Y 之后继续,与服务器执行 SSL 启动握手(此处不描述,属于 SSL 规范的一部分)。如果成功,继续发送通常的 StartupPacket。在这种情况下 StartupPacket 和所有后续数据都将被 SSL 加密。要在 N 之后继续,发送通常的 StartupPacket 并不加密地继续。
前端还应准备好处理服务器对 SSLRequest 的 ErrorMessage 响应。这只会在服务器早于向 PostgreSQL 添加 SSL 支持的版本时发生。在这种情况下必须关闭连接,但前端可以选择打开一个新连接并且不请求 SSL 地继续。
如果建立连接是为了发送 CancelRequest 消息,也可以先发送 SSLRequest。
虽然协议本身没有提供让服务器强制使用 SSL 加密的方法,但管理员可以配置服务器,使其在认证检查中拒绝未加密的会话。