三十六.区块链网络(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/7/25 2:25:26 阅读更多 →
Modbus 协议全面详解:02 工业背景与协议定位

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

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

2026/7/25 2:25:26 阅读更多 →
CircuitKIT:降低大语言模型机制可解释性研究门槛的完整工具链

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

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

2026/7/25 2:24:26 阅读更多 →

最新新闻

解锁AMD锐龙处理器隐藏潜力:SMUDebugTool硬件级调试完全指南

解锁AMD锐龙处理器隐藏潜力:SMUDebugTool硬件级调试完全指南

解锁AMD锐龙处理器隐藏潜力:SMUDebugTool硬件级调试完全指南 【免费下载链接】SMUDebugTool A dedicated tool to help write/read various parameters of Ryzen-based systems, such as manual overclock, SMU, PCI, CPUID, MSR and Power Table. 项目地址: http…

2026/7/25 2:36:29 阅读更多 →
UE4项目PSO缓存构建与热更实战:彻底消除着色器编译卡顿

UE4项目PSO缓存构建与热更实战:彻底消除着色器编译卡顿

1. 项目概述:为什么PSO缓存是UE4项目性能的“定海神针”?如果你在UE4项目开发后期,尤其是在移动端或低端PC上,遇到过游戏启动后第一次进入新场景时那令人抓狂的卡顿,或者角色释放新技能时画面突然“定住”几帧&#xf…

2026/7/25 2:36:29 阅读更多 →
Krea 2图像生成模型:本地部署与API调用全解析

Krea 2图像生成模型:本地部署与API调用全解析

Krea 2 技术报告的发布标志着图像生成领域的一个重要里程碑。作为 Krea AI 推出的首个完全从零训练的图像基础模型,Krea 2 的核心价值在于让创作者对生成图像的每个细节拥有前所未有的控制能力。不同于传统模型往往"压平"艺术表现力,Krea 2 特别强调美学多样性和创…

2026/7/25 2:36:29 阅读更多 →
vLLM与OpenClaw实现高性能本地大模型部署方案

vLLM与OpenClaw实现高性能本地大模型部署方案

1. 项目背景与核心价值去年在部署本地大语言模型时,我发现很多开源方案要么性能堪忧,要么API兼容性差。直到遇到vLLM这个基于PagedAttention的高性能推理引擎,配合OpenClaw的API兼容层,终于实现了接近商业API的本地部署体验。今天…

2026/7/25 2:36:29 阅读更多 →
光学神经网络:原理、挑战与应用前景

光学神经网络:原理、挑战与应用前景

1. 光学神经网络技术概述光学神经网络(Optical Neural Networks, ONNs)是一种利用光子而非电子进行信息处理的下一代计算架构。这种技术通过光波的传播、干涉和衍射等物理现象来实现神经网络的计算过程,从根本上区别于传统的电子计算范式。我…

2026/7/25 2:36:29 阅读更多 →
MSP430FR599x引脚复用与低功耗模式实战解析

MSP430FR599x引脚复用与低功耗模式实战解析

1. 项目概述与核心价值在嵌入式系统开发中,尤其是面向电池供电的物联网节点、便携式医疗设备或环境传感器,我们总是在两个核心诉求之间寻找最佳平衡点:功能丰富度与功耗控制。功能丰富意味着需要更多的外设接口,而功耗控制则要求系…

2026/7/25 2:35:28 阅读更多 →

日新闻

突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存

突破文档下载限制:kill-doc让你看到的都能保存 【免费下载链接】kill-doc 看到经常有小伙伴们需要下载一些免费文档,但是相关网站浏览体验不好各种广告,各种登录验证,需要很多步骤才能下载文档,该脚本就是为了解决您的…

2026/7/25 0:00:35 阅读更多 →
C++ string类模拟实现:从深拷贝到内存管理的完整指南

C++ string类模拟实现:从深拷贝到内存管理的完整指南

1. 项目概述:为什么我们要“手撕”string类?在C的学习道路上,尤其是从C语言过渡到C的“初阶”阶段,string类绝对是一个绕不开的核心。标准库里的std::string用起来太方便了,、find、substr,几个操作符和函数…

2026/7/25 0:00:35 阅读更多 →
三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

三角洲寻宝鼠工具:高效文件搜索与资源管理实战指南

1. 先搞清楚“三角洲寻宝鼠”到底是什么工具从名称来看,“三角洲寻宝鼠”更像是一个资源查找或文件检索类工具,而不是游戏或娱乐软件。这类工具的核心价值在于帮助用户快速定位特定资源,比如文档、图片、压缩包或特定格式的文件。如果你经常需…

2026/7/25 0:00:35 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 3:59:20 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 1:23:39 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/24 18:52:18 阅读更多 →

月新闻