# 53.2.消息流
本节描述消息流和每种消息类型的语义。(每封邮件的详细信息见第53.7节)根据连接的状态,有几种不同的子协议:启动、查询、函数调用、,复制
,以及终止。异步操作(包括通知响应和命令取消)也有特殊规定,可在启动阶段后的任何时间进行。
# 53.2.1.启动
要开始会话,前端会打开与服务器的连接,并发送启动消息。此消息包括用户和用户希望连接到的数据库的名称;它还标识了要使用的特定协议版本。(可选地,启动消息可以包括运行时参数的其他设置。)然后,服务器使用这些信息及其配置文件的内容(例如pg_hba。形态
)确定连接是否暂时可接受,以及需要什么额外的身份验证(如果有)。
然后,服务器发送适当的身份验证请求消息,前端必须使用适当的身份验证响应消息(如密码)进行回复。对于除GSSAPI、SSPI和SASL之外的所有身份验证方法,最多有一个请求和一个响应。在某些方法中,根本不需要来自前端的响应,因此不会发生身份验证请求。对于GSSAPI、SSPI和SASL,可能需要多次交换数据包来完成身份验证。
身份验证周期以服务器拒绝连接尝试(ErrorResponse)或发送AuthenticationOk结束。
此阶段可能来自服务器的消息包括:
错误响应
连接尝试已被拒绝。然后服务器立即关闭连接。
认证成功
身份验证交换已成功完成。
AuthenticationKerberosV5
前端现在必须与服务器一起参与Kerberos V5身份验证对话(此处未描述,这是Kerberos规范的一部分)。如果成功,服务器将使用AuthenticationOk进行响应,否则将使用ErrorResponse进行响应。这一点不再得到支持。
身份验证ClearTextPassword
前端现在必须以明文形式发送包含密码的PasswordMessage。如果这是正确的密码,服务器将以AuthenticationOk响应,否则将以ErrorResponse响应。
AuthenticationMD5Password
前端现在必须发送一条密码消息,其中包含通过MD5加密的密码(带有用户名),然后使用AuthenticationMD5Password消息中指定的4字节随机salt再次加密。如果这是正确的密码,服务器将以AuthenticationOk响应,否则将以ErrorResponse响应。实际的PasswordMessage可以在SQL中计算为concat('md5',md5(concat(md5(concat(密码,用户名)),random salt)))
(记住md5()
函数以十六进制字符串的形式返回其结果。)
认证SCMCredential
此响应仅适用于支持SCM凭据消息的平台上的本地Unix域连接。前端必须发出SCM凭证消息,然后发送单个数据字节。(数据字节的内容没有意思;它只用于确保服务器等待足够长的时间来接收凭证消息。)如果凭证可接受,服务器将以AuthenticationOk响应,否则将以ErrorResponse响应。(此消息类型仅由9.1之前的服务器发布。它可能最终从协议规范中删除。)
认证
前端现在必须启动GSSAPI谈判。前端将发送带有GSSAPI数据流第一部分的GSSResponse消息以响应此请求。如果需要进一步的消息,服务器将以AuthenticationsScotinue响应。
认证SSPI
前端现在必须启动SSPI谈判。前端将发送一个带有SSPI数据流第一部分的GSSResponse以响应此请求。如果需要进一步的消息,服务器将以AuthenticationsScotinue响应。
身份验证继续
此消息包含来自GSSAPI或SSPI协商(AuthenticationGSS、AuthenticationSSPI或先前AuthenticationGSSCOcontinue)的上一步的响应数据。如果此消息中的GSSAPI或SSPI数据表明需要更多数据来完成身份验证,则前端必须将该数据作为另一条GSSResponse消息发送。如果此消息完成了GSSAPI或SSPI身份验证,则服务器接下来将发送AuthenticationOk以指示身份验证成功,或发送ErrorResponse以指示失败。
认证SASL
前端现在必须使用消息中列出的SASL机制之一启动SASL协商。前端将发送一个SASLiniatialResponse,其中包含所选机制的名称,以及SASL数据流的第一部分,以响应该请求。如果需要进一步的消息,服务器将以AuthenticationsAllContinue响应。看见第53.3节详细信息。
身份验证继续
此消息包含来自SASL协商的上一步(AuthenticationSASL或上一个AuthenticationSASL Continue)的质询数据。前端必须以SASLResponse消息进行响应。
认证最终
SASL身份验证已完成,并为客户端提供了其他特定于机制的数据。服务器接下来将发送AuthenticationOk以指示身份验证成功,或发送ErrorResponse以指示失败。仅当SASL机制指定在完成时从服务器发送到客户端的其他数据时,才会发送此消息。
协商协议
服务器不支持客户端请求的次要协议版本,但支持协议的早期版本;此消息表示支持的最高次要版本。如果客户端请求不受支持的协议选项(例如,从_pq_2;。
)在启动包中。此消息后面会有一个ErrorResponse或一条指示身份验证成功或失败的消息。
如果前端不支持服务器请求的身份验证方法,则应立即关闭连接。
在收到AuthenticationOk后,前端必须等待来自服务器的进一步消息。在这个阶段,后端进程正在启动,前端只是一个感兴趣的旁观者。启动尝试仍有可能失败(ErrorResponse)或服务器拒绝支持请求的次要协议版本(NegotiateProtocolVersion),但在正常情况下,后端将发送一些ParameterStatus消息、BackendKeyData,最后发送ReadyForQuery。
在此阶段,后端将尝试应用启动消息中给出的任何其他运行时参数设置。如果成功,这些值将成为会话默认值。错误会导致错误响应和退出。
此阶段中可能来自后端的消息包括:
BackendKeyData
如果前端想要在以后发出取消请求,则此消息提供了必须保存的密钥数据。前端不应响应此消息,但应继续侦听ReadyForQuery消息。
参数状态
此消息通知前端后端参数的当前(初始)设置,例如客户_编码或日期风格.前端可以忽略此消息,或记录设置以备将来使用;看见第53.2.6节更多细节。前端不应响应此消息,但应继续侦听ReadyForQuery消息。
ReadyForQuery
启动完成。前端现在可以发出命令。
错误响应
启动失败。发送此消息后,连接将关闭。
通知响应
已发出警告消息。前端应该显示消息,但继续监听ReadyForQuery或ErrorResponse。
ReadyForQuery消息与后端在每个命令周期后发出的消息相同。根据前端的编码需求,合理地考虑Read yFQuess作为启动命令循环,或者考虑RealyFoQuQuy作为结束启动阶段和每个后续命令循环。
# 53.2.2.简单查询
一个简单的查询周期由前端向后端发送查询消息来启动。该消息包含一个或多个以文本字符串表示的SQL命令。然后,后端根据查询命令字符串的内容发送一条或多条响应消息,最后发送ReadyForQuery响应消息。ReadyForQuery通知前端它可以安全地发送新命令。(实际上,在发出另一个命令之前,前端没有必要等待ReadyForQuery,但是前端必须负责弄清楚如果先前的命令失败,并且已经发出的后续命令成功,会发生什么。)
来自后端的可能响应消息包括:
命令完成
SQL命令正常完成。
复制响应
后端准备将数据从前端复制到表中;看见第53.2.5节.
CopyOutResponse
后端准备将数据从表复制到前端;看见第53.2.5节.
行描述
指示将返回行以响应选择
, 取来
等等。此消息的内容描述了行的列布局。这之后将为返回到前端的每一行发送一条DataRow消息。
数据行
返回的一组行中的一行选择
, 取来
等等。
EmptyQueryResponse
识别出一个空的查询字符串。
错误响应
发生了一个错误。
ReadyForQuery
查询字符串的处理已完成。将发送一条单独的消息来指示这一点,因为查询字符串可能包含多个SQL命令。(CommandComplete标志着处理一条SQL命令的结束,而不是整个字符串的结束。)无论处理是否成功终止或出现错误,ReadyForQuery都将始终发送。
通知响应
已发出与查询相关的警告消息。通知是对其他响应的补充,即后端将继续处理该命令。
对选择
查询(或返回行集的其他查询,例如解释
或显示
)通常由RowDescription、零条或多条DataRow消息组成,然后是CommandComplete。复制
从前端到前端调用特殊协议,如中所述第53.2.5节。所有其他查询类型通常只生成命令完整消息。
由于查询字符串可能包含多个查询(用分号分隔),因此在后端完成处理查询字符串之前,可能会有多个这样的响应序列。ReadyForQuery在处理完整个字符串并且后端准备接受新的查询字符串时发出。
如果收到一个完全为空(除空格外没有其他内容)的查询字符串,则响应为EmptyQueryResponse,后跟ReadyForQuery。
如果出现错误,会发出ErrorResponse,然后是ReadyForQuery。ErrorResponse会中止对查询字符串的所有进一步处理(即使其中还有更多查询)。请注意,这可能发生在单个查询生成的消息序列的中间。
在简单查询模式下,检索到的值的格式总是文本,除非给定的命令是文本取来
从用二进制的
选项在这种情况下,检索到的值是二进制格式的。RowDescription消息中给出的格式代码告诉我们正在使用哪种格式。
前端必须准备好在需要任何其他类型的消息时接受ErrorResponse和NoticeResponse消息。另见第53.2.6节关于后端可能因外部事件而生成的消息。
建议的做法是以状态机风格对前端进行编码,在任何时候都可以接受任何有意义的消息类型,而不是假设消息的确切顺序。
# 53.2.2.1.一个简单查询中的多个语句
当简单查询消息包含多个SQL语句(用分号分隔)时,这些语句将作为单个事务执行,除非包含显式事务控制命令以强制执行不同的行为。例如,如果消息包含
INSERT INTO mytable VALUES(1);
SELECT 1/0;
INSERT INTO mytable VALUES(2);
然后,在选择
将强制回滚第一个插入
。此外,由于在第一个错误时放弃执行消息,因此第二个错误插入
从未尝试过。
如果消息包含
BEGIN;
INSERT INTO mytable VALUES(1);
COMMIT;
INSERT INTO mytable VALUES(2);
SELECT 1/0;
然后第一个插入
是由明确的犯罪
命令第二个插入
还有选择
仍被视为单个事务,因此被零除失败将回滚第二个事务插入
,但不是第一个。
这种行为是通过在一个数据库中运行多语句查询消息中的语句来实现的隐式事务块除非有明确的事务块供它们运行。隐式事务块和常规事务块之间的主要区别在于,隐式块在查询消息结束时自动关闭,如果没有错误,则通过隐式提交,如果有错误,则通过隐式回滚。这类似于对自身执行的语句(不在事务块中时)执行的隐式提交或回滚。
如果会话已经在事务块中,由于开始
在前面的某条消息中,查询消息只是继续该事务块,不管该消息包含一条语句还是多条语句。但是,如果查询消息包含犯罪
或回降
关闭现有事务块,然后在隐式事务块中执行以下任何语句。相反,如果开始
出现在多语句查询消息中,然后它启动一个常规事务块,该事务块将仅由显式犯罪
或回降
,无论它出现在本查询消息中还是以后的查询消息中。如果开始
遵循一些作为隐式事务块执行的语句,这些语句不会立即提交;实际上,它们被追溯到新的常规交易区块中。
A.犯罪
或回降
出现在隐式事务块中时,正常执行,关闭隐式事务块;然而,自事故发生后将发出警告犯罪
或回降
没有前科开始
可能是个错误。如果后面有更多语句,将为它们启动一个新的隐式事务块。
隐式事务块中不允许使用保存点,因为它们会与出现任何错误时自动关闭块的行为相冲突。
请记住,无论是否存在任何事务控制命令,查询消息的执行都会在第一个错误时停止。例如
BEGIN;
SELECT 1/0;
ROLLBACK;
在单个查询消息中,会话将留在失败的常规事务块中,因为回降
在被零除错误后未达到。另一个回降
将会话恢复到可用状态时需要。
另一个值得注意的行为是,在执行任何查询字符串之前,对整个查询字符串进行初始词法和语法分析。因此,后面语句中的简单错误(例如拼写错误的关键字)可能会阻止任何语句的执行。这对于用户来说通常是不可见的,因为当作为隐式事务块执行时,语句无论如何都会回滚。但是,当尝试在多语句查询中执行多个事务时,它是可见的。例如,如果一个打字错误把我们前面的例子变成了
BEGIN;
INSERT INTO mytable VALUES(1);
COMMIT;
INSERT INTO mytable VALUES(2);
SELCT 1/0;
然后,所有语句都不会运行,从而产生第一个语句的明显差异插入
没有承诺。在语义分析或更高版本中检测到的错误(如表或列名拼写错误)不会产生这种影响。
# 53.2.3.扩展查询
扩展查询协议将上述简单查询协议分解为多个步骤。准备步骤的结果可以多次重复使用,以提高效率。此外,还提供了其他功能,例如可以将数据值作为单独的参数提供,而不必将它们直接插入查询字符串中。
在扩展协议中,前端首先发送一条解析消息,该消息包含一个文本查询字符串、可选的一些有关参数占位符数据类型的信息,以及目标准备语句对象的名称(空字符串选择未命名的准备语句)。响应为ParseComplete或ErrorResponse。参数数据类型可由OID指定;如果没有给出,解析器将尝试以与非类型化文本字符串常量相同的方式推断数据类型。
# 笔记
通过将参数数据类型设置为零,或使参数类型OID的数组短于参数符号的数量,可以不指定参数数据类型($
n
)在查询字符串中使用。另一种特殊情况是,参数的类型可以指定为无效的
(也就是说无效的
伪类型)。这意味着允许将参数符号用于实际为OUT参数的函数参数。通常情况下,不存在无效的
可以使用参数,但如果函数的参数列表中出现这样的参数符号,它实际上会被忽略。例如,函数调用,例如foo(1美元、2美元、3美元、4美元)
可以匹配具有两个IN和两个OUT参数的函数,如果$3
和$4
被指定为具有类型无效的
.
# 笔记
解析消息中包含的查询字符串不能包含多个SQL语句;否则会报告语法错误。这种限制在简单查询协议中不存在,但在扩展协议中确实存在,因为允许准备好的语句或门户包含多个命令会使协议过度复杂化。
如果成功创建,命名的prepared语句对象将持续到当前会话结束,除非显式销毁。未命名的prepared语句只会持续到下一个将未命名语句指定为目标的Parse语句发出为止。(请注意,简单的查询消息也会破坏未命名语句。)命名的预处理语句必须显式关闭,才能由另一条解析消息重新定义,但未命名语句不需要这样做。还可以在SQL命令级别使用准备
和处决
.
一旦准备好的语句存在,就可以使用绑定消息准备执行它。Bind消息给出了源准备语句的名称(空字符串表示未命名的准备语句)、目标门户的名称(空字符串表示未命名的门户)以及准备语句中存在的任何参数占位符要使用的值。提供的参数集必须与准备好的语句所需的参数集匹配。(如果你申报了无效的
解析消息中的参数,在绑定消息中为其传递空值。)Bind还指定用于查询返回的任何数据的格式;格式可以是整体指定的,也可以是每列指定的。响应为BindComplete或ErrorResponse。
# 笔记
文本输出和二进制输出之间的选择取决于Bind中给出的格式代码,而与涉及的SQL命令无关。这个二进制的
使用扩展查询协议时,游标声明中的属性无关紧要。
查询计划通常发生在处理 Bind 消息时。如果准备好的语句没有参数,或者被重复执行,服务器可能会保存创建的计划并在随后的 Bind 消息中为同一准备好的语句重新使用它。但是,只有当它发现可以创建一个比依赖于所提供的特定参数值的计划效率不低的通用计划时,它才会这样做。就协议而言,这是透明地发生的。
如果成功创建,命名门户对象将持续到当前事务结束,除非显式销毁。未命名的门户在事务结束时被销毁,或者在发出下一个将未命名的门户指定为目的地的 Bind 语句后立即销毁。(注意,一个简单的 Query 消息也会破坏未命名的门户。)命名的门户必须显式关闭,然后才能由另一个 Bind 消息重新定义,但这对于未命名的门户不是必需的。命名门户也可以在 SQL 命令级别创建和访问,使用声明光标
和拿来
.
一旦门户存在,就可以使用 Execute 消息来执行它。Execute 消息指定门户名称(空字符串表示未命名的门户)和最大结果行数(零表示“获取所有行”)。结果行计数仅对包含返回行集的命令的门户有意义;在其他情况下,该命令始终执行完成,并且忽略行数。对 Execute 的可能响应与上述通过简单查询协议发出的查询的响应相同,只是 Execute 不会导致发出 ReadyForQuery 或 RowDescription。
如果 Execute 在完成门户执行之前终止(由于达到非零结果行计数),它将发送 PortalSuspended 消息;此消息的出现告诉前端应针对同一门户发出另一个 Execute 以完成操作。在门户执行完成之前,不会发送指示源 SQL 命令完成的 CommandComplete 消息。因此,Execute 阶段总是因出现以下消息之一而终止:CommandComplete、EmptyQueryResponse(如果门户是从空查询字符串创建的)、ErrorResponse 或 PortalSuspended。
在完成每一系列扩展查询消息时,前端应该发出一条同步消息。此无参数消息会导致后端关闭当前事务,如果它不在开始
/犯罪
事务块(“关闭”意味着如果没有错误则提交,或者如果错误则回滚)。然后发出 ReadyForQuery 响应。Sync 的目的是为错误恢复提供重新同步点。如果在处理任何扩展查询消息时检测到错误,后端会发出 ErrorResponse,然后读取并丢弃消息,直到达到 Sync,然后发出 ReadyForQuery 并返回到正常的消息处理。(但请注意,如果检测到错误,则不会发生跳过尽管处理 Sync — 这确保为每个 Sync 发送一个且只有一个 ReadyForQuery。)
# 笔记
同步不会导致事务块打开开始
被关闭。由于 ReadyForQuery 消息包含事务状态信息,因此可以检测到这种情况。
除了这些基本的必需操作之外,还有几个可选操作可以与扩展查询协议一起使用。
描述消息(门户变体)指定现有门户的名称(或未命名门户的空字符串)。响应是一个 RowDescription 消息,描述将通过执行门户返回的行;如果门户不包含将返回行的查询,则为 NoData 消息;或 ErrorResponse 如果没有这样的门户。
Describe 消息(语句变体)指定现有预准备语句的名称(或未命名的预准备语句的空字符串)。响应是描述语句所需参数的 ParameterDescription 消息,然后是描述语句最终执行时将返回的行的 RowDescription 消息(如果语句不返回行,则为 NoData 消息)。如果没有这样的准备好的语句,则会发出 ErrorResponse。请注意,由于尚未发布 Bind,因此后端尚不知道要用于返回列的格式;在这种情况下,RowDescription 消息中的格式代码字段将为零。
# 提示
在大多数情况下,前端应该在发出 Execute 之前发出 Describe 的一个或另一个变体,以确保它知道如何解释它将返回的结果。
Close 消息关闭现有的准备好的语句或门户并释放资源。对不存在的语句或门户名称发出关闭不是错误。响应通常是 CloseComplete,但如果在释放资源时遇到一些困难,则可能是 ErrorResponse。请注意,关闭准备好的语句会隐式关闭从该语句构造的任何打开的门户。
Flush 消息不会导致生成任何特定的输出,但会强制后端交付其输出缓冲区中未决的任何数据。如果前端希望在发出更多命令之前检查该命令的结果,则必须在除 Sync 之外的任何扩展查询命令之后发送 Flush。如果没有 Flush,后端返回的消息将被组合成尽可能少的数据包,以最大限度地减少网络开销。
# 笔记
简单的 Query 消息大致等价于 Parse、Bind、portal Describe、Execute、Close、Sync 系列,使用未命名的准备好的语句和门户对象,并且没有参数。一个区别是它将接受查询字符串中的多个 SQL 语句,并自动为每个 SQL 语句连续执行绑定/描述/执行序列。另一个区别是它不会返回 ParseComplete、BindComplete、CloseComplete 或 NoData 消息。
# 53.2.4.函数调用
函数调用子协议允许客户端请求直接调用数据库中存在的任何函数pg_程序
系统目录。客户端必须具有函数的执行权限。
# 笔记
函数调用子协议是一个遗留特性,在新代码中可能是最好避免的。类似的结果也可以通过设置一个事先准备好的语句来实现选择功能($1,…)
.然后可以用Bind/Execute替换函数调用周期。
函数调用周期由前端向后端发送函数调用消息来启动。然后,后端根据函数调用的结果发送一条或多条响应消息,最后发送ReadyForQuery响应消息。ReadyForQuery通知前端它可以安全地发送新的查询或函数调用。
来自后端的可能响应消息包括:
错误响应
发生了一个错误。
函数调用响应
函数调用已完成,并返回消息中给出的结果。(请注意,函数调用协议只能处理单个标量结果,不能处理行类型或结果集。)
ReadyForQuery
函数调用的处理完成。无论处理是否成功终止或出现错误,ReadyForQuery都将始终发送。
通知响应
已发出与函数调用有关的警告消息。通知是对其他响应的补充,即后端将继续处理该命令。
# 53.2.5.复制操作
这个复制
命令允许向服务器或从服务器进行高速批量数据传输。复制入和复制出操作都会将连接切换到一个不同的子协议中,该子协议将持续到操作完成。
当后端执行复制时,将启动复制模式(向服务器传输数据)从STDIN复制
SQL语句。后端向前端发送CopyInResponse消息。然后,前端应发送零条或多条CopyData消息,形成输入数据流。(消息边界不需要与行边界有任何关系,尽管这通常是一个合理的选择。)前端可以通过发送CopyDone消息(允许成功终止)或CopyFail消息(这将导致复制
SQL语句失败并出现错误)。然后,后端将恢复到命令执行之前的命令处理模式复制
已启动,可以是简单查询协议,也可以是扩展查询协议。接下来它将发送CommandComplete(如果成功)或ErrorResponse(如果不成功)。
如果在复制模式(包括接收到CopyFail消息)期间后端检测到错误,后端将发出错误响应消息。如果复制
命令是通过扩展查询消息发出的,后端现在将丢弃前端消息,直到收到同步消息,然后它将发出ReadyForQuery并返回正常处理。如果复制
命令是在一条简单的查询消息中发出的,该消息的其余部分将被丢弃,并发出ReadyForQuery。在任何一种情况下,前端发出的任何后续CopyData、CopyDone或CopyFail消息都将被删除。
后端将忽略在复制模式下接收的刷新和同步消息。收到任何其他非复制消息类型将构成一个错误,将在上述状态下中止复制。(Flush和Sync的例外是为了方便客户端库在执行消息后始终发送Flush或Sync,而不检查要执行的命令是否为从STDIN复制
.)
复制模式(从服务器传输数据)在后端执行复制到标准输出
SQL语句。后端向前端发送CopyOutResponse消息,然后是零条或多条CopyData消息(始终每行一条),最后是CopyDone。然后,后端将恢复到命令执行之前的命令处理模式复制
已启动,并已完成。前端无法中止传输(除非关闭连接或发出取消请求),但它可以丢弃不需要的CopyData和CopyDone消息。
如果在复制模式下后端检测到错误,后端将发出错误响应消息并恢复正常处理。前端应将收到ErrorResponse视为终止复制模式。
NoticeResponse和ParameterStatus消息可能散布在CopyData消息之间;前端必须处理这些情况,并且还应该为其他异步消息类型做好准备(参见第53.2.6节)。否则,除CopyData或CopyDone之外的任何消息类型都可能被视为终止复制模式。
还有另一种与复制相关的模式,称为“复制两者”,它允许高速批量数据传输到和从服务器。当处于walsender模式的后端执行开始复制
陈述后端向前端发送CopyBothResponse消息。后端和前端都可以发送CopyData消息,直到任何一端发送CopyDone消息。客户端发送CopyDone消息后,连接将从“复制两个”模式转到“复制输出”模式,客户端可能不再发送任何CopyData消息。类似地,当服务器发送CopyDone消息时,连接进入copy in模式,服务器可能不再发送任何CopyData消息。在双方都发送CopyDone消息后,复制模式终止,后端恢复到命令处理模式。如果在复制两种模式期间后端检测到错误,后端将发出错误响应消息,丢弃前端消息,直到收到同步消息,然后发出ReadyForQuery并返回正常处理。前端应将收到错误响应视为在两个方向终止副本;在这种情况下,不应发送CopyDone。看见第53.4节有关通过复制两种模式传输的子程序的更多信息。
CopyInResponse、CopyOutResponse和CopyBothResponse消息包括通知前端每行的列数以及每列使用的格式代码的字段。(从目前的实现开始,一个给定的复制
操作将使用相同的格式,但消息设计不采用这种格式。)
# 53.2.6.异步操作
有几种情况下,后端会发送未经前端命令流特别提示的消息。前端必须随时准备好处理这些消息,即使不参与查询。至少,在开始阅读查询响应之前,应该检查这些情况。
由于外部活动,可能会生成NoticeResponse消息;例如,如果数据库管理员命令“快速”关闭数据库,后端将在关闭连接之前发送一个NoticeResponse,指示这一事实。因此,即使连接名义上处于空闲状态,前端也应该随时准备接受和显示NoticeResponse消息。
只要后端认为前端应该知道的任何参数的活动值发生变化,就会生成ParameterStatus消息。最常见的情况是,这是对设置
SQL命令由前端执行,这种情况实际上是同步的,但也可能发生参数状态更改,因为管理员更改了配置文件,然后向服务器发送了SIGHUP信号。此外,如果设置
命令回滚时,将生成相应的ParameterStatus消息以报告当前有效值。
目前,有一组硬连线的参数将为其生成ParameterStatus:它们是服务器版本
, 服务器编码
, 客户机编码
, 应用程序名称
, 默认事务只读
, 处于待机状态
, 是超级用户吗
, 会话\u授权
, 日期风格
, 间隔方式
, 时区
, 整数时间
和标准_一致_字符串
. (服务器编码
, 时区
和整数时间
在8.0之前的版本中没有报告;标准_一致_字符串
8.1之前的版本中未报告;间隔方式
8.4之前的版本中未报告;应用程序名称
9.0之前的版本中没有报告;默认事务只读
和处于待机状态
14年前发布的版本中未报告。)注意服务器版本
, 服务器编码
和整数时间
是启动后无法更改的伪参数。这个集合将来可能会改变,甚至可以配置。因此,前端应该简单地忽略它不了解或不关心的参数的ParameterStatus。
如果前端发布听
命令,则后端将发送NotificationResponse消息(不要与NoticeResponse混淆!)每当通知
命令将针对相同的通道名执行。
# 笔记
目前,通知响应只能在事务之外发送,因此它不会发生在命令响应系列的中间,尽管它可能发生在RealyFordQuebug之前。然而,设计前端逻辑时假设这一点是不明智的。良好的实践是能够在协议中的任何时候接受通知响应。
# 53.2.7.取消正在进行的请求
在查询处理过程中,前端可能会请求取消查询。出于实现效率的考虑,取消请求不会直接通过开放连接发送到后端:我们不希望后端在查询处理期间不断检查前端的新输入。取消请求应该相对较少,所以我们会让它们稍微麻烦一些,以避免在正常情况下受到惩罚。
要发出取消请求,前端将打开与服务器的新连接,并发送一条CancelRequest消息,而不是通常通过新连接发送的StartupMessage消息。服务器将处理此请求,然后关闭连接。出于安全原因,不会直接回复取消请求消息。
CancelRequest消息将被忽略,除非它包含在连接启动期间传递给前端的相同密钥数据(PID和密钥)。如果请求与当前正在执行的后端的PID和密钥匹配,则当前查询的处理将中止。(在现有的实现中,这是通过向处理查询的后端进程发送特殊信号来实现的。)
取消信号可能有任何影响,也可能没有任何影响——例如,如果它在后端完成查询处理后到达,那么它将没有影响。如果取消有效,则会导致当前命令提前终止,并显示错误消息。
所有这些的结果是,出于安全和效率的考虑,前端无法直接判断取消请求是否成功。它必须继续等待后端响应查询。发出“取消”只会提高当前查询很快完成的几率,并提高查询失败而不是成功的几率。
由于取消请求是通过与服务器的新连接发送的,而不是通过常规前端/后端通信链路发送的,因此可以通过任何进程发出取消请求,而不仅仅是要取消查询的前端。这可能会在构建多个流程应用程序时提供额外的灵活性。它还带来了安全风险,因为未经授权的人可能会试图取消查询。通过要求在取消请求中提供动态生成的密钥来解决安全风险。
# 53.2.8.终止
正常、优雅的终止过程是前端发送终止消息并立即关闭连接。收到此消息后,后端将关闭连接并终止。
在极少数情况下(如管理员命令关闭数据库),后端可能会在没有任何前端请求的情况下断开连接。在这种情况下,后端会在关闭连接之前尝试发送错误或通知消息,给出断开连接的原因。
其他终止场景由各种故障情况引起,例如一端的内核转储、通信链路丢失、消息边界同步丢失等。如果前端或后端发现连接意外关闭,则应清理并终止连接。如果前端不想自行终止,它可以通过重新连接服务器来启动新的后端。如果收到无法识别的消息类型,也建议关闭连接,因为这可能表示消息边界同步丢失。
对于正常或异常终止,任何打开的事务都会回滚,而不是提交。但是应该注意,如果前端断开连接,而非-选择
正在处理查询,后端可能会在注意到断开连接之前完成查询。如果查询在任何事务块之外(开始
... 犯罪
顺序),则其结果可能会在断开连接被识别之前提交。
# 53.2.9.SSL会话加密
如果PostgreSQL是使用SSL支持构建的,那么前端/后端通信可以使用SSL加密。这在攻击者可能捕获会话流量的环境中提供了通信安全性。有关使用SSL加密PostgreSQL会话的更多信息,请参阅第19.9节.
要启动SSL加密连接,前端首先发送SSLRequest消息,而不是StartupMessage。然后,服务器用一个包含S
或N
,分别表示它愿意或不愿意执行SSL。如果前端对响应不满意,它可能会在此时关闭连接。继续S
,与服务器执行SSL启动握手(此处未描述,SSL规范的一部分)。如果成功,继续发送通常的启动消息。在这种情况下,StartupMessage和所有后续数据都将进行SSL加密。继续N
,发送通常的StartupMessage并在不加密的情况下继续。(或者,允许在发送消息后发出GSSENCRequest消息。)N
响应尝试使用GSSAPI加密而不是SSL。)
前端还应该准备好处理来自服务器的对SSLRequest的ErrorMessage响应。只有在服务器早于PostgreSQL添加SSL支持时,才会出现这种情况。(这种服务器现在非常古老,很可能在野外已经不存在了。)在这种情况下,连接必须关闭,但前端可能会选择打开一个新的连接,并在不请求SSL的情况下继续。
当可以执行SSL加密时,服务器将只发送单个S
字节,然后等待前端启动SSL握手。如果此时有额外的字节可供读取,这可能意味着中间的人正试图执行缓冲区填充攻击(CVE-2021-23222 (opens new window)).前端应进行编码,以便在将套接字移交给其SSL库之前从套接字中准确读取一个字节,或者如果发现读取了额外字节,则将其视为违反协议。
初始SSLRequest也可以用于正在打开的连接中,以发送CancelRequest消息。
虽然协议本身没有为服务器提供强制SSL加密的方法,但管理员可以将服务器配置为拒绝未加密的会话,作为身份验证检查的副产品。
# 53.2.10.GSSAPI会话加密
如果PostgreSQL是使用GSSAPI支持构建的,那么前端/后端通信可以使用GSSAPI加密。这在攻击者可能捕获会话流量的环境中提供了通信安全性。有关使用GSSAPI加密PostgreSQL会话的更多信息,请参阅第19.10节.
要启动GSSAPI加密连接,前端首先发送GSSENCRequest消息,而不是StartupMessage。然后,服务器用一个包含G
或N
,表示它愿意或不愿意分别执行GSSAPI加密。如果前端对响应不满意,它可能会在此时关闭连接。继续G
,使用中讨论的GSSAPI C绑定RFC 2744 (opens new window)或同等功能,通过调用gss_init_sec_context()
在循环中,将结果发送到服务器,从空输入开始,然后从服务器返回每个结果,直到它不返回任何输出。在发送gss_init_sec_context()
对于服务器,以网络字节顺序将消息的长度前置为四字节整数。继续N
,发送通常的StartupMessage并在不加密的情况下继续。(或者,允许在发出SSLRequest消息后发出SSLRequest消息。)N
响应尝试使用SSL加密而不是GSSAPI。)
前端还应该准备好处理来自服务器的对GSSENCRequest的错误消息响应。只有在服务器在PostgreSQL中添加GSSAPI加密支持之前,才会出现这种情况。在这种情况下,连接必须关闭,但前端可能会选择打开一个新的连接,并在不请求GSSAPI加密的情况下继续。
当可以执行GSSAPI加密时,服务器将只发送单个G
字节,然后等待前端启动GSSAPI握手。如果此时有额外的字节可供读取,这可能意味着中间的人正试图执行缓冲区填充攻击(CVE-2021-23222 (opens new window))。前端应进行编码,以便在将套接字移交给其GSSAPI库之前从套接字中准确读取一个字节,或者如果发现读取了额外字节,则将其视为违反协议。
初始GSSENCRequest也可以用于正在打开的连接中,以发送CancelRequest消息。
成功建立GSSAPI加密后,使用gss_wrap()
要加密常用的StartupMessage和所有后续数据,请在gss_wrap()
作为网络字节顺序中的四字节整数,以实际加密的有效负载为准。注意,服务器只接受来自客户端的小于16kB的加密数据包;gss_包装_尺寸_限制()
客户端应使用该参数来确定未加密消息的大小,该大小将符合此限制,较大的消息应拆分为多个gss_wrap()
电话。典型的数据段是8kB的未加密数据,导致加密数据包略大于8kB,但在16kB的最大值范围内。服务器可能不会向客户端发送大于16kB的加密数据包。
虽然协议本身没有为服务器提供强制GSSAPI加密的方法,但管理员可以将服务器配置为拒绝未加密的会话,作为身份验证检查的副产品。