Skip to content

Commit edf7ff4

Browse files
chore: automated publish
1 parent c491736 commit edf7ff4

6 files changed

Lines changed: 181 additions & 0 deletions

File tree

public/blog/2025-04-15/index.pdf

125 KB
Binary file not shown.

public/blog/2025-04-15/index.tex

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
\title{"PostgreSQL 连接协议解析与自定义客户端开发"}
2+
\author{"黄京"}
3+
\date{"Apr 15, 2025"}
4+
\maketitle
5+
在数据库系统的核心交互中,客户端与服务端的通信协议承载着所有数据交换的基石。理解 PostgreSQL 连接协议不仅能够帮助开发者深入掌握数据库工作原理,更为构建高性能客户端、实现协议级扩展提供了可能。本文将穿透 TCP 层的字节流,揭示协议消息的构造逻辑,并指导读者实现一个具备完整生命周期的自定义客户端。\par
6+
\chapter{PostgreSQL 连接协议基础}
7+
PostgreSQL 使用基于消息的通信模型,前端(客户端)与后端(服务端)通过 TCP/IP 建立连接后,以消息交换形式完成所有操作。协议当前主流版本为 3.0,对应协议号 \verb!196608!(\verb!0x00030000!)。每个消息由 1 字节消息类型标识符、4 字节消息长度(含自身)及消息体构成,所有整型字段均采用大端序(Big-Endian)编码。\par
8+
连接生命周期包含五个核心阶段:通过 Startup Message 建立初始握手;根据认证要求完成身份验证;传输查询指令;接收结果数据集;最终通过 Terminate 消息关闭连接。每个阶段的消息交换模式都有严格定义,例如在 SSL 协商阶段,客户端会先发送魔法值 \verb!80877103! 来检测服务端是否支持加密传输。\par
9+
\chapter{连接协议逐层解析}
10+
\section{认证流程的密码学实现}
11+
以当前推荐的 SCRAM-SHA-256 认证为例,其交互流程基于挑战-响应机制。服务端首先发送包含盐值 \verb!s!、迭代次数 \verb!i! 的 \verb!AuthenticationSASLContinue! 消息。客户端需计算:\par
12+
$$ \begin{aligned} \text{ClientKey} &= \text{HMAC(SHA256, SaltedPassword, ``Client Key'')} \\ \text{StoredKey} &= \text{SHA256(ClientKey)} \\ \text{ClientSignature} &= \text{HMAC(SHA256, StoredKey, AuthMessage)} \\ \text{ClientProof} &= \text{ClientKey} \oplus \text{ClientSignature} \end{aligned} $$\par
13+
其中 \verb!SaltedPassword! 通过 PBKDF2 函数生成。代码实现时需严格处理编码转换,例如将二进制哈希值转换为 Base64 字符串:\par
14+
\begin{lstlisting}[language=python]
15+
def generate_client_proof(password, salt, iterations):
16+
salted_password = pbkdf2_hmac('sha256', password.encode(), salt, iterations)
17+
client_key = hmac.digest(salted_password, b'Client Key', 'sha256')
18+
stored_key = hashlib.sha256(client_key).digest()
19+
auth_msg = f"n=user,r={nonce},r={server_nonce},s={salt},i={iterations},..."
20+
client_signature = hmac.digest(stored_key, auth_msg.encode(), 'sha256')
21+
client_proof = bytes(a ^ b for a, b in zip(client_key, client_signature))
22+
return base64.b64encode(client_proof).decode()
23+
\end{lstlisting}
24+
该代码片段展示了如何根据 RFC 5802 规范实现客户端证明计算,其中 \verb!pbkdf2_hmac! 函数负责生成盐值密码,异或运算实现证明的不可逆性。\par
25+
\section{扩展查询协议的消息流水线}
26+
相较于简单查询协议的单消息往返,扩展查询协议通过 \verb!Parse!、\verb!Bind!、\verb!Execute! 的流水线实现预处理语句复用。假设需要执行带参数的插入操作:\par
27+
\begin{itemize}
28+
\item \textbf{Parse 阶段}:发送语句名称与参数类型 OID\begin{lstlisting}[language=python]
29+
msg = b'P\x00\x00\x00\x27' # 'P' 为消息类型
30+
msg += b'\x00stmt1\x00INSERT INTO t VALUES($1)\x00'
31+
msg += b'\x00\x01\x00\x00\x23\x8c' # 参数数量 1,类型 OID 23 为整型
32+
\end{lstlisting}
33+
34+
\item \textbf{Bind 阶段}:绑定参数值与结果格式\begin{lstlisting}[language=python]
35+
msg = b'B\x00\x00\x00\x1a'
36+
msg += b'\x00portal1\x00stmt1\x00\x01\x00\x01\x00\x00\x00\x04\x00\x00\x00\x0a'
37+
\end{lstlisting}
38+
其中 \verb!\x00\x00\x00\x0a! 表示整型参数值为 10,采用二进制格式传输。
39+
\item \textbf{Execute 阶段}:触发查询并指定返回行数限制
40+
\end{itemize}
41+
这种分阶段设计使得高频查询可以避免重复解析 SQL,提升执行效率。开发客户端时需要维护语句名称到预备语句的映射关系。\par
42+
\chapter{自定义客户端开发实战}
43+
\section{网络层核心实现}
44+
建立 TCP 连接后,客户端首先发送 Startup Message。以下代码展示如何构造协议版本与参数:\par
45+
\begin{lstlisting}[language=python]
46+
def build_startup_message(user, database):
47+
params = {
48+
'user': user,
49+
'database': database,
50+
'client_encoding': 'UTF8'
51+
}
52+
body = b'\x00\x03\x00\x00' # 协议版本 3.0
53+
for k, v in params.items():
54+
body += k.encode() + b'\x00' + v.encode() + b'\x00'
55+
body += b'\x00'
56+
length = len(body) + 4
57+
return struct.pack('!I', length) + body
58+
\end{lstlisting}
59+
此处 \verb!struct.pack('!I', length)! 使用大端序打包 4 字节长度值,\verb!!! 表示网络字节序。参数列表以 \verb!key\0value\0! 形式拼接,最后以双 \verb!\0! 结束。\par
60+
\section{结果集解析策略}
61+
当收到 \verb!RowDescription! 消息(类型 \verb!'T'!)时,客户端需要解析字段元数据:\par
62+
\begin{lstlisting}[language=python]
63+
def parse_row_desc(data):
64+
fields = []
65+
pos = 0
66+
num_fields = struct.unpack('!H', data[pos:pos+2])[0]
67+
pos += 2
68+
for _ in range(num_fields):
69+
name = _read_cstr(data, pos)
70+
pos += len(name) + 1
71+
table_oid, col_attnum, type_oid, typmod, fmt_code = struct.unpack('!IHIHh', data[pos:pos+17])
72+
pos += 17
73+
fields.append(Field(name.decode(), type_oid, fmt_code))
74+
return fields
75+
\end{lstlisting}
76+
每个字段描述包含名称、类型 OID 及格式代码(0 表示文本,1 表示二进制)。后续的 \verb!DataRow! 消息将按此结构返回数据,客户端需根据类型 OID 调用对应的解析器,例如将 \verb!BYTEA! 类型(OID 17)的十六进制编码 \verb!\x48656c6c6f! 转换为二进制数据 \verb!b'Hello'!。\par
77+
\chapter{高级优化与协议扩展}
78+
对于批量数据导入场景,\verb!COPY! 协议的性能远超常规插入。客户端在发送 \verb!COPY FROM STDIN! 命令后,进入特殊数据传输模式:\par
79+
\begin{lstlisting}[language=python]
80+
conn.send(b'C\x00\x00\x00\x0fCOPY t FROM STDIN\x00') # 发送 CopyIn 请求
81+
conn.send(b'd 数据行 1\nd 数据行 2\n') # 发送数据块
82+
conn.send(b'\.\x00') # 发送结束标记
83+
\end{lstlisting}
84+
该协议避免了 SQL 解析开销,实测中可实现 10 倍以上的吞吐量提升。开发者还可通过预留消息类型(112-127)实现私有协议扩展,例如添加心跳检测或自定义压缩算法。\par
85+
深入 PostgreSQL 协议层开发自定义客户端,不仅需要精确处理字节流与状态机转换,更要理解数据库核心工作机制。本文展示的实现方案为开发者提供了可扩展的框架基础,读者可在此基础上探索异步 IO 优化、连接池管理等进阶主题。随着 QUIC 等新型传输协议的发展,未来数据库连接协议或将迎来更深层次的变革。\par

public/blog/2025-04-15/sha256

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
94aa69e0229e8f599614f35d5133959fd3252a9114cced0ab62cf321c90d6c13

public/blog/2025-04-16/index.pdf

125 KB
Binary file not shown.

public/blog/2025-04-16/index.tex

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
\title{"6502 编程探秘"}
2+
\author{"黄京"}
3+
\date{"Apr 16, 2025"}
4+
\maketitle
5+
\chapter{导言}
6+
在 Apple II 与 NES 等经典设备的黄金年代,程序员们用 1.79MHz 的 6502 处理器创造了无数奇迹。当现代开发者习惯于 GB 级内存时,这些先驱者却在 64KB 的「画布」上绘制出了精妙绝伦的代码画卷。本文将揭示如何通过精准的内存操控,让每个字节都发挥出最大效能。\par
7+
\chapter{一、6502 内存架构速览}
8+
6502 的寻址空间如同精密钟表,每个区域都有独特的设计哲学。零页(\${}0000-\${}00FF)的访问周期比普通内存少 1 个时钟周期,这看似微小的差异在循环中会产生惊人的累积效应。例如 \verb!LDA $00! 仅需 3 个周期,而 \verb!LDA $0100! 则需要 4 个周期。\par
9+
栈空间的 256 字节限制催生了独特的编程范式。当处理器执行 \verb!JSR! 指令时,返回地址被压入栈顶,但若在中断服务程序中过度使用栈空间,可能引发指针回绕灾难——这是许多早期游戏出现随机崩溃的元凶之一。\par
10+
\chapter{二、零页攻防战}
11+
零页是 6502 编程的兵家必争之地。优秀开发者会为高频变量保留零页地址:\par
12+
\begin{lstlisting}[language=asm]
13+
player_x = $10 ; 零页地址分配
14+
bullet_cnt = $20
15+
\end{lstlisting}
16+
通过零页偏移可模拟额外寄存器。考虑以下伪寄存器扩展技巧:\par
17+
\begin{lstlisting}[language=asm]
18+
MACRO LOAD_ZP_INDEX idx
19+
LDA $F0, X ; 当 F0 是零页基址时
20+
ENDMACRO
21+
\end{lstlisting}
22+
动态重映射技术更将零页效用发挥到极致。在 NES 的《超级马里奥兄弟》中,通过 \verb!MMC3! 芯片在飞行关卡时切换内存 bank,实现了零页空间的动态扩展。\par
23+
\chapter{三、内存拓扑设计}
24+
数据段规划需要遵循热力学第二定律——高频访问数据应靠近处理器。将精灵坐标放在 \${}0200-\${}02FF 区域,而背景音乐数据置于 \${}C000 区域,这种冷热分离策略可减少跨页访问。页面边界惩罚的数学表达式为:\par
25+
$$ 周期惩罚 = \begin{cases} 0 & \text{当 } \text{addr}_{\text{新}}\ \&\ {\tt 0xFF00} = \text{addr}_{\text{旧}}\ \&\ {\tt 0xFF00} \\ 1 & \text{其他情况} \end{cases} $$\par
26+
代码段优化则充满几何美感。将关键循环体置于内存中部的 \${}8000 地址,可使相对跳转指令 \verb!BCC! 的覆盖范围最大化。一个经典的页面对齐案例:\par
27+
\begin{lstlisting}[language=asm]
28+
ORG $8100 ; 确保子程序起始于页面边界
29+
draw_sprite:
30+
; 高频调用代码
31+
\end{lstlisting}
32+
\chapter{四、动态内存管理}
33+
在 64KB 世界中,静态分配是首选策略。《魂斗罗》的关卡加载器在切换场景时执行批量释放:\par
34+
\begin{lstlisting}[language=c]
35+
// 伪代码示意
36+
void load_stage(uint8_t stage) {
37+
release_all_bullets();
38+
dealloc(prev_stage_data);
39+
alloc(stage_data[stage]);
40+
}
41+
\end{lstlisting}
42+
固定大小内存池是粒子系统的救星。以下 256 字节管理器的核心逻辑:\par
43+
\begin{lstlisting}[language=asm]
44+
mem_pool_init:
45+
LDA #<pool_start
46+
STA free_ptr
47+
LDA #>pool_start
48+
STA free_ptr+1 ; 初始化空闲指针
49+
50+
alloc_block:
51+
LDY #0
52+
LDA (free_ptr), Y ; 读取下一空闲块地址
53+
STA temp_ptr
54+
INY
55+
LDA (free_ptr), Y
56+
STA temp_ptr+1
57+
; 更新空闲指针 ...
58+
\end{lstlisting}
59+
\chapter{五、硬件协同优化}
60+
DMA 时序是艺术与科学的结晶。在 NES 的垂直消隐期执行 \verb!PPUADDR! 设置,可实现无闪烁的画面更新。音频双缓冲的实现关键:\par
61+
\begin{lstlisting}[language=asm]
62+
LDA #<buffer1
63+
STA APU_ADDR
64+
LDA #>buffer1
65+
STA APU_ADDR ; 填充后台缓冲
66+
; 等待 VSYNC
67+
LDA active_buffer
68+
EOR #1
69+
STA active_buffer ; 切换缓冲
70+
\end{lstlisting}
71+
内存镜像区域的写重定向技术,使得 Commodore 64 能在 \${}D000-\${}DFFF 区域通过 \verb!$0001! 寄存器的第 0-2 位选择 I/O 设备,这种设计大幅减少了地址解码电路的复杂度。\par
72+
\chapter{六、调试与逆向}
73+
自制内存监视器是每个 6502 程序员的成人礼。以下断点处理代码可在触发时保存状态:\par
74+
\begin{lstlisting}[language=asm]
75+
break_handler:
76+
PHA
77+
TXA
78+
PHA
79+
TYA
80+
PHA ; 保存寄存器
81+
LDA #<dump_area
82+
STA $FB
83+
LDA #>dump_area
84+
STA $FC ; 设置存储地址
85+
LDY #0
86+
dump_loop:
87+
LDA ($FD), Y ; $FD 存储监视地址
88+
STA ($FB), Y
89+
INY
90+
CPY #16
91+
BNE dump_loop
92+
\end{lstlisting}
93+
《超级马里奥兄弟》的对象池复用机制,将敌人结构体大小压缩到 16 字节,通过状态字段的位掩码实现多态行为,这种设计使得同屏 5 个敌人仅消耗 80 字节内存。\par
94+
6502 的内存管理艺术在今天仍闪耀着智慧光芒。从物联网设备到航天器控制系统,这些诞生于 8 位时代的优化思想仍在继续传承。当你下次面对现代系统的海量内存时,不妨设想:若将其视为 64KB 的珍宝,是否能用更优雅的方式解决问题?\par

public/blog/2025-04-16/sha256

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
20a8ed72638031af0d66b34b41edf95c83b80fbeae3635c5b11ef308cedbcba7

0 commit comments

Comments
 (0)