1、libpq简介
libpq 是 C 应用程序与 PostgreSQL 的接口。libpq 是一组库函数,允许客户端程序将访问请求传递到 PostgreSQL 后端服务器并接收这些访问请求的结果。libpq 同时也是 C++ 、 Perl 、 Python 、 Tcl 等应用程序接口的底层引擎。
因此,如果使用以上其中一个包,libpq 某些行为就非常重要,特别是使用 libpq 的应用程序时用户可以看到的行为。使用 libpq 的客户端程序必须包含头文件 libpq-fe.h,且必须与 libpq 库链接。
2、通过 libpq 创建连接的过程
用户使用 libpq 驱动创建与数据库的连接,用于应用程序执行查询并返回结果时,需要使用 libpq 驱动提供的 Connect 方法进行连接,包括 PQconnectdb 和 PQconnectdbParams 两个接口。
二者底层连接是相同的逻辑,区别在于创建连接的 url 传入方式不同。 PQconnectdb 的传入参数即连接的字符串,而 PQconnectdbParams 将连接的信息以列表的形式传入。应用使用 libpq 创建连接的流程,主要分为 3 步:
创建 PGconn 类型连接对象 conn;
连接数据库(通过接口 PQconnectdb 或 PQconnectdbParams );
判断连接对象 conn 的状态,若为 CONNECTION_OK ,则连接成功。
Client 通过 libpq 与 server 建立连接的过程,主要涉及到 3 个状态图:
PostgreSQL 建立连接轮询状态图,驱动建立连接的流程;
PostgreSQL 的连接状态图,驱动建立连接的流程;
PostgreSQL 设置环境变量状态图( protocol version < 3 )。
(1) PostgreSQL建立连接轮询状态图
轮询主要是用于等待 conn 对象创建 socket 、写入连接参数、等待 server 返回结果、等待连接认证等。此过程中 conn 对象已经创建,但未完成与 server 的服务连接过程。
connect 过程不是瞬间完成的,需要有一系列的处理过程,在此过程中的等待流程由轮询控制,是一个短暂的过程。轮询的所有状态定义如下:
用户需要创建连接时,libpq 使用轮询的方式查看当前轮询的状态。涉及到的几个状态包括 PGRES_POLLING_FAILED, PGRES_POLLING_READING,PGRES_POLLING_WRITING, PGRES_POLLING_OK。
PGRES_POLLING_FAILED 和 PGRES_POLLING_OK 为轮询终止的状态条件, PGRES_POLLING_READING,PGRES_POLLING_WRITING 为需要持续询问当前 conn 的状态条件。
在空的 conn 对象建立后,轮询进入初始状态 PGRES_POLLING_WRITING,调用 PQconnectPoll 询问到是否需要等待 conn 建立完成。若仍需等待,则继续等待和状态轮询直到连接建立完成,否则错误返回。
建立连接轮询的接口如下:
(2)PostgreSQL 的建立连接流程
conn 建立与 server 连接的过程,需要经过复杂的流程,主要分为 3 个阶段:
1、连接建立阶段
主要是建立 socket 对象,以及连接 socket,为后续的加密协商阶段和认证协商阶段提供通信通道。
2、加密协商阶段
加密协商阶段是在连接建立后进行的第一个阶段,为了保证后续的认证协商阶段中会话信息不会泄漏,需要先对连接进行通信加密。
在连接建立后前端发出的第一个通信包将开始加密协商阶段,加密协商阶段前端会发出 4 种类型的通信消息:Startup message、SSL encrypt request、GSS encrypt request 和 Cancel request。
其中 Startup message 和 Cancel request 表示前端不需要进行加密协商阶段,但后端可以根据配置给予对应的应答,允许进入认证协商阶段或关闭连接。
3、认证协商阶段
当加密协商阶段完成或跳过后,PostgreSQL 协议将开始进行认证阶段。认证阶段由 Startup message 消息开始。
前端发出 Startup message 消息后,后端会进行认证应答,认证应答信息的类型为“ R ”,其内容大致分为 3 种情况:
完成认证(相当于不需要认证)
提供认证方式与所需的参数
认证错误
认证错误消息 ErrorResponse 会导致后端直接关闭连接,停止认证协商。前端通过认证应答信息提供的认证方式(如果有的话)向后端发送认证请求,认证请求消息中包含后端所需要的认证参数,例如密码或密码的 MD5 值等。
认证请求的类型为“ P ”,其内容需要根据上下文进行推断,例如之前认证应答消息中的认证方式为 MD5,则认证请求消息中的内容就为密码的 MD5 值。
前端向后端发送认证请求后,后端会再次根据认证请求中的内容进行认证应答,直到认证完成或认证错误。所以,认证阶段完成的标志是:后端发送的内容为认证完成的认证应答消息或者发送了 ErrorResponse 的认证错误消息。
当认证完成时,后端会在认证应答信息后发送一些其他协议,来通知前端一些必要的参数,其中有:
类型为“ S ”的 ParameterStatus :是一个 Key-value 对,进行参数设置;
类型为“ K ”的 BackendKeyData:描述了一个取消请求的 Key,主要用户在开始阶段时 Cancel request 需要的 Key 值,用于在一个新建会话中中断另一个会话中阻塞操作;
类型为“ Z ”的 ReadyForQuery:代表后端已经准备好开始一个新的数据请求。
至此,一个建立连接的过程已经完全准备完成。建立连接的状态图如下:
conn 连接状态定义如下:
具体连接过程的接口定义如下:
该方法主要是进行整个连接流程的控制,包括了连接阶段、加密协商阶段和认证协商阶段。代码行数 1000+,此处不进行赘述,代码位置为 PostgreSQL REL9_5_25 的:./src/interfaces/libpq/fe-connect.c
(3)PostgreSQL 设置环境变量流程
当建立连接的状态图完成认证之后,此时连接已经建立成功,达到状态 CONNECTION_AUTH_OK。
此时,需要考虑 libpq 的协议版本兼容性,如果协议版本>= 3,进入 CONNECTION_OK 状态,完成连接的创建;如果协议版本< 3,进入 CONNECTION_SETENV 状态,对 connection 对象 conn 进行参数的设置,设置成功之后进入 CONNECTION_OK 状态,并返回。
CONNECTION_SETENV 设置环境变量的流程中,也有一个状态机进行设置环境变量的流程控制,状态图如下:
设置环境变量流程中的状态定义如下:
/* PGSetenvStatusType defines the state of the PQSetenv state machine */ /* (this is used only for 2.0-protocol connections) */typedef enum{ SETENV_STATE_CLIENT_ENCODING_SEND, /* About to send an Environment Option */ SETENV_STATE_CLIENT_ENCODING_WAIT, /* Waiting for above send to complete */ SETENV_STATE_OPTION_SEND, /* About to send an Environment Option */ SETENV_STATE_OPTION_WAIT, /* Waiting for above send to complete */ SETENV_STATE_QUERY1_SEND, /* About to send a status query */ SETENV_STATE_QUERY1_WAIT, /* Waiting for query to complete */ SETENV_STATE_QUERY2_SEND, /* About to send a status query */ SETENV_STATE_QUERY2_WAIT, /* Waiting for query to complete */ SETENV_STATE_IDLE } PGSetenvStatusType;
环境变量的设置,主要是发送请求 set client_encoding,设置其他的变量 options,查询服务端 version 并设置 server_version、查询服务端设置的 client_encoding 并更新到连接对象 conn。设置环境变量的状态控制接口定义为:
代码行数 350+,此处不进行赘述,代码位置为 PostgreSQL REL9_5_25 的:./src/interfaces/libpq/fe-protocol2.c
通过 libpq 与 PostgreSQL 建立连接是一个比较复杂的过程,主要通过 libpq 所在的 client 端进行驱动:发起请求,等待响应。
在建立连接轮询状态机、建立连接流程状态机和设置环境变量状态机中,有些状态会存在多次转换以完成连接建立的过程。经过连接建立、加密协商、认证协商三个阶段之后,一个连接到 PostgreSQL 的 PGconn 连接对象就准备完成,应用程序可以通过该对象进行后续各种业务的执行,向 Server 发起请求,并解析返回结果。
使用 libpq 建立与 PostgreSQL Server 的连接,并使用连接发送业务请求,详情参考示例>>https://www.postgresql.org/docs/9.5/libpq-example.html