三十六.区块链网络(7)--修复net与测试例子
现在进行编译我们在main()函数调用一下StartNode()函数然后编译。int main() { StartNode(); }出现很多编译时的错误。第一条还是printf.1.printf在stdio.h报 printf: 不是 global namespace 的成员;这是因为之前这句// 解决 printf 冲突 #undef printf #define printf ::printf // stdio.h 是库的头文件代码肯定不会错的。这是由于加载stdio.h代码后然后宏定义生效把原来stdio.h里的printf都改为了::printf。所以我们现在要去掉上面两句代码。然后手动给所有的提示printf不明确的地方加上::以解决两者冲突的问题。但是我删掉后并没有出现报错提示可能之前是别的错误引起这个printf不明确。现在删掉正常了。那这样最好。2.ToString我们来解决别的错误CTransaction提示未定义的标识符ToStringvoid print() const { printf(%s, ToString().c_str()); }这是因为我们没在CTransaction里写ToString函数把相关函数代码复制过来就行。然后把ToString里下级调用ToString补上就行就是给CTxIn和CTxOutCOutPoint添加ToString函数。如有其它函数或变量根据提示补上即可。3.警告弃用函数然后就是一些错误警告就是gethostbyname之类的老函数有隐患已弃用之类的。我们在文件最前面加上这个取消警告:#define _WINSOCK_DEPRECATED_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS3.CWalletTx接着“std::pairconst uint256,CWalletTx::second”使用未定义的 class“CWalletTx”报到库文件里去了应该是pair传入了 CWalletTx只有声明没有定义。我们来看一下具体是哪里使用采取直接注释如果比较重要我们就完成CWalletTx的定义。这个CWalletTx牵扯的似乎有些多不能简单注释。我们直接恢复CWalletTx吧一次性解决。在源码中找到CWalletTx类的定义复制到main.h中将它的父类CMerkleTx定义也一起复制过来。然后补全CTransaction类中的IsCoinBase和GetCredit函数因为上面两个子类调用了最上级的父类CTransaction里的这两个函数。CTxOut也要添加相应的GetCredit函数。这里的CBlock类以及其下包含的类我们现在都不一次性复制是因为要最小化修复。选择用到什么加上什么。到最后它们依赖都补充的差不多了我们再一次性复制。如果不完整的话。好现在编译已经通过了。接下来来解决链接时的问题(obj)。4.htons无法解析的外部符号 htons---util.obj这个htons是属于#include winsock2.h因为我把这个头文件放在net.cpp。导致其它的cpp没有。所以我们把它放回到headers.h里面最上面如下#define _CRT_SECURE_NO_WARNINGS //设置不报安全警告 #define _WINSOCK_DEPRECATED_NO_WARNINGS #include winsock2.h #includewindows.h #include direct.h ...其它头文件这样其它文件都能认到。5.ws2_32.lib但是还是报有些函数无法解析的外部符号 __imp_accept函数 void __cdecl ThreadSocketHandler2(void *) (?ThreadSocketHandler2YAXPEAXZ) 中引用了该符号。还是winsock2.h里定义的accept函数说明根据头文件找不到函数代码。是因为如果使用winsock在编译器里要指定加载相关的lib,然后根据lib找到dll函数代码。winsock2.h 的accpet只有声明相关。系统不会帮你自动加载所以我们显式加载指定就可以了。就是这个ws2_32.lib库headers.h里如下#define _CRT_SECURE_NO_WARNINGS //设置不报安全警告 #define _WINSOCK_DEPRECATED_NO_WARNINGS #include winsock2.h #pragma comment(lib, ws2_32.lib) #includewindows.h #include direct.h好现在编译和链接都成功了我们来看运行时会不会异常好StartNode运行时不报异常获取到IP了。获取到局域网IP。但外网失败应该是网络的原因这个先不急着解决。还有其它很多信息没显示出来这是正常的因为我去掉了一些功能。所以这个StartNode是不完整的。但网络功能已经正常了接下来我们可以根据情况单独测试例子以及开放相应的功能。6.StartNode从之前的报错信息来看StartNode函数正常执行到了GetMyExternalIP函数这里了而这里报错并不会返回false,而是还是会往下执行再往下就是三个线程了。那么这三个线程应该是被启动了。这里的GetMyExternalIP我们暂时不要就把它注释了然后在三个线程后加上语句输出看是否执行到位如下/* if (GetMyExternalIP(addrLocalHost.ip)) { addrIncoming addrLocalHost; CWalletDB().WriteSetting(addrIncoming, addrIncoming); }*/ //// Get addresses from IRC and advertise ours IRC功能暂不使用 //if (_beginthread(ThreadIRCSeed, 0, NULL) -1) // printf(Error: _beginthread(ThreadIRCSeed) failed\n); // // Start threads // if (_beginthread(ThreadSocketHandler, 0, new SOCKET(hListenSocket)) -1) { strError Error: _beginthread(ThreadSocketHandler) failed; printf(%s\n, strError.c_str()); return false; } if (_beginthread(ThreadOpenConnections, 0, NULL) -1) { strError Error: _beginthread(ThreadOpenConnections) failed; printf(%s\n, strError.c_str()); return false; } if (_beginthread(ThreadMessageHandler, 0, NULL) -1) { strError Error: _beginthread(ThreadMessageHandler) failed; printf(%s\n, strError.c_str()); return false; } cout 三个线程已启动 endl; return true;输出如下我们可以看到主程序退出了下面的thshandler输出,是我在第一个线程ThreadSocketHandler里添加的测试语句看函数有没有被执行。void ThreadSocketHandler(void* parg) { while (true) { cout thshandler endl; Sleep(1000 * 5); }7.主程序只输出了一下就退出了就是就是主程序任务完成了所以会退出我们把main()的代码改成如下不让主程序退出就行int main() { StartNode(); while (true) { Sleep(1000 * 60); } }这样运行后第一次报错反DB数据库相关的吧然后第二次却正常了接着一直正常关于第一次db数据报错的问题我想可能由于r模式打开由于我之前并没有调用load之类的函数创建对应数据库或者要写入东西都数据库写完后释放啥的释放报错)然后第二次因为已经写入所以不会有这个操作。又或者对数据库的操作由于某种情况后面不再操作。所以导致第一次有这个现象。这个问题我们先放着因为现在也找不到。问题没出现了。我们正常的测试就行一个功能一个功能的测试如果真有问题测试到那个功能的应该是能发现的。8.ThreadSocketHandler2我们看一下ThreadSocketHandler2函数在此之前我们先分析整理一下即做为一个p2p应该有服务端的socket部分和做为客户端的socket部分就是服务端的socket部分在哪里创建然后在哪里处理接收别人的连接客户端的socket又在哪里创建然后在哪里去连接别人。在这里的话服务端的socket创建是在StartNode函数开头处完成。然后在ThreadSocketHandler2接收别人的连接并且ThreadSocketHandler2函数统一处理所有的socket数据服务端和客户端,你也可以看成处理所有节点的收发数据因为是p2p)。而客户端socket的创建是在ThreadOpenConnections2函数完成的连接也是在这个函数里。ThreadMessageHandler2就是处理消息。它们通过共享vNodes协同工作即所有连接的节点都放到vNodes里。这样就可以很方便的统一处理消息。好我们回到ThreadSocketHandler2函数当这个函数启动时意味我们已经可以接收别人的连接了。9.连接流程由于节点刚启动vNodes是空的所以在等待别人连接时假设自己不去连接别人),如果有一个节点连接你就会进入下面的处理代码// // Accept new connections // if (FD_ISSET(hListenSocket, fdsetRecv)) { struct sockaddr_in sockaddr; int len sizeof(sockaddr); SOCKET hSocket accept(hListenSocket, (struct sockaddr*)sockaddr, len); CAddress addr(sockaddr); if (hSocket INVALID_SOCKET) { if (WSAGetLastError() ! WSAEWOULDBLOCK) printf(ERROR ThreadSocketHandler accept failed: %d\n, WSAGetLastError()); } else { printf(accepted connection from %s\n, addr.ToString().c_str()); CNode* pnode new CNode(hSocket, addr, true); pnode-AddRef(); CRITICAL_BLOCK(cs_vNodes) vNodes.push_back(pnode); } }获取连接上的socket,然后依此构建node,加入到vNodes里面。pnode-AddRef给这个节点标记一下表明正在被使用。不要delete。降为0会被delete。流程简图就是下面这样节点A启动 ↓ 创建 hListenSocket listen() //StartNode函数里 ↓ 启动 ThreadSocketHandler2 ↓ 循环中select() 监听 hListenSocket ↓ 节点B 调用 connect() → 连 A ↓ A 的 select() 返回FD_ISSET(hListenSocket) true ↓ 执行 accept() → 创建新 socket ↓ new CNode(...) → 加入 vNodes ↓ 下次循环开始正常收发消息10.ThreadOpenConnections2在上面的流程中我们看到需要节点b连接那这个连接代码在哪里呢在第二个线程ThreadOpenConnections2函数中。但是这个函数连接节点是先从海量节点中(加载到mapAddresses中建立网段集合再随机从网段中挑选一个ip。然后才连接而我们测试在局域网只有两个节点。即我们将这个节点的IP写进数据库或对应的文本中加载到mapAddresses中里也只有一个节点由于这个随机机制不一定能连接上。这样我们很难测试用它原来的代码的话。所以我们在这个函数前自写一个手动连接我们节点的代码。在此之前我们先来了解原代码连接上了做了什么事然后我们手动写的代码连接上了也要增加这部分的写法。关键的代码在这里CNode* pnode ConnectNode(addrConnect); if (!pnode) continue; pnode-fNetworkNode true; if (addrLocalHost.IsRoutable()) { // Advertise our address vectorCAddress vAddrToSend; vAddrToSend.push_back(addrLocalHost); pnode-PushMessage(addr, vAddrToSend); } // Get as many addresses as we can pnode-PushMessage(getaddr); ////// should the one on the receiving end do this too? // Subscribe our local subscription list const unsigned int nHops 0; for (unsigned int nChannel 0; nChannel pnodeLocalHost-vfSubscribe.size(); nChannel) if (pnodeLocalHost-vfSubscribe[nChannel]) pnode-PushMessage(subscribe, nChannel, nHops);选中ip后是调用ConnectNode函数进行连接的在这之后的代码就是连接上了所做的事。11.连接成功客户端连接成功会干什么呢if (addrLocalHost.IsRoutable()) { // Advertise our address vectorCAddress vAddrToSend; vAddrToSend.push_back(addrLocalHost); pnode-PushMessage(addr, vAddrToSend); }判断自身是否可路由地址即有没有外网地址如果有的话就可以告诉对方节点我的外网地址。通过发送addr消息。这里获取外网地址被我们注释了所以这里可以忽略。然后发送地址请求即把你知道的其他节点地址尽量多发给我一些// Get as many addresses as we can pnode-PushMessage(getaddr);接着就是订阅消息已弃用忽略即可。好现在我们连接上后主要的事是getaddr这个消息但是我们的地址库现在也没有节点。所以也暂时搁置。12.第一次握手为什么要了解上面这些消息呢是因为两个节点建立连接时第一次不是发送这些消息。在这之前第一次通信是互相发送version,确认一下版本信息协议等以便更好通信。但是我们没看到发送version的地方这是因为这个是在别的地方中完成的从ConnectNode函数里找。我们在ConnectNode函数找到如下语句CNode* pnode new CNode(hSocket, addrConnect, false);当连接上后就会根据这个socket new一个新节点。13.version我们看这个类的构造函数CNode(SOCKET hSocketIn, CAddress addrIn, bool fInboundIn false) { nServices 0; hSocket hSocketIn; vSend.SetType(SER_NETWORK); vRecv.SetType(SER_NETWORK); nPushPos -1; addr addrIn; nVersion 0; fClient false; // set by version message fInbound fInboundIn; fNetworkNode false; fDisconnect false; nRefCount 0; nReleaseTime 0; vfSubscribe.assign(256, false); // Push a version message /// when NTP implemented, change to just nTime GetAdjustedTime() int64 nTime (fInbound ? GetAdjustedTime() : GetTime()); PushMessage(version, VERSION, nLocalServices, nTime, addr); }最后一句看到了没是先发送的version信息而服务端也会new node也就是说双方连接上后就是先互相发送version消息。13.version处理那我们来看接收到version会做什么我们只需要到消息处理函数看相应的version“处理代码就行如下if (strCommand version) { // Can only do this once if (pfrom-nVersion ! 0) return false; // 防止同一个连接重复处理 version 消息握手只能做一次 int64 nTime; CAddress addrMe; // 从接收缓冲区中解析对方发来的 version 消息字段 vRecv pfrom-nVersion pfrom-nServices nTime addrMe; if (pfrom-nVersion 0) return false; // 对方版本号无效拒绝 // 版本协商 // 双方取较小的版本号作为后续通信使用的版本 pfrom-vSend.SetVersion(min(pfrom-nVersion, VERSION)); pfrom-vRecv.SetVersion(min(pfrom-nVersion, VERSION)); // 判断对方类型 // 如果对方不提供 NODE_NETWORK 服务则认为是轻客户端只同步区块头 pfrom-fClient !(pfrom-nServices NODE_NETWORK); if (pfrom-fClient) { // 对轻客户端只收发区块头节省带宽 pfrom-vSend.nType | SER_BLOCKHEADERONLY; pfrom-vRecv.nType | SER_BLOCKHEADERONLY; } // 时间同步 // 记录对方时间用于全网时间校准防止时间偏差过大 AddTimeData(pfrom-addr.ip, nTime); // 请求区块链数据 // 只向第一个连接的非轻客户端节点请求区块避免重复请求 static bool fAskedForBlocks; // 静态变量只执行一次 if (!fAskedForBlocks !pfrom-fClient) { fAskedForBlocks true; // 请求从创世区块到当前最新区块的更新 pfrom-PushMessage(getblocks, CBlockLocator(pindexBest), uint256(0)); } // 打印调试信息对方认为的我们的地址有助于判断 NAT printf(version addrMe %s\n, addrMe.ToString().c_str()); }14.连接测试好我们现在就先了解这里做一个小例子测试一下连接。我们现在需要两台电脑一台当客户端在其下ThreadOpenConnections2函数开头处添加如下代码指定IP连接注意服务端不用void ThreadOpenConnections2(void* parg) { printf(ThreadOpenConnections started\n); static int flag 0; if (flag 0) { CAddress addrConnect CAddress(192.168.1.101, 8333); CNode* pnode ConnectNode(addrConnect); if (pnode) { pnode-fNetworkNode true; cout 连接服务端成功! endl; //以下暂时不执行不请求其它消息 /* if (addrLocalHost.IsRoutable()) { vectorCAddress vAddrToSend; vAddrToSend.push_back(addrLocalHost); pnode-PushMessage(addr, vAddrToSend); } pnode-PushMessage(getaddr);*/ } flag 1; }我们利用ConnectNode函数来连接结点这里我们直接指定IP地址。不能采取默认的方法。因为默认算法IP是随机抽取的。不能保证抽到我们另一台的IP。我们只需要调用ConnectNode即可这个函数里面如果节点连接成功会自动将其添加到vNodes里面不需要我们在外面手动维护。好注意上面的局域地址192.168.1.101填你自己的被连接的那个节点地址。注意被连接的节点代码需要将上面的添加的代码去掉(“服务端”)。注这里的因为手动指定了连接所以区分客户端和服务端事实上一个节点是不定的对等的可以是服务端也可以是客户端我们先运行服务端再运行客户端。运行结果客户端服务端好连接测试成功在下一章中我们将详细的了解这个过程并解释输出的信息。然后我们将了解是怎么通信分享区块验证的。

相关新闻

基于知识图谱(DFMEA)、大数据、大模型的PHM(故障预测与健康管理)系统

基于知识图谱(DFMEA)、大数据、大模型的PHM(故障预测与健康管理)系统

目录 一、组成部分及作用未来的产品化方向 成企业数据 先说下产生这个想法的背景 大语言模型的出现: 这是最重要的原因,因为大模型可以打通多模态数据和结构化数据之间的隔阂,AI去理解业务意图、理解数据变成了可能 2 DFMEA的痛点 大家…

2026/10/12 15:43:00 阅读更多 →
Modbus 协议全面详解:02 工业背景与协议定位

Modbus 协议全面详解:02 工业背景与协议定位

第2章 工业背景与协议定位 学习 Modbus 之前,必须先回答一个问题: 为什么工业现场已经有那么多协议了,Modbus 还活着? 1979 年诞生的协议,为什么到了今天: PLC 还在支持 变频器还在支持 电表还在支持 SCADA 还在支持 IIoT 网关还在支持 甚至很多最新设备的协议列表第一页…

2026/10/13 17:24:11 阅读更多 →
CircuitKIT:降低大语言模型机制可解释性研究门槛的完整工具链

CircuitKIT:降低大语言模型机制可解释性研究门槛的完整工具链

如果你正在研究大语言模型的内部工作机制,特别是想理解神经网络中的"电路"如何工作,那么你很可能已经感受到了机制可解释性(Mechanistic Interpretability)领域的复杂性。传统上,这个领域需要大量的手动分析…

2026/10/12 21:51:33 阅读更多 →

最新新闻

一条富碱性多肽的进阶之路:序列解析、合成纯化与核定位验证

一条富碱性多肽的进阶之路:序列解析、合成纯化与核定位验证

1. 从一条氨基酸序列反推蛋白身份:这条肽的“户口本”是这样查的拿到 PDGF A-Chain (194-211) 这个编号,加上后面那一串 GRPREGSKKRKRKRLKPT,我的第一反应不是急着做实验,而是先把它当成一个“嫌疑人”来审:它到底来自…

2026/10/14 6:04:23 阅读更多 →
Spring Boot微服务架构设计与实战指南

Spring Boot微服务架构设计与实战指南

1. 为什么Spring Boot成为微服务首选框架十年前我刚接触企业级应用开发时,EJB的复杂配置让人望而生畏。直到Spring Boot出现,用约定优于配置的理念彻底改变了Java开发方式。现在回想第一次用Spring Boot启动项目的场景:没有XML配置&#xff0…

2026/10/14 6:04:23 阅读更多 →
bl knowledge search 报 400 RagAgentGroupNotExist 怎么解决?阿里云百炼知识库检索与 agent 记忆节点使用步骤与常见报错排查

bl knowledge search 报 400 RagAgentGroupNotExist 怎么解决?阿里云百炼知识库检索与 agent 记忆节点使用步骤与常见报错排查

给 agent 补知识,阿里云百炼的 CLI(bl)里有两条路:bl knowledge 建知识库、包一层检索服务让它查;bl memory 按用户记节点。两条路的 ID 体系不一样,混用会撞上一个措辞很像服务端故障的 HTTP 400。 这篇按…

2026/10/14 6:04:23 阅读更多 →
微电网群协同调度中的目标级联法原理与Matlab实现

微电网群协同调度中的目标级联法原理与Matlab实现

1. 项目背景与核心价值微电网群协同调度是当前分布式能源领域的热点研究方向。传统集中式优化方法在面对多主体、高维度的微网群系统时,往往面临通信压力大、隐私保护难、计算复杂度高等问题。目标级联法(Analytical Target Cascading, ATC)通…

2026/10/14 6:04:23 阅读更多 →
汽车线束开发中的BOM图纸与检验记录管理

汽车线束开发中的BOM图纸与检验记录管理

汽车线束从设计到样品、再到量产,BOM材料清单、图纸和检验记录需要保持关联。连接器替换或支路调整后,如果相关文件没有同步更新,资料评审就可能遗漏要求。建立清晰的版本和关联关系,有助于工程与采购对齐信息。首先,为…

2026/10/14 6:04:23 阅读更多 →
PS5进阶玩家手册:系统设置、SSD扩容、网络调优与串流实战

PS5进阶玩家手册:系统设置、SSD扩容、网络调优与串流实战

接手的这台“AnyPS5”时,我第一反应是:这名字起得挺大。光是PS5相关的外设、存储、网络、串流、保养,随便列一列就够写好几篇长文。后来想明白了,所谓“Any PS5”,并不是什么第三方工具或改装固件,而是玩家…

2026/10/14 6:03:23 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/13 16:22:04 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →