超硬核区块链共识算法仿真:教你C语言彻底学会PBFT算法仿真 (第一部分) 1

小引子:

 

PBFT算法仿真:从Win11到共识算法核心(上篇)

前言:告别“前端内卷”,拥抱“硬核共识”!

老铁们,如果你点开了这篇文章,那恭喜你,你绝对是想在技术领域“搞点大事情”的狠人!尤其是那些正在从“前端码农”转型“硬核嵌入式工程师”的兄弟们,你们的眼光太毒辣了!前端再怎么“卷”,也终究是“上层建筑”,而我们今天要“掀底裤”的,是支撑区块链、分布式系统乃至未来物联网核心的共识算法——真正的“地基”!

你是不是也曾被各种“区块链原理”、“共识机制”的概念搞得云里雾里?是不是觉得这些东西太抽象,离我们C语言程序员太远?是不是市面上那些课程,要么只讲概念,要么只给个Python玩具代码,根本无法满足我们这种“不把原理抠到汇编层面不罢休”的硬核需求?

别急!今天,我就带你用最熟悉的C语言,在你的Windows 11 VS Code环境下,亲手搭建一个PBFT(Practical Byzantine Fault Tolerance,实用拜占庭容错)算法仿真器!不光PBFT,我们还要一步步深入,摸透Credit-PBFT、Tree-PBFT、优化PBFT,甚至Raft和DPOS这些主流共识算法的“底细”!

这不只是一个技术教程,更是一场思维的洗礼!它将带你:

  • 彻底搞懂PBFT的每一个细节、每一个状态、每一条消息!

  • 掌握C语言在分布式系统仿真中的“骚操作”!

  • 理解共识算法如何解决分布式系统中的“拜占庭将军问题”!

  • 为你的嵌入式开发之路,打下最坚实的“分布式系统”底层基础!

这篇是上篇,咱们先从共识算法的背景、PBFT的核心概念和消息机制开刀,并搭建一个最基础的C语言仿真框架,让你亲手跑起来!

第一章:共识算法的“前世今生”——为什么我们需要它?

在正式“开撸”PBFT之前,我们得先搞清楚一个问题:共识算法到底是个啥?为什么它在分布式系统里这么重要?

1.1 什么是分布式系统?

简单来说,分布式系统就是多台计算机协同工作,共同完成一个任务的系统。比如,你访问一个大型网站,背后可能不是一台服务器在为你服务,而是成百上千台服务器共同协作。

分布式系统的特点:

  • 并发性: 多个组件同时运行。

  • 透明性: 用户感知不到底层是多台机器。

  • 可扩展性: 容易增加新的机器来提升性能。

  • 容错性: 即使部分机器宕机,系统也能继续运行。

  • 异构性: 不同的机器可能使用不同的硬件和操作系统。

1.2 分布式系统中的“老大难”问题:一致性

分布式系统最大的挑战之一就是一致性。想象一下,多台服务器同时处理你的订单,如果它们之间的数据不一致,那你的订单可能就会出错。

一致性: 指的是分布式系统中所有节点对某个数据或状态的看法保持一致。

为了实现一致性,就引出了各种共识算法

1.3 拜占庭将军问题:共识算法的“终极挑战”

在分布式系统中,最著名的难题就是拜占庭将军问题 (Byzantine Generals Problem)

问题描述:

想象一下,一群拜占庭将军围攻一座城池。他们需要达成一个共识:是进攻还是撤退

  • 将军们通过信使传递消息。

  • 有些将军可能是叛徒(拜占庭节点),他们可能会发送虚假消息,或者不发送消息。

  • 目标: 忠诚的将军们必须达成一致的行动(要么都进攻,要么都撤退),并且这个行动必须是忠诚将军们决定的。

难点:

  • 消息传递不可靠: 信使可能被截杀、消息可能被篡改。

  • 节点可能作恶: 叛徒将军会故意发送错误信息,试图破坏共识。

  • 无法确定消息来源的真实性: 你不知道收到的消息是不是叛徒发出的。

结论: 在存在叛徒(拜占庭故障)的情况下,如何让忠诚的将军们达成一致的行动,这就是拜占庭将军问题。

面试考点:

  • “什么是拜占庭将军问题?它在分布式系统中的意义是什么?” 这是共识算法的哲学起点,必考!

  • “拜占庭容错 (BFT) 和崩溃容错 (CFT) 有什么区别?”

    • CFT: 只能容忍节点崩溃(宕机,不响应)。

    • BFT: 还能容忍节点作恶(发送错误消息、不按协议执行)。BFT的容错能力更强。

1.4 CAP 定理:分布式系统设计的“三难选择”

在分布式系统设计中,CAP定理是一个绕不开的话题。

CAP定理: 在一个分布式系统中,你最多只能同时满足以下三点中的两点:

  • 一致性 (Consistency): 所有节点在同一时间看到的数据是一致的。

  • 可用性 (Availability): 系统总是能够响应请求,即使部分节点出现故障。

  • 分区容错性 (Partition Tolerance): 即使网络出现分区(节点之间无法通信),系统也能继续运行。

面试考点:

  • “什么是CAP定理?请解释C、A、P的含义。”

  • “在实际系统中,如何进行CAP的选择和权衡?”

    • CP系统: 追求强一致性和分区容错性,但在网络分区时可能牺牲可用性(例如,为了保证一致性,部分服务可能暂停)。PBFT就属于CP系统。

    • AP系统: 追求高可用性和分区容错性,但在网络分区时可能牺牲一致性(允许数据暂时不一致,待网络恢复后同步)。例如,DNS、一些NoSQL数据库。

    • CA系统: 追求强一致性和高可用性,但无法容忍网络分区(一旦分区就可能无法工作)。这在分布式系统中几乎不可能实现。

第二章:PBFT算法核心原理——拜占庭容错的“实用方案”

PBFT算法是第一个被广泛研究和实现的实用拜占庭容错算法。它在保证强一致性的同时,兼顾了性能。

2.1 PBFT算法概述

  • 全称: Practical Byzantine Fault Tolerance (实用拜占庭容错)。

  • 提出时间: 1999年由Miguel Castro和Barbara Liskov提出。

  • 特点:

    • 强一致性: 保证所有正常节点对同一操作达成一致。

    • 活性: 只要有足够多的正常节点,系统就能持续运行。

    • 高效: 相较于其他拜占庭容错算法,PBFT在正常情况下(无故障)的性能开销较小。

    • 状态机复制: PBFT通过复制状态机(State Machine Replication)来保证一致性。所有节点都维护一个相同的状态机,并按照相同的顺序执行客户端请求。

  • 容错能力: 能够容忍最多 (n-1)/3 个拜占庭节点,其中 n 是总节点数。也就是说,如果系统有 n 个节点,那么最多可以有 f 个恶意节点,且 n >= 3f + 1

面试考点:

  • “PBFT算法的容错能力是多少?为什么是 3f+1?” 这是一个高频考点,需要理解其数学原理。

2.2 PBFT算法角色

PBFT算法中,节点分为两种角色:

  • 主节点 (Primary): 负责接收客户端请求,并协调其他节点达成共识。在每个视图(View)中,只有一个主节点。主节点是轮换的,以防止单点故障。

  • 副本节点 (Backup/Replica): 接收主节点的消息,并与其他副本节点协作,共同验证和执行请求。

2.3 PBFT算法核心流程:三阶段提交

PBFT算法的核心是一个三阶段提交协议,用于在主节点和副本节点之间达成共识。

前提:

  • 视图 (View): PBFT算法在一个称为“视图”的阶段中运行。每个视图有一个唯一的主节点。如果主节点出现故障,系统会通过视图切换 (View Change) 机制选举新的主节点。

  • 序列号 (Sequence Number): 每个客户端请求都有一个唯一的序列号,确保请求按顺序执行。

  • 消息摘要 (Digest): 对消息内容进行哈希运算得到的一个固定长度的字符串,用于验证消息的完整性。

三阶段提交流程:

  1. 预准备阶段 (Pre-prepare Phase):

    • 发起者: 主节点。

    • 消息: <PRE-PREPARE, v, n, d, m>

      • v:当前视图编号。

      • n:请求的序列号。

      • d:客户端请求 m 的消息摘要。

      • m:客户端请求内容。

    • 流程:

      1. 主节点接收到客户端请求 m

      2. 主节点为请求分配一个序列号 n,计算消息摘要 d

      3. 主节点构建 PRE-PREPARE 消息,并广播给所有副本节点。

    • 副本节点处理: 副本节点接收到 PRE-PREPARE 消息后,会验证其合法性(如视图编号、序列号是否在有效范围内、消息摘要是否正确等)。如果验证通过,则进入准备阶段。

  2. 准备阶段 (Prepare Phase):

    • 发起者: 所有副本节点(包括主节点自身,但主节点无需发送)。

    • 消息: <PREPARE, v, n, d, i>

      • v:当前视图编号。

      • n:请求的序列号。

      • d:客户端请求 m 的消息摘要。

      • i:发送此消息的节点ID。

    • 流程:

      1. 每个副本节点收到有效的 PRE-PREPARE 消息后,会向所有其他节点广播 PREPARE 消息。

      2. 每个节点(包括主节点)会收集 PREPARE 消息。当一个节点收集到 2f 个来自不同节点PREPARE 消息(包括它自己发送的,如果它是副本节点)和主节点的 PRE-PREPARE 消息,并且这些消息的 vnd 都一致时,就认为自己进入了“准备好”状态。

    • 目的: 确保所有正常节点都同意请求的顺序(序列号)和内容。

  3. 提交阶段 (Commit Phase):

    • 发起者: 所有节点(包括主节点)。

    • 消息: <COMMIT, v, n, d, i>

      • v:当前视图编号。

      • n:请求的序列号。

      • d:客户端请求 m 的消息摘要。

      • i:发送此消息的节点ID。

    • 流程:

      1. 当一个节点进入“准备好”状态后,它会向所有其他节点广播 COMMIT 消息。

      2. 每个节点会收集 COMMIT 消息。当一个节点收集到 2f + 1 个来自不同节点COMMIT 消息(包括它自己发送的)并且这些消息的 vnd 都一致时,就认为自己进入了“提交好”状态。

    • 目的: 确保所有正常节点都同意执行该请求。一旦进入“提交好”状态,节点就可以安全地执行请求并将结果返回给客户端。

  4. 回复阶段 (Reply Phase):

    • 发起者: 所有进入“提交好”状态的节点。

    • 消息: <REPLY, v, n, r, i>

      • v:当前视图编号。

      • n:请求的序列号。

      • r:执行请求后的结果。

      • i:发送此消息的节点ID。

    • 流程:

      1. 每个进入“提交好”状态的节点执行客户端请求。

      2. 将执行结果 r 返回给客户端。

    • 客户端处理: 客户端会等待接收 f+1 个来自不同节点的相同 REPLY 消息,以确认请求已被多数正常节点执行并达成一致。

思维导图:PBFT三阶段提交流程

graph TD
    A[客户端发送请求] --> B{主节点接收请求}
    B --> C[主节点: PRE-PREPARE]
    C --广播--> D{副本节点接收PRE-PREPARE}
    D --> E[副本节点: PREPARE]
    E --广播--> F{所有节点收集PREPARE消息}
    F --收集到2f个PREPARE--> G[所有节点: COMMIT]
    G --广播--> H{所有节点收集COMMIT消息}
    H --收集到2f+1个COMMIT--> I[所有节点: 执行请求]
    I --> J[所有节点: REPLY]
    J --发送给客户端--> K{客户端收集f+1个REPLY}
    K --> L[客户端: 确认请求完成]

面试考点:

  • “详细描述PBFT的三阶段提交协议,包括每个阶段的消息类型和验证条件。” 这是PBFT的核心,必须烂熟于心。

  • “为什么PBFT需要三阶段而不是两阶段提交?” 为了解决拜占庭故障,确保即使有恶意节点,正常节点也能对消息的顺序达成一致。两阶段提交无法解决主节点作恶或消息丢失的问题。

  • “PBFT算法的活性和安全性是如何保证的?”

    • 安全性: 通过三阶段提交和 2f+1 多数确认,确保所有正常节点对请求的执行顺序和结果达成一致。

    • 活性: 通过视图切换机制,解决主节点故障或作恶导致的系统停滞。

2.4 视图切换 (View Change):解决主节点故障

如果主节点出现故障(宕机或作恶),导致无法正常推进共识,系统会触发视图切换。

  • 触发条件:

    • 副本节点长时间未收到主节点的 PRE-PREPARE 消息。

    • 副本节点收到无效的 PRE-PREPARE 消息。

    • 客户端长时间未收到 REPLY 消息。

  • 流程:

    1. 当一个副本节点认为主节点有问题时,它会启动一个计时器。

    2. 计时器超时后,该副本节点会向所有其他节点广播 VIEW-CHANGE 消息。

    3. 当一个节点收集到 2f+1 个来自不同节点的 VIEW-CHANGE 消息时,它就认为需要进行视图切换。

    4. 系统会根据预设的规则(例如,新主节点 = (新视图编号) % N),选举一个新的主节点。

    5. 新的主节点会收集所有 VIEW-CHANGE 消息,并发送 NEW-VIEW 消息给所有节点,其中包含新的视图编号和一些未完成请求的信息。

    6. 所有节点进入新的视图,并继续处理未完成的请求。

面试考点:

  • “PBFT的视图切换机制是如何工作的?为什么需要视图切换?”

  • “PBFT的性能瓶颈在哪里?” O(N^2) 的消息复杂度(N是节点数),导致节点数增加时性能急剧下降。

第三章:C语言PBFT仿真环境搭建——从零开始构建你的共识网络

现在,理论知识已经“武装到牙齿”了,是时候撸起袖子,用C语言把PBFT跑起来了!

为了在Windows 11的VS Code环境下方便地进行C语言网络编程,我强烈建议你使用WSL2 (Windows Subsystem for Linux 2)。它能让你在Windows下拥有一个完整的Linux环境,享受Linux下C语言开发和网络编程的便利,同时又能使用VS Code的强大功能。

如果你还没有WSL2,可以参考微软官方文档进行安装:安装 WSL | Microsoft Learn

安装好WSL2后,在WSL2中安装GCC、make等开发工具,并在VS Code中安装“Remote - WSL”扩展,你就可以在VS Code里直接开发Linux C程序了!

3.1 仿真架构设计:多进程/多线程模拟节点

PBFT算法涉及多个节点之间的通信。在C语言中,我们可以通过以下方式模拟这些节点:

  • 多进程 (Multi-process): 每个PBFT节点作为一个独立的进程。进程之间通过套接字 (Sockets) 进行网络通信。这是最接近真实分布式系统的方式,但进程间通信和管理相对复杂。

  • 多线程 (Multi-thread): 所有PBFT节点运行在同一个进程中,每个节点作为一个独立的线程。线程之间可以通过共享内存、消息队列等方式通信,也可以通过套接字进行通信。这种方式实现起来相对简单,但无法模拟真正的网络分区和独立进程故障。

  • 单进程模拟 (Single-process Simulation): 在一个进程中,通过事件循环和消息队列来模拟多个节点的消息处理。这种方式最简单,但与真实分布式系统的行为偏差较大。

考虑到PBFT的分布式特性,以及我们希望模拟真实的网络通信,我建议采用多进程的方式来模拟PBFT节点。每个节点都是一个独立的C程序,它们通过TCP套接字相互连接和通信。

仿真环境概览:

graph LR
    subgraph Windows 11 (VS Code)
        A[VS Code IDE] --> B[WSL2 Ubuntu Terminal]
    end

    subgraph WSL2 Ubuntu Environment
        C[Node 0 Process (Primary)]
        D[Node 1 Process (Backup)]
        E[Node 2 Process (Backup)]
        F[Node 3 Process (Backup)]
        G[Client Process]

        C --TCP Socket--> D
        C --TCP Socket--> E
        C --TCP Socket--> F
        D --TCP Socket--> E
        D --TCP Socket--> F
        E --TCP Socket--> F

        G --TCP Socket--> C
        G --TCP Socket--> D
        G --TCP Socket--> E
        G --TCP Socket--> F
    end

3.2 消息结构定义:PBFT通信的“语言”

PBFT算法中,节点之间传递的消息是核心。我们需要用C语言定义这些消息的结构体。

消息类型 (Opcode):

// 定义PBFT消息类型
typedef enum {
    MSG_TYPE_CLIENT_REQUEST, // 客户端请求
    MSG_TYPE_PRE_PREPARE,    // 预准备消息
    MSG_TYPE_PREPARE,        // 准备消息
    MSG_TYPE_COMMIT,         // 提交消息
    MSG_TYPE_REPLY,          // 回复消息
    MSG_TYPE_VIEW_CHANGE,    // 视图切换消息
    MSG_TYPE_NEW_VIEW        // 新视图消息
} MessageType;

消息头 (Common Header):

所有PBFT消息都需要包含一些公共信息,例如视图编号、序列号、发送者ID等。

#include <stdint.h> // For uint32_t, uint8_t

// 定义消息摘要长度 (例如,SHA256摘要是32字节)
#define DIGEST_SIZE 32
// 定义最大消息内容长度
#define MAX_MSG_CONTENT_SIZE 256

// 消息公共头部
typedef struct {
    MessageType type;      // 消息类型
    uint32_t view_id;      // 视图编号
    uint32_t sequence_num; // 序列号
    uint8_t sender_id;     // 发送者节点ID
} MessageHeader;

具体消息结构体:

我们将根据PBFT的协议定义,为每种消息类型创建对应的结构体。

  1. 客户端请求消息 (ClientRequest):

    • 客户端发送给主节点的请求。

    // 客户端请求消息
    typedef struct {
        MessageHeader header; // 通用消息头 (类型为MSG_TYPE_CLIENT_REQUEST)
        uint8_t client_id;    // 客户端ID
        uint32_t timestamp;   // 时间戳 (用于防止重放攻击)
        char operation[MAX_MSG_CONTENT_SIZE]; // 客户端请求的操作内容
        uint8_t digest[DIGEST_SIZE]; // 请求内容的摘要
    } ClientRequest;
    
    
  2. 预准备消息 (PrePrepareMsg):

    • 主节点发送给副本节点。

    // 预准备消息 (主节点广播给副本节点)
    typedef struct {
        MessageHeader header; // 通用消息头 (类型为MSG_TYPE_PRE_PREPARE)
        uint8_t client_id;    // 客户端ID (来自原始请求)
        uint32_t timestamp;   // 时间戳 (来自原始请求)
        char operation[MAX_MSG_CONTENT_SIZE]; // 客户端请求的操作内容
        // 注意:PBFT协议中,PRE-PREPARE消息通常包含原始请求的完整内容或其摘要
        // 这里为了简化,直接包含原始请求的operation和digest
        uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
    } PrePrepareMsg;
    
    
  3. 准备消息 (PrepareMsg):

    • 副本节点(包括主节点自身)相互发送。

    // 准备消息 (所有节点相互发送)
    typedef struct {
        MessageHeader header; // 通用消息头 (类型为MSG_TYPE_PREPARE)
        uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
    } PrepareMsg;
    
    
  4. 提交消息 (CommitMsg):

    • 所有节点相互发送。

    // 提交消息 (所有节点相互发送)
    typedef struct {
        MessageHeader header; // 通用消息头 (类型为MSG_TYPE_COMMIT)
        uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
    } CommitMsg;
    
    
  5. 回复消息 (ReplyMsg):

    • 所有节点发送给客户端。

    // 回复消息 (节点发送给客户端)
    typedef struct {
        MessageHeader header; // 通用消息头 (类型为MSG_TYPE_REPLY)
        uint8_t client_id;    // 客户端ID
        uint32_t timestamp;   // 时间戳 (来自原始请求)
        char result[MAX_MSG_CONTENT_SIZE]; // 执行请求后的结果
    } ReplyMsg;
    
    

统一消息结构体:

为了方便网络传输和解析,我们可以使用一个共用体(union)将所有消息类型封装到一个统一的结构体中。

// 统一消息结构体 (使用union节省内存,一次只存储一种消息)
typedef struct {
    MessageHeader common_header; // 所有消息共有的头部信息
    union {
        ClientRequest client_request;
        PrePrepareMsg pre_prepare;
        PrepareMsg prepare;
        CommitMsg commit;
        ReplyMsg reply;
        // 视图切换和新视图消息暂时省略,后续添加
    } payload; // 消息的具体内容
} PBFTMessage;

面试考点:

  • “请设计PBFT算法中的消息结构体。” 考察你对协议细节的理解和C语言结构体的使用。

  • “为什么需要消息摘要?它是如何保证消息完整性的?” 防止消息被篡改。

  • “视图编号和序列号的作用?” 视图编号用于标识主节点,序列号用于保证请求顺序。

3.3 网络通信基础:TCP套接字编程

在C语言中进行网络通信,最常用的是套接字 (Socket) 编程。PBFT算法需要可靠的连接来传输消息,因此我们选择TCP (Transmission Control Protocol) 套接字。

TCP套接字特点:

  • 面向连接: 在数据传输前需要建立连接。

  • 可靠传输: 保证数据不丢失、不重复、按序到达。

  • 流量控制和拥塞控制: 确保发送方不会压垮接收方,也不会导致网络拥塞。

核心API (Linux/WSL2环境):

  • socket() 创建套接字。

  • bind() 将套接字绑定到本地IP地址和端口。

  • listen() 服务器端套接字进入监听状态,等待客户端连接。

  • accept() 服务器端接受客户端连接,返回一个新的套接字文件描述符用于与客户端通信。

  • connect() 客户端套接字连接到服务器。

  • send() / write() 发送数据。

  • recv() / read() 接收数据。

  • close() 关闭套接字。

C语言代码:基础TCP通信(服务端与客户端)

我们先搭建一个最简单的TCP服务器和客户端,来验证网络通信的基础能力。

文件:common.h (公共头文件,定义消息结构和一些常量)

#ifndef COMMON_H
#define COMMON_H

#include <stdint.h> // For uint32_t, uint8_t
#include <stdbool.h> // For bool

// 定义PBFT系统中的节点总数 (N) 和可容忍的拜占庭节点数 (f)
// N = 3f + 1
#define NUM_NODES 4 // 例如,4个节点,f = (4-1)/3 = 1,可容忍1个拜占庭节点
#define FAULTY_NODES ((NUM_NODES - 1) / 3) // f

// 定义消息摘要长度 (例如,SHA256摘要是32字节)
#define DIGEST_SIZE 32
// 定义最大消息内容长度
#define MAX_MSG_CONTENT_SIZE 256
// 定义每个节点的监听端口
#define BASE_NODE_PORT 8000 // Node 0 监听 8000, Node 1 监听 8001, ...
// 定义客户端监听端口 (用于接收Reply)
#define CLIENT_PORT 9000

// 定义PBFT消息类型
typedef enum {
    MSG_TYPE_CLIENT_REQUEST, // 客户端请求
    MSG_TYPE_PRE_PREPARE,    // 预准备消息
    MSG_TYPE_PREPARE,        // 准备消息
    MSG_TYPE_COMMIT,         // 提交消息
    MSG_TYPE_REPLY,          // 回复消息
    MSG_TYPE_VIEW_CHANGE,    // 视图切换消息
    MSG_TYPE_NEW_VIEW,       // 新视图消息
    MSG_TYPE_HEARTBEAT       // 心跳消息 (用于检测节点存活)
} MessageType;

// 消息公共头部
typedef struct {
    MessageType type;      // 消息类型
    uint32_t view_id;      // 视图编号
    uint32_t sequence_num; // 序列号
    uint8_t sender_id;     // 发送者节点ID
} MessageHeader;

// 客户端请求消息 (由客户端发送给主节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_CLIENT_REQUEST)
    uint8_t client_id;    // 客户端ID
    uint32_t timestamp;   // 时间戳 (用于防止重放攻击)
    char operation[MAX_MSG_CONTENT_SIZE]; // 客户端请求的操作内容 (例如 "transfer 100 to Bob")
    uint8_t digest[DIGEST_SIZE]; // 请求内容的摘要 (SHA256(operation))
} ClientRequest;

// 预准备消息 (由主节点广播给副本节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_PRE_PREPARE)
    uint8_t client_id;    // 原始客户端ID
    uint32_t timestamp;   // 原始客户端请求时间戳
    char operation[MAX_MSG_CONTENT_SIZE]; // 原始客户端请求的操作内容
    uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
    // 实际PBFT中,这里通常只包含原始请求的摘要,而不是整个操作内容
    // 为了简化仿真,我们直接包含操作内容,方便调试
} PrePrepareMsg;

// 准备消息 (由所有节点相互发送)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_PREPARE)
    uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
} PrepareMsg;

// 提交消息 (由所有节点相互发送)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_COMMIT)
    uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
} CommitMsg;

// 回复消息 (由节点发送给客户端)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_REPLY)
    uint8_t client_id;    // 客户端ID
    uint32_t timestamp;   // 时间戳 (来自原始请求)
    char result[MAX_MSG_CONTENT_SIZE]; // 执行请求后的结果 (例如 "OK" 或 "Error")
} ReplyMsg;

// 统一消息结构体 (使用union节省内存,一次只存储一种消息)
// 方便网络传输和处理
typedef struct {
    MessageHeader common_header; // 所有消息共有的头部信息
    union {
        ClientRequest client_request;
        PrePrepareMsg pre_prepare;
        PrepareMsg prepare;
        CommitMsg commit;
        ReplyMsg reply;
        // 视图切换和新视图消息暂时省略,后续添加
    } payload; // 消息的具体内容
} PBFTMessage;

// --- 辅助函数声明 ---
// 计算消息摘要 (这里简化为简单的哈希,实际应使用SHA256等)
void calculate_digest(const char* data, size_t len, uint8_t* digest_out);

// 打印消息内容 (用于调试)
void print_message(const PBFTMessage* msg);

#endif // COMMON_H

文件:utils.c (辅助函数实现)

#include "common.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h> // For exit

// 简化版摘要计算 (实际应使用SHA256等加密哈希函数)
void calculate_digest(const char* data, size_t len, uint8_t* digest_out) {
    // 这里只是一个非常简单的模拟摘要,实际应用中绝对不能这样用!
    // 实际应使用如 OpenSSL 库中的 SHA256 函数
    uint32_t hash = 0;
    for (size_t i = 0; i < len; ++i) {
        hash = (hash * 31) + data[i]; // 简单的乘法哈希
    }
    // 将32位哈希值填充到DIGEST_SIZE字节的数组中
    memset(digest_out, 0, DIGEST_SIZE); // 先清零
    memcpy(digest_out, &hash, sizeof(uint32_t)); // 复制哈希值
}

// 打印消息内容 (用于调试)
void print_message(const PBFTMessage* msg) {
    if (!msg) {
        printf("空消息指针。\n");
        return;
    }

    printf("--- 消息详情 ---\n");
    printf("  类型: ");
    switch (msg->common_header.type) {
        case MSG_TYPE_CLIENT_REQUEST: printf("CLIENT_REQUEST\n"); break;
        case MSG_TYPE_PRE_PREPARE:    printf("PRE_PREPARE\n"); break;
        case MSG_TYPE_PREPARE:        printf("PREPARE\n"); break;
        case MSG_TYPE_COMMIT:         printf("COMMIT\n"); break;
        case MSG_TYPE_REPLY:          printf("REPLY\n"); break;
        case MSG_TYPE_VIEW_CHANGE:    printf("VIEW_CHANGE\n"); break;
        case MSG_TYPE_NEW_VIEW:       printf("NEW_VIEW\n"); break;
        case MSG_TYPE_HEARTBEAT:      printf("HEARTBEAT\n"); break;
        default:                      printf("未知类型\n"); break;
    }
    printf("  视图ID: %u\n", msg->common_header.view_id);
    printf("  序列号: %u\n", msg->common_header.sequence_num);
    printf("  发送者ID: %u\n", msg->common_header.sender_id);

    // 根据消息类型打印具体内容
    switch (msg->common_header.type) {
        case MSG_TYPE_CLIENT_REQUEST:
            printf("  客户端ID: %u\n", msg->payload.client_request.client_id);
            printf("  时间戳: %u\n", msg->payload.client_request.timestamp);
            printf("  操作: %s\n", msg->payload.client_request.operation);
            printf("  摘要: ");
            for (int i = 0; i < DIGEST_SIZE; ++i) {
                printf("%02x", msg->payload.client_request.digest[i]);
            }
            printf("\n");
            break;
        case MSG_TYPE_PRE_PREPARE:
            printf("  原始客户端ID: %u\n", msg->payload.pre_prepare.client_id);
            printf("  原始时间戳: %u\n", msg->payload.pre_prepare.timestamp);
            printf("  操作: %s\n", msg->payload.pre_prepare.operation);
            printf("  请求摘要: ");
            for (int i = 0; i < DIGEST_SIZE; ++i) {
                printf("%02x", msg->payload.pre_prepare.request_digest[i]);
            }
            printf("\n");
            break;
        case MSG_TYPE_PREPARE:
        case MSG_TYPE_COMMIT:
            printf("  请求摘要: ");
            for (int i = 0; i < DIGEST_SIZE; ++i) {
                printf("%02x", msg->payload.prepare.request_digest[i]); // prepare和commit结构体中摘要字段相同
            }
            printf("\n");
            break;
        case MSG_TYPE_REPLY:
            printf("  客户端ID: %u\n", msg->payload.reply.client_id);
            printf("  时间戳: %u\n", msg->payload.reply.timestamp);
            printf("  结果: %s\n", msg->payload.reply.result);
            break;
        default:
            // 其他消息类型暂不详细打印
            break;
    }
    printf("------------------\n");
}

文件:node.c (PBFT节点代码,包含服务端和客户端逻辑)

#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>     // For close(), read(), write()
#include <arpa/inet.h>  // For inet_ntop(), inet_pton()
#include <sys/socket.h> // For socket(), bind(), listen(), accept(), connect(), send(), recv()
#include <pthread.h>    // For pthread_create(), pthread_join()
#include <errno.h>      // For errno

// --- 节点状态信息 (简化) ---
typedef struct {
    uint8_t node_id;         // 当前节点的ID
    uint32_t current_view;   // 当前视图编号
    uint32_t last_seq_num;   // 上一个已提交的序列号
    bool is_primary;         // 是否是主节点
    // 实际PBFT节点状态会复杂得多,包括消息日志、高水位、低水位等
} NodeState;

NodeState g_node_state; // 全局节点状态

// --- 线程参数结构体 ---
typedef struct {
    int client_sock_fd; // 客户端连接的套接字
    uint8_t remote_node_id; // 远程节点的ID
} ThreadArgs;

// --- 辅助函数声明 ---
void *handle_incoming_connection(void *arg); // 处理传入连接的线程函数
void connect_to_peers(uint8_t my_id); // 连接到其他节点
void send_message_to_node(uint8_t target_node_id, const PBFTMessage* msg); // 发送消息给指定节点

// --- 全局套接字数组,用于存储与其他节点的连接 ---
// conn_sockets[i] 存储与节点i的连接
// 如果 conn_sockets[i] > 0,表示已连接
int conn_sockets[NUM_NODES];

// --- 互斥锁,保护全局变量和共享资源 ---
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;

/**
 * @brief 节点启动函数
 * @param node_id 当前节点的ID
 */
void start_node(uint8_t node_id) {
    g_node_state.node_id = node_id;
    g_node_state.current_view = 0; // 初始视图为0
    g_node_state.last_seq_num = 0;
    g_node_state.is_primary = (node_id == (g_node_state.current_view % NUM_NODES)); // 初始主节点是Node 0

    printf("[Node %u] 启动中... 端口: %d\n", g_node_state.node_id, BASE_NODE_PORT + g_node_state.node_id);
    if (g_node_state.is_primary) {
        printf("[Node %u] 当前是主节点。\n", g_node_state.node_id);
    } else {
        printf("[Node %u] 当前是副本节点。\n", g_node_state.node_id);
    }

    // 初始化连接套接字数组
    for (int i = 0; i < NUM_NODES; ++i) {
        conn_sockets[i] = -1; // -1 表示未连接
    }

    // 1. 启动监听线程 (作为服务器端,接收来自其他节点的连接)
    int listen_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock_fd == -1) {
        perror("[Node] 创建监听套接字失败");
        exit(EXIT_FAILURE);
    }

    // 允许端口重用 (解决 Address already in use 问题)
    int optval = 1;
    if (setsockopt(listen_sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
        perror("[Node] setsockopt SO_REUSEADDR 失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(BASE_NODE_PORT + g_node_state.node_id);
    server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用IP地址

    if (bind(listen_sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("[Node] 绑定监听地址失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(listen_sock_fd, NUM_NODES) == -1) { // 监听队列长度为节点总数
        perror("[Node] 监听失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    printf("[Node %u] 正在监听连接...\n", g_node_state.node_id);

    // 启动一个线程来处理传入连接
    pthread_t listen_thread;
    if (pthread_create(&listen_thread, NULL, handle_incoming_connection, (void*)(long)listen_sock_fd) != 0) {
        perror("[Node] 创建监听线程失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    // 2. 连接到其他节点 (作为客户端,主动建立连接)
    // 延迟一小段时间,确保其他节点的服务端已启动并监听
    sleep(1);
    connect_to_peers(g_node_state.node_id);

    // 3. 进入主循环,处理PBFT逻辑 (这里只是一个占位符)
    printf("[Node %u] 进入主循环,等待消息...\n", g_node_state.node_id);
    while (1) {
        // 在这里实现PBFT算法的逻辑
        // 例如:主节点等待客户端请求,副本节点等待PRE-PREPARE消息
        // 接收到消息后,进行验证,状态转换,广播下一阶段消息等
        // 暂时只是一个空循环,等待消息处理线程工作
        sleep(10); // 避免CPU空转
    }

    // 等待监听线程结束 (实际不会结束,除非程序退出)
    pthread_join(listen_thread, NULL);
    close(listen_sock_fd);
}

/**
 * @brief 处理传入连接的线程函数 (每个传入连接会创建一个新线程来处理)
 * @param arg 监听套接字文件描述符
 */
void *handle_incoming_connection(void *arg) {
    int listen_sock_fd = (int)(long)arg;
    struct sockaddr_in remote_addr;
    socklen_t addr_len = sizeof(remote_addr);
    char remote_ip[INET_ADDRSTRLEN];

    while (1) {
        int client_sock_fd = accept(listen_sock_fd, (struct sockaddr*)&remote_addr, &addr_len);
        if (client_sock_fd == -1) {
            if (errno == EINTR) { // 被信号中断,继续
                continue;
            }
            perror("[Node] 接受连接失败");
            break;
        }

        inet_ntop(AF_INET, &(remote_addr.sin_addr), remote_ip, INET_ADDRSTRLEN);
        printf("[Node %u] 接收到来自 %s:%d 的连接。\n", g_node_state.node_id, remote_ip, ntohs(remote_addr.sin_port));

        // 识别连接的远程节点ID (通过端口号推断,非常简化)
        // 实际PBFT中,节点会通过握手协议交换ID
        uint8_t remote_node_id = (ntohs(remote_addr.sin_port) - BASE_NODE_PORT);
        if (remote_node_id >= NUM_NODES) { // 可能是客户端或其他未知连接
            printf("[Node %u] 警告: 无法识别远程节点ID %u,可能不是PBFT节点连接。\n", g_node_state.node_id, remote_node_id);
            // 对于客户端连接,我们可能需要单独处理
            // 这里为了简化,我们假设所有连接都是PBFT节点之间的连接
            // 客户端连接将在client.c中单独处理
            close(client_sock_fd);
            continue;
        }

        pthread_mutex_lock(&g_mutex);
        conn_sockets[remote_node_id] = client_sock_fd; // 存储连接套接字
        pthread_mutex_unlock(&g_mutex);

        printf("[Node %u] 已与 Node %u 建立连接 (作为服务器端)。\n", g_node_state.node_id, remote_node_id);

        // 为每个连接启动一个独立的线程来接收消息
        pthread_t recv_thread;
        ThreadArgs* args = (ThreadArgs*)malloc(sizeof(ThreadArgs));
        if (args == NULL) {
            perror("分配线程参数失败");
            close(client_sock_fd);
            continue;
        }
        args->client_sock_fd = client_sock_fd;
        args->remote_node_id = remote_node_id;

        if (pthread_create(&recv_thread, NULL, (void *(*)(void *))recv_message_thread, (void*)args) != 0) {
            perror("[Node] 创建接收消息线程失败");
            free(args);
            close(client_sock_fd);
            continue;
        }
        pthread_detach(recv_thread); // 将线程设置为分离状态,资源在结束时自动回收
    }
    return NULL;
}

/**
 * @brief 连接到其他节点的函数 (作为客户端)
 * @param my_id 当前节点的ID
 */
void connect_to_peers(uint8_t my_id) {
    for (uint8_t i = 0; i < NUM_NODES; ++i) {
        if (i == my_id) {
            continue; // 不连接自己
        }

        // 避免重复连接:如果已经有连接,则跳过
        pthread_mutex_lock(&g_mutex);
        if (conn_sockets[i] != -1) {
            pthread_mutex_unlock(&g_mutex);
            printf("[Node %u] 已与 Node %u 连接,跳过。\n", my_id, i);
            continue;
        }
        pthread_mutex_unlock(&g_mutex);

        int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (sock_fd == -1) {
            perror("[Node] 创建连接套接字失败");
            continue;
        }

        struct sockaddr_in peer_addr;
        memset(&peer_addr, 0, sizeof(peer_addr));
        peer_addr.sin_family = AF_INET;
        peer_addr.sin_port = htons(BASE_NODE_PORT + i);
        // 连接到本地IP (127.0.0.1),因为所有节点都在同一台机器上模拟
        if (inet_pton(AF_INET, "127.0.0.1", &peer_addr.sin_addr) <= 0) {
            perror("[Node] 无效的对端IP地址");
            close(sock_fd);
            continue;
        }

        printf("[Node %u] 尝试连接到 Node %u (端口: %d)...\n", my_id, i, BASE_NODE_PORT + i);
        // 尝试连接,如果失败(如对端未启动),等待并重试
        int retry_count = 0;
        const int MAX_CONNECT_RETRIES = 5;
        while (connect(sock_fd, (struct sockaddr*)&peer_addr, sizeof(peer_addr)) == -1) {
            if (errno == ECONNREFUSED && retry_count < MAX_CONNECT_RETRIES) {
                printf("[Node %u] 连接 Node %u 失败 (连接被拒绝),重试中... (%d/%d)\n", my_id, i, retry_count + 1, MAX_CONNECT_RETRIES);
                sleep(1); // 等待1秒后重试
                retry_count++;
            } else {
                perror("[Node] 连接对端失败");
                close(sock_fd);
                sock_fd = -1; // 标记连接失败
                break;
            }
        }

        if (sock_fd != -1) {
            pthread_mutex_lock(&g_mutex);
            conn_sockets[i] = sock_fd; // 存储连接套接字
            pthread_mutex_unlock(&g_mutex);
            printf("[Node %u] 已与 Node %u 建立连接 (作为客户端)。\n", my_id, i);
            // 为每个连接启动一个独立的线程来接收消息
            pthread_t recv_thread;
            ThreadArgs* args = (ThreadArgs*)malloc(sizeof(ThreadArgs));
            if (args == NULL) {
                perror("分配线程参数失败");
                close(sock_fd);
                continue;
            }
            args->client_sock_fd = sock_fd;
            args->remote_node_id = i;

            if (pthread_create(&recv_thread, NULL, (void *(*)(void *))recv_message_thread, (void*)args) != 0) {
                perror("[Node] 创建接收消息线程失败");
                free(args);
                close(sock_fd);
                continue;
            }
            pthread_detach(recv_thread); // 将线程设置为分离状态
        }
    }
}

/**
 * @brief 接收消息的线程函数
 * @param arg 线程参数 (包含套接字文件描述符和远程节点ID)
 */
void *recv_message_thread(void *arg) {
    ThreadArgs* args = (ThreadArgs*)arg;
    int sock_fd = args->client_sock_fd;
    uint8_t remote_node_id = args->remote_node_id;
    free(args); // 释放线程参数内存

    PBFTMessage msg_buffer;
    ssize_t bytes_received;

    printf("[Node %u] 接收线程启动,监听来自 Node %u 的消息...\n", g_node_state.node_id, remote_node_id);

    while (1) {
        // 接收消息头部
        bytes_received = recv(sock_fd, &msg_buffer.common_header, sizeof(MessageHeader), MSG_WAITALL);
        if (bytes_received <= 0) {
            if (bytes_received == 0) {
                printf("[Node %u] Node %u 断开连接。\n", g_node_state.node_id, remote_node_id);
            } else {
                perror("[Node] 接收消息头部失败");
            }
            break; // 连接断开或出错
        }

        // 根据消息类型接收剩余的payload
        size_t payload_size = 0;
        switch (msg_buffer.common_header.type) {
            case MSG_TYPE_CLIENT_REQUEST: payload_size = sizeof(ClientRequest) - sizeof(MessageHeader); break;
            case MSG_TYPE_PRE_PREPARE:    payload_size = sizeof(PrePrepareMsg) - sizeof(MessageHeader); break;
            case MSG_TYPE_PREPARE:        payload_size = sizeof(PrepareMsg) - sizeof(MessageHeader); break;
            case MSG_TYPE_COMMIT:         payload_size = sizeof(CommitMsg) - sizeof(MessageHeader); break;
            case MSG_TYPE_REPLY:          payload_size = sizeof(ReplyMsg) - sizeof(MessageHeader); break;
            // 其他消息类型待添加
            default:
                fprintf(stderr, "[Node %u] 接收到未知消息类型 %u,跳过payload接收。\n", g_node_state.node_id, msg_buffer.common_header.type);
                break;
        }

        if (payload_size > 0) {
            bytes_received = recv(sock_fd, &msg_buffer.payload, payload_size, MSG_WAITALL);
            if (bytes_received <= 0) {
                if (bytes_received == 0) {
                    printf("[Node %u] Node %u 断开连接 (接收payload时)。\n", g_node_state.node_id, remote_node_id);
                } else {
                    perror("[Node] 接收消息payload失败");
                }
                break;
            }
            if (bytes_received != payload_size) {
                fprintf(stderr, "[Node %u] 接收payload长度不匹配!期望 %zu, 实际 %zd。\n", g_node_state.node_id, payload_size, bytes_received);
                break;
            }
        }

        // 成功接收一条完整的消息
        printf("[Node %u] 收到来自 Node %u 的消息:\n", g_node_state.node_id, msg_buffer.common_header.sender_id);
        print_message(&msg_buffer);

        // --- 在这里添加PBFT消息处理逻辑 ---
        // 例如:
        // if (msg_buffer.common_header.type == MSG_TYPE_PRE_PREPARE) {
        //     // 验证消息,如果通过,广播PREPARE消息
        // }
        // else if (msg_buffer.common_header.type == MSG_TYPE_PREPARE) {
        //     // 收集PREPARE消息,检查是否达到2f个,如果达到,广播COMMIT消息
        // }
    }

    // 连接断开,清理资源
    pthread_mutex_lock(&g_mutex);
    if (conn_sockets[remote_node_id] == sock_fd) { // 确保是当前连接
        conn_sockets[remote_node_id] = -1;
    }
    pthread_mutex_unlock(&g_mutex);
    close(sock_fd);
    printf("[Node %u] 与 Node %u 的连接已关闭。\n", g_node_state.node_id, remote_node_id);
    return NULL;
}

/**
 * @brief 发送消息给指定节点
 * @param target_node_id 目标节点ID
 * @param msg 要发送的消息指针
 */
void send_message_to_node(uint8_t target_node_id, const PBFTMessage* msg) {
    pthread_mutex_lock(&g_mutex);
    int sock_fd = conn_sockets[target_node_id];
    pthread_mutex_unlock(&g_mutex);

    if (sock_fd == -1) {
        fprintf(stderr, "[Node %u] 错误: 无法发送消息给 Node %u,连接未建立或已断开。\n", g_node_state.node_id, target_node_id);
        return;
    }

    // 计算要发送的消息总长度
    size_t total_msg_len = sizeof(MessageHeader);
    switch (msg->common_header.type) {
        case MSG_TYPE_CLIENT_REQUEST: total_msg_len += sizeof(ClientRequest) - sizeof(MessageHeader); break;
        case MSG_TYPE_PRE_PREPARE:    total_msg_len += sizeof(PrePrepareMsg) - sizeof(MessageHeader); break;
        case MSG_TYPE_PREPARE:        total_msg_len += sizeof(PrepareMsg) - sizeof(MessageHeader); break;
        case MSG_TYPE_COMMIT:         total_msg_len += sizeof(CommitMsg) - sizeof(MessageHeader); break;
        case MSG_TYPE_REPLY:          total_msg_len += sizeof(ReplyMsg) - sizeof(MessageHeader); break;
        default:
            fprintf(stderr, "[Node %u] 错误: 尝试发送未知消息类型 %u。\n", g_node_state.node_id, msg->common_header.type);
            return;
    }

    // 发送消息
    ssize_t bytes_sent = send(sock_fd, msg, total_msg_len, 0);
    if (bytes_sent == -1) {
        perror("[Node] 发送消息失败");
    } else if (bytes_sent != total_msg_len) {
        fprintf(stderr, "[Node %u] 警告: 发送消息长度不匹配!期望 %zu, 实际 %zd。\n", g_node_state.node_id, total_msg_len, bytes_sent);
    } else {
        printf("[Node %u] 成功发送消息 (类型: %u, 序列号: %u) 给 Node %u。\n",
               g_node_state.node_id, msg->common_header.type, msg->common_header.sequence_num, target_node_id);
    }
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        fprintf(stderr, "用法: %s <node_id>\n", argv[0]);
        fprintf(stderr, "示例: %s 0 (启动Node 0)\n", argv[0]);
        return 1;
    }

    uint8_t node_id = (uint8_t)atoi(argv[1]);
    if (node_id >= NUM_NODES) {
        fprintf(stderr, "错误: 节点ID必须小于 %d。\n", NUM_NODES);
        return 1;
    }

    start_node(node_id);

    return 0;
}

文件:client.c (客户端代码)

#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <time.h> // For time()

// --- 客户端状态信息 (简化) ---
typedef struct {
    uint8_t client_id;
    // 实际客户端状态会包含已发送请求的记录,以及等待的回复等
} ClientState;

ClientState g_client_state;

// --- 辅助函数声明 ---
void send_client_request(const char* operation);
void *receive_reply_thread(void *arg); // 接收回复的线程函数

int main(int argc, char* argv[]) {
    if (argc != 2) {
        fprintf(stderr, "用法: %s <client_id>\n", argv[0]);
        fprintf(stderr, "示例: %s 100 (启动Client 100)\n", argv[0]);
        return 1;
    }

    g_client_state.client_id = (uint8_t)atoi(argv[1]);
    printf("[Client %u] 启动中...\n", g_client_state.client_id);

    // 1. 启动一个线程来接收来自节点的回复 (作为服务器端)
    int listen_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock_fd == -1) {
        perror("[Client] 创建监听套接字失败");
        exit(EXIT_FAILURE);
    }

    int optval = 1;
    if (setsockopt(listen_sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
        perror("[Client] setsockopt SO_REUSEADDR 失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in client_listen_addr;
    memset(&client_listen_addr, 0, sizeof(client_listen_addr));
    client_listen_addr.sin_family = AF_INET;
    client_listen_addr.sin_port = htons(CLIENT_PORT + g_client_state.client_id); // 每个客户端监听不同端口
    client_listen_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(listen_sock_fd, (struct sockaddr*)&client_listen_addr, sizeof(client_listen_addr)) == -1) {
        perror("[Client] 绑定监听地址失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(listen_sock_fd, NUM_NODES) == -1) { // 监听节点数量
        perror("[Client] 监听失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    printf("[Client %u] 正在监听回复 (端口: %d)...\n", g_client_state.client_id, CLIENT_PORT + g_client_state.client_id);

    pthread_t reply_recv_thread;
    if (pthread_create(&reply_recv_thread, NULL, receive_reply_thread, (void*)(long)listen_sock_fd) != 0) {
        perror("[Client] 创建回复接收线程失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }
    pthread_detach(reply_recv_thread); // 分离线程

    // 2. 发送客户端请求
    char operation_buffer[MAX_MSG_CONTENT_SIZE];
    while (1) {
        printf("\n[Client %u] 请输入要发送的操作 (例如: 'transfer 100 to Bob', 输入 'quit' 退出):\n", g_client_state.client_id);
        if (fgets(operation_buffer, sizeof(operation_buffer), stdin) == NULL) {
            continue;
        }
        operation_buffer[strcspn(operation_buffer, "\n")] = 0; // 移除换行符

        if (strcmp(operation_buffer, "quit") == 0) {
            printf("[Client %u] 客户端退出。\n", g_client_state.client_id);
            break;
        }

        send_client_request(operation_buffer);
        sleep(2); // 等待回复
    }

    close(listen_sock_fd);
    return 0;
}

/**
 * @brief 发送客户端请求给主节点 (简化:直接发送给Node 0)
 * @param operation 客户端操作内容
 */
void send_client_request(const char* operation) {
    PBFTMessage client_msg;
    memset(&client_msg, 0, sizeof(PBFTMessage));

    // 填充消息头部
    client_msg.common_header.type = MSG_TYPE_CLIENT_REQUEST;
    client_msg.common_header.view_id = 0; // 客户端不知道当前视图,这里简化为0
    client_msg.common_header.sequence_num = 0; // 客户端请求没有序列号,由主节点分配
    client_msg.common_header.sender_id = g_client_state.client_id;

    // 填充客户端请求内容
    client_msg.payload.client_request.client_id = g_client_state.client_id;
    client_msg.payload.client_request.timestamp = (uint32_t)time(NULL); // 使用当前时间戳
    strncpy(client_msg.payload.client_request.operation, operation, MAX_MSG_CONTENT_SIZE - 1);
    client_msg.payload.client_request.operation[MAX_MSG_CONTENT_SIZE - 1] = '\0';
    calculate_digest(client_msg.payload.client_request.operation, strlen(client_msg.payload.client_request.operation), client_msg.payload.client_request.digest);

    printf("[Client %u] 准备发送客户端请求:\n", g_client_state.client_id);
    print_message(&client_msg);

    // 连接到主节点 (简化:假设Node 0是主节点)
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("[Client] 创建套接字失败");
        return;
    }

    struct sockaddr_in primary_node_addr;
    memset(&primary_node_addr, 0, sizeof(primary_node_addr));
    primary_node_addr.sin_family = AF_INET;
    primary_node_addr.sin_port = htons(BASE_NODE_PORT + 0); // 连接到Node 0
    if (inet_pton(AF_INET, "127.0.0.1", &primary_node_addr.sin_addr) <= 0) {
        perror("[Client] 无效的IP地址");
        close(sock_fd);
        return;
    }

    printf("[Client %u] 尝试连接到主节点 (Node 0 端口: %d)...\n", g_client_state.client_id, BASE_NODE_PORT + 0);
    if (connect(sock_fd, (struct sockaddr*)&primary_node_addr, sizeof(primary_node_addr)) == -1) {
        perror("[Client] 连接主节点失败");
        close(sock_fd);
        return;
    }
    printf("[Client %u] 成功连接到主节点 (Node 0)。\n", g_client_state.client_id);

    // 发送消息
    size_t total_msg_len = sizeof(ClientRequest);
    ssize_t bytes_sent = send(sock_fd, &client_msg, total_msg_len, 0);
    if (bytes_sent == -1) {
        perror("[Client] 发送请求失败");
    } else if (bytes_sent != total_msg_len) {
        fprintf(stderr, "[Client %u] 警告: 发送请求长度不匹配!期望 %zu, 实际 %zd。\n", g_client_state.client_id, total_msg_len, bytes_sent);
    } else {
        printf("[Client %u] 成功发送请求给主节点。\n", g_client_state.client_id);
    }

    close(sock_fd); // 发送完请求即可关闭连接
}

/**
 * @brief 接收回复的线程函数
 * @param arg 监听套接字文件描述符
 */
void *receive_reply_thread(void *arg) {
    int listen_sock_fd = (int)(long)arg;
    struct sockaddr_in remote_addr;
    socklen_t addr_len = sizeof(remote_addr);
    char remote_ip[INET_ADDRSTRLEN];

    while (1) {
        int node_sock_fd = accept(listen_sock_fd, (struct sockaddr*)&remote_addr, &addr_len);
        if (node_sock_fd == -1) {
            perror("[Client] 接受节点回复连接失败");
            break;
        }

        inet_ntop(AF_INET, &(remote_addr.sin_addr), remote_ip, INET_ADDRSTRLEN);
        printf("[Client %u] 接收到来自 Node %d 的回复连接。\n", g_client_state.client_id, ntohs(remote_addr.sin_port) - BASE_NODE_PORT);

        PBFTMessage reply_msg;
        ssize_t bytes_received;

        // 接收消息头部
        bytes_received = recv(node_sock_fd, &reply_msg.common_header, sizeof(MessageHeader), MSG_WAITALL);
        if (bytes_received <= 0) {
            perror("[Client] 接收回复头部失败");
            close(node_sock_fd);
            continue;
        }

        if (reply_msg.common_header.type != MSG_TYPE_REPLY) {
            fprintf(stderr, "[Client %u] 警告: 收到非REPLY消息类型 %u,忽略。\n", g_client_state.client_id, reply_msg.common_header.type);
            close(node_sock_fd);
            continue;
        }

        // 接收回复payload
        size_t payload_size = sizeof(ReplyMsg) - sizeof(MessageHeader);
        bytes_received = recv(node_sock_fd, &reply_msg.payload, payload_size, MSG_WAITALL);
        if (bytes_received <= 0) {
            perror("[Client] 接收回复payload失败");
            close(node_sock_fd);
            continue;
        }
        if (bytes_received != payload_size) {
            fprintf(stderr, "[Client %u] 接收回复payload长度不匹配!期望 %zu, 实际 %zd。\n", g_client_state.client_id, payload_size, bytes_received);
            close(node_sock_fd);
            continue;
        }

        printf("[Client %u] 收到来自 Node %u 的回复:\n", g_client_state.client_id, reply_msg.common_header.sender_id);
        print_message(&reply_msg);

        // --- 在这里添加客户端回复验证逻辑 ---
        // 客户端需要收集 f+1 个相同的回复才能确认请求完成
        // 这里简化为收到一个就打印
        close(node_sock_fd);
    }
    return NULL;
}

文件:Makefile (自动化编译)

# 定义编译器
CC = gcc

# 定义编译选项
# -Wall: 开启所有警告
# -g: 生成调试信息,方便GDB调试
# -pthread: 链接POSIX线程库
# -std=c11: 使用C11标准
CFLAGS = -Wall -g -pthread -std=c11

# 定义目标文件
TARGETS = node client

# 定义源文件
NODE_SRCS = node.c utils.c
CLIENT_SRCS = client.c utils.c

# 定义目标文件 (中间的.o文件)
NODE_OBJS = $(NODE_SRCS:.c=.o)
CLIENT_OBJS = $(CLIENT_SRCS:.c=.o)

# 默认目标:编译所有可执行文件
all: $(TARGETS)

# 编译 node 可执行文件
node: $(NODE_OBJS)
	$(CC) $(CFLAGS) $(NODE_OBJS) -o node

# 编译 client 可执行文件
client: $(CLIENT_OBJS)
	$(CC) $(CFLAGS) $(CLIENT_OBJS) -o client

# 编译 .c 文件到 .o 文件 (通用规则)
%.o: %.c
	$(CC) $(CFLAGS) -c $< -o $@

# 清理生成的文件
clean:
	rm -f $(NODE_OBJS) $(CLIENT_OBJS) $(TARGETS)

# 伪目标声明,避免与同名文件冲突
.PHONY: all clean

代码分析与逻辑透析:

这三段C语言代码(common.h, utils.c, node.c, client.c)和Makefile共同构建了一个最基础的PBFT算法仿真框架。它实现了多进程节点间的TCP通信和消息的序列化/反序列化,但尚未实现完整的PBFT共识逻辑。

  1. common.h:公共定义与消息结构

    • 宏定义: NUM_NODES (总节点数)、FAULTY_NODES (可容忍的拜占庭节点数,根据3f+1规则计算)、DIGEST_SIZEMAX_MSG_CONTENT_SIZEBASE_NODE_PORT (节点监听端口基准)、CLIENT_PORT (客户端监听端口基准)。这些是整个仿真系统的基本参数。

    • MessageType 枚举: 定义了PBFT协议中各种消息的类型,这是消息路由和解析的基础。

    • MessageHeader 结构体: 所有PBFT消息的公共头部。包含消息类型、视图ID、序列号、发送者ID。这是消息传输和校验的基本信息。

    • 具体消息结构体: ClientRequestPrePrepareMsgPrepareMsgCommitMsgReplyMsg。这些结构体严格按照PBFT协议中各阶段消息的定义来设计,包含各自特有的字段。

      • 注意: 为了简化仿真和调试,PrePrepareMsg中包含了完整的operation,而不是仅仅request_digest。在实际PBFT协议中,为了减少网络带宽,通常只传递摘要,接收方再根据摘要从本地缓存中查找原始请求。

    • PBFTMessage 共用体结构体: 将所有具体消息结构体封装在一个union中。这样,在网络传输时,我们只需要发送和接收一个固定大小的PBFTMessage结构体,然后根据common_header.type来判断其具体内容,并访问payload中的相应成员。这是一种常见的网络编程技巧,用于处理多种消息类型。

    • 辅助函数声明: calculate_digest (简易哈希) 和 print_message (调试用)。

  2. utils.c:辅助函数实现

    • calculate_digest() 一个非常简化的哈希函数。在实际PBFT中,这里必须使用安全的加密哈希算法,如SHA256,以保证消息摘要的不可伪造性。这里仅用于演示概念。

    • print_message() 这是一个非常重要的调试工具。它能够根据消息类型,详细打印出PBFTMessage结构体中的内容,让你在仿真过程中清晰地看到每条消息的传输和解析情况。

  3. node.c:PBFT节点实现

    • NodeState 结构体: 存储当前节点的ID、视图编号、最新序列号等基本状态信息。

    • g_node_state 全局变量,表示当前节点的运行时状态。

    • conn_sockets[NUM_NODES] 全局数组,用于存储当前节点与其他所有节点(包括客户端)建立的TCP连接套接字。conn_sockets[i]表示与节点i的连接。

    • g_mutex 互斥锁,用于保护conn_sockets等全局共享资源,防止多线程并发访问导致数据不一致。

    • start_node(uint8_t node_id)

      • 初始化节点状态。

      • 服务器端逻辑: 创建监听套接字,绑定端口(BASE_NODE_PORT + node_id),进入监听状态。然后启动一个独立的线程handle_incoming_connection来处理所有传入的连接请求。

      • 客户端逻辑: 调用connect_to_peers函数,主动尝试连接到其他所有节点。

      • 主循环:目前只是一个空循环,未来将在这里实现PBFT的核心状态机逻辑。

    • handle_incoming_connection()

      • 在一个无限循环中,accept()新的客户端连接。

      • 每接受一个连接,就创建一个新的线程recv_message_thread来专门处理该连接上的消息接收。

      • 连接识别: 简单地通过远程端口号推断连接方是哪个节点。注意: 实际系统中,节点之间会通过更安全的握手协议来交换身份信息。

      • 将新建立的连接套接字存储到conn_sockets数组中。

    • connect_to_peers()

      • 遍历所有其他节点ID。

      • 为每个未连接的节点,创建一个TCP套接字,并尝试connect()到其监听端口。

      • 实现了简单的连接重试机制,以应对对端节点尚未启动的情况。

      • 连接成功后,同样启动一个独立的线程recv_message_thread来接收来自该对端节点的消息。

    • recv_message_thread()

      • 这是每个连接对应的消息接收线程

      • 在一个无限循环中,首先接收消息头部 (MessageHeader)。MSG_WAITALL标志确保完整接收指定字节数。

      • 根据消息头部中的type字段,判断消息类型,然后接收剩余的payload部分。

      • 接收到完整消息后,调用print_message打印消息内容(用于调试)。

      • 核心: 未来PBFT算法的各个阶段消息处理逻辑将集成到这里。

      • 连接断开或出错时,关闭套接字并清理资源。

    • send_message_to_node()

      • 通过target_node_idconn_sockets数组中获取对应的套接字。

      • 计算要发送的PBFTMessage的总长度。

      • send():发送完整的消息结构体。

      • 打印发送日志。

  4. client.c:客户端实现

    • ClientState 存储客户端ID。

    • main()

      • 初始化客户端ID。

      • 服务器端逻辑: 客户端也需要启动一个监听端口(CLIENT_PORT + client_id),用于接收来自PBFT节点的REPLY消息。启动receive_reply_thread来处理这些回复。

      • 主循环: 客户端通过命令行输入操作,然后调用send_client_request将请求发送给主节点。

    • send_client_request()

      • 构建ClientRequest消息,包含客户端ID、时间戳、操作内容和摘要。

      • 简化: 客户端直接连接到Node 0(假设它是主节点)。在实际PBFT中,客户端需要知道当前的主节点是谁,或者向所有节点发送请求。

      • 发送完请求后,关闭连接。

    • receive_reply_thread()

      • 与节点端的接收线程类似,接受来自节点的连接。

      • 接收并解析REPLY消息。

      • 打印收到的回复。

      • 核心: 客户端需要收集f+1个相同的REPLY消息才能确认请求已完成。这部分逻辑将在后续添加。

  5. Makefile:自动化编译

    • 定义了编译器CC、编译选项CFLAGS(包含-pthread用于链接线程库)。

    • 定义了nodeclient两个可执行目标。

    • 通过$(SRCS:.c=.o)自动生成.o文件列表。

    • 定义了编译.c.o的通用规则。

    • 提供了clean目标用于清理生成的文件。

如何运行这个PBFT基础仿真框架:

  1. 保存文件: 将上述所有代码分别保存为common.hutils.cnode.cclient.cMakefile,放在同一个目录下。

  2. 打开WSL2终端 (或Linux终端): 进入你保存文件的目录。

  3. 编译: 在终端中执行 make

    • 如果编译成功,你会看到生成了nodeclient两个可执行文件。

  4. 启动节点: 打开四个独立的WSL2终端窗口。在每个窗口中分别启动一个PBFT节点:

    • 终端1: ./node 0 (启动Node 0)

    • 终端2: ./node 1 (启动Node 1)

    • 终端3: ./node 2 (启动Node 2)

    • 终端4: ./node 3 (启动Node 3)

    • 观察每个节点的输出,它们会尝试相互连接。当所有节点都启动后,你会看到它们之间建立了连接。

  5. 启动客户端: 打开第五个WSL2终端窗口

    • 终端5: ./client 100 (启动Client 100)

    • 客户端会尝试连接到Node 0,并提示你输入操作。

  6. 发送请求: 在客户端终端输入一个操作,例如 transfer 100 to Bob,然后按回车。

    • 客户端会打印发送请求的消息详情。

    • Node 0(主节点)会收到这个客户端请求,并打印消息详情。

    • 目前,由于还没有实现PBFT的核心逻辑,节点收到消息后不会有进一步的动作。 客户端也只会收到Node 0的回复(如果Node 0被设置为回复)。

当前阶段的局限性:

  • 未实现PBFT核心逻辑: 当前代码只是搭建了通信框架,PBFT的三阶段提交、视图切换等核心逻辑尚未实现。

  • 简化的摘要函数: calculate_digest只是一个简单的哈希,不具备加密安全性。

  • 简化的节点识别: 通过端口号推断节点ID,实际系统需要更健壮的身份验证。

  • 无故障处理: 当前代码没有处理网络断开、节点崩溃等故障情况。

  • 无并发控制: 消息处理线程只是打印消息,没有对共享状态进行修改,未来需要增加更多锁和同步机制。

小结与展望

老铁们,恭喜你!你已经成功迈出了PBFT算法C语言仿真的第一步!

在这一部分中,我们:

  • 深入理解了分布式系统中的“一致性”挑战,特别是拜占庭将军问题CAP定理,这是理解所有共识算法的基石。

  • 详细剖析了PBFT算法的核心原理,包括其角色、三阶段提交流程、视图切换机制,以及**3f+1容错能力**的数学基础。

  • 搭建了一个C语言PBFT仿真框架的骨架,包括:

    • 设计了PBFT消息类型和结构体,特别是利用union实现统一消息格式。

    • 实现了基于TCP套接字的多进程节点间通信,包括服务器端监听、客户端连接、以及多线程消息收发。

    • 提供了基础的辅助函数(消息摘要、消息打印)和Makefile自动化编译脚本。

虽然我们还没有实现完整的PBFT共识逻辑,但你已经亲手构建了一个能够让多个“节点”和“客户端”在你的电脑上“说话”的网络环境!这正是PBFT算法仿真的第一步,也是最关键的一步!

接下来,我们将进入更具挑战性的中篇,在现有框架的基础上,逐步填充PBFT算法的三阶段提交核心逻辑,包括消息的验证、状态的转换、以及消息的广播。那将是真正让你的PBFT仿真器“活”起来的秘密武器!

请记住,技术学习就像“打怪升级”,每一步的积累都至关重要。如果你在搭建环境或运行代码时遇到任何问题,随时在评论区告诉我,咱们一起交流,一起成为区块链共识算法的“大神”!

敬请期待我的下一次更新!

------------------------------------------------------------------------------------------------------------更新于2025.6.26 

PBFT算法仿真:从Win11到共识算法核心(中篇)

前言:让你的PBFT仿真器真正“动”起来!

老铁们,上篇我们已经把PBFT(Practical Byzantine Fault Tolerance,实用拜占庭容错)算法的理论基础和C语言仿真框架的“骨架”搭起来了。你是不是已经迫不及待地想看到这些节点在你的WSL2终端里“跑”起来,真正地达成共识了?

没错!今天,我们就要让这个“骨架”长出“血肉”,让你的PBFT仿真器真正“动”起来!

本篇作为“一文教你C语言彻底学会PBFT算法仿真”系列的中篇,我们将聚焦于PBFT算法最核心的部分——三阶段提交协议的C语言实现。这可不是简单的代码堆砌,我们将深入到每一个消息的验证、每一个状态的转换、每一个计数的更新,让你彻底理解PBFT是如何在拜占庭故障下保证一致性的!

我们将手把手带你:

  • 设计并实现PBFT节点内部的“记忆”:消息日志!

  • 理解并实现PBFT的“生命线”:状态机与水位线!

  • 逐个攻克Pre-prepare、Prepare、Commit这三个核心阶段的C语言逻辑!

  • 实现Replica节点对客户端请求的回复!

这部分内容会非常硬核,代码量也会让你“吃到饱”,而且每一行代码都带详细注释和逻辑分析,保证你不仅能看懂,还能亲手跑起来,彻底学透PBFT的每一个细节!

准备好了吗?咱们这就开始,让你的PBFT仿真器,从“静态模型”变为“动态网络”!

第四章:PBFT节点内部状态与消息管理——共识的“记忆”与“脉搏”

要实现PBFT算法,每个节点不仅仅是简单地收发消息,它还需要维护自己的状态,记录收到的消息,并根据这些信息来做出决策。这一章,我们来设计PBFT节点的“记忆”和“脉搏”。

4.1 节点状态机:PBFT的“生命周期”

PBFT算法是一个基于状态转换的协议。每个节点在处理请求时,会经历一系列的状态。理解这些状态及其转换条件,是理解PBFT算法的关键。

PBFT节点状态转换图(简化版):

graph LR
    A[Idle/Initial] --> B{主节点接收请求}
    B --> C[Pre-prepared]
    C --收到有效PRE-PREPARE--> D[Prepared]
    D --收到2f个PREPARE--> E[Committed]
    E --收到2f+1个COMMIT--> F[Executed/Replied]
    F --> A
    B --超时或主节点作恶--> G[View-Change (待实现)]
    D --超时或主节点作恶--> G
    E --超时或主节点作恶--> G

状态解释:

  • Idle/Initial (空闲/初始): 节点处于等待请求或等待Pre-prepare消息的初始状态。

  • Pre-prepared (预准备完成): 节点(副本节点)收到并验证了主节点发来的PRE-PREPARE消息,并将其记录到日志中。此时,节点认为主节点已经为这个请求分配了序列号。

  • Prepared (准备完成): 节点(包括主节点和副本节点)收集到了 2f 个(包括自己发送的)有效的PREPARE消息,这些消息都针对同一个请求(视图、序列号、摘要一致)。此时,节点认为大多数正常节点都同意了请求的顺序和内容。

  • Committed (提交完成): 节点(包括主节点和副本节点)收集到了 2f+1 个(包括自己发送的)有效的COMMIT消息,这些消息都针对同一个请求。此时,节点认为大多数正常节点都同意执行这个请求。

  • Executed/Replied (已执行/已回复): 节点执行了请求(更新了状态机),并向客户端发送了REPLY消息。

  • View-Change (视图切换): 当主节点出现故障或作恶时,节点会进入视图切换状态,尝试选举新的主节点。这部分将在后续实现。

C语言中节点状态的扩展:

我们需要在NodeState结构体中添加更多字段来维护这些状态信息。

// common.h 中 NodeState 的扩展
// 定义PBFT处理阶段
typedef enum {
    PHASE_IDLE,
    PHASE_PRE_PREPARE, // 收到PRE-PREPARE (副本节点) 或发送PRE-PREPARE (主节点)
    PHASE_PREPARE,     // 收到或发送PREPARE
    PHASE_COMMIT,      // 收到或发送COMMIT
    PHASE_REPLY        // 收到或发送REPLY
} PBFTPhase;

// --- 节点状态信息 ---
typedef struct {
    uint8_t node_id;         // 当前节点的ID
    uint32_t current_view;   // 当前视图编号
    uint32_t last_executed_seq_num; // 上一个已执行的序列号 (低水位线)
    uint32_t next_seq_num;   // 下一个可用的序列号 (主节点分配)
    bool is_primary;         // 是否是主节点
    PBFTPhase current_phase; // 当前节点所处的PBFT阶段

    // 实际PBFT节点状态会复杂得多,包括消息日志、高水位、低水位、检查点等
    // 这里先添加核心的用于三阶段提交的状态
} NodeState;

4.2 消息日志:PBFT的“记忆”

PBFT算法的安全性依赖于节点能够“记住”它们收到的消息。这些消息构成了消息日志。消息日志对于以下几个方面至关重要:

  • 验证消息: 节点收到消息后,需要检查它是否与之前收到的消息一致。

  • 防止重放攻击: 通过序列号和时间戳,防止恶意节点重放旧的消息。

  • 视图切换恢复: 当发生视图切换时,新主节点需要从副本节点的消息日志中恢复未完成的请求状态。

消息日志的数据结构设计:

我们需要存储PRE-PREPAREPREPARECOMMIT消息。由于消息数量可能很多,并且需要根据视图ID、序列号和摘要进行查找,我们可以考虑使用哈希表链表的组合。

为了简化,我们先使用动态数组链表来存储消息,并用一个结构体来表示每条日志条目。

ER图:消息日志结构(简化)

erDiagram
    NODE ||--o{ MESSAGE_LOG : "stores"
    MESSAGE_LOG {
        uint32_t view_id
        uint32_t sequence_num
        uint8_t request_digest[32]
        PBFTMessage message_content
        bool is_prepared
        bool is_committed
        int prepare_count
        int commit_count
        // 其他状态,如是否已执行
    }

C语言代码:消息日志定义与管理

我们将定义一个MessageLogEntry结构体来存储每条消息及其状态,并使用一个简单的链表来管理这些日志条目。

// common.h 中添加消息日志相关定义
// PBFT消息日志条目
typedef struct MessageLogEntry {
    uint32_t view_id;
    uint32_t sequence_num;
    uint8_t request_digest[DIGEST_SIZE];
    PBFTMessage original_client_request; // 原始客户端请求的副本 (用于执行)
    PBFTMessage pre_prepare_msg;         // 存储收到的PRE-PREPARE消息
    // 存储收到的PREPARE和COMMIT消息的集合 (这里简化为计数,实际应存储消息本身)
    bool received_prepare[NUM_NODES]; // 标记是否收到某个节点的PREPARE消息
    bool received_commit[NUM_NODES];  // 标记是否收到某个节点的COMMIT消息
    int prepare_count;                // 收集到的PREPARE消息数量
    int commit_count;                 // 收集到的COMMIT消息数量
    bool is_prepared;                 // 是否已达到Prepared状态
    bool is_committed;                // 是否已达到Committed状态
    bool is_executed;                 // 是否已执行
    struct MessageLogEntry *next;     // 链表指针
} MessageLogEntry;

// common.h 中 NodeState 的扩展 (继续扩展)
typedef struct {
    uint8_t node_id;         // 当前节点的ID
    uint32_t current_view;   // 当前视图编号
    uint32_t last_executed_seq_num; // 上一个已执行的序列号 (低水位线)
    uint32_t next_seq_num;   // 下一个可用的序列号 (主节点分配)
    bool is_primary;         // 是否是主节点
    PBFTPhase current_phase; // 当前节点所处的PBFT阶段

    MessageLogEntry *message_log_head; // 消息日志链表头
    pthread_mutex_t log_mutex;         // 保护消息日志的互斥锁
    // 实际PBFT节点状态会复杂得多,包括高水位、检查点、视图切换相关消息等
} NodeState;

// common.h 中辅助函数声明
// 查找或创建消息日志条目
MessageLogEntry* get_or_create_log_entry(uint32_t view_id, uint32_t seq_num, const uint8_t* digest);
// 标记日志条目为Prepared状态
void mark_as_prepared(MessageLogEntry* entry);
// 标记日志条目为Committed状态
void mark_as_committed(MessageLogEntry* entry);
// 执行操作并标记为Executed状态
void execute_operation(MessageLogEntry* entry);
// 清理旧的日志条目 (基于水位线,简化)
void clean_old_log_entries();
```c
// utils.c 中实现新增的辅助函数

// 全局节点状态 (在node.c中定义,这里只是声明 extern)
extern NodeState g_node_state;

// 查找或创建消息日志条目
MessageLogEntry* get_or_create_log_entry(uint32_t view_id, uint32_t seq_num, const uint8_t* digest) {
    pthread_mutex_lock(&g_node_state.log_mutex);
    MessageLogEntry* current = g_node_state.message_log_head;
    while (current != NULL) {
        if (current->view_id == view_id &&
            current->sequence_num == seq_num &&
            memcmp(current->request_digest, digest, DIGEST_SIZE) == 0) {
            pthread_mutex_unlock(&g_node_state.log_mutex);
            return current; // 找到现有条目
        }
        current = current->next;
    }

    // 未找到,创建新条目
    MessageLogEntry* new_entry = (MessageLogEntry*)malloc(sizeof(MessageLogEntry));
    if (new_entry == NULL) {
        fprintf(stderr, "[Node %u] 错误: 分配消息日志条目内存失败!\n", g_node_state.node_id);
        pthread_mutex_unlock(&g_node_state.log_mutex);
        exit(EXIT_FAILURE);
    }
    memset(new_entry, 0, sizeof(MessageLogEntry));
    new_entry->view_id = view_id;
    new_entry->sequence_num = seq_num;
    memcpy(new_entry->request_digest, digest, DIGEST_SIZE);
    new_entry->is_prepared = false;
    new_entry->is_committed = false;
    new_entry->is_executed = false;
    new_entry->prepare_count = 0;
    new_entry->commit_count = 0;
    for (int i = 0; i < NUM_NODES; ++i) {
        new_entry->received_prepare[i] = false;
        new_entry->received_commit[i] = false;
    }

    // 将新条目添加到链表头部 (简化)
    new_entry->next = g_node_state.message_log_head;
    g_node_state.message_log_head = new_entry;
    printf("[Node %u] 创建新的消息日志条目: 视图 %u, 序列号 %u\n", g_node_state.node_id, view_id, seq_num);
    pthread_mutex_unlock(&g_node_state.log_mutex);
    return new_entry;
}

// 标记日志条目为Prepared状态
void mark_as_prepared(MessageLogEntry* entry) {
    pthread_mutex_lock(&g_node_state.log_mutex);
    if (!entry->is_prepared) {
        entry->is_prepared = true;
        printf("[Node %u] 消息日志条目 (视图 %u, 序列号 %u) 已标记为 Prepared。\n",
               g_node_state.node_id, entry->view_id, entry->sequence_num);
    }
    pthread_mutex_unlock(&g_node_state.log_mutex);
}

// 标记日志条目为Committed状态
void mark_as_committed(MessageLogEntry* entry) {
    pthread_mutex_lock(&g_node_state.log_mutex);
    if (!entry->is_committed) {
        entry->is_committed = true;
        printf("[Node %u] 消息日志条目 (视图 %u, 序列号 %u) 已标记为 Committed。\n",
               g_node_state.node_id, entry->view_id, entry->sequence_num);
    }
    pthread_mutex_unlock(&g_node_state.log_mutex);
}

// 执行操作并标记为Executed状态 (简化:这里只是打印)
void execute_operation(MessageLogEntry* entry) {
    pthread_mutex_lock(&g_node_state.log_mutex);
    if (!entry->is_executed) {
        entry->is_executed = true;
        printf("[Node %u] 执行操作: '%s' (视图 %u, 序列号 %u)\n",
               g_node_state.node_id, entry->original_client_request.payload.client_request.operation,
               entry->view_id, entry->sequence_num);
        // 实际应用中,这里会更新状态机,例如:
        // apply_transaction(entry->original_client_request.payload.client_request.operation);

        // 更新已执行序列号 (低水位线)
        if (entry->sequence_num > g_node_state.last_executed_seq_num) {
            g_node_state.last_executed_seq_num = entry->sequence_num;
            printf("[Node %u] 更新 last_executed_seq_num 为 %u。\n", g_node_state.node_id, g_node_state.last_executed_seq_num);
        }
    }
    pthread_mutex_unlock(&g_node_state.log_mutex);
}

// 清理旧的日志条目 (基于水位线,简化:清理所有小于last_executed_seq_num的条目)
void clean_old_log_entries() {
    pthread_mutex_lock(&g_node_state.log_mutex);
    MessageLogEntry* current = g_node_state.message_log_head;
    MessageLogEntry* prev = NULL;

    while (current != NULL) {
        if (current->sequence_num <= g_node_state.last_executed_seq_num && current->is_executed) {
            // 找到已执行且序列号小于等于低水位线的条目,可以清理
            printf("[Node %u] 清理旧日志条目: 视图 %u, 序列号 %u\n", g_node_state.node_id, current->view_id, current->sequence_num);
            if (prev == NULL) {
                g_node_state.message_log_head = current->next;
                free(current);
                current = g_node_state.message_log_head;
            } else {
                prev->next = current->next;
                free(current);
                current = prev->next;
            }
        } else {
            prev = current;
            current = current->next;
        }
    }
    pthread_mutex_unlock(&g_node_state.log_mutex);
}

4.3 水位线 (Watermarks):优化存储与垃圾回收

随着请求的不断处理,消息日志会越来越大。PBFT引入了水位线 (Watermarks)检查点 (Checkpoint) 机制来管理日志大小,进行垃圾回收。

  • 低水位线 (h, Low Watermark): 表示所有节点都已提交并执行的最小序列号。所有序列号小于或等于 h 的请求及其相关消息都可以从日志中删除。在我们的仿真中,last_executed_seq_num 就扮演了低水位线的角色。

  • 高水位线 (H, High Watermark): 表示当前主节点可以分配的最大序列号。通常是 h + K,其中 K 是一个常数(窗口大小)。这防止了主节点分配过大的序列号,从而限制了副本节点需要维护的日志大小。

  • 检查点 (Checkpoint): 周期性地,系统会创建一个检查点,记录当前状态机的最新状态和对应的序列号。检查点一旦被 2f+1 个节点确认,就可以将所有小于检查点序列号的日志条目安全地删除。

在我们的仿真中,我们将简化水位线管理:

  • g_node_state.last_executed_seq_num 作为低水位线,每次执行完操作就更新。

  • clean_old_log_entries() 函数会清理所有序列号小于等于 last_executed_seq_num 且已执行的日志条目。

  • 高水位线和检查点机制将在后续更复杂的仿真中考虑。

第五章:PBFT三阶段提交协议的C语言实现——让共识真正发生!

现在,我们有了节点状态和消息日志,可以开始实现PBFT的三阶段提交协议了!我们将修改node.c中的recv_message_thread函数,根据收到的消息类型,调用不同的处理逻辑。

5.1 客户端请求处理 (主节点 Primary)

流程:

  1. 接收客户端请求: 主节点(Primary)通过其监听端口接收客户端发来的CLIENT_REQUEST消息。

  2. 验证请求: 检查请求的合法性(例如,客户端ID、时间戳、操作内容等)。

  3. 分配序列号: 为请求分配一个唯一的序列号 n。这个序列号必须大于 last_executed_seq_num

  4. 构建 PRE-PREPARE 消息: 包含视图ID v、序列号 n、请求摘要 d 和原始请求 m

  5. 广播 PRE-PREPARE 消息:PRE-PREPARE 消息发送给所有副本节点。

  6. 存储到日志:PRE-PREPARE 消息及其对应的客户端请求存储到自己的消息日志中。

C语言代码:node.c 中添加主节点处理逻辑

// node.c 中添加 PBFT 核心处理函数声明
void handle_client_request_msg(const ClientRequest* req_msg);
void handle_pre_prepare_msg(const PrePrepareMsg* pp_msg);
void handle_prepare_msg(const PrepareMsg* p_msg);
void handle_commit_msg(const CommitMsg* c_msg);
void send_reply_to_client(uint8_t client_id, uint32_t timestamp, const char* result);

// node.c 中修改 recv_message_thread 函数
void *recv_message_thread(void *arg) {
    ThreadArgs* args = (ThreadArgs*)arg;
    int sock_fd = args->client_sock_fd;
    uint8_t remote_node_id = args->remote_node_id;
    free(args); // 释放线程参数内存

    PBFTMessage msg_buffer;
    ssize_t bytes_received;

    printf("[Node %u] 接收线程启动,监听来自 Node %u 的消息...\n", g_node_state.node_id, remote_node_id);

    while (1) {
        // 接收消息头部
        bytes_received = recv(sock_fd, &msg_buffer.common_header, sizeof(MessageHeader), MSG_WAITALL);
        if (bytes_received <= 0) {
            if (bytes_received == 0) {
                printf("[Node %u] Node %u 断开连接。\n", g_node_state.node_id, remote_node_id);
            } else {
                perror("[Node] 接收消息头部失败");
            }
            break; // 连接断开或出错
        }

        // 根据消息类型接收剩余的payload
        size_t payload_size = 0;
        switch (msg_buffer.common_header.type) {
            case MSG_TYPE_CLIENT_REQUEST: payload_size = sizeof(ClientRequest) - sizeof(MessageHeader); break;
            case MSG_TYPE_PRE_PREPARE:    payload_size = sizeof(PrePrepareMsg) - sizeof(MessageHeader); break;
            case MSG_TYPE_PREPARE:        payload_size = sizeof(PrepareMsg) - sizeof(MessageHeader); break;
            case MSG_TYPE_COMMIT:         payload_size = sizeof(CommitMsg) - sizeof(MessageHeader); break;
            case MSG_TYPE_REPLY:          payload_size = sizeof(ReplyMsg) - sizeof(MessageHeader); break;
            // 其他消息类型待添加
            default:
                fprintf(stderr, "[Node %u] 接收到未知消息类型 %u,跳过payload接收。\n", g_node_state.node_id, msg_buffer.common_header.type);
                break;
        }

        if (payload_size > 0) {
            bytes_received = recv(sock_fd, &msg_buffer.payload, payload_size, MSG_WAITALL);
            if (bytes_received <= 0) {
                if (bytes_received == 0) {
                    printf("[Node %u] Node %u 断开连接 (接收payload时)。\n", g_node_state.node_id, remote_node_id);
                } else {
                    perror("[Node] 接收消息payload失败");
                }
                break;
            }
            if (bytes_received != payload_size) {
                fprintf(stderr, "[Node %u] 接收payload长度不匹配!期望 %zu, 实际 %zd。\n", g_node_state.node_id, payload_size, bytes_received);
                break;
            }
        }

        // 成功接收一条完整的消息
        printf("[Node %u] 收到来自 Node %u 的消息:\n", g_node_state.node_id, msg_buffer.common_header.sender_id);
        print_message(&msg_buffer);

        // --- PBFT消息处理逻辑 ---
        pthread_mutex_lock(&g_mutex); // 保护g_node_state和其他共享资源
        switch (msg_buffer.common_header.type) {
            case MSG_TYPE_CLIENT_REQUEST:
                // 只有主节点处理客户端请求
                if (g_node_state.is_primary) {
                    handle_client_request_msg(&msg_buffer.payload.client_request);
                } else {
                    printf("[Node %u] 警告: 副本节点收到客户端请求,忽略。\n", g_node_state.node_id);
                }
                break;
            case MSG_TYPE_PRE_PREPARE:
                // 副本节点处理PRE-PREPARE消息
                if (!g_node_state.is_primary) {
                    handle_pre_prepare_msg(&msg_buffer.payload.pre_prepare);
                } else {
                    printf("[Node %u] 警告: 主节点收到PRE-PREPARE消息,忽略。\n", g_node_state.node_id);
                }
                break;
            case MSG_TYPE_PREPARE:
                // 所有节点处理PREPARE消息
                handle_prepare_msg(&msg_buffer.payload.prepare);
                break;
            case MSG_TYPE_COMMIT:
                // 所有节点处理COMMIT消息
                handle_commit_msg(&msg_buffer.payload.commit);
                break;
            case MSG_TYPE_REPLY:
                // 节点通常不处理REPLY消息,REPLY是发给客户端的
                printf("[Node %u] 收到REPLY消息,忽略 (REPLY是发给客户端的)。\n", g_node_state.node_id);
                break;
            default:
                fprintf(stderr, "[Node %u] 未知消息类型 %u,无法处理。\n", g_node_state.node_id, msg_buffer.common_header.type);
                break;
        }
        pthread_mutex_unlock(&g_mutex);
    }

    // 连接断开,清理资源
    pthread_mutex_lock(&g_mutex);
    if (conn_sockets[remote_node_id] == sock_fd) { // 确保是当前连接
        conn_sockets[remote_node_id] = -1;
    }
    pthread_mutex_unlock(&g_mutex);
    close(sock_fd);
    printf("[Node %u] 与 Node %u 的连接已关闭。\n", g_node_state.node_id, remote_node_id);
    return NULL;
}

/**
 * @brief 处理客户端请求消息 (仅主节点调用)
 * @param req_msg 客户端请求消息
 */
void handle_client_request_msg(const ClientRequest* req_msg) {
    printf("[Node %u] (主节点) 收到客户端 %u 的请求: '%s'\n",
           g_node_state.node_id, req_msg->client_id, req_msg->operation);

    // 1. 验证请求 (简化:这里只检查时间戳,实际需要签名验证、防重放等)
    // 假设时间戳必须大于0
    if (req_msg->timestamp == 0) {
        fprintf(stderr, "[Node %u] 错误: 无效的客户端请求 (时间戳为0)。\n", g_node_state.node_id);
        // 实际PBFT会向客户端发送错误回复
        return;
    }

    // 2. 分配序列号
    // 序列号必须是递增的,且在水位线范围内
    // 这里简化为直接递增,不考虑高水位线和检查点
    g_node_state.next_seq_num++; // 分配下一个序列号
    uint32_t current_seq_num = g_node_state.next_seq_num;
    printf("[Node %u] (主节点) 为请求分配序列号: %u\n", g_node_state.node_id, current_seq_num);

    // 3. 构建 PRE-PREPARE 消息
    PBFTMessage pp_msg;
    memset(&pp_msg, 0, sizeof(PBFTMessage));
    pp_msg.common_header.type = MSG_TYPE_PRE_PREPARE;
    pp_msg.common_header.view_id = g_node_state.current_view;
    pp_msg.common_header.sequence_num = current_seq_num;
    pp_msg.common_header.sender_id = g_node_state.node_id;

    pp_msg.payload.pre_prepare.client_id = req_msg->client_id;
    pp_msg.payload.pre_prepare.timestamp = req_msg->timestamp;
    strncpy(pp_msg.payload.pre_prepare.operation, req_msg->operation, MAX_MSG_CONTENT_SIZE - 1);
    memcpy(pp_msg.payload.pre_prepare.request_digest, req_msg->digest, DIGEST_SIZE);

    // 4. 存储到消息日志 (主节点也需要存储)
    MessageLogEntry* log_entry = get_or_create_log_entry(g_node_state.current_view, current_seq_num, req_msg->digest);
    memcpy(&log_entry->original_client_request, req_msg, sizeof(ClientRequest)); // 存储原始请求
    memcpy(&log_entry->pre_prepare_msg, &pp_msg.payload.pre_prepare, sizeof(PrePrepareMsg)); // 存储PRE-PREPARE消息

    // 5. 广播 PRE-PREPARE 消息给所有副本节点
    for (uint8_t i = 0; i < NUM_NODES; ++i) {
        if (i == g_node_state.node_id) continue; // 不发给自己
        send_message_to_node(i, &pp_msg);
    }

    // 主节点自己也进入Prepared状态 (因为自己发了PRE-PREPARE)
    // 并且为自己增加一个PREPARE计数
    log_entry->prepare_count++;
    log_entry->received_prepare[g_node_state.node_id] = true;
    printf("[Node %u] (主节点) 自身PREPARE计数: %d\n", g_node_state.node_id, log_entry->prepare_count);

    // 检查是否达到Prepared状态 (主节点也需要收集PREPARE)
    if (log_entry->prepare_count >= (2 * FAULTY_NODES)) { // 2f个PREPARE (不包括自己)
        mark_as_prepared(log_entry);
        // 如果达到Prepared状态,主节点也需要广播COMMIT消息
        PBFTMessage commit_msg;
        memset(&commit_msg, 0, sizeof(PBFTMessage));
        commit_msg.common_header.type = MSG_TYPE_COMMIT;
        commit_msg.common_header.view_id = g_node_state.current_view;
        commit_msg.common_header.sequence_num = current_seq_num;
        commit_msg.common_header.sender_id = g_node_state.node_id;
        memcpy(commit_msg.payload.commit.request_digest, req_msg->digest, DIGEST_SIZE);

        for (uint8_t i = 0; i < NUM_NODES; ++i) {
            send_message_to_node(i, &commit_msg);
        }
        // 主节点自己也增加一个COMMIT计数
        log_entry->commit_count++;
        log_entry->received_commit[g_node_state.node_id] = true;
        printf("[Node %u] (主节点) 自身COMMIT计数: %d\n", g_node_state.node_id, log_entry->commit_count);
    }
}

5.2 预准备阶段 (副本节点 Backup)

流程:

  1. 接收 PRE-PREPARE 消息: 副本节点(Backup)收到主节点发来的PRE-PREPARE消息。

  2. 验证 PRE-PREPARE 消息:

    • 视图ID v 是否与当前视图匹配。

    • 序列号 n 是否在有效范围内(h < n < H)。

    • 消息摘要 d 是否与原始请求 m 的摘要匹配。

    • 发送者是否是当前视图的主节点。

    • 检查是否已经收到过相同 v, n 但不同 dPRE-PREPARE 消息(防止主节点作恶)。

  3. 存储到日志: 如果验证通过,将 PRE-PREPARE 消息及其对应的客户端请求存储到自己的消息日志中。

  4. 构建 PREPARE 消息: 包含视图ID v、序列号 n、请求摘要 d 和自己的节点ID i

  5. 广播 PREPARE 消息:PREPARE 消息发送给所有其他节点(包括主节点)。

C语言代码:node.c 中添加副本节点处理逻辑

// node.c 中 handle_pre_prepare_msg 函数实现
/**
 * @brief 处理PRE-PREPARE消息 (仅副本节点调用)
 * @param pp_msg PRE-PREPARE消息
 */
void handle_pre_prepare_msg(const PrePrepareMsg* pp_msg) {
    printf("[Node %u] (副本节点) 收到来自主节点 %u 的PRE-PREPARE消息 (视图 %u, 序列号 %u)\n",
           g_node_state.node_id, pp_msg->common_header.sender_id, pp_msg->common_header.view_id, pp_msg->common_header.sequence_num);

    // 1. 验证PRE-PREPARE消息
    // 验证视图ID是否匹配
    if (pp_msg->common_header.view_id != g_node_state.current_view) {
        fprintf(stderr, "[Node %u] 错误: PRE-PREPARE视图ID不匹配 (期望 %u, 实际 %u)。\n",
                g_node_state.node_id, g_node_state.current_view, pp_msg->common_header.view_id);
        // 实际PBFT会触发视图切换
        return;
    }
    // 验证发送者是否是当前视图的主节点
    if (pp_msg->common_header.sender_id != (g_node_state.current_view % NUM_NODES)) {
        fprintf(stderr, "[Node %u] 错误: PRE-PREPARE发送者不是当前主节点 (期望 %u, 实际 %u)。\n",
                g_node_state.node_id, (g_node_state.current_view % NUM_NODES), pp_msg->common_header.sender_id);
        // 实际PBFT会触发视图切换
        return;
    }
    // 验证序列号是否在有效范围内 (简化:这里只检查大于已执行的序列号)
    if (pp_msg->common_header.sequence_num <= g_node_state.last_executed_seq_num) {
        fprintf(stderr, "[Node %u] 错误: PRE-PREPARE序列号过低 (序列号 %u <= 已执行 %u)。\n",
                g_node_state.node_id, pp_msg->common_header.sequence_num, g_node_state.last_executed_seq_num);
        return;
    }
    // 验证消息摘要是否正确 (这里依赖简化哈希)
    uint8_t calculated_digest[DIGEST_SIZE];
    calculate_digest(pp_msg->operation, strlen(pp_msg->operation), calculated_digest);
    if (memcmp(calculated_digest, pp_msg->request_digest, DIGEST_SIZE) != 0) {
        fprintf(stderr, "[Node %u] 错误: PRE-PREPARE摘要不匹配!\n", g_node_state.node_id);
        return;
    }

    // 2. 存储到消息日志
    MessageLogEntry* log_entry = get_or_create_log_entry(pp_msg->common_header.view_id,
                                                         pp_msg->common_header.sequence_num,
                                                         pp_msg->request_digest);
    // 检查是否已经收到过相同 v, n 但不同 d 的 PRE-PREPARE 消息 (防止主节点作恶)
    if (log_entry->pre_prepare_msg.common_header.type == MSG_TYPE_PRE_PREPARE &&
        memcmp(log_entry->pre_prepare_msg.payload.pre_prepare.request_digest, pp_msg->request_digest, DIGEST_SIZE) != 0) {
        fprintf(stderr, "[Node %u] 警告: 收到冲突的PRE-PREPARE消息 (视图 %u, 序列号 %u),触发视图切换!\n",
                g_node_state.node_id, pp_msg->common_header.view_id, pp_msg->common_header.sequence_num);
        // 实际PBFT会立刻触发视图切换
        return;
    }
    memcpy(&log_entry->pre_prepare_msg, pp_msg, sizeof(PrePrepareMsg)); // 存储PRE-PREPARE消息
    // 存储原始客户端请求 (从PRE-PREPARE中提取)
    log_entry->original_client_request.common_header.type = MSG_TYPE_CLIENT_REQUEST;
    log_entry->original_client_request.common_header.sender_id = pp_msg->client_id; // 客户端ID作为发送者
    log_entry->original_client_request.payload.client_request.client_id = pp_msg->client_id;
    log_entry->original_client_request.payload.client_request.timestamp = pp_msg->timestamp;
    strncpy(log_entry->original_client_request.payload.client_request.operation, pp_msg->operation, MAX_MSG_CONTENT_SIZE - 1);
    memcpy(log_entry->original_client_request.payload.client_request.digest, pp_msg->request_digest, DIGEST_SIZE);


    // 3. 构建 PREPARE 消息
    PBFTMessage p_msg;
    memset(&p_msg, 0, sizeof(PBFTMessage));
    p_msg.common_header.type = MSG_TYPE_PREPARE;
    p_msg.common_header.view_id = g_node_state.current_view;
    p_msg.common_header.sequence_num = pp_msg->common_header.sequence_num;
    p_msg.common_header.sender_id = g_node_state.node_id;
    memcpy(p_msg.payload.prepare.request_digest, pp_msg->request_digest, DIGEST_SIZE);

    // 4. 广播 PREPARE 消息给所有节点
    for (uint8_t i = 0; i < NUM_NODES; ++i) {
        send_message_to_node(i, &p_msg);
    }

    // 副本节点自己也增加一个PREPARE计数
    log_entry->prepare_count++;
    log_entry->received_prepare[g_node_state.node_id] = true;
    printf("[Node %u] 自身PREPARE计数: %d\n", g_node_state.node_id, log_entry->prepare_count);
}

5.3 准备阶段 (所有节点)

流程:

  1. 收集 PREPARE 消息: 所有节点(包括主节点和副本节点)都会接收来自其他节点的PREPARE消息。

  2. 验证 PREPARE 消息:

    • 视图ID v、序列号 n、摘要 d 是否与当前正在处理的请求一致。

    • 发送者ID i 是否合法。

    • 检查是否重复收到某个节点的PREPARE消息。

  3. 更新消息日志: 记录收到PREPARE消息的节点ID,并增加prepare_count

  4. 判断是否达到 Prepared 状态:prepare_count达到 2f 个(包括自己发送的)有效的PREPARE消息时,节点进入Prepared状态。

  5. 构建 COMMIT 消息: 如果进入Prepared状态,节点会构建 COMMIT 消息,包含视图ID v、序列号 n、请求摘要 d 和自己的节点ID i

  6. 广播 COMMIT 消息:COMMIT 消息发送给所有其他节点。

C语言代码:node.c 中添加处理PREPARE消息的逻辑

// node.c 中 handle_prepare_msg 函数实现
/**
 * @brief 处理PREPARE消息 (所有节点调用)
 * @param p_msg PREPARE消息
 */
void handle_prepare_msg(const PrepareMsg* p_msg) {
    printf("[Node %u] 收到来自 Node %u 的PREPARE消息 (视图 %u, 序列号 %u)\n",
           g_node_state.node_id, p_msg->common_header.sender_id, p_msg->common_header.view_id, p_msg->common_header.sequence_num);

    // 1. 验证PREPARE消息
    // 视图ID、序列号是否匹配当前处理的请求 (简化:这里只检查视图ID)
    if (p_msg->common_header.view_id != g_node_state.current_view) {
        fprintf(stderr, "[Node %u] 错误: PREPARE视图ID不匹配 (期望 %u, 实际 %u)。\n",
                g_node_state.node_id, g_node_state.current_view, p_msg->common_header.view_id);
        return;
    }
    // 检查发送者ID是否合法
    if (p_msg->common_header.sender_id >= NUM_NODES) {
        fprintf(stderr, "[Node %u] 错误: PREPARE发送者ID非法 %u。\n", g_node_state.node_id, p_msg->common_header.sender_id);
        return;
    }

    // 2. 更新消息日志
    MessageLogEntry* log_entry = get_or_create_log_entry(p_msg->common_header.view_id,
                                                         p_msg->common_header.sequence_num,
                                                         p_msg->request_digest);

    // 检查是否已经收到过此节点的PREPARE消息
    pthread_mutex_lock(&g_node_state.log_mutex);
    if (log_entry->received_prepare[p_msg->common_header.sender_id]) {
        printf("[Node %u] 警告: 收到重复的PREPARE消息来自 Node %u (视图 %u, 序列号 %u)。\n",
               g_node_state.node_id, p_msg->common_header.sender_id, p_msg->common_header.view_id, p_msg->common_header.sequence_num);
        pthread_mutex_unlock(&g_node_state.log_mutex);
        return;
    }
    log_entry->received_prepare[p_msg->common_header.sender_id] = true;
    log_entry->prepare_count++;
    printf("[Node %u] 消息 (视图 %u, 序列号 %u) PREPARE计数: %d\n",
           g_node_state.node_id, log_entry->view_id, log_entry->sequence_num, log_entry->prepare_count);

    // 3. 判断是否达到Prepared状态
    // PBFT要求收集 2f 个PREPARE消息 (包括自己发送的) 才能进入Prepared状态
    if (!log_entry->is_prepared && log_entry->prepare_count >= (2 * FAULTY_NODES + 1)) { // 2f+1
        mark_as_prepared(log_entry);

        // 4. 构建 COMMIT 消息并广播
        PBFTMessage commit_msg;
        memset(&commit_msg, 0, sizeof(PBFTMessage));
        commit_msg.common_header.type = MSG_TYPE_COMMIT;
        commit_msg.common_header.view_id = g_node_state.current_view;
        commit_msg.common_header.sequence_num = log_entry->sequence_num;
        commit_msg.common_header.sender_id = g_node_state.node_id;
        memcpy(commit_msg.payload.commit.request_digest, log_entry->request_digest, DIGEST_SIZE);

        for (uint8_t i = 0; i < NUM_NODES; ++i) {
            send_message_to_node(i, &commit_msg);
        }
        // 节点自己也增加一个COMMIT计数
        log_entry->commit_count++;
        log_entry->received_commit[g_node_state.node_id] = true;
        printf("[Node %u] 自身COMMIT计数: %d\n", g_node_state.node_id, log_entry->commit_count);
    }
    pthread_mutex_unlock(&g_node_state.log_mutex);
}

5.4 提交阶段 (所有节点)

流程:

  1. 收集 COMMIT 消息: 所有节点都会接收来自其他节点的COMMIT消息。

  2. 验证 COMMIT 消息:

    • 视图ID v、序列号 n、摘要 d 是否与当前正在处理的请求一致。

    • 发送者ID i 是否合法。

    • 检查是否重复收到某个节点的COMMIT消息。

  3. 更新消息日志: 记录收到COMMIT消息的节点ID,并增加commit_count

  4. 判断是否达到 Committed 状态:commit_count达到 2f+1 个(包括自己发送的)有效的COMMIT消息时,节点进入Committed状态。

  5. 执行操作: 如果进入Committed状态,节点就可以安全地执行请求(更新状态机)。

  6. 构建 REPLY 消息: 包含视图ID v、序列号 n、执行结果 r 和自己的节点ID i

  7. 发送 REPLY 消息:REPLY 消息发送给客户端。

  8. 清理日志: 如果执行的序列号是当前已执行的最高序列号,则更新低水位线,并清理旧的日志条目。

C语言代码:node.c 中添加处理COMMIT消息的逻辑

// node.c 中 handle_commit_msg 函数实现
/**
 * @brief 处理COMMIT消息 (所有节点调用)
 * @param c_msg COMMIT消息
 */
void handle_commit_msg(const CommitMsg* c_msg) {
    printf("[Node %u] 收到来自 Node %u 的COMMIT消息 (视图 %u, 序列号 %u)\n",
           g_node_state.node_id, c_msg->common_header.sender_id, c_msg->common_header.view_id, c_msg->common_header.sequence_num);

    // 1. 验证COMMIT消息
    // 视图ID、序列号是否匹配当前处理的请求 (简化:只检查视图ID)
    if (c_msg->common_header.view_id != g_node_state.current_view) {
        fprintf(stderr, "[Node %u] 错误: COMMIT视图ID不匹配 (期望 %u, 实际 %u)。\n",
                g_node_state.node_id, g_node_state.current_view, c_msg->common_header.view_id);
        return;
    }
    // 检查发送者ID是否合法
    if (c_msg->common_header.sender_id >= NUM_NODES) {
        fprintf(stderr, "[Node %u] 错误: COMMIT发送者ID非法 %u。\n", g_node_state.node_id, c_msg->common_header.sender_id);
        return;
    }

    // 2. 更新消息日志
    MessageLogEntry* log_entry = get_or_create_log_entry(c_msg->common_header.view_id,
                                                         c_msg->common_header.sequence_num,
                                                         c_msg->request_digest);

    // 检查是否已经收到过此节点的COMMIT消息
    pthread_mutex_lock(&g_node_state.log_mutex);
    if (log_entry->received_commit[c_msg->common_header.sender_id]) {
        printf("[Node %u] 警告: 收到重复的COMMIT消息来自 Node %u (视图 %u, 序列号 %u)。\n",
               g_node_state.node_id, c_msg->common_header.sender_id, c_msg->common_header.view_id, c_msg->common_header.sequence_num);
        pthread_mutex_unlock(&g_node_state.log_mutex);
        return;
    }
    log_entry->received_commit[c_msg->common_header.sender_id] = true;
    log_entry->commit_count++;
    printf("[Node %u] 消息 (视图 %u, 序列号 %u) COMMIT计数: %d\n",
           g_node_state.node_id, log_entry->view_id, log_entry->sequence_num, log_entry->commit_count);

    // 3. 判断是否达到Committed状态
    // PBFT要求收集 2f+1 个COMMIT消息 (包括自己发送的) 才能进入Committed状态
    if (!log_entry->is_committed && log_entry->commit_count >= (2 * FAULTY_NODES + 1)) {
        mark_as_committed(log_entry);

        // 4. 执行操作 (如果尚未执行)
        if (!log_entry->is_executed) {
            execute_operation(log_entry); // 执行操作并更新last_executed_seq_num
            clean_old_log_entries(); // 清理旧日志
        }

        // 5. 构建 REPLY 消息并发送给客户端
        // 只有当PBFT节点执行完操作后,才会向客户端发送REPLY
        PBFTMessage reply_msg;
        memset(&reply_msg, 0, sizeof(PBFTMessage));
        reply_msg.common_header.type = MSG_TYPE_REPLY;
        reply_msg.common_header.view_id = g_node_state.current_view;
        reply_msg.common_header.sequence_num = log_entry->sequence_num;
        reply_msg.common_header.sender_id = g_node_state.node_id;

        reply_msg.payload.reply.client_id = log_entry->original_client_request.payload.client_request.client_id;
        reply_msg.payload.reply.timestamp = log_entry->original_client_request.payload.client_request.timestamp;
        // 简化:执行结果为“OK”
        snprintf(reply_msg.payload.reply.result, MAX_MSG_CONTENT_SIZE, "OK for op '%s'", log_entry->original_client_request.payload.client_request.operation);

        // 客户端连接是临时的,由客户端主动连接,这里需要重新建立连接或使用预存的客户端连接
        // 为了简化,我们假设客户端会监听一个固定的端口,节点主动连接过去发送回复
        int client_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (client_sock_fd == -1) {
            perror("[Node] 创建客户端回复套接字失败");
            pthread_mutex_unlock(&g_node_state.log_mutex);
            return;
        }

        struct sockaddr_in client_addr;
        memset(&client_addr, 0, sizeof(client_addr));
        client_addr.sin_family = AF_INET;
        client_addr.sin_port = htons(CLIENT_PORT + reply_msg.payload.reply.client_id); // 连接到客户端的监听端口
        if (inet_pton(AF_INET, "127.0.0.1", &client_addr.sin_addr) <= 0) {
            perror("[Node] 无效的客户端IP地址");
            close(client_sock_fd);
            pthread_mutex_unlock(&g_node_state.log_mutex);
            return;
        }

        printf("[Node %u] 尝试连接客户端 %u (端口: %d) 发送回复...\n", g_node_state.node_id, reply_msg.payload.reply.client_id, CLIENT_PORT + reply_msg.payload.reply.client_id);
        if (connect(client_sock_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) == -1) {
            perror("[Node] 连接客户端失败,无法发送回复");
            close(client_sock_fd);
            pthread_mutex_unlock(&g_node_state.log_mutex);
            return;
        }
        printf("[Node %u] 成功连接客户端 %u,发送回复。\n", g_node_state.node_id, reply_msg.payload.reply.client_id);

        size_t total_msg_len = sizeof(ReplyMsg);
        ssize_t bytes_sent = send(client_sock_fd, &reply_msg, total_msg_len, 0);
        if (bytes_sent == -1) {
            perror("[Node] 发送回复失败");
        } else if (bytes_sent != total_msg_len) {
            fprintf(stderr, "[Node %u] 警告: 发送回复长度不匹配!期望 %zu, 实际 %zd。\n", g_node_state.node_id, total_msg_len, bytes_sent);
        } else {
            printf("[Node %u] 成功发送回复给客户端 %u。\n", g_node_state.node_id, reply_msg.payload.reply.client_id);
        }
        close(client_sock_fd);
    }
    pthread_mutex_unlock(&g_node_state.log_mutex);
}

5.5 回复阶段 (客户端)

流程:

  1. 收集 REPLY 消息: 客户端会接收来自PBFT节点的REPLY消息。

  2. 验证 REPLY 消息: 检查视图ID、序列号、客户端ID、时间戳是否与自己发出的请求匹配。

  3. 判断是否达到 f+1 个相同回复: 客户端会等待收集到 f+1 个来自不同节点相同 REPLY 消息(结果一致)。

  4. 确认请求完成: 一旦收集到足够的相同回复,客户端就确认其请求已在分布式系统中达成共识并执行。

C语言代码:client.c 中添加回复计数和验证逻辑

// client.c 中添加客户端状态扩展
// 客户端状态信息
typedef struct {
    uint8_t client_id;
    // 存储已发送请求的记录,以及等待的回复
    // 简化:这里只记录最近一次请求的信息
    uint32_t last_sent_timestamp;
    uint8_t last_sent_digest[DIGEST_SIZE];
    char last_sent_operation[MAX_MSG_CONTENT_SIZE];

    // 收集到的回复
    // 实际需要一个更复杂的数据结构来存储不同请求的回复,并进行去重和计数
    // 这里简化为只处理一个请求的回复
    int reply_count;
    bool received_reply[NUM_NODES]; // 标记是否收到某个节点的回复
    // 存储收到的第一个有效回复的结果,用于比较
    char first_valid_reply_result[MAX_MSG_CONTENT_SIZE];
    pthread_mutex_t reply_mutex; // 保护回复计数的互斥锁
} ClientState;

ClientState g_client_state; // 全局客户端状态

// client.c 中 main 函数初始化互斥锁
int main(int argc, char* argv[]) {
    // ... (省略其他初始化代码)

    pthread_mutex_init(&g_client_state.reply_mutex, NULL); // 初始化互斥锁

    // ... (省略其他代码)
    
    pthread_mutex_destroy(&g_client_state.reply_mutex); // 销毁互斥锁
    return 0;
}

// client.c 中 send_client_request 函数修改
void send_client_request(const char* operation) {
    // ... (省略构建消息代码)

    // 存储最近一次发送请求的信息
    pthread_mutex_lock(&g_client_state.reply_mutex);
    g_client_state.last_sent_timestamp = client_msg.payload.client_request.timestamp;
    memcpy(g_client_state.last_sent_digest, client_msg.payload.client_request.digest, DIGEST_SIZE);
    strncpy(g_client_state.last_sent_operation, client_msg.payload.client_request.operation, MAX_MSG_CONTENT_SIZE - 1);
    g_client_state.last_sent_operation[MAX_MSG_CONTENT_SIZE - 1] = '\0';
    g_client_state.reply_count = 0; // 重置回复计数
    for (int i = 0; i < NUM_NODES; ++i) {
        g_client_state.received_reply[i] = false;
    }
    memset(g_client_state.first_valid_reply_result, 0, MAX_MSG_CONTENT_SIZE); // 清空结果
    pthread_mutex_unlock(&g_client_state.reply_mutex);

    // ... (省略发送消息代码)
}

// client.c 中 receive_reply_thread 函数修改
void *receive_reply_thread(void *arg) {
    // ... (省略接收消息头部和payload的代码)

    printf("[Client %u] 收到来自 Node %u 的回复:\n", g_client_state.client_id, reply_msg.common_header.sender_id);
    print_message(&reply_msg);

    // --- 客户端回复验证逻辑 ---
    pthread_mutex_lock(&g_client_state.reply_mutex);
    // 1. 验证回复是否与最近发送的请求匹配
    if (reply_msg.payload.reply.client_id != g_client_state.client_id ||
        reply_msg.payload.reply.timestamp != g_client_state.last_sent_timestamp) {
        fprintf(stderr, "[Client %u] 警告: 收到不匹配的回复 (客户端ID或时间戳不符)。\n", g_client_state.client_id);
        pthread_mutex_unlock(&g_client_state.reply_mutex);
        close(node_sock_fd);
        return NULL;
    }

    // 2. 检查是否重复收到此节点的回复
    if (g_client_state.received_reply[reply_msg.common_header.sender_id]) {
        printf("[Client %u] 警告: 收到重复的回复来自 Node %u。\n", g_client_state.client_id, reply_msg.common_header.sender_id);
        pthread_mutex_unlock(&g_client_state.reply_mutex);
        close(node_sock_fd);
        return NULL;
    }

    // 3. 首次收到有效回复,记录结果
    if (g_client_state.reply_count == 0) {
        strncpy(g_client_state.first_valid_reply_result, reply_msg.payload.reply.result, MAX_MSG_CONTENT_SIZE - 1);
        g_client_state.first_valid_reply_result[MAX_MSG_CONTENT_SIZE - 1] = '\0';
    } else {
        // 后续回复,检查结果是否一致
        if (strcmp(g_client_state.first_valid_reply_result, reply_msg.payload.reply.result) != 0) {
            fprintf(stderr, "[Client %u] 警告: 收到不一致的回复结果!\n", g_client_state.client_id);
            // 实际PBFT中,客户端会等待 f+1 个相同结果的回复
            // 如果收到不一致的回复,可能表明有拜占庭节点,需要继续等待或触发视图切换
            pthread_mutex_unlock(&g_client_state.reply_mutex);
            close(node_sock_fd);
            return NULL;
        }
    }

    // 4. 增加回复计数
    g_client_state.received_reply[reply_msg.common_header.sender_id] = true;
    g_client_state.reply_count++;
    printf("[Client %u] 当前收集到 %d 个有效回复。\n", g_client_state.client_id, g_client_state.reply_count);

    // 5. 判断是否达到 f+1 个相同回复
    if (g_client_state.reply_count >= (FAULTY_NODES + 1)) { // f+1 个回复
        printf("\n[Client %u] 请求 '%s' 已达成共识并执行!最终结果: '%s'\n",
               g_client_state.client_id, g_client_state.last_sent_operation, g_client_state.first_valid_reply_result);
        // 可以在这里重置客户端状态,准备发送下一个请求
        g_client_state.reply_count = 0; // 重置计数,以便处理下一个请求
    }
    pthread_mutex_unlock(&g_client_state.reply_mutex);
    close(node_sock_fd);
    return NULL;
}

更新后的 common.h (完整版):

#ifndef COMMON_H
#define COMMON_H

#include <stdint.h> // For uint32_t, uint8_t
#include <stdbool.h> // For bool
#include <pthread.h> // For pthread_mutex_t

// 定义PBFT系统中的节点总数 (N) 和可容忍的拜占庭节点数 (f)
// N = 3f + 1
#define NUM_NODES 4 // 例如,4个节点,f = (4-1)/3 = 1,可容忍1个拜占庭节点
#define FAULTY_NODES ((NUM_NODES - 1) / 3) // f

// 定义消息摘要长度 (例如,SHA256摘要是32字节)
#define DIGEST_SIZE 32
// 定义最大消息内容长度
#define MAX_MSG_CONTENT_SIZE 256
// 定义每个节点的监听端口
#define BASE_NODE_PORT 8000 // Node 0 监听 8000, Node 1 监听 8001, ...
// 定义客户端监听端口 (用于接收Reply)
#define CLIENT_PORT 9000

// 定义PBFT消息类型
typedef enum {
    MSG_TYPE_CLIENT_REQUEST, // 客户端请求
    MSG_TYPE_PRE_PREPARE,    // 预准备消息
    MSG_TYPE_PREPARE,        // 准备消息
    MSG_TYPE_COMMIT,         // 提交消息
    MSG_TYPE_REPLY,          // 回复消息
    MSG_TYPE_VIEW_CHANGE,    // 视图切换消息
    MSG_TYPE_NEW_VIEW,       // 新视图消息
    MSG_TYPE_HEARTBEAT       // 心跳消息 (用于检测节点存活)
} MessageType;

// 消息公共头部
typedef struct {
    MessageType type;      // 消息类型
    uint32_t view_id;      // 视图编号
    uint32_t sequence_num; // 序列号
    uint8_t sender_id;     // 发送者节点ID
} MessageHeader;

// 客户端请求消息 (由客户端发送给主节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_CLIENT_REQUEST)
    uint8_t client_id;    // 客户端ID
    uint32_t timestamp;   // 时间戳 (用于防止重放攻击)
    char operation[MAX_MSG_CONTENT_SIZE]; // 客户端请求的操作内容 (例如 "transfer 100 to Bob")
    uint8_t digest[DIGEST_SIZE]; // 请求内容的摘要 (SHA256(operation))
} ClientRequest;

// 预准备消息 (由主节点广播给副本节点)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_PRE_PREPARE)
    uint8_t client_id;    // 原始客户端ID
    uint32_t timestamp;   // 原始客户端请求时间戳
    char operation[MAX_MSG_CONTENT_SIZE]; // 原始客户端请求的操作内容
    uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
    // 实际PBFT中,这里通常只包含原始请求的摘要,而不是整个操作内容
    // 为了简化仿真,我们直接包含操作内容,方便调试
} PrePrepareMsg;

// 准备消息 (由所有节点相互发送)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_PREPARE)
    uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
} PrepareMsg;

// 提交消息 (由所有节点相互发送)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_COMMIT)
    uint8_t request_digest[DIGEST_SIZE]; // 原始客户端请求的摘要
} CommitMsg;

// 回复消息 (由节点发送给客户端)
typedef struct {
    MessageHeader header; // 通用消息头 (type = MSG_TYPE_REPLY)
    uint8_t client_id;    // 客户端ID
    uint32_t timestamp;   // 时间戳 (来自原始请求)
    char result[MAX_MSG_CONTENT_SIZE]; // 执行请求后的结果 (例如 "OK" 或 "Error")
} ReplyMsg;

// 统一消息结构体 (使用union节省内存,一次只存储一种消息)
// 方便网络传输和处理
typedef struct {
    MessageHeader common_header; // 所有消息共有的头部信息
    union {
        ClientRequest client_request;
        PrePrepareMsg pre_prepare;
        PrepareMsg prepare;
        CommitMsg commit;
        ReplyMsg reply;
        // 视图切换和新视图消息暂时省略,后续添加
    } payload; // 消息的具体内容
} PBFTMessage;

// 定义PBFT处理阶段
typedef enum {
    PHASE_IDLE,
    PHASE_PRE_PREPARE, // 收到PRE-PREPARE (副本节点) 或发送PRE-PREPARE (主节点)
    PHASE_PREPARE,     // 收到或发送PREPARE
    PHASE_COMMIT,      // 收到或发送COMMIT
    PHASE_REPLY        // 收到或发送REPLY
} PBFTPhase;

// PBFT消息日志条目
typedef struct MessageLogEntry {
    uint32_t view_id;
    uint32_t sequence_num;
    uint8_t request_digest[DIGEST_SIZE];
    PBFTMessage original_client_request; // 原始客户端请求的副本 (用于执行)
    PBFTMessage pre_prepare_msg;         // 存储收到的PRE-PREPARE消息
    // 存储收到的PREPARE和COMMIT消息的集合 (这里简化为计数,实际应存储消息本身)
    bool received_prepare[NUM_NODES]; // 标记是否收到某个节点的PREPARE消息
    bool received_commit[NUM_NODES];  // 标记是否收到某个节点的COMMIT消息
    int prepare_count;                // 收集到的PREPARE消息数量
    int commit_count;                 // 收集到的COMMIT消息数量
    bool is_prepared;                 // 是否已达到Prepared状态
    bool is_committed;                // 是否已达到Committed状态
    bool is_executed;                 // 是否已执行
    struct MessageLogEntry *next;     // 链表指针
} MessageLogEntry;

// --- 节点状态信息 ---
typedef struct {
    uint8_t node_id;         // 当前节点的ID
    uint32_t current_view;   // 当前视图编号
    uint32_t last_executed_seq_num; // 上一个已执行的序列号 (低水位线)
    uint32_t next_seq_num;   // 下一个可用的序列号 (主节点分配)
    bool is_primary;         // 是否是主节点
    PBFTPhase current_phase; // 当前节点所处的PBFT阶段

    MessageLogEntry *message_log_head; // 消息日志链表头
    pthread_mutex_t log_mutex;         // 保护消息日志的互斥锁
    // 实际PBFT节点状态会复杂得多,包括高水位、检查点、视图切换相关消息等
} NodeState;


// --- 辅助函数声明 ---
// 计算消息摘要 (这里简化为简单的哈希,实际应使用SHA256等)
void calculate_digest(const char* data, size_t len, uint8_t* digest_out);

// 打印消息内容 (用于调试)
void print_message(const PBFTMessage* msg);

// 查找或创建消息日志条目
MessageLogEntry* get_or_create_log_entry(uint32_t view_id, uint32_t seq_num, const uint8_t* digest);
// 标记日志条目为Prepared状态
void mark_as_prepared(MessageLogEntry* entry);
// 标记日志条目为Committed状态
void mark_as_committed(MessageLogEntry* entry);
// 执行操作并标记为Executed状态
void execute_operation(MessageLogEntry* entry);
// 清理旧的日志条目 (基于水位线,简化)
void clean_old_log_entries();

#endif // COMMON_H

更新后的 node.c (完整版):

#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>     // For close(), read(), write(), sleep()
#include <arpa/inet.h>  // For inet_ntop(), inet_pton()
#include <sys/socket.h> // For socket(), bind(), listen(), accept(), connect(), send(), recv()
#include <pthread.h>    // For pthread_create(), pthread_join(), pthread_mutex_t
#include <errno.h>      // For errno

// --- 全局节点状态 ---
NodeState g_node_state;

// --- 线程参数结构体 ---
typedef struct {
    int client_sock_fd; // 客户端连接的套接字
    uint8_t remote_node_id; // 远程节点的ID
} ThreadArgs;

// --- 全局套接字数组,用于存储与其他节点的连接 ---
// conn_sockets[i] 存储与节点i的连接
// 如果 conn_sockets[i] > 0,表示已连接
int conn_sockets[NUM_NODES];

// --- 互斥锁,保护全局套接字数组 ---
pthread_mutex_t g_conn_sockets_mutex = PTHREAD_MUTEX_INITIALIZER;

// --- 辅助函数声明 (已在common.h中声明,这里是实现) ---
void *handle_incoming_connection(void *arg); // 处理传入连接的线程函数
void connect_to_peers(uint8_t my_id); // 连接到其他节点
void send_message_to_node(uint8_t target_node_id, const PBFTMessage* msg); // 发送消息给指定节点
void *recv_message_thread(void *arg); // 接收消息的线程函数

// --- PBFT核心处理函数声明 ---
void handle_client_request_msg(const ClientRequest* req_msg);
void handle_pre_prepare_msg(const PrePrepareMsg* pp_msg);
void handle_prepare_msg(const PrepareMsg* p_msg);
void handle_commit_msg(const CommitMsg* c_msg);
// send_reply_to_client 函数在 handle_commit_msg 中直接实现,不再单独声明

/**
 * @brief 节点启动函数
 * @param node_id 当前节点的ID
 */
void start_node(uint8_t node_id) {
    g_node_state.node_id = node_id;
    g_node_state.current_view = 0; // 初始视图为0
    g_node_state.last_executed_seq_num = 0; // 初始已执行序列号为0
    g_node_state.next_seq_num = 0; // 主节点从1开始分配序列号
    g_node_state.is_primary = (node_id == (g_node_state.current_view % NUM_NODES)); // 初始主节点是Node 0
    g_node_state.current_phase = PHASE_IDLE; // 初始阶段为空闲

    // 初始化消息日志链表头和互斥锁
    g_node_state.message_log_head = NULL;
    pthread_mutex_init(&g_node_state.log_mutex, NULL);

    printf("[Node %u] 启动中... 端口: %d\n", g_node_state.node_id, BASE_NODE_PORT + g_node_state.node_id);
    if (g_node_state.is_primary) {
        printf("[Node %u] 当前是主节点。\n", g_node_state.node_id);
    } else {
        printf("[Node %u] 当前是副本节点。\n", g_node_state.node_id);
    }

    // 初始化连接套接字数组
    for (int i = 0; i < NUM_NODES; ++i) {
        conn_sockets[i] = -1; // -1 表示未连接
    }

    // 1. 启动监听线程 (作为服务器端,接收来自其他节点的连接)
    int listen_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock_fd == -1) {
        perror("[Node] 创建监听套接字失败");
        exit(EXIT_FAILURE);
    }

    // 允许端口重用 (解决 Address already in use 问题)
    int optval = 1;
    if (setsockopt(listen_sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
        perror("[Node] setsockopt SO_REUSEADDR 失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(BASE_NODE_PORT + g_node_state.node_id);
    server_addr.sin_addr.s_addr = INADDR_ANY; // 监听所有可用IP地址

    if (bind(listen_sock_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {
        perror("[Node] 绑定监听地址失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(listen_sock_fd, NUM_NODES) == -1) { // 监听队列长度为节点总数
        perror("[Node] 监听失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    printf("[Node %u] 正在监听连接...\n", g_node_state.node_id);

    // 启动一个线程来处理传入连接
    pthread_t listen_thread;
    if (pthread_create(&listen_thread, NULL, handle_incoming_connection, (void*)(long)listen_sock_fd) != 0) {
        perror("[Node] 创建监听线程失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    // 2. 连接到其他节点 (作为客户端,主动建立连接)
    // 延迟一小段时间,确保其他节点的服务端已启动并监听
    sleep(1);
    connect_to_peers(g_node_state.node_id);

    // 3. 进入主循环,处理PBFT逻辑 (这里只是一个占位符)
    printf("[Node %u] 进入主循环,等待消息...\n", g_node_state.node_id);
    while (1) {
        // 在这里可以添加一些周期性任务,例如发送心跳、检查超时等
        // 目前,PBFT逻辑主要在recv_message_thread中被动触发
        sleep(5); // 避免CPU空转,每5秒检查一次
        clean_old_log_entries(); // 周期性清理日志
    }

    // 等待监听线程结束 (实际不会结束,除非程序退出)
    pthread_join(listen_thread, NULL);
    close(listen_sock_fd);
    pthread_mutex_destroy(&g_node_state.log_mutex); // 销毁互斥锁
    pthread_mutex_destroy(&g_conn_sockets_mutex);
}

/**
 * @brief 处理传入连接的线程函数 (每个传入连接会创建一个新线程来处理)
 * @param arg 监听套接字文件描述符
 */
void *handle_incoming_connection(void *arg) {
    int listen_sock_fd = (int)(long)arg;
    struct sockaddr_in remote_addr;
    socklen_t addr_len = sizeof(remote_addr);
    char remote_ip[INET_ADDRSTRLEN];

    while (1) {
        int client_sock_fd = accept(listen_sock_fd, (struct sockaddr*)&remote_addr, &addr_len);
        if (client_sock_fd == -1) {
            if (errno == EINTR) { // 被信号中断,继续
                continue;
            }
            perror("[Node] 接受连接失败");
            break;
        }

        inet_ntop(AF_INET, &(remote_addr.sin_addr), remote_ip, INET_ADDRSTRLEN);
        printf("[Node %u] 接收到来自 %s:%d 的连接。\n", g_node_state.node_id, remote_ip, ntohs(remote_addr.sin_port));

        // 识别连接的远程节点ID (通过端口号推断,非常简化)
        // 实际PBFT中,节点会通过握手协议交换ID
        uint8_t remote_node_id = (ntohs(remote_addr.sin_port) - BASE_NODE_PORT);
        if (remote_node_id >= NUM_NODES) { // 可能是客户端或其他未知连接
            printf("[Node %u] 警告: 无法识别远程节点ID %u,可能不是PBFT节点连接。\n", g_node_state.node_id, remote_node_id);
            // 对于客户端连接,我们可能需要单独处理
            // 这里为了简化,我们假设所有连接都是PBFT节点之间的连接
            // 客户端连接将在client.c中单独处理
            close(client_sock_fd);
            continue;
        }

        pthread_mutex_lock(&g_conn_sockets_mutex);
        conn_sockets[remote_node_id] = client_sock_fd; // 存储连接套接字
        pthread_mutex_unlock(&g_conn_sockets_mutex);

        printf("[Node %u] 已与 Node %u 建立连接 (作为服务器端)。\n", g_node_state.node_id, remote_node_id);

        // 为每个连接启动一个独立的线程来接收消息
        pthread_t recv_thread;
        ThreadArgs* args = (ThreadArgs*)malloc(sizeof(ThreadArgs));
        if (args == NULL) {
            perror("分配线程参数失败");
            close(client_sock_fd);
            continue;
        }
        args->client_sock_fd = client_sock_fd;
        args->remote_node_id = remote_node_id;

        if (pthread_create(&recv_thread, NULL, recv_message_thread, (void*)args) != 0) {
            perror("[Node] 创建接收消息线程失败");
            free(args);
            close(client_sock_fd);
            continue;
        }
        pthread_detach(recv_thread); // 将线程设置为分离状态,资源在结束时自动回收
    }
    return NULL;
}

/**
 * @brief 连接到其他节点的函数 (作为客户端)
 * @param my_id 当前节点的ID
 */
void connect_to_peers(uint8_t my_id) {
    for (uint8_t i = 0; i < NUM_NODES; ++i) {
        if (i == my_id) {
            continue; // 不连接自己
        }

        // 避免重复连接:如果已经有连接,则跳过
        pthread_mutex_lock(&g_conn_sockets_mutex);
        if (conn_sockets[i] != -1) {
            pthread_mutex_unlock(&g_conn_sockets_mutex);
            printf("[Node %u] 已与 Node %u 连接,跳过。\n", my_id, i);
            continue;
        }
        pthread_mutex_unlock(&g_conn_sockets_mutex);

        int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (sock_fd == -1) {
            perror("[Node] 创建连接套接字失败");
            continue;
        }

        struct sockaddr_in peer_addr;
        memset(&peer_addr, 0, sizeof(peer_addr));
        peer_addr.sin_family = AF_INET;
        peer_addr.sin_port = htons(BASE_NODE_PORT + i);
        // 连接到本地IP (127.0.0.1),因为所有节点都在同一台机器上模拟
        if (inet_pton(AF_INET, "127.0.0.1", &peer_addr.sin_addr) <= 0) {
            perror("[Node] 无效的对端IP地址");
            close(sock_fd);
            continue;
        }

        printf("[Node %u] 尝试连接到 Node %u (端口: %d)...\n", my_id, i, BASE_NODE_PORT + i);
        // 尝试连接,如果失败(如对端未启动),等待并重试
        int retry_count = 0;
        const int MAX_CONNECT_RETRIES = 5;
        while (connect(sock_fd, (struct sockaddr*)&peer_addr, sizeof(peer_addr)) == -1) {
            if (errno == ECONNREFUSED && retry_count < MAX_CONNECT_RETRIES) {
                printf("[Node %u] 连接 Node %u 失败 (连接被拒绝),重试中... (%d/%d)\n", my_id, i, retry_count + 1, MAX_CONNECT_RETRIES);
                sleep(1); // 等待1秒后重试
                retry_count++;
            } else {
                perror("[Node] 连接对端失败");
                close(sock_fd);
                sock_fd = -1; // 标记连接失败
                break;
            }
        }

        if (sock_fd != -1) {
            pthread_mutex_lock(&g_conn_sockets_mutex);
            conn_sockets[i] = sock_fd; // 存储连接套接字
            pthread_mutex_unlock(&g_conn_sockets_mutex);
            printf("[Node %u] 已与 Node %u 建立连接 (作为客户端)。\n", my_id, i);
            // 为每个连接启动一个独立的线程来接收消息
            pthread_t recv_thread;
            ThreadArgs* args = (ThreadArgs*)malloc(sizeof(ThreadArgs));
            if (args == NULL) {
                perror("分配线程参数失败");
                close(sock_fd);
                continue;
            }
            args->client_sock_fd = sock_fd;
            args->remote_node_id = i;

            if (pthread_create(&recv_thread, NULL, recv_message_thread, (void*)args) != 0) {
                perror("[Node] 创建接收消息线程失败");
                free(args);
                close(sock_fd);
                continue;
            }
            pthread_detach(recv_thread); // 将线程设置为分离状态
        }
    }
}

/**
 * @brief 接收消息的线程函数
 * @param arg 线程参数 (包含套接字文件描述符和远程节点ID)
 */
void *recv_message_thread(void *arg) {
    ThreadArgs* args = (ThreadArgs*)arg;
    int sock_fd = args->client_sock_fd;
    uint8_t remote_node_id = args->remote_node_id;
    free(args); // 释放线程参数内存

    PBFTMessage msg_buffer;
    ssize_t bytes_received;

    printf("[Node %u] 接收线程启动,监听来自 Node %u 的消息...\n", g_node_state.node_id, remote_node_id);

    while (1) {
        // 接收消息头部
        bytes_received = recv(sock_fd, &msg_buffer.common_header, sizeof(MessageHeader), MSG_WAITALL);
        if (bytes_received <= 0) {
            if (bytes_received == 0) {
                printf("[Node %u] Node %u 断开连接。\n", g_node_state.node_id, remote_node_id);
            } else {
                perror("[Node] 接收消息头部失败");
            }
            break; // 连接断开或出错
        }

        // 根据消息类型接收剩余的payload
        size_t payload_size = 0;
        switch (msg_buffer.common_header.type) {
            case MSG_TYPE_CLIENT_REQUEST: payload_size = sizeof(ClientRequest) - sizeof(MessageHeader); break;
            case MSG_TYPE_PRE_PREPARE:    payload_size = sizeof(PrePrepareMsg) - sizeof(MessageHeader); break;
            case MSG_TYPE_PREPARE:        payload_size = sizeof(PrepareMsg) - sizeof(MessageHeader); break;
            case MSG_TYPE_COMMIT:         payload_size = sizeof(CommitMsg) - sizeof(MessageHeader); break;
            case MSG_TYPE_REPLY:          payload_size = sizeof(ReplyMsg) - sizeof(MessageHeader); break;
            // 其他消息类型待添加
            default:
                fprintf(stderr, "[Node %u] 接收到未知消息类型 %u,跳过payload接收。\n", g_node_state.node_id, msg_buffer.common_header.type);
                break;
        }

        if (payload_size > 0) {
            bytes_received = recv(sock_fd, &msg_buffer.payload, payload_size, MSG_WAITALL);
            if (bytes_received <= 0) {
                if (bytes_received == 0) {
                    printf("[Node %u] Node %u 断开连接 (接收payload时)。\n", g_node_state.node_id, remote_node_id);
                } else {
                    perror("[Node] 接收消息payload失败");
                }
                break;
            }
            if (bytes_received != payload_size) {
                fprintf(stderr, "[Node %u] 接收payload长度不匹配!期望 %zu, 实际 %zd。\n", g_node_state.node_id, payload_size, bytes_received);
                break;
            }
        }

        // 成功接收一条完整的消息
        printf("[Node %u] 收到来自 Node %u 的消息:\n", g_node_state.node_id, msg_buffer.common_header.sender_id);
        print_message(&msg_buffer);

        // --- PBFT消息处理逻辑 ---
        pthread_mutex_lock(&g_node_state.log_mutex); // 保护g_node_state和其他共享资源
        switch (msg_buffer.common_header.type) {
            case MSG_TYPE_CLIENT_REQUEST:
                // 只有主节点处理客户端请求
                if (g_node_state.is_primary) {
                    handle_client_request_msg(&msg_buffer.payload.client_request);
                } else {
                    printf("[Node %u] 警告: 副本节点收到客户端请求,忽略。\n", g_node_state.node_id);
                }
                break;
            case MSG_TYPE_PRE_PREPARE:
                // 副本节点处理PRE-PREPARE消息
                if (!g_node_state.is_primary) {
                    handle_pre_prepare_msg(&msg_buffer.payload.pre_prepare);
                } else {
                    printf("[Node %u] 警告: 主节点收到PRE-PREPARE消息,忽略。\n", g_node_state.node_id);
                }
                break;
            case MSG_TYPE_PREPARE:
                // 所有节点处理PREPARE消息
                handle_prepare_msg(&msg_buffer.payload.prepare);
                break;
            case MSG_TYPE_COMMIT:
                // 所有节点处理COMMIT消息
                handle_commit_msg(&msg_buffer.payload.commit);
                break;
            case MSG_TYPE_REPLY:
                // 节点通常不处理REPLY消息,REPLY是发给客户端的
                printf("[Node %u] 收到REPLY消息,忽略 (REPLY是发给客户端的)。\n", g_node_state.node_id);
                break;
            default:
                fprintf(stderr, "[Node %u] 未知消息类型 %u,无法处理。\n", g_node_state.node_id, msg_buffer.common_header.type);
                break;
        }
        pthread_mutex_unlock(&g_node_state.log_mutex);
    }

    // 连接断开,清理资源
    pthread_mutex_lock(&g_conn_sockets_mutex);
    if (conn_sockets[remote_node_id] == sock_fd) { // 确保是当前连接
        conn_sockets[remote_node_id] = -1;
    }
    pthread_mutex_unlock(&g_conn_sockets_mutex);
    close(sock_fd);
    printf("[Node %u] 与 Node %u 的连接已关闭。\n", g_node_state.node_id, remote_node_id);
    return NULL;
}

/**
 * @brief 发送消息给指定节点
 * @param target_node_id 目标节点ID
 * @param msg 要发送的消息指针
 */
void send_message_to_node(uint8_t target_node_id, const PBFTMessage* msg) {
    pthread_mutex_lock(&g_conn_sockets_mutex);
    int sock_fd = conn_sockets[target_node_id];
    pthread_mutex_unlock(&g_conn_sockets_mutex);

    if (sock_fd == -1) {
        fprintf(stderr, "[Node %u] 错误: 无法发送消息给 Node %u,连接未建立或已断开。\n", g_node_state.node_id, target_node_id);
        return;
    }

    // 计算要发送的消息总长度
    size_t total_msg_len = sizeof(MessageHeader);
    switch (msg->common_header.type) {
        case MSG_TYPE_CLIENT_REQUEST: total_msg_len += sizeof(ClientRequest) - sizeof(MessageHeader); break;
        case MSG_TYPE_PRE_PREPARE:    total_msg_len += sizeof(PrePrepareMsg) - sizeof(MessageHeader); break;
        case MSG_TYPE_PREPARE:        total_msg_len += sizeof(PrepareMsg) - sizeof(MessageHeader); break;
        case MSG_TYPE_COMMIT:         total_msg_len += sizeof(CommitMsg) - sizeof(MessageHeader); break;
        case MSG_TYPE_REPLY:          total_msg_len += sizeof(ReplyMsg) - sizeof(MessageHeader); break;
        default:
            fprintf(stderr, "[Node %u] 错误: 尝试发送未知消息类型 %u。\n", g_node_state.node_id, msg->common_header.type);
            return;
    }

    // 发送消息
    ssize_t bytes_sent = send(sock_fd, msg, total_msg_len, 0);
    if (bytes_sent == -1) {
        perror("[Node] 发送消息失败");
    } else if (bytes_sent != total_msg_len) {
        fprintf(stderr, "[Node %u] 警告: 发送消息长度不匹配!期望 %zu, 实际 %zd。\n", g_node_state.node_id, total_msg_len, bytes_sent);
    } else {
        printf("[Node %u] 成功发送消息 (类型: %u, 序列号: %u) 给 Node %u。\n",
               g_node_state.node_id, msg->common_header.type, msg->common_header.sequence_num, target_node_id);
    }
}

/**
 * @brief 处理客户端请求消息 (仅主节点调用)
 * @param req_msg 客户端请求消息
 */
void handle_client_request_msg(const ClientRequest* req_msg) {
    printf("[Node %u] (主节点) 收到客户端 %u 的请求: '%s'\n",
           g_node_state.node_id, req_msg->client_id, req_msg->operation);

    // 1. 验证请求 (简化:这里只检查时间戳,实际需要签名验证、防重放等)
    // 假设时间戳必须大于0
    if (req_msg->timestamp == 0) {
        fprintf(stderr, "[Node %u] 错误: 无效的客户端请求 (时间戳为0)。\n", g_node_state.node_id);
        // 实际PBFT会向客户端发送错误回复
        return;
    }

    // 2. 分配序列号
    // 序列号必须是递增的,且在水位线范围内
    // 这里简化为直接递增,不考虑高水位线和检查点
    g_node_state.next_seq_num++; // 分配下一个序列号
    uint32_t current_seq_num = g_node_state.next_seq_num;
    printf("[Node %u] (主节点) 为请求分配序列号: %u\n", g_node_state.node_id, current_seq_num);

    // 3. 构建 PRE-PREPARE 消息
    PBFTMessage pp_msg;
    memset(&pp_msg, 0, sizeof(PBFTMessage));
    pp_msg.common_header.type = MSG_TYPE_PRE_PREPARE;
    pp_msg.common_header.view_id = g_node_state.current_view;
    pp_msg.common_header.sequence_num = current_seq_num;
    pp_msg.common_header.sender_id = g_node_state.node_id;

    pp_msg.payload.pre_prepare.client_id = req_msg->client_id;
    pp_msg.payload.pre_prepare.timestamp = req_msg->timestamp;
    strncpy(pp_msg.payload.pre_prepare.operation, req_msg->operation, MAX_MSG_CONTENT_SIZE - 1);
    memcpy(pp_msg.payload.pre_prepare.request_digest, req_msg->digest, DIGEST_SIZE);

    // 4. 存储到消息日志 (主节点也需要存储)
    MessageLogEntry* log_entry = get_or_create_log_entry(g_node_state.current_view, current_seq_num, req_msg->digest);
    memcpy(&log_entry->original_client_request, req_msg, sizeof(ClientRequest)); // 存储原始请求
    memcpy(&log_entry->pre_prepare_msg, &pp_msg.payload.pre_prepare, sizeof(PrePrepareMsg)); // 存储PRE-PREPARE消息

    // 5. 广播 PRE-PREPARE 消息给所有副本节点
    for (uint8_t i = 0; i < NUM_NODES; ++i) {
        if (i == g_node_state.node_id) continue; // 不发给自己
        send_message_to_node(i, &pp_msg);
    }

    // 主节点自己也进入Prepared状态 (因为自己发了PRE-PREPARE)
    // 并且为自己增加一个PREPARE计数
    log_entry->prepare_count++;
    log_entry->received_prepare[g_node_state.node_id] = true;
    printf("[Node %u] (主节点) 自身PREPARE计数: %d\n", g_node_state.node_id, log_entry->prepare_count);

    // 检查是否达到Prepared状态 (主节点也需要收集PREPARE)
    // 注意:主节点在发送PRE-PREPARE后,自身就认为进入了Prepared状态,并立即发送COMMIT
    // PBFT协议中,主节点在发送PRE-PREPARE后,会等待2f个PREPARE消息,然后进入Prepared状态
    // 这里简化为:主节点发送PRE-PREPARE后,自身PREPARE计数+1,然后等待其他PREPARE消息
    // 当自身PREPARE计数达到2f+1时,才进入Prepared状态并发送COMMIT
    // 这个逻辑与副本节点一致,简化了实现
    if (log_entry->prepare_count >= (2 * FAULTY_NODES + 1)) { // 2f+1个PREPARE
        mark_as_prepared(log_entry);
        // 如果达到Prepared状态,主节点也需要广播COMMIT消息
        PBFTMessage commit_msg;
        memset(&commit_msg, 0, sizeof(PBFTMessage));
        commit_msg.common_header.type = MSG_TYPE_COMMIT;
        commit_msg.common_header.view_id = g_node_state.current_view;
        commit_msg.common_header.sequence_num = current_seq_num;
        commit_msg.common_header.sender_id = g_node_state.node_id;
        memcpy(commit_msg.payload.commit.request_digest, req_msg->digest, DIGEST_SIZE);

        for (uint8_t i = 0; i < NUM_NODES; ++i) {
            send_message_to_node(i, &commit_msg);
        }
        // 主节点自己也增加一个COMMIT计数
        log_entry->commit_count++;
        log_entry->received_commit[g_node_state.node_id] = true;
        printf("[Node %u] (主节点) 自身COMMIT计数: %d\n", g_node_state.node_id, log_entry->commit_count);
    }
}

/**
 * @brief 处理PRE-PREPARE消息 (仅副本节点调用)
 * @param pp_msg PRE-PREPARE消息
 */
void handle_pre_prepare_msg(const PrePrepareMsg* pp_msg) {
    printf("[Node %u] (副本节点) 收到来自主节点 %u 的PRE-PREPARE消息 (视图 %u, 序列号 %u)\n",
           g_node_state.node_id, pp_msg->common_header.sender_id, pp_msg->common_header.view_id, pp_msg->common_header.sequence_num);

    // 1. 验证PRE-PREPARE消息
    // 验证视图ID是否匹配
    if (pp_msg->common_header.view_id != g_node_state.current_view) {
        fprintf(stderr, "[Node %u] 错误: PRE-PREPARE视图ID不匹配 (期望 %u, 实际 %u)。\n",
                g_node_state.node_id, g_node_state.current_view, pp_msg->common_header.view_id);
        // 实际PBFT会触发视图切换
        return;
    }
    // 验证发送者是否是当前视图的主节点
    if (pp_msg->common_header.sender_id != (g_node_state.current_view % NUM_NODES)) {
        fprintf(stderr, "[Node %u] 错误: PRE-PREPARE发送者不是当前主节点 (期望 %u, 实际 %u)。\n",
                g_node_state.node_id, (g_node_state.current_view % NUM_NODES), pp_msg->common_header.sender_id);
        // 实际PBFT会触发视图切换
        return;
    }
    // 验证序列号是否在有效范围内 (简化:这里只检查大于已执行的序列号)
    if (pp_msg->common_header.sequence_num <= g_node_state.last_executed_seq_num) {
        fprintf(stderr, "[Node %u] 错误: PRE-PREPARE序列号过低 (序列号 %u <= 已执行 %u)。\n",
                g_node_state.node_id, pp_msg->common_header.sequence_num, g_node_state.last_executed_seq_num);
        return;
    }
    // 验证消息摘要是否正确 (这里依赖简化哈希)
    uint8_t calculated_digest[DIGEST_SIZE];
    calculate_digest(pp_msg->operation, strlen(pp_msg->operation), calculated_digest);
    if (memcmp(calculated_digest, pp_msg->request_digest, DIGEST_SIZE) != 0) {
        fprintf(stderr, "[Node %u] 错误: PRE-PREPARE摘要不匹配!\n", g_node_state.node_id);
        return;
    }

    // 2. 存储到消息日志
    MessageLogEntry* log_entry = get_or_create_log_entry(pp_msg->common_header.view_id,
                                                         pp_msg->common_header.sequence_num,
                                                         pp_msg->request_digest);
    // 检查是否已经收到过相同 v, n 但不同 d 的 PRE-PREPARE 消息 (防止主节点作恶)
    if (log_entry->pre_prepare_msg.common_header.type == MSG_TYPE_PRE_PREPARE &&
        memcmp(log_entry->pre_prepare_msg.payload.pre_prepare.request_digest, pp_msg->request_digest, DIGEST_SIZE) != 0) {
        fprintf(stderr, "[Node %u] 警告: 收到冲突的PRE-PREPARE消息 (视图 %u, 序列号 %u),触发视图切换!\n",
                g_node_state.node_id, pp_msg->common_header.view_id, pp_msg->common_header.sequence_num);
        // 实际PBFT会立刻触发视图切换
        return;
    }
    memcpy(&log_entry->pre_prepare_msg, pp_msg, sizeof(PrePrepareMsg)); // 存储PRE-PREPARE消息
    // 存储原始客户端请求 (从PRE-PREPARE中提取)
    log_entry->original_client_request.common_header.type = MSG_TYPE_CLIENT_REQUEST;
    log_entry->original_client_request.common_header.sender_id = pp_msg->client_id; // 客户端ID作为发送者
    log_entry->original_client_request.payload.client_request.client_id = pp_msg->client_id;
    log_entry->original_client_request.payload.client_request.timestamp = pp_msg->timestamp;
    strncpy(log_entry->original_client_request.payload.client_request.operation, pp_msg->operation, MAX_MSG_CONTENT_SIZE - 1);
    memcpy(log_entry->original_client_request.payload.client_request.digest, pp_msg->request_digest, DIGEST_SIZE);


    // 3. 构建 PREPARE 消息
    PBFTMessage p_msg;
    memset(&p_msg, 0, sizeof(PBFTMessage));
    p_msg.common_header.type = MSG_TYPE_PREPARE;
    p_msg.common_header.view_id = g_node_state.current_view;
    p_msg.common_header.sequence_num = pp_msg->common_header.sequence_num;
    p_msg.common_header.sender_id = g_node_state.node_id;
    memcpy(p_msg.payload.prepare.request_digest, pp_msg->request_digest, DIGEST_SIZE);

    // 4. 广播 PREPARE 消息给所有节点
    for (uint8_t i = 0; i < NUM_NODES; ++i) {
        send_message_to_node(i, &p_msg);
    }

    // 副本节点自己也增加一个PREPARE计数
    log_entry->prepare_count++;
    log_entry->received_prepare[g_node_state.node_id] = true;
    printf("[Node %u] 自身PREPARE计数: %d\n", g_node_state.node_id, log_entry->prepare_count);
}

/**
 * @brief 处理PREPARE消息 (所有节点调用)
 * @param p_msg PREPARE消息
 */
void handle_prepare_msg(const PrepareMsg* p_msg) {
    printf("[Node %u] 收到来自 Node %u 的PREPARE消息 (视图 %u, 序列号 %u)\n",
           g_node_state.node_id, p_msg->common_header.sender_id, p_msg->common_header.view_id, p_msg->common_header.sequence_num);

    // 1. 验证PREPARE消息
    // 视图ID、序列号是否匹配当前处理的请求 (简化:这里只检查视图ID)
    if (p_msg->common_header.view_id != g_node_state.current_view) {
        fprintf(stderr, "[Node %u] 错误: PREPARE视图ID不匹配 (期望 %u, 实际 %u)。\n",
                g_node_state.node_id, g_node_state.current_view, p_msg->common_header.view_id);
        return;
    }
    // 检查发送者ID是否合法
    if (p_msg->common_header.sender_id >= NUM_NODES) {
        fprintf(stderr, "[Node %u] 错误: PREPARE发送者ID非法 %u。\n", g_node_state.node_id, p_msg->common_header.sender_id);
        return;
    }

    // 2. 更新消息日志
    MessageLogEntry* log_entry = get_or_create_log_entry(p_msg->common_header.view_id,
                                                         p_msg->common_header.sequence_num,
                                                         p_msg->request_digest);

    // 检查是否已经收到过此节点的PREPARE消息
    pthread_mutex_lock(&g_node_state.log_mutex); // 保护日志条目
    if (log_entry->received_prepare[p_msg->common_header.sender_id]) {
        printf("[Node %u] 警告: 收到重复的PREPARE消息来自 Node %u (视图 %u, 序列号 %u)。\n",
               g_node_state.node_id, p_msg->common_header.sender_id, p_msg->common_header.view_id, p_msg->common_header.sequence_num);
        pthread_mutex_unlock(&g_node_state.log_mutex);
        return;
    }
    log_entry->received_prepare[p_msg->common_header.sender_id] = true;
    log_entry->prepare_count++;
    printf("[Node %u] 消息 (视图 %u, 序列号 %u) PREPARE计数: %d\n",
           g_node_state.node_id, log_entry->view_id, log_entry->sequence_num, log_entry->prepare_count);

    // 3. 判断是否达到Prepared状态
    // PBFT要求收集 2f+1 个PREPARE消息 (包括自己发送的) 才能进入Prepared状态
    if (!log_entry->is_prepared && log_entry->prepare_count >= (2 * FAULTY_NODES + 1)) {
        mark_as_prepared(log_entry);

        // 4. 构建 COMMIT 消息并广播
        PBFTMessage commit_msg;
        memset(&commit_msg, 0, sizeof(PBFTMessage));
        commit_msg.common_header.type = MSG_TYPE_COMMIT;
        commit_msg.common_header.view_id = g_node_state.current_view;
        commit_msg.common_header.sequence_num = log_entry->sequence_num;
        commit_msg.common_header.sender_id = g_node_state.node_id;
        memcpy(commit_msg.payload.commit.request_digest, log_entry->request_digest, DIGEST_SIZE);

        for (uint8_t i = 0; i < NUM_NODES; ++i) {
            send_message_to_node(i, &commit_msg);
        }
        // 节点自己也增加一个COMMIT计数
        log_entry->commit_count++;
        log_entry->received_commit[g_node_state.node_id] = true;
        printf("[Node %u] 自身COMMIT计数: %d\n", g_node_state.node_id, log_entry->commit_count);
    }
    pthread_mutex_unlock(&g_node_state.log_mutex);
}

/**
 * @brief 处理COMMIT消息 (所有节点调用)
 * @param c_msg COMMIT消息
 */
void handle_commit_msg(const CommitMsg* c_msg) {
    printf("[Node %u] 收到来自 Node %u 的COMMIT消息 (视图 %u, 序列号 %u)\n",
           g_node_state.node_id, c_msg->common_header.sender_id, c_msg->common_header.view_id, c_msg->common_header.sequence_num);

    // 1. 验证COMMIT消息
    // 视图ID、序列号是否匹配当前处理的请求 (简化:只检查视图ID)
    if (c_msg->common_header.view_id != g_node_state.current_view) {
        fprintf(stderr, "[Node %u] 错误: COMMIT视图ID不匹配 (期望 %u, 实际 %u)。\n",
                g_node_state.node_id, g_node_state.current_view, c_msg->common_header.view_id);
        return;
    }
    // 检查发送者ID是否合法
    if (c_msg->common_header.sender_id >= NUM_NODES) {
        fprintf(stderr, "[Node %u] 错误: COMMIT发送者ID非法 %u。\n", g_node_state.node_id, c_msg->common_header.sender_id);
        return;
    }

    // 2. 更新消息日志
    MessageLogEntry* log_entry = get_or_create_log_entry(c_msg->common_header.view_id,
                                                         c_msg->common_header.sequence_num,
                                                         c_msg->request_digest);

    // 检查是否已经收到过此节点的COMMIT消息
    pthread_mutex_lock(&g_node_state.log_mutex); // 保护日志条目
    if (log_entry->received_commit[c_msg->common_header.sender_id]) {
        printf("[Node %u] 警告: 收到重复的COMMIT消息来自 Node %u (视图 %u, 序列号 %u)。\n",
               g_node_state.node_id, c_msg->common_header.sender_id, c_msg->common_header.view_id, c_msg->common_header.sequence_num);
        pthread_mutex_unlock(&g_node_state.log_mutex);
        return;
    }
    log_entry->received_commit[c_msg->common_header.sender_id] = true;
    log_entry->commit_count++;
    printf("[Node %u] 消息 (视图 %u, 序列号 %u) COMMIT计数: %d\n",
           g_node_state.node_id, log_entry->view_id, log_entry->sequence_num, log_entry->commit_count);

    // 3. 判断是否达到Committed状态
    // PBFT要求收集 2f+1 个COMMIT消息 (包括自己发送的) 才能进入Committed状态
    if (!log_entry->is_committed && log_entry->commit_count >= (2 * FAULTY_NODES + 1)) {
        mark_as_committed(log_entry);

        // 4. 执行操作 (如果尚未执行)
        if (!log_entry->is_executed) {
            execute_operation(log_entry); // 执行操作并更新last_executed_seq_num
            clean_old_log_entries(); // 清理旧日志
        }

        // 5. 构建 REPLY 消息并发送给客户端
        // 只有当PBFT节点执行完操作后,才会向客户端发送REPLY
        PBFTMessage reply_msg;
        memset(&reply_msg, 0, sizeof(PBFTMessage));
        reply_msg.common_header.type = MSG_TYPE_REPLY;
        reply_msg.common_header.view_id = g_node_state.current_view;
        reply_msg.common_header.sequence_num = log_entry->sequence_num;
        reply_msg.common_header.sender_id = g_node_state.node_id;

        reply_msg.payload.reply.client_id = log_entry->original_client_request.payload.client_request.client_id;
        reply_msg.payload.reply.timestamp = log_entry->original_client_request.payload.client_request.timestamp;
        // 简化:执行结果为“OK”
        snprintf(reply_msg.payload.reply.result, MAX_MSG_CONTENT_SIZE, "OK for op '%s'", log_entry->original_client_request.payload.client_request.operation);

        // 客户端连接是临时的,由客户端主动连接,这里需要重新建立连接或使用预存的客户端连接
        // 为了简化,我们假设客户端会监听一个固定的端口,节点主动连接过去发送回复
        int client_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
        if (client_sock_fd == -1) {
            perror("[Node] 创建客户端回复套接字失败");
            pthread_mutex_unlock(&g_node_state.log_mutex);
            return;
        }

        struct sockaddr_in client_addr;
        memset(&client_addr, 0, sizeof(client_addr));
        client_addr.sin_family = AF_INET;
        client_addr.sin_port = htons(CLIENT_PORT + reply_msg.payload.reply.client_id); // 连接到客户端的监听端口
        if (inet_pton(AF_INET, "127.0.0.1", &client_addr.sin_addr) <= 0) {
            perror("[Node] 无效的客户端IP地址");
            close(client_sock_fd);
            pthread_mutex_unlock(&g_node_state.log_mutex);
            return;
        }

        printf("[Node %u] 尝试连接客户端 %u (端口: %d) 发送回复...\n", g_node_state.node_id, reply_msg.payload.reply.client_id, CLIENT_PORT + reply_msg.payload.reply.client_id);
        if (connect(client_sock_fd, (struct sockaddr*)&client_addr, sizeof(client_addr)) == -1) {
            perror("[Node] 连接客户端失败,无法发送回复");
            close(client_sock_fd);
            pthread_mutex_unlock(&g_node_state.log_mutex);
            return;
        }
        printf("[Node %u] 成功连接客户端 %u,发送回复。\n", g_node_state.node_id, reply_msg.payload.reply.client_id);

        size_t total_msg_len = sizeof(ReplyMsg);
        ssize_t bytes_sent = send(client_sock_fd, &reply_msg, total_msg_len, 0);
        if (bytes_sent == -1) {
            perror("[Node] 发送回复失败");
        } else if (bytes_sent != total_msg_len) {
            fprintf(stderr, "[Node %u] 警告: 发送回复长度不匹配!期望 %zu, 实际 %zd。\n", g_node_state.node_id, total_msg_len, bytes_sent);
        } else {
            printf("[Node %u] 成功发送回复给客户端 %u。\n", g_node_state.node_id, reply_msg.payload.reply.client_id);
        }
        close(client_sock_fd);
    }
    pthread_mutex_unlock(&g_node_state.log_mutex);
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        fprintf(stderr, "用法: %s <node_id>\n", argv[0]);
        fprintf(stderr, "示例: %s 0 (启动Node 0)\n", argv[0]);
        return 1;
    }

    uint8_t node_id = (uint8_t)atoi(argv[1]);
    if (node_id >= NUM_NODES) {
        fprintf(stderr, "错误: 节点ID必须小于 %d。\n", NUM_NODES);
        return 1;
    }

    start_node(node_id);

    return 0;
}

更新后的 client.c (完整版):

#include "common.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <time.h> // For time()
#include <pthread.h> // For pthread_mutex_t

// --- 客户端状态信息 ---
typedef struct {
    uint8_t client_id;
    // 存储已发送请求的记录,以及等待的回复
    // 简化:这里只记录最近一次请求的信息
    uint32_t last_sent_timestamp;
    uint8_t last_sent_digest[DIGEST_SIZE];
    char last_sent_operation[MAX_MSG_CONTENT_SIZE];

    // 收集到的回复
    // 实际需要一个更复杂的数据结构来存储不同请求的回复,并进行去重和计数
    // 这里简化为只处理一个请求的回复
    int reply_count;
    bool received_reply[NUM_NODES]; // 标记是否收到某个节点的回复
    // 存储收到的第一个有效回复的结果,用于比较
    char first_valid_reply_result[MAX_MSG_CONTENT_SIZE];
    pthread_mutex_t reply_mutex; // 保护回复计数的互斥锁
} ClientState;

ClientState g_client_state; // 全局客户端状态

// --- 辅助函数声明 ---
void send_client_request(const char* operation);
void *receive_reply_thread(void *arg); // 接收回复的线程函数

int main(int argc, char* argv[]) {
    if (argc != 2) {
        fprintf(stderr, "用法: %s <client_id>\n", argv[0]);
        fprintf(stderr, "示例: %s 100 (启动Client 100)\n", argv[0]);
        return 1;
    }

    g_client_state.client_id = (uint8_t)atoi(argv[1]);
    printf("[Client %u] 启动中...\n", g_client_state.client_id);

    pthread_mutex_init(&g_client_state.reply_mutex, NULL); // 初始化互斥锁

    // 1. 启动一个线程来接收来自节点的回复 (作为服务器端)
    int listen_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock_fd == -1) {
        perror("[Client] 创建监听套接字失败");
        exit(EXIT_FAILURE);
    }

    int optval = 1;
    if (setsockopt(listen_sock_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
        perror("[Client] setsockopt SO_REUSEADDR 失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    struct sockaddr_in client_listen_addr;
    memset(&client_listen_addr, 0, sizeof(client_listen_addr));
    client_listen_addr.sin_family = AF_INET;
    client_listen_addr.sin_port = htons(CLIENT_PORT + g_client_state.client_id); // 每个客户端监听不同端口
    client_listen_addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(listen_sock_fd, (struct sockaddr*)&client_listen_addr, sizeof(client_listen_addr)) == -1) {
        perror("[Client] 绑定监听地址失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    if (listen(listen_sock_fd, NUM_NODES) == -1) { // 监听节点数量
        perror("[Client] 监听失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }

    printf("[Client %u] 正在监听回复 (端口: %d)...\n", g_client_state.client_id, CLIENT_PORT + g_client_state.client_id);

    pthread_t reply_recv_thread;
    if (pthread_create(&reply_recv_thread, NULL, receive_reply_thread, (void*)(long)listen_sock_fd) != 0) {
        perror("[Client] 创建回复接收线程失败");
        close(listen_sock_fd);
        exit(EXIT_FAILURE);
    }
    pthread_detach(reply_recv_thread); // 分离线程

    // 2. 发送客户端请求
    char operation_buffer[MAX_MSG_CONTENT_SIZE];
    while (1) {
        printf("\n[Client %u] 请输入要发送的操作 (例如: 'transfer 100 to Bob', 输入 'quit' 退出):\n", g_client_state.client_id);
        if (fgets(operation_buffer, sizeof(operation_buffer), stdin) == NULL) {
            continue;
        }
        operation_buffer[strcspn(operation_buffer, "\n")] = 0; // 移除换行符

        if (strcmp(operation_buffer, "quit") == 0) {
            printf("[Client %u] 客户端退出。\n", g_client_state.client_id);
            break;
        }

        send_client_request(operation_buffer);
        sleep(5); // 等待回复,给节点处理时间
    }

    close(listen_sock_fd);
    pthread_mutex_destroy(&g_client_state.reply_mutex); // 销毁互斥锁
    return 0;
}

/**
 * @brief 发送客户端请求给主节点 (简化:直接发送给Node 0)
 * @param operation 客户端操作内容
 */
void send_client_request(const char* operation) {
    PBFTMessage client_msg;
    memset(&client_msg, 0, sizeof(PBFTMessage));

    // 填充消息头部
    client_msg.common_header.type = MSG_TYPE_CLIENT_REQUEST;
    client_msg.common_header.view_id = 0; // 客户端不知道当前视图,这里简化为0
    client_msg.common_header.sequence_num = 0; // 客户端请求没有序列号,由主节点分配
    client_msg.common_header.sender_id = g_client_state.client_id;

    // 填充客户端请求内容
    client_msg.payload.client_request.client_id = g_client_state.client_id;
    client_msg.payload.client_request.timestamp = (uint32_t)time(NULL); // 使用当前时间戳
    strncpy(client_msg.payload.client_request.operation, operation, MAX_MSG_CONTENT_SIZE - 1);
    client_msg.payload.client_request.operation[MAX_MSG_CONTENT_SIZE - 1] = '\0';
    calculate_digest(client_msg.payload.client_request.operation, strlen(client_msg.payload.client_request.operation), client_msg.payload.client_request.digest);

    printf("[Client %u] 准备发送客户端请求:\n", g_client_state.client_id);
    print_message(&client_msg);

    // 存储最近一次发送请求的信息
    pthread_mutex_lock(&g_client_state.reply_mutex);
    g_client_state.last_sent_timestamp = client_msg.payload.client_request.timestamp;
    memcpy(g_client_state.last_sent_digest, client_msg.payload.client_request.digest, DIGEST_SIZE);
    strncpy(g_client_state.last_sent_operation, client_msg.payload.client_request.operation, MAX_MSG_CONTENT_SIZE - 1);
    g_client_state.last_sent_operation[MAX_MSG_CONTENT_SIZE - 1] = '\0';
    g_client_state.reply_count = 0; // 重置回复计数
    for (int i = 0; i < NUM_NODES; ++i) {
        g_client_state.received_reply[i] = false;
    }
    memset(g_client_state.first_valid_reply_result, 0, MAX_MSG_CONTENT_SIZE); // 清空结果
    pthread_mutex_unlock(&g_client_state.reply_mutex);

    // 连接到主节点 (简化:假设Node 0是主节点)
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (sock_fd == -1) {
        perror("[Client] 创建套接字失败");
        return;
    }

    struct sockaddr_in primary_node_addr;
    memset(&primary_node_addr, 0, sizeof(primary_node_addr));
    primary_node_addr.sin_family = AF_INET;
    primary_node_addr.sin_port = htons(BASE_NODE_PORT + 0); // 连接到Node 0
    if (inet_pton(AF_INET, "127.0.0.1", &primary_node_addr.sin_addr) <= 0) {
        perror("[Client] 无效的IP地址");
        close(sock_fd);
        return;
    }

    printf("[Client %u] 尝试连接到主节点 (Node 0 端口: %d)...\n", g_client_state.client_id, BASE_NODE_PORT + 0);
    if (connect(sock_fd, (struct sockaddr*)&primary_node_addr, sizeof(primary_node_addr)) == -1) {
        perror("[Client] 连接主节点失败");
        close(sock_fd);
        return;
    }
    printf("[Client %u] 成功连接到主节点 (Node 0)。\n", g_client_state.client_id);

    // 发送消息
    size_t total_msg_len = sizeof(ClientRequest);
    ssize_t bytes_sent = send(sock_fd, &client_msg, total_msg_len, 0);
    if (bytes_sent == -1) {
        perror("[Client] 发送请求失败");
    } else if (bytes_sent != total_msg_len) {
        fprintf(stderr, "[Client %u] 警告: 发送请求长度不匹配!期望 %zu, 实际 %zd。\n", g_client_state.client_id, total_msg_len, bytes_sent);
    } else {
        printf("[Client %u] 成功发送请求给主节点。\n", g_client_state.client_id);
    }

    close(sock_fd); // 发送完请求即可关闭连接
}

/**
 * @brief 接收回复的线程函数
 * @param arg 监听套接字文件描述符
 */
void *receive_reply_thread(void *arg) {
    int listen_sock_fd = (int)(long)arg;
    struct sockaddr_in remote_addr;
    socklen_t addr_len = sizeof(remote_addr);
    char remote_ip[INET_ADDRSTRLEN];

    while (1) {
        int node_sock_fd = accept(listen_sock_fd, (struct sockaddr*)&remote_addr, &addr_len);
        if (node_sock_fd == -1) {
            perror("[Client] 接受节点回复连接失败");
            break;
        }

        inet_ntop(AF_INET, &(remote_addr.sin_addr), remote_ip, INET_ADDRSTRLEN);
        printf("[Client %u] 接收到来自 Node %d 的回复连接。\n", g_client_state.client_id, ntohs(remote_addr.sin_port) - BASE_NODE_PORT);

        PBFTMessage reply_msg;
        ssize_t bytes_received;

        // 接收消息头部
        bytes_received = recv(node_sock_fd, &reply_msg.common_header, sizeof(MessageHeader), MSG_WAITALL);
        if (bytes_received <= 0) {
            perror("[Client] 接收回复头部失败");
            close(node_sock_fd);
            continue;
        }

        if (reply_msg.common_header.type != MSG_TYPE_REPLY) {
            fprintf(stderr, "[Client %u] 警告: 收到非REPLY消息类型 %u,忽略。\n", g_client_state.client_id, reply_msg.common_header.type);
            close(node_sock_fd);
            continue;
        }

        // 接收回复payload
        size_t payload_size = sizeof(ReplyMsg) - sizeof(MessageHeader);
        bytes_received = recv(node_sock_fd, &reply_msg.payload, payload_size, MSG_WAITALL);
        if (bytes_received <= 0) {
            perror("[Client] 接收回复payload失败");
            close(node_sock_fd);
            continue;
        }
        if (bytes_received != payload_size) {
            fprintf(stderr, "[Client %u] 接收回复payload长度不匹配!期望 %zu, 实际 %zd。\n", g_client_state.client_id, payload_size, bytes_received);
            close(node_sock_fd);
            continue;
        }

        printf("[Client %u] 收到来自 Node %u 的回复:\n", g_client_state.client_id, reply_msg.common_header.sender_id);
        print_message(&reply_msg);

        // --- 客户端回复验证逻辑 ---
        pthread_mutex_lock(&g_client_state.reply_mutex);
        // 1. 验证回复是否与最近发送的请求匹配
        // PBFT客户端会根据请求的摘要和时间戳来匹配回复
        // 这里简化为只匹配时间戳
        if (reply_msg.payload.reply.client_id != g_client_state.client_id ||
            reply_msg.payload.reply.timestamp != g_client_state.last_sent_timestamp) {
            fprintf(stderr, "[Client %u] 警告: 收到不匹配的回复 (客户端ID或时间戳不符)。\n", g_client_state.client_id);
            pthread_mutex_unlock(&g_client_state.reply_mutex);
            close(node_sock_fd);
            return NULL;
        }

        // 2. 检查是否重复收到此节点的回复
        if (g_client_state.received_reply[reply_msg.common_header.sender_id]) {
            printf("[Client %u] 警告: 收到重复的回复来自 Node %u。\n", g_client_state.client_id, reply_msg.common_header.sender_id);
            pthread_mutex_unlock(&g_client_state.reply_mutex);
            close(node_sock_fd);
            return NULL;
        }

        // 3. 首次收到有效回复,记录结果
        if (g_client_state.reply_count == 0) {
            strncpy(g_client_state.first_valid_reply_result, reply_msg.payload.reply.result, MAX_MSG_CONTENT_SIZE - 1);
            g_client_state.first_valid_reply_result[MAX_MSG_CONTENT_SIZE - 1] = '\0';
        } else {
            // 后续回复,检查结果是否一致
            if (strcmp(g_client_state.first_valid_reply_result, reply_msg.payload.reply.result) != 0) {
                fprintf(stderr, "[Client %u] 警告: 收到不一致的回复结果!\n", g_client_state.client_id);
                // 实际PBFT中,客户端会等待 f+1 个相同结果的回复
                // 如果收到不一致的回复,可能表明有拜占庭节点,需要继续等待或触发视图切换
                pthread_mutex_unlock(&g_client_state.reply_mutex);
                close(node_sock_fd);
                return NULL;
            }
        }

        // 4. 增加回复计数
        g_client_state.received_reply[reply_msg.common_header.sender_id] = true;
        g_client_state.reply_count++;
        printf("[Client %u] 当前收集到 %d 个有效回复。\n", g_client_state.client_id, g_client_state.reply_count);

        // 5. 判断是否达到 f+1 个相同回复
        if (g_client_state.reply_count >= (FAULTY_NODES + 1)) { // f+1 个回复
            printf("\n[Client %u] 请求 '%s' 已达成共识并执行!最终结果: '%s'\n",
                   g_client_state.client_id, g_client_state.last_sent_operation, g_client_state.first_valid_reply_result);
            // 可以在这里重置客户端状态,准备发送下一个请求
            g_client_state.reply_count = 0; // 重置计数,以便处理下一个请求
            // 实际客户端这里会通知应用层请求完成
        }
        pthread_mutex_unlock(&g_client_state.reply_mutex);
        close(node_sock_fd);
    }
    return NULL;
}

运行这个PBFT仿真器:

  1. 更新文件: 将上述所有更新后的代码分别替换掉你之前的common.hutils.cnode.cclient.c

  2. 打开WSL2终端 (或Linux终端): 进入你保存文件的目录。

  3. 重新编译: 在终端中执行 make clean 然后 make

    • 确保编译成功,没有错误。

  4. 启动节点: 打开四个独立的WSL2终端窗口。在每个窗口中分别启动一个PBFT节点:

    • 终端1: ./node 0 (Node 0 是主节点)

    • 终端2: ./node 1

    • 终端3: ./node 2

    • 终端4: ./node 3

    • 观察每个节点的输出,它们会尝试相互连接。当所有节点都启动后,你会看到它们之间建立了连接。Node 0 会显示它是主节点。

  5. 启动客户端: 打开第五个WSL2终端窗口

    • 终端5: ./client 100 (启动Client 100)

    • 客户端会尝试连接到Node 0,并提示你输入操作。

  6. 发送请求: 在客户端终端输入一个操作,例如 transfer 100 to Bob,然后按回车。

    • 观察所有终端的输出! 你会看到:

      • 客户端发送CLIENT_REQUEST给Node 0。

      • Node 0 收到CLIENT_REQUEST,分配序列号,然后广播PRE-PREPARE给Node 1, 2, 3。

      • Node 1, 2, 3 收到PRE-PREPARE,验证后广播PREPARE给所有节点(包括Node 0)。

      • 所有节点(包括Node 0)收到PREPARE消息,计数。当某个节点收集到2f+1PREPARE后,它会广播COMMIT消息。

      • 所有节点收到COMMIT消息,计数。当某个节点收集到2f+1COMMIT后,它会执行操作(打印“执行操作”),然后向客户端发送REPLY

      • 客户端收到来自不同节点的REPLY消息,当收集到f+1个相同的REPLY后,客户端会确认请求已达成共识并执行。

恭喜你!你现在已经拥有了一个能够模拟PBFT三阶段提交协议的C语言仿真器了!这可不是简单的“Hello World”,这是分布式共识算法的真实模拟!

第六章:当前仿真的局限性与未来展望

现在你已经亲手实现了PBFT的三阶段提交协议,是不是感觉对共识算法的理解又上了一个台阶?但作为一名硬核的C语言程序员,我们也要清醒地认识到,当前的仿真器还存在一些局限性。

6.1 当前仿真的局限性回顾

  1. 简化的消息摘要: 目前的calculate_digest函数只是一个简单的哈希,不具备加密安全性。在实际PBFT中,消息的完整性和不可伪造性依赖于安全的加密哈希(如SHA256)和数字签名。

  2. 无数字签名: PBFT协议要求所有消息都进行数字签名,以验证发送者的身份和消息的完整性。我们的仿真中没有实现签名和验签机制。

  3. 简化的消息日志管理: 消息日志目前只是一个简单的链表,没有考虑高效的查找(例如哈希表)、持久化存储、以及更高级的水位线管理(高水位线、检查点)。

  4. 无视图切换机制: 这是PBFT的关键容错机制,当主节点故障或作恶时,系统无法自动恢复。

  5. 无故障注入: 无法模拟节点宕机、网络延迟、消息丢失或恶意节点发送错误消息等故障情况,因此无法真正验证PBFT的拜占庭容错能力。

  6. 简化的客户端回复: 客户端只简单地等待f+1个回复,没有处理超时、重复请求等复杂逻辑。

  7. 单线程消息处理: 虽然每个连接都有一个接收线程,但PBFT核心逻辑(handle_xxx_msg)是在主线程的互斥锁保护下顺序执行的,这限制了并发处理能力。真实的PBFT节点会使用事件驱动或线程池来并发处理消息。

  8. 无状态机复制: 节点只是“打印”执行操作,没有真正维护一个共享的状态机(例如一个共享的账本)。

6.2 下一步:视图切换机制的实现预告

在接下来的篇章中,我们将着重解决上述部分局限性,特别是PBFT的另一个核心机制——视图切换 (View Change)

视图切换是PBFT算法保证活性的关键。当主节点出现故障(如宕机,或者它不发送PRE-PREPARE消息,或者发送了无效消息)时,副本节点需要能够检测到并触发视图切换,选举一个新的主节点,并从旧视图的状态中恢复,继续推进共识。

实现视图切换将涉及:

  • 计时器机制: 副本节点需要启动计时器,如果长时间未收到主节点的消息,则触发视图切换。

  • VIEW-CHANGE 消息: 副本节点发送给其他节点的视图切换请求。

  • NEW-VIEW 消息: 新主节点在收集到足够多的VIEW-CHANGE消息后,发送给所有节点的确认消息。

  • 状态恢复: 新主节点需要从VIEW-CHANGE消息中包含的信息,恢复未完成的请求状态。

这将是PBFT仿真器从“正常运行”到“容错运行”的关键一步!

6.3 性能优化与安全性增强(概念性提及)

在更高级的PBFT仿真中,我们还会考虑:

  • 消息压缩与序列化: 优化消息传输效率。

  • 非对称加密(数字签名): 引入OpenSSL等库实现消息的签名和验签,确保安全性。

  • 更高效的消息日志: 使用哈希表或数据库来管理消息日志,提高查找效率。

  • 多核并行处理: 利用线程池或事件驱动模型,提高节点的消息处理吞吐量。

  • 网络模拟: 引入网络延迟、丢包等模拟,更真实地测试算法性能。

这些高级特性,将让你的PBFT仿真器更接近真实世界的分布式系统!

小结与展望

老铁们,恭喜你!你已经成功闯过了PBFT算法C语言仿真的第二关:三阶段提交协议的实现

在这一部分中,我们:

  • 设计并实现了PBFT节点的核心状态管理,包括状态机阶段和消息日志(链表实现)。

  • 逐个实现了PBFT三阶段提交协议的C语言逻辑

    • 主节点处理CLIENT_REQUEST并广播PRE-PREPARE

    • 副本节点处理PRE-PREPARE并广播PREPARE

    • 所有节点收集PREPARE并广播COMMIT

    • 所有节点收集COMMIT,执行操作并向客户端发送REPLY

  • 实现了客户端收集f+1个回复并确认请求的逻辑

现在,你的PBFT仿真器已经能够在一个无故障的场景下,完整地走完从客户端请求到共识达成并回复的全过程了!这绝对是值得你炫耀的硬核技能!

请记住,共识算法的魅力在于它解决了分布式系统中“信任”和“一致性”的难题。通过C语言亲手实现它,你将对这些抽象概念有更深刻的理解。这对于你未来在嵌入式、区块链、分布式系统等领域的职业发展,将是巨大的财富!

敬请期待我的下一次更新——我们将深入PBFT的视图切换机制,让你的仿真器具备真正的拜占庭容错能力

如果你在学习过程中有任何疑问,或者对代码有任何改进的想法,随时在评论区告诉我,咱们一起交流,!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值