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

news2024/11/26 2:51:59

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

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字节 S
		pqPuts(target, conn) < 0 || // strlen("p2") + 1 = 3
		pqPutMsgEnd(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;
        }

        /* ---------------------------------------------------------------- */
        // prepare

        if (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_prepared

        if (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);
        }

        /* ---------------------------------------------------------------- */
        // deallocate

        result = 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: 0

exec prepare p23456789 send成功
exec prepare p23456789 send成功 n_rows: 1 ntups: 1

close 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 / Backend
	int			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);
	else
		fprintf(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);
			else
				fprintf(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);
			else
				fprintf(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);
	else
		fprintf(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 = 15

2024-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;
	else
		endPos = 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);
		else
			pqTraceOutputNoTypeByteMessage(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;
		}
		else
			PqRecvLength = 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 - 15
	if (pqPutMsgStart(command, conn) < 0 ||
		pqPutc(type, conn) < 0 ||
		pqPuts(target, conn) < 0 ||
		pqPutMsgEnd(conn) < 0)
		goto sendFailed;

	/* construct the Sync message */ // 16 - 20
	if (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.coloradmin.cn/o/1604509.html

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

相关文章

09-ESP32-IDF中的日志系统

ESP32-IDF中的日志系统 在ESP32-IDF中&#xff0c;日志系统是一个重要的组成部分&#xff0c;它可以帮助开发者调试代码和理解系统的运行状态。 1. 日志系统的作用 日志系统在ESP32-IDF中扮演着非常重要的角色。它可以记录系统的运行信息&#xff0c;包括系统启动、运行、错…

OSPF笔记+大实验

OSPF综合大实验---实验报告 配置IP地址 R1&#xff1a; [R1]int g0/0/0 [R1-GigabitEthernet0/0/0]ip add 172.16.33.1 24 [R1-GigabitEthernet0/0/0]int l0 [R1-LoopBack0]ip add 172.168.34.1 24 R2: [R2]int g0/0/0 [R2-GigabitEthernet0/0/0]ip add 172.16.33.2 24…

电脑怎么创建加密磁盘?方法很简单!

在电脑中创建加密磁盘&#xff0c;可以有效地保护电脑数据&#xff0c;避免数据泄露。那么&#xff0c;电脑怎么创建加密磁盘呢&#xff1f;下面我们就一起来了解一下吧。 密盘创建方法 创建密盘建议使用超级秘密磁盘3000&#xff0c;软件界面简约&#xff0c;操作简单&#x…

Linux上比top更直观和美观的监控软件htop

2024年4月18日&#xff0c;周四上午 htop 是一个交互式的系统监控工具&#xff0c;类似于 Linux 系统中的 top 命令&#xff0c;但提供了更加友好的界面和功能。它允许用户以直观的方式查看系统的进程、CPU、内存、交换空间以及各种系统指标的使用情况。 一些 htop 的特点包括…

【hive】lateral view侧视图

文档地址&#xff1a;https://cwiki.apache.org/confluence/display/Hive/LanguageManualLateralView 1.介绍2.语法3.code demo1&#xff09;单重侧视图2&#xff09;多重侧视图3&#xff09;tips&#xff1a;lateral view outer 1.介绍 lateral view也叫侧视图&#xff0c;属…

矿用连续式负压自动排渣放水器——YC型

从今天起&#xff0c;努力去做一个可爱的人&#xff0c;不羡慕谁&#xff0c;也不埋怨谁&#xff0c;在自己的道路上&#xff0c;欣赏自己的风景&#xff0c;遇见自己的幸福。 矿用连续式负压自动排渣放水器——YC型 【1-5-9】产品介绍 连续式式负压自动排渣放水器采用双罐体结…

项目管理的主要内容包括哪些?

项目管理作为现代企业管理的重要组成部分&#xff0c;其核心在于通过科学的方法和手段&#xff0c;对项目的启动、规划、执行、监控和收尾等全过程进行有效管理&#xff0c;以实现项目的预定目标。随着市场竞争的加剧和企业经营环境的不断变化&#xff0c;项目管理已成为企业提…

探索存内计算—基于 SRAM 的存内计算与基于 MRAM 的存算一体的探究

CSDN存内社区招募&#xff1a;https://bbs.csdn.net/forums/computinginmemory 首个存内计算开发者社区&#xff0c;现0门槛新人加入&#xff0c;发文享积分兑超值礼品&#xff1b; 存内计算先锋/大使在社区投稿&#xff0c;可获得双倍积分&#xff0c;以及社区精选流量推送&a…

项目7-音乐播放器4+喜欢/收藏音乐

1.喜欢/收藏音乐模块设计 1.1 请求响应模块设计 请求&#xff1a; { post, /lovemusic/likeMusic data: id//音乐id } 响应&#xff1a; { "status": 0, "message": "点赞音乐成功", "da…

瑞芯微RK3328(ROC-RK3328-PC)buildroot 开发QT的hello world

第一部分&#xff1a;编译rk3328 sdk 0. 环境 - EC-R3328PC&#xff08;ROC-RK3328-PC&#xff09; - ubuntu18&#xff08;100GB&#xff09; 1. 安装依赖 sudo apt-get updatesudo apt-get install repo git-core gitk git-gui gcc-arm-linux-gnueabihf u-boot-tools devi…

汽车充电桩充电效率的四大决定因素

随着电动汽车的快速普及&#xff0c;交流充电桩作为电动汽车的充电基础设施&#xff0c;其充电效率受到了广泛的关注。接下来&#xff0c;我们将深入探讨交流充电桩的充电效率&#xff0c;包括充电效率的定义、影响因素以及提升方法。 充电效率的定义 交流充电桩的充电效率指的…

Transformer with Transfer CNN for Remote-Sensing-Image Object Detection

遥感图像&#xff08;RSI&#xff09;中的目标检测始终是遥感界一个充满活力的研究主题。 最近&#xff0c;基于深度卷积神经网络 (CNN) 的方法&#xff0c;包括基于区域 CNN 和基于 You-Only-Look-Once 的方法&#xff0c;已成为 RSI 目标检测的事实上的标准。 CNN 擅长局部特…

基于springboot实现编程训练系统设计项目【项目源码+论文说明】计算机毕业设计

基于springboot实现编程训练系统设计演示 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;管理信息系统的实施在技术上已逐步成熟。本文介绍了编程训练系统的开发全过程。通过分析编程训练系统管理的不足&#xff0c;创建了一个计算机管理编程训练系统的方案。文…

【软件工程与实践】(第四版)第5章习题答案详解

第5章 一、填空题二、选择题三、简答题四、实践题 一、填空题 (1) 软件工程中的总体设计又称为 系统设计。 (2) 在软件工程中的设计阶段&#xff0c;需要充分体现软件工程的“模块化”“抽象”“信息隐藏”等基本原则。 (3) 系统详细设计阶段最后产生的文档是 软件设计文档。…

创建SpringBoot控制台程序并打包运行(Windows)

1. 新建一个Springboot项目Study (PS&#xff1a; 这个不需要演示了吧?) 注意pom.xml&#xff0c;是spring-boot-starter&#xff0c;不是spring-boot-web-starter <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot…

[spring] Spring Boot REST API - CRUD 操作

Spring Boot REST API - CRUD 操作 这里主要提一下 spring boot 创建 rest api&#xff0c;并对其进行 CRUD 操作 jackson & gson 目前浏览器和服务端主流的交互方式是使用 JSON(JavaScript Object Notation)&#xff0c;但是 JSON 没有办法直接和 Java 的 POJO 创建对应…

IO引脚服用和映射

什么是端口复用 STM32F4 有很多的内置外设&#xff0c;这些外设的外部引脚都是与 GPIO 复用的。也就是说&#xff0c;一个 GPIO如果可以复用为内置外设的功能引脚&#xff0c;那么当这个 GPIO 作为内置外设使用的时候&#xff0c;就叫做复用。在芯片数据手册或STM32F4XX参考手…

光纤和铜缆:了解不同通信媒介的优势

在现代通信技术中&#xff0c;光纤和铜缆是两种主要的数据传输媒介。它们各有优势和局限性&#xff0c;但都在我们的日常生活中扮演着不可或缺的角色。 左侧&#xff08;网络跳线&#xff09;右侧&#xff08;光纤跳线&#xff09; 一、光纤的原理与优势 ADOP光纤跳线 光纤通信…

Day38: 动态规划 LeedCode:509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯 蓝桥杯: 更小的数

对于动态规划问题&#xff0c;拆解为如下五步曲 确定dp数组&#xff08;dp table&#xff09;以及下标的含义确定递推公式dp数组如何初始化(容易数组溢出)确定遍历顺序举例推导dp数组 509. 斐波那契数 斐波那契数 &#xff08;通常用 F(n) 表示&#xff09;形成的序列称为 斐…

STM32 HAL库F103系列之ADC实验(1)

ADC工作原理&#xff1a; 1、输入通道&#xff1a; 2、转换序列&#xff1a; A/D转换被组织为两组&#xff1a;规则组&#xff08;常规转换组&#xff09;和注入组&#xff08;注入转换组&#xff09; 规则组最多可以有16个转换&#xff0c;注入组最多有4个转换 规则组和注入…