PostgreSQL的学习心得和知识总结(一百三十八)|深入理解PostgreSQL数据库之Protocol message构造和解析逻辑


注:提前言明 本文借鉴了以下博主、书籍或网站的内容,其列表如下:

1、参考书籍:《PostgreSQL数据库内核分析》
2、参考书籍:《数据库事务处理的艺术:事务管理与并发控制》
3、PostgreSQL数据库仓库链接,点击前往
4、日本著名PostgreSQL数据库专家 铃木启修 网站主页,点击前往
5、参考书籍:《PostgreSQL中文手册》
6、参考书籍:《PostgreSQL指南:内幕探索》,点击前往
7、深度解析 PostgreSQL Protocol v3.0(一),点击前往
8、技术贴 | 深度解析 PostgreSQL Protocol v3.0(二)— 扩展查询,点击前往


1、本文内容全部来源于开源社区 GitHub和以上博主的贡献,本文也免费开源(可能会存在问题,评论区等待大佬们的指正)
2、本文目的:开源共享 抛砖引玉 一起学习
3、本文不提供任何资源 不存在任何交易 与任何组织和机构无关
4、大家可以根据需要自行 复制粘贴以及作为其他个人用途,但是不允许转载 不允许商用 (写作不易,还请见谅 💖)
5、本文内容基于PostgreSQL master源码开发而成


深入理解PostgreSQL数据库之Protocol message构造和解析逻辑

  • 文章快速说明索引
  • 功能使用背景说明
  • 功能实现源码解析
    • 辅助调试信息
    • 前端信息构造
    • 后端消息解析



文章快速说明索引

学习目标:

做数据库内核开发久了就会有一种 少年得志,年少轻狂 的错觉,然鹅细细一品觉得自己其实不算特别优秀 远远没有达到自己想要的。也许光鲜的表面掩盖了空洞的内在,每每想到于此,皆有夜半临渊如履薄冰之感。为了睡上几个踏实觉,即日起 暂缓其他基于PostgreSQL数据库的兼容功能开发,近段时间 将着重于学习分享Postgres的基础知识和实践内幕。


学习内容:(详见目录)

1、深入理解PostgreSQL数据库之Protocol message构造和解析逻辑


学习时间:

2024年04月17日 22:14:16 星期三


学习产出:

1、PostgreSQL数据库基础知识回顾 1个
2、CSDN 技术博客 1篇
3、PostgreSQL数据库内核深入学习


注:下面我们所有的学习环境是Centos8+PostgreSQL master+Oracle19C+MySQL8.0

postgres=# select version();version                                                   
------------------------------------------------------------------------------------------------------------PostgreSQL 17devel on x86_64-pc-linux-gnu, compiled by gcc (GCC) 8.5.0 20210514 (Red Hat 8.5.0-21), 64-bit
(1 row)postgres=##-----------------------------------------------------------------------------#SQL> select * from v$version;          BANNER        Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
BANNER_FULL	  Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production Version 19.17.0.0.0	
BANNER_LEGACY Oracle Database 19c EE Extreme Perf Release 19.0.0.0.0 - Production	
CON_ID 0#-----------------------------------------------------------------------------#mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.27    |
+-----------+
1 row in set (0.06 sec)mysql>

功能使用背景说明

在之前的博客 PostgreSQL的学习心得和知识总结(一百三十七)|深入理解PostgreSQL数据库之Add support for Close on portals and statements,点击前往 的结尾的时候,简单介绍了一下close的message构造:


在这里插入图片描述

在这里插入图片描述


这里解释一下close消息的构造,如下:

	/* construct the Close message */if (pqPutMsgStart(command, conn) < 0 ||pqPutc(type, conn) < 0 || // 1字节 SpqPuts(target, conn) < 0 || // strlen("p2") + 1 = 3pqPutMsgEnd(conn) < 0) // (conn->outMsgEnd - conn->outMsgStart)长度 4字节goto sendFailed;

功能实现源码解析

辅助调试信息

上面是通过抓包工具得到的,接下来我们看一下PostgreSQL中提供的debug方式,如下:

#include <iostream>#include "libpq-fe.h"using namespace std;int main()
{const char *conninfo = "host=localhost port=5432 user=postgres dbname=postgres password=1";const char *dropTableCmd = "drop table if exists t1;";const char *createTableCmd = "create table t1(id int, name text);";const char *insertCmd = "insert into t1 values(1, 'Oracle'), (2, 'MySQL'), (3, 'SQL Server');";const char *prepareCmd0 = "table t1;";const char *prepareCmd1 = "select * from t1 where id < $1;";const char *prepareCmd2 = "select * from t1 where id = $1 and name = $2;";PGresult *result = NULL;ExecStatusType result_status;int n_rows = 0, ntups = 0;const char *values2[2] = {"2", "MySQL"};FILE *file = fopen("/home/postgres/test/bin/1.txt", "w+");PGconn *conn = PQconnectdb(conninfo);PQtrace(conn, file);if (PQstatus(conn) == CONNECTION_OK){cout << "连接PostgreSQL数据库 成功!" << endl;if (PQexec(conn, dropTableCmd) != NULL){cout << "删除表成功" << endl;}if (PQexec(conn, createTableCmd) != NULL){cout << "创建表成功" << endl;}if (PQexec(conn, insertCmd) != NULL){cout << "插入表成功" << endl<< endl;}/* ---------------------------------------------------------------- */// prepareif (PQsendPrepare(conn, "p23456789", prepareCmd2, 2, NULL) != NULL) // 异步{cout << "prepare p23456789 send成功" << endl;}while (NULL != (result = PQgetResult(conn))){result_status = PQresultStatus(result);if ((PGRES_EMPTY_QUERY != result_status) && (PGRES_COMMAND_OK != result_status) && (PGRES_TUPLES_OK != result_status) && (PGRES_NONFATAL_ERROR != result_status)){cout << "prepare p23456789 失败" << endl;}else{n_rows = atoi(PQcmdTuples(result));ntups = PQntuples(result);cout << "prepare p23456789 成功"<< " n_rows: " << n_rows << " ntups: " << ntups << endl<< endl;}PQclear(result);}/* ---------------------------------------------------------------- */// exec_preparedif (PQsendQueryPrepared(conn, "p23456789", 2, values2, NULL, NULL, 0) != NULL) // 异步{cout << "exec prepare p23456789 send成功" << endl;}while (NULL != (result = PQgetResult(conn))){result_status = PQresultStatus(result);if ((PGRES_EMPTY_QUERY != result_status) && (PGRES_COMMAND_OK != result_status) && (PGRES_TUPLES_OK != result_status) && (PGRES_NONFATAL_ERROR != result_status)){cout << "exec prepare p23456789 失败" << endl;}else{n_rows = atoi(PQcmdTuples(result));ntups = PQntuples(result);cout << "exec prepare p23456789 send成功"<< " n_rows: " << n_rows << " ntups: " << ntups << endl<< endl;}PQclear(result);}/* ---------------------------------------------------------------- */// deallocateresult = PQexec(conn, "select pg_sleep(60);"); // 同步PQclear(result);if (PQsendClosePrepared(conn, "p23456789") != NULL) // 异步{cout << "close prepare p23456789 send成功" << endl;}while (NULL != (result = PQgetResult(conn))){result_status = PQresultStatus(result);if ((PGRES_EMPTY_QUERY != result_status) && (PGRES_COMMAND_OK != result_status) && (PGRES_TUPLES_OK != result_status) && (PGRES_NONFATAL_ERROR != result_status)){cout << "close prepare p23456789 失败" << endl;}else{n_rows = atoi(PQcmdTuples(result));ntups = PQntuples(result);cout << "close prepare p23456789 成功"<< " n_rows: " << n_rows << " ntups: " << ntups << endl<< endl;}PQclear(result);}/* ---------------------------------------------------------------- */PQfinish(conn);PQuntrace(conn);cout << "与PostgreSQL数据库连接 关闭!" << endl;}else{cout << "连接失败!" << endl;}fclose(file);return 0;
}/** export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$PG_HOME/lib** g++ libpqtest2.cpp -lpq -L/home/postgres/test/lib -I/home/postgres/test/include -o main -w -g -O0*/

执行结果以及打印信息,如下:

[postgres@localhost:~/test/bin]$ ./main 
连接PostgreSQL数据库 成功!
删除表成功
创建表成功
插入表成功prepare p23456789 send成功
prepare p23456789 成功 n_rows: 0 ntups: 0exec prepare p23456789 send成功
exec prepare p23456789 send成功 n_rows: 1 ntups: 1close prepare p23456789 send成功
close prepare p23456789 成功 n_rows: 0 ntups: 0与PostgreSQL数据库连接 关闭!
[postgres@localhost:~/test/bin]$ cat 1.txt 
2024-04-16 20:22:43.371152      F       29      Query    "drop table if exists t1;"
2024-04-16 20:22:43.381514      B       15      CommandComplete  "DROP TABLE"
2024-04-16 20:22:43.381531      B       5       ReadyForQuery    I
2024-04-16 20:22:43.381561      F       40      Query    "create table t1(id int, name text);"
2024-04-16 20:22:43.388197      B       17      CommandComplete  "CREATE TABLE"
2024-04-16 20:22:43.388215      B       5       ReadyForQuery    I
2024-04-16 20:22:43.388239      F       73      Query    "insert into t1 values(1, 'Oracle'), (2, 'MySQL'), (3, 'SQL Server');"
2024-04-16 20:22:43.388729      B       15      CommandComplete  "INSERT 0 3"
2024-04-16 20:22:43.388736      B       5       ReadyForQuery    I
2024-04-16 20:22:43.388758      F       62      Parse    "p23456789" "select * from t1 where id = $1 and name = $2;" 0
2024-04-16 20:22:43.388762      F       4       Sync
2024-04-16 20:22:43.390728      B       4       ParseComplete
2024-04-16 20:22:43.390767      B       5       ReadyForQuery    I
2024-04-16 20:22:43.390772      F       37      Bind     "" "p23456789" 0 2 1 '2' 5 'MySQL' 1 0
2024-04-16 20:22:43.390775      F       6       Describe         P ""
2024-04-16 20:22:43.390777      F       9       Execute  "" 0
2024-04-16 20:22:43.390779      F       4       Sync
2024-04-16 20:22:43.392433      B       4       BindComplete
2024-04-16 20:22:43.392483      B       50      RowDescription   2 "id" 16403 1 23 4 -1 0 "name" 16403 2 25 65535 -1 0
2024-04-16 20:22:43.392494      B       20      DataRow  2 1 '2' 5 'MySQL'
2024-04-16 20:22:43.392500      B       13      CommandComplete  "SELECT 1"
2024-04-16 20:22:43.392644      B       5       ReadyForQuery    I
2024-04-16 20:22:43.392725      F       25      Query    "select pg_sleep(60);"
2024-04-16 20:23:43.406609      B       33      RowDescription   1 "pg_sleep" 0 0 2278 4 -1 0
2024-04-16 20:23:43.406637      B       10      DataRow  1 0 ''
2024-04-16 20:23:43.406642      B       13      CommandComplete  "SELECT 1"
2024-04-16 20:23:43.406645      B       5       ReadyForQuery    I
2024-04-16 20:23:43.406654      F       15      Close    S "p23456789"
2024-04-16 20:23:43.406657      F       4       Sync
2024-04-16 20:23:43.406885      B       4       CloseComplete
2024-04-16 20:23:43.406928      B       5       ReadyForQuery    I
2024-04-16 20:23:43.406938      F       4       Terminate
[postgres@localhost:~/test/bin]$

在这里插入图片描述


如上是通过直接调用libpq中的函数PQtracePQuntrace,如下:

// src/interfaces/libpq/fe-trace.c/* Enable tracing */
void
PQtrace(PGconn *conn, FILE *debug_port)
{if (conn == NULL)return;PQuntrace(conn);if (debug_port == NULL)return;conn->Pfdebug = debug_port;conn->traceFlags = 0;
}/* Disable tracing */
void
PQuntrace(PGconn *conn)
{if (conn == NULL)return;if (conn->Pfdebug){fflush(conn->Pfdebug);conn->Pfdebug = NULL;}conn->traceFlags = 0;
}

当然如上两函数是trace的开关函数,真正实现打印的逻辑 如下:

// src/interfaces/libpq/fe-trace.c/** Print the given message to the trace output stream.* 将给定消息打印到跟踪输出流*/
void
pqTraceOutputMessage(PGconn *conn, const char *message, bool toServer)
{char		id;int			length;char	   *prefix = toServer ? "F" : "B"; // 这里指的是 Front / Backendint			logCursor = 0;bool		regress;if ((conn->traceFlags & PQTRACE_SUPPRESS_TIMESTAMPS) == 0){char		timestr[128];pqTraceFormatTimestamp(timestr, sizeof(timestr));fprintf(conn->Pfdebug, "%s\t", timestr);}regress = (conn->traceFlags & PQTRACE_REGRESS_MODE) != 0;id = message[logCursor++];memcpy(&length, message + logCursor, 4);length = (int) pg_ntoh32(length);logCursor += 4;/** In regress mode, suppress the length of ErrorResponse and* NoticeResponse.  The F (file name), L (line number) and R (routine* name) fields can change as server code is modified, and if their* lengths differ from the originals, that would break tests.*  * 在回归模式下,抑制ErrorResponse和NoticeResponse的长度* F(文件名)、L(行号)和 R(例程名称)字段可能会随着服务器代码的修改而更改* 如果它们的长度与原始长度不同,则会破坏测试*/if (regress && !toServer && (id == 'E' || id == 'N'))fprintf(conn->Pfdebug, "%s\tNN\t", prefix);elsefprintf(conn->Pfdebug, "%s\t%d\t", prefix, length);switch (id){case PqMsg_ParseComplete:fprintf(conn->Pfdebug, "ParseComplete");/* No message content */break;case PqMsg_BindComplete:fprintf(conn->Pfdebug, "BindComplete");/* No message content */break;case PqMsg_CloseComplete:fprintf(conn->Pfdebug, "CloseComplete");/* No message content */break;case PqMsg_NotificationResponse:pqTraceOutputA(conn->Pfdebug, message, &logCursor, regress);break;case PqMsg_Bind:pqTraceOutputB(conn->Pfdebug, message, &logCursor);break;case PqMsg_CopyDone:fprintf(conn->Pfdebug, "CopyDone");/* No message content */break;case PqMsg_CommandComplete:/* Close(F) and CommandComplete(B) use the same identifier. */Assert(PqMsg_Close == PqMsg_CommandComplete);pqTraceOutputC(conn->Pfdebug, toServer, message, &logCursor);break;case PqMsg_CopyData:/* Drop COPY data to reduce the overhead of logging. */break;case PqMsg_Describe:/* Describe(F) and DataRow(B) use the same identifier. */Assert(PqMsg_Describe == PqMsg_DataRow);pqTraceOutputD(conn->Pfdebug, toServer, message, &logCursor);break;case PqMsg_Execute:/* Execute(F) and ErrorResponse(B) use the same identifier. */Assert(PqMsg_Execute == PqMsg_ErrorResponse);pqTraceOutputE(conn->Pfdebug, toServer, message, &logCursor,regress);break;case PqMsg_CopyFail:pqTraceOutputf(conn->Pfdebug, message, &logCursor);break;case PqMsg_FunctionCall:pqTraceOutputF(conn->Pfdebug, message, &logCursor, regress);break;case PqMsg_CopyInResponse:pqTraceOutputG(conn->Pfdebug, message, &logCursor);break;case PqMsg_Flush:/* Flush(F) and CopyOutResponse(B) use the same identifier */Assert(PqMsg_CopyOutResponse == PqMsg_Flush);if (!toServer)pqTraceOutputH(conn->Pfdebug, message, &logCursor);elsefprintf(conn->Pfdebug, "Flush");	/* no message content */break;case PqMsg_EmptyQueryResponse:fprintf(conn->Pfdebug, "EmptyQueryResponse");/* No message content */break;case PqMsg_BackendKeyData:pqTraceOutputK(conn->Pfdebug, message, &logCursor, regress);break;case PqMsg_NoData:fprintf(conn->Pfdebug, "NoData");/* No message content */break;case PqMsg_NoticeResponse:pqTraceOutputNR(conn->Pfdebug, "NoticeResponse", message,&logCursor, regress);break;case PqMsg_Parse:pqTraceOutputP(conn->Pfdebug, message, &logCursor, regress);break;case PqMsg_Query:pqTraceOutputQ(conn->Pfdebug, message, &logCursor);break;case PqMsg_AuthenticationRequest:pqTraceOutputR(conn->Pfdebug, message, &logCursor);break;case PqMsg_PortalSuspended:fprintf(conn->Pfdebug, "PortalSuspended");/* No message content */break;case PqMsg_Sync:/* Parameter Status(B) and Sync(F) use the same identifier */Assert(PqMsg_ParameterStatus == PqMsg_Sync);if (!toServer)pqTraceOutputS(conn->Pfdebug, message, &logCursor);elsefprintf(conn->Pfdebug, "Sync"); /* no message content */break;case PqMsg_ParameterDescription:pqTraceOutputt(conn->Pfdebug, message, &logCursor, regress);break;case PqMsg_RowDescription:pqTraceOutputT(conn->Pfdebug, message, &logCursor, regress);break;case PqMsg_NegotiateProtocolVersion:pqTraceOutputv(conn->Pfdebug, message, &logCursor);break;case PqMsg_FunctionCallResponse:pqTraceOutputV(conn->Pfdebug, message, &logCursor);break;case PqMsg_CopyBothResponse:pqTraceOutputW(conn->Pfdebug, message, &logCursor, length);break;case PqMsg_Terminate:fprintf(conn->Pfdebug, "Terminate");/* No message content */break;case PqMsg_ReadyForQuery:pqTraceOutputZ(conn->Pfdebug, message, &logCursor);break;default:fprintf(conn->Pfdebug, "Unknown message: %02x", id);break;}fputc('\n', conn->Pfdebug);/** Verify the printing routine did it right.  Note that the one-byte* message identifier is not included in the length, but our cursor does* include it.*  * 验证打印例程是否正确* 请注意,一字节消息标识符不包括在长度中,但我们的光标确实包括它*/if (logCursor - 1 != length)fprintf(conn->Pfdebug,"mismatched message length: consumed %d, expected %d\n",logCursor - 1, length);
}

接下来我们重点看一下close消息的打印,如下:

pqTraceOutputMessage|pqTraceOutputC/* Close(F) or CommandComplete(B) */
static void
pqTraceOutputC(FILE *f, bool toServer, const char *message, int *cursor)
{if (toServer){fprintf(f, "Close\t");pqTraceOutputByte1(f, message, cursor);pqTraceOutputString(f, message, cursor, false);}else{fprintf(f, "CommandComplete\t");pqTraceOutputString(f, message, cursor, false);}
}	
/**   pqTraceOutputByte1: output a 1-char message to the log*	 pqTraceOutputByte1:将 1 个字符的消息输出到日志*/
static void
pqTraceOutputByte1(FILE *pfdebug, const char *data, int *cursor)
{const char *v = data + *cursor;/** Show non-printable data in hex format, including the terminating \0* that completes ErrorResponse and NoticeResponse messages.*  * 以十六进制格式显示不可打印的数据* 包括完成ErrorResponse 和NoticeResponse 消息的终止\0*/if (!isprint((unsigned char) *v))fprintf(pfdebug, " \\x%02x", *v);elsefprintf(pfdebug, " %c", *v);*cursor += 1;
}
/**   pqTraceOutputString: output a string message to the log*   pqTraceOutputString:输出字符串消息到日志*/
static void
pqTraceOutputString(FILE *pfdebug, const char *data, int *cursor, bool suppress)
{int			len;if (suppress){fprintf(pfdebug, " \"SSSS\"");*cursor += strlen(data + *cursor) + 1;}else{len = fprintf(pfdebug, " \"%s\"", data + *cursor);/** This is a null-terminated string. So add 1 after subtracting 3* which is the double quotes and space length from len.* * 这是一个以 null 结尾的字符串* 因此,从 len 中减去双引号和空格长度 3 后加 1*/*cursor += (len - 3 + 1);}
}

接下来我们继续以close消息为例,详细看一下message的构造和解析过程!

## len = 152024-04-16 20:23:43.406654	F	15	Close	 S "p23456789"

前端信息构造

此时的函数堆栈,如下:

libpq.so.5!PQsendTypedCommand(PGconn * conn, char command, char type, const char * target) (\home\postgres\postgres\src\interfaces\libpq\fe-exec.c:2605)
libpq.so.5!PQsendClosePrepared(PGconn * conn, const char * stmt) (\home\postgres\postgres\src\interfaces\libpq\fe-exec.c:2558)
main() (\home\postgres\test\bin\libpqtest2.cpp:110)

在这里插入图片描述

接下来就详细看一下这里的处理逻辑,如下:

第一步:

// src/interfaces/libpq/fe-exec.c/** pqPutMsgStart: begin construction of a message to the server* pqPutMsgStart:开始向服务器构造消息** msg_type is the message type byte, or 0 for a message without type byte* (only startup messages have no type byte)* msg_type 是消息类型字节,如果没有类型字节,则为 0(只有启动消息没有类型字节)** Returns 0 on success, EOF on error* 成功时返回 0,错误时返回 EOF** The idea here is that we construct the message in conn->outBuffer,* beginning just past any data already in outBuffer (ie, at* outBuffer+outCount).  We enlarge the buffer as needed to hold the message.* When the message is complete, we fill in the length word (if needed) and* then advance outCount past the message, making it eligible to send.* 这里的想法是,我们在 conn->outBuffer 中构造消息,从 outBuffer 中已有的任何数据开始(即,在 outBuffer+outCount 处)* 我们根据需要扩大缓冲区来保存消息* 消息完成后,我们填写长度字(如果需要),然后将 outCount 提前到消息后面,使其符合发送条件** The state variable conn->outMsgStart points to the incomplete message's* length word: it is either outCount or outCount+1 depending on whether* there is a type byte.  The state variable conn->outMsgEnd is the end of* the data collected so far.* 状态变量 conn->outMsgStart 指向不完整消息的长度字:* 它是 outCount 或 outCount+1,具体取决于是否存在类型字节* 状态变量 conn->outMsgEnd 是迄今为止收集的数据的结尾*/
int
pqPutMsgStart(char msg_type, PGconn *conn)
{int			lenPos;int			endPos;/* allow room for message type byte */if (msg_type)endPos = conn->outCount + 1;elseendPos = conn->outCount;/* do we want a length word? */lenPos = endPos;/* allow room for message length */endPos += 4;/* make sure there is room for message header */if (pqCheckOutBufferSpace(endPos, conn))return EOF;/* okay, save the message type byte if any */if (msg_type)conn->outBuffer[conn->outCount] = msg_type;/* set up the message pointers */conn->outMsgStart = lenPos;conn->outMsgEnd = endPos;/* length word, if needed, will be filled in by pqPutMsgEnd */return 0;
}

在这里插入图片描述

解释一下:

  • msg_type此时是 C
  • endPos += 4; 就代表了 已经把消息长度的空间分配好了
  • conn->outCount = 0
  • conn->outBuffer[conn->outCount] = msg_type; // 放的就是 C
  • conn->outMsgStart = 1
  • conn->outMsgEnd = 5

第二步:

libpq.so.5!pqPutMsgBytes(const void * buf, size_t len, PGconn * conn) (\home\postgres\postgres\src\interfaces\libpq\fe-misc.c:497)
libpq.so.5!pqPutc(char c, PGconn * conn) (\home\postgres\postgres\src\interfaces\libpq\fe-misc.c:94)
libpq.so.5!PQsendTypedCommand(PGconn * conn, char command, char type, const char * target) (\home\postgres\postgres\src\interfaces\libpq\fe-exec.c:2606)
libpq.so.5!PQsendClosePrepared(PGconn * conn, const char * stmt) (\home\postgres\postgres\src\interfaces\libpq\fe-exec.c:2558)
main() (\home\postgres\test\bin\libpqtest2.cpp:110)
/** pqPutc: write 1 char to the current message*/
int
pqPutc(char c, PGconn *conn)
{if (pqPutMsgBytes(&c, 1, conn))return EOF;return 0;
}.../** pqPutMsgBytes: add bytes to a partially-constructed message* pqPutMsgBytes:向部分构造的消息添加字节** Returns 0 on success, EOF on error*/
static int
pqPutMsgBytes(const void *buf, size_t len, PGconn *conn)
{/* make sure there is room for it */if (pqCheckOutBufferSpace(conn->outMsgEnd + len, conn))return EOF;/* okay, save the data */memcpy(conn->outBuffer + conn->outMsgEnd, buf, len);conn->outMsgEnd += len;/* no Pfdebug call here, caller should do it */return 0;
}

在这里插入图片描述

解释一下:

  • 因为 type 是’S’,因此这里写的就是它
  • 因为内存copy是从 conn->outBuffer + conn->outMsgEnd 开始的,也就放到了conn->outBuffer[5]
  • conn->outMsgEnd += 1

第三步:

libpq.so.5!pqPutMsgBytes(const void * buf, size_t len, PGconn * conn) (\home\postgres\postgres\src\interfaces\libpq\fe-misc.c:500)
libpq.so.5!pqPuts(const char * s, PGconn * conn) (\home\postgres\postgres\src\interfaces\libpq\fe-misc.c:154)
libpq.so.5!PQsendTypedCommand(PGconn * conn, char command, char type, const char * target) (\home\postgres\postgres\src\interfaces\libpq\fe-exec.c:2607)
libpq.so.5!PQsendClosePrepared(PGconn * conn, const char * stmt) (\home\postgres\postgres\src\interfaces\libpq\fe-exec.c:2558)
main() (\home\postgres\test\bin\libpqtest2.cpp:110)
/** pqPuts: write a null-terminated string to the current message* pqPuts:将空终止字符串写入当前消息*/
int
pqPuts(const char *s, PGconn *conn)
{if (pqPutMsgBytes(s, strlen(s) + 1, conn))return EOF;return 0;
}

在这里插入图片描述

解释一下:

  • len = strlen + 1; strlen(“p23456789”) + 1 = 10
  • 内存copy是从 conn->outMsgEnd = 6 开始的,len = 10。自然结果如上
  • conn->outMsgEnd += 10

第四步:

libpq.so.5!pqPutMsgEnd(PGconn * conn) (\home\postgres\postgres\src\interfaces\libpq\fe-misc.c:520)
libpq.so.5!PQsendTypedCommand(PGconn * conn, char command, char type, const char * target) (\home\postgres\postgres\src\interfaces\libpq\fe-exec.c:2608)
libpq.so.5!PQsendClosePrepared(PGconn * conn, const char * stmt) (\home\postgres\postgres\src\interfaces\libpq\fe-exec.c:2558)
main() (\home\postgres\test\bin\libpqtest2.cpp:110)
/** pqPutMsgEnd: finish constructing a message and possibly send it* pqPutMsgEnd:完成构建消息并可能发送它** Returns 0 on success, EOF on error** We don't actually send anything here unless we've accumulated at least* 8K worth of data (the typical size of a pipe buffer on Unix systems).* This avoids sending small partial packets.  The caller must use pqFlush* when it's important to flush all the data out to the server.*  * 除非我们已经积累了至少 8K 的数据(Unix 系统上管道缓冲区的典型大小),否则我们实际上不会在这里发送任何内容* 这避免了发送小部分数据包* 当需要将所有数据刷新到服务器时,调用者必须使用 pqFlush*/
int
pqPutMsgEnd(PGconn *conn)
{/* Fill in length word if needed */if (conn->outMsgStart >= 0){uint32		msgLen = conn->outMsgEnd - conn->outMsgStart;msgLen = pg_hton32(msgLen);memcpy(conn->outBuffer + conn->outMsgStart, &msgLen, 4);}/* trace client-to-server message */if (conn->Pfdebug){if (conn->outCount < conn->outMsgStart)pqTraceOutputMessage(conn, conn->outBuffer + conn->outCount, true);elsepqTraceOutputNoTypeByteMessage(conn,conn->outBuffer + conn->outMsgStart);}/* Make message eligible to send */conn->outCount = conn->outMsgEnd;if (conn->outCount >= 8192){int			toSend = conn->outCount - (conn->outCount % 8192);if (pqSendSome(conn, toSend) < 0)return EOF;/* in nonblock mode, don't complain if unable to send it all */}return 0;
}

在这里插入图片描述

解释一下:

  • 如上 message_len = 16 - 1 = 15,然后大端存储
  • 内存copy是从 conn->outMsgStart = 1开始,长度是4 自然就是上面的四字节
  • conn->outCount = conn->outMsgEnd = 16

至此,该15字节的close消息构建完毕!


后端消息解析

我们这里调试服务进程,如下:

在这里插入图片描述


第一步:消息读取,如下:

在这里插入图片描述

pq_recvbuf() (\home\postgres\postgres\src\backend\libpq\pqcomm.c:926)
pq_getbyte() (\home\postgres\postgres\src\backend\libpq\pqcomm.c:969)
SocketBackend(StringInfo inBuf) (\home\postgres\postgres\src\backend\tcop\postgres.c:370)
ReadCommand(StringInfo inBuf) (\home\postgres\postgres\src\backend\tcop\postgres.c:493)
PostgresMain(const char * dbname, const char * username) (\home\postgres\postgres\src\backend\tcop\postgres.c:4612)
BackendMain(char * startup_data, size_t startup_data_len) (\home\postgres\postgres\src\backend\tcop\backend_startup.c:105)
postmaster_child_launch(BackendType child_type, char * startup_data, size_t startup_data_len, ClientSocket * client_sock) (\home\postgres\postgres\src\backend\postmaster\launch_backend.c:265)
BackendStartup(ClientSocket * client_sock) (\home\postgres\postgres\src\backend\postmaster\postmaster.c:3593)
ServerLoop() (\home\postgres\postgres\src\backend\postmaster\postmaster.c:1674)
PostmasterMain(int argc, char ** argv) (\home\postgres\postgres\src\backend\postmaster\postmaster.c:1372)
main(int argc, char ** argv) (\home\postgres\postgres\src\backend\main\main.c:197)
// src/backend/libpq/pqcomm.c/* --------------------------------*		pq_recvbuf - load some bytes into the input buffer*		pq_recvbuf - 将一些字节加载到输入缓冲区中**		returns 0 if OK, EOF if trouble* --------------------------------*/
static int
pq_recvbuf(void)
{if (PqRecvPointer > 0){if (PqRecvLength > PqRecvPointer){/* still some unread data, left-justify it in the buffer */// 还有一些未读数据,将其在缓冲区中左对齐memmove(PqRecvBuffer, PqRecvBuffer + PqRecvPointer,PqRecvLength - PqRecvPointer);PqRecvLength -= PqRecvPointer;PqRecvPointer = 0;}elsePqRecvLength = PqRecvPointer = 0;}/* Ensure that we're in blocking mode */// 确保我们处于阻塞模式socket_set_nonblocking(false);/* Can fill buffer from PqRecvLength and upwards */// 可以从 PqRecvLength 及以上填充缓冲区for (;;){int			r;errno = 0;r = secure_read(MyProcPort, PqRecvBuffer + PqRecvLength,PQ_RECV_BUFFER_SIZE - PqRecvLength);if (r < 0){if (errno == EINTR)continue;		/* Ok if interrupted *//** Careful: an ereport() that tries to write to the client would* cause recursion to here, leading to stack overflow and core* dump!  This message must go *only* to the postmaster log.* 小心:尝试写入客户端的 ereport() 会导致递归到此处,从而导致堆栈溢出和核心转储* 此消息必须 *仅* 发送到邮件管理员日志** If errno is zero, assume it's EOF and let the caller complain.* 如果 errno 为零,则假设它是 EOF 并让调用者抱怨*/if (errno != 0)ereport(COMMERROR,(errcode_for_socket_access(),errmsg("could not receive data from client: %m")));return EOF;}if (r == 0){/** EOF detected.  We used to write a log message here, but it's* better to expect the ultimate caller to do that.* 检测到 EOF* 我们曾经在这里编写日志消息,但最好期望最终调用者这样做*/return EOF;}/* r contains number of bytes read, so just incr length */// r 包含读取的字节数,因此只需增加长度PqRecvLength += r;return 0;}
}

解释一下:

  • PqRecvLength = 21 这个其实是两条消息 如下:
	/* construct the Close message */ // 0 - 15if (pqPutMsgStart(command, conn) < 0 ||pqPutc(type, conn) < 0 ||pqPuts(target, conn) < 0 ||pqPutMsgEnd(conn) < 0)goto sendFailed;/* construct the Sync message */ // 16 - 20if (conn->pipelineStatus == PQ_PIPELINE_OFF){if (pqPutMsgStart(PqMsg_Sync, conn) < 0 ||pqPutMsgEnd(conn) < 0)goto sendFailed;}

第二步:

/* --------------------------------*		pq_getbyte	- get a single byte from connection, or return EOF* --------------------------------*/
int
pq_getbyte(void)
{Assert(PqCommReadingMsg);while (PqRecvPointer >= PqRecvLength){if (pq_recvbuf())		/* If nothing in buffer, then recv some */return EOF;			/* Failed to recv data */}return (unsigned char) PqRecvBuffer[PqRecvPointer++];
}

解释一下:

  • 此时 PqRecvPointer = 0,自然返回的是 'C'

第三步:

.../** In protocol version 3, all frontend messages have a length word next* after the type code; we can read the message contents independently of* the type.* * 在协议版本 3 中,所有前端消息在类型代码之后都有一个长度字* 我们可以独立于类型来读取消息内容*/if (pq_getmessage(inBuf, maxmsglen))return EOF;				/* suitable message already logged */RESUME_CANCEL_INTERRUPTS();
...
/* --------------------------------*		pq_getmessage	- get a message with length word from connection*		pq_getmessage - 从连接获取长度字的消息**		The return value is placed in an expansible StringInfo, which has*		already been initialized by the caller.*		Only the message body is placed in the StringInfo; the length word*		is removed.  Also, s->cursor is initialized to zero for convenience*		in scanning the message contents.*		返回值放置在可扩展的 StringInfo 中,该 StringInfo 已由调用者初始化*		StringInfo中只放置消息体; 长度词被删除*		另外,为了方便扫描消息内容,s->cursor 被初始化为零**		maxlen is the upper limit on the length of the*		message we are willing to accept.  We abort the connection (by*		returning EOF) if client tries to send more than that.*		maxlen 是我们愿意接受的消息长度的上限*		如果客户端尝试发送更多内容,我们将中止连接(通过返回 EOF)**		returns 0 if OK, EOF if trouble* --------------------------------*/
int
pq_getmessage(StringInfo s, int maxlen)
{int32		len;Assert(PqCommReadingMsg);resetStringInfo(s);/* Read message length word */if (pq_getbytes((char *) &len, 4) == EOF){ereport(COMMERROR,(errcode(ERRCODE_PROTOCOL_VIOLATION),errmsg("unexpected EOF within message length word")));return EOF;}len = pg_ntoh32(len);if (len < 4 || len > maxlen){ereport(COMMERROR,(errcode(ERRCODE_PROTOCOL_VIOLATION),errmsg("invalid message length")));return EOF;}len -= 4;					/* discount length itself */if (len > 0){/** Allocate space for message.  If we run out of room (ridiculously* large message), we will elog(ERROR), but we want to discard the* message body so as not to lose communication sync.*/PG_TRY();{enlargeStringInfo(s, len);}PG_CATCH();{if (pq_discardbytes(len) == EOF)ereport(COMMERROR,(errcode(ERRCODE_PROTOCOL_VIOLATION),errmsg("incomplete message from client")));/* we discarded the rest of the message so we're back in sync. */PqCommReadingMsg = false;PG_RE_THROW();}PG_END_TRY();/* And grab the message */if (pq_getbytes(s->data, len) == EOF){ereport(COMMERROR,(errcode(ERRCODE_PROTOCOL_VIOLATION),errmsg("incomplete message from client")));return EOF;}s->len = len;/* Place a trailing null per StringInfo convention */s->data[len] = '\0';}/* finished reading the message. */PqCommReadingMsg = false;return 0;
}

解释一下:

  • 读取长度 4字节:memcpy(s, PqRecvBuffer + PqRecvPointer, amount);此时 PqRecvPointer = 1
  • pg_ntoh32 网络序–>主机序 len = 15
  • len -= 4; /* discount length itself */
  • 读取长度 11 字节:memcpy(s, PqRecvBuffer + PqRecvPointer, amount);此时 PqRecvPointer = 5
  • 此时读取的内容:0x1e54c88 "Sp23456789",也就是 input_message

接下来ReadCommand的结果,如下:

firstchar = ReadCommand(&input_message); // C

最后一步,如下:

在这里插入图片描述

OK,至此 服务端解析完毕!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/823767.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Java开发指南 | 第十一篇】Java运算符

读者可订阅专栏&#xff1a;Java开发指南 |【CSDN秋说】 文章目录 算术运算符关系运算符位运算符逻辑运算符赋值运算符条件运算符&#xff08;?:&#xff09;instanceof 运算符Java运算符优先级 Java运算符包括&#xff1a;算术运算符、关系运算符、位运算符、逻辑运算符、赋值…

Postman之版本信息查看

Postman之版本信息查看 一、为何需要查看版本信息&#xff1f;二、查看Postman的版本信息的步骤 一、为何需要查看版本信息&#xff1f; 不同的版本之间可能存在功能和界面的差异。 二、查看Postman的版本信息的步骤 1、打开 Postman 2、打开设置项 点击页面右上角的 “Set…

Java中的容器

Java中的容器主要包括以下几类&#xff1a; Collection接口及其子接口/实现类&#xff1a; List 接口及其实现类&#xff1a; ArrayList&#xff1a;基于动态数组实现的列表&#xff0c;支持随机访问&#xff0c;插入和删除元素可能导致大量元素移动。LinkedList&#xff1a;基…

只用键盘的技巧

技巧一&#xff1a;将常用软件固定在任务栏使用winnum/winT(shift)打开 技巧二&#xff1a;winX快捷键&#xff08;显示快捷键的快捷键&#xff09; ALT F4    关闭当前应用程序 技巧三&#xff1a;使用好Chrome快键键 ctrl h&#xff1b;历史纪录。 ctrl shift esc&am…

充电桩--OCPP 充电通讯协议介绍

一、OCPP协议介绍 OCPP的全称是 Open Charge Point Protocol 即开放充电点协议&#xff0c; 它是免费开放的协议&#xff0c;该协议由位于荷兰的组织 OCA&#xff08;开放充电联盟&#xff09;进行制定。Open Charge Point Protocol (OCPP) 开放充电点协议用于充电站(CS)和任何…

大模型开发轻松入门——(1)从搭建自己的环境开始

pip install openai import openai import osfrom dotenv import load_dotenv, find_dotenv _ load_dotenv(find_dotenv())openai.api_key os.getenv(OPENAI_API_KEY)

mac IDEA激活 亲测有效

1、官网下载mac版本IDEA并安装 2、打开激活页面 3、下载脚本文件 链接: https://pan.baidu.com/s/1I2BqdfxSJv1A96422rflnA?pwdm494 提取码: m494 4、命令行到该界面&#xff0c;执行 sudo bash idea.sh 可能出现的问题&#xff1a; 查看sh文件&#xff0c;targetFilePath…

vue快速入门(十六)事件修饰符

注释很详细&#xff0c;直接上代码 上一篇 新增内容 事件修饰符之阻止冒泡事件修饰符之阻止默认行为 源码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdev…

重定向原理和缓冲区

文章目录 重定向缓冲区 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站。 重定向 内核中为了管理被打开的文件&#xff0c;一定会存在描述一…

ESP8266闪存文件系统(SPIFFS)

开发环境&#xff1a; 1、安装ESP8266的开发环境&#xff0c;如Arduino IDE。 2、下载并安装ESP8266的相关开发库和工具。 我们使用的是Arduino IDE。 基本介绍&#xff1a; 每一个ESP8266都配有一个闪存&#xff0c;这个闪存很像是一个小硬盘&#xff0c;我们上传的文件就被…

MCU最小系统晶振模块设计

单片机的心脏&#xff1a;晶振 晶振模块 单片机有两个心脏&#xff0c;一个是8M的心脏&#xff0c;一个是32.768的心脏 8M的精度较低&#xff0c;所以需要外接一个32.768khz 为什么是8MHZ呢&#xff0c;因为内部自带的 频率越高&#xff0c;精度越高&#xff0c;功耗越大&am…

[Java EE] 多线程(二): 线程的创建与常用方法(下)

2.3 启动一个线程–>start() 之前我们已经看到了如何通过重写run()方法来创建一个线程对象,但是线程对象被创建出来并不意味着线程就开始运行了. 覆写run方法是给线程提供了所要做的事情的指令清单创建线程对象就是把干活的人叫了过来.而调用start方法,就是喊一声"行…

贪心法确定补水地点

贪心算法是一个简单有趣的算法&#xff0c;它总是做出当前看来最好的选择&#xff0c;每次的局部最优选择最终可以产生整体最优解或整体最优解的近似。本文将介绍如何用贪心法解决补水问题。 1. 补水问题 2升水可以走 k k k英里&#xff0c;水站可以把水补满为2升&#xff0c…

【五十四】【算法分析与设计】Manacher算法,Manacher算法作用,Manacher算法流程,Manacher算法证明,Manacher算法代码

Manacher算法作用 1. 给你一个字符串str&#xff0c;要你求这个字符串的最长回文子串的长度&#xff0c;或者求这个字符串的最长回文子串在str中开始位置的下标。 2. 暴力解法&#xff0c;中心扩散算法&#xff0c;时间复杂度O(N*2)。Manacher算法可以用O(N)解决这个问题。…

鸿蒙相关岗位需求突增!你具体知道都有哪些岗位吗?

1 月 18 日&#xff0c;鸿蒙 Next 预览版面向开发者正式开放申请。至此&#xff0c;鸿蒙原生应用版图已成型&#xff0c;这个中国自主研发的操作系统&#xff0c;正式走上了独立之路。 随后迎来了不少互联网公司与华为鸿蒙原生应用达成了合作&#xff0c;像我们常见的阿里、京…

【Android GUI】FramebufferNativeWindow与Surface

文章目录 显示整体体系FramebufferNativeWindowFramebufferNativeWindow构造函数 dequeueBufferSurface总结参考 显示整体体系 native window为OpenGL与本地窗口系统之间搭建了桥梁。 这个窗口系统中&#xff0c;有两类本地窗口&#xff0c;nativewindow1是能直接显示在屏幕的…

上班族副业指南:六种实用赚钱途径

在现今竞争激烈的社会中&#xff0c;许多上班族都选择开辟副业来增加收入与实现自我价值。副业不仅能够增强经济安全感&#xff0c;还能满足个人兴趣爱好&#xff0c;并为未来铺设更坚实的财务基石。本文将为你揭示六种适合上班族的副业选择&#xff0c;帮助你找到最适合自己的…

JookDB下载安装使用

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

CUDA优化入门

本文记录了我的cuda学习经历&#xff0c;和大多数人一样&#xff0c;通过优化矩阵乘法的过程来了解一些基本的概念。仓库链接&#xff1a; GiteeGithub Refences NVIIDA Fermi Architecture WhitepaperCUDA C Programming GuideCUDA C Best Practices Guide 其中Fermi架构是…

LINUX中使用cron定时任务被隐藏,咋回事?

一、问题现象 线上服务器运行过程中&#xff0c;进程有莫名进程被启动&#xff0c;怀疑是有定时任务自动启动&#xff0c;当你用常规方法去查看&#xff0c;比如使用crontab去查看定时器任务&#xff0c;提示no crontab for root 或者使用cat到/var/spool/cron目录下去查看定时…