上一节讨论了实现json-rpc服务器。现在讨论网络同步数据了。
作为一个节点要同步区块数据。交易数据。让我们来看看咋们实现的吧
首先我们启动自己的节点:
我们在源码中的会有一个节点列表,我们可以自己增加种子节点和删除种子节点。我们也可以在客户端通过命令自己添加种子节点。addnode
当节点与对等节点建立好链接后,第一步先是握手。
当节点与对等节点建立好连接后,首先要做的就是握手。其过程如下:
(1)节点向对等断发送version消息开始握手,此消息中包含如下一些内容:
PROTOCOL_VERSION:当前节点的比特币P2P协议的版本号;
nLocalServices:节点支持的本地服务列表,目前仅支持NODE_NETWORK;
nTime:当前时间;
addrYou:当前节点可见的远程节点的IP地址;
addrMe:本节点发现的本地IP地址;
subver:当前节点运行的软件类型的子版本号;
baseHeight:当前节点上的区块链的高度。
对等节点收到version消息后,会回应verack进行确认并建立连接。有时候对等端可能需要互换连接并连回起始节点,此时对等端也会发送该节点的version消息。
参考下图:
完成握手后,新的节点会发送一天包含自己地址的消息给对方。对等段收到后又会想与它连接的其他对等节点发送该消息。也会发送getaddr给对等端,要求发送对等端的ip地址给自己。如下图:
节点必须连接到若干不同的对等节点才能在比特币网络中建立通向比特币网络的种类各异的路径(path)。由于节点可以随时加入和离开,通讯路径是不可靠的。因此,节点必须持续进行两项工作:在失去已有连接时发现新节点,并在其他节点启动时为其提供帮助。节点启动时只需要一个连接,因为第一个节点可以将它引荐给它的对等节点,而这些节点又会进一步提供引荐。一个节点,如果连接到大量的其他对等节点,这既没必要,也是对网络资源的浪费。在启动完成后,节点会记住它最近成功连接的对等节点;因此,当重新启动后它可以迅速与先前的对等节点网络重新建立连接。如果先前的网络的对等节点对连接请求无应答,该节点可以使用种子节点进行重启动。
接下来通过源码进行分析,看是怎么实现的。
第一个类是connnman
这个类表示网络管理连接类。负责节点的初始化以及启动,p2p消息推送和接收,接收其他节点连接等等。
peerlogicvalayion
这个类继承了两个借款口类cvalidationinterface 和 NetEventsInterface
其中CValidationInterface是和钱包相关的一个接口,暂且不提。NetEventsInterface是和网络相关的,看一下这个接口的定义:
class NetEventsInterface
{
public:
virtual bool ProcessMessages(CNode* pnode, std::atomic& interrupt) = 0;
virtual bool SendMessages(CNode* pnode, std::atomic& interrupt) = 0;
virtual void InitializeNode(CNode* pnode) = 0;
virtual void FinalizeNode(NodeId id, bool& update_connection_time) = 0;
protected:
/**
* Protected destructor so that instances can only be deleted by derived classes.
* If that restriction is no longer desired, this should be made public and virtual.
*/
~NetEventsInterface() = default;
};
NetEventsInterface::ProcessMessage:处理接收到的消息;
NetEventsInterface::SendMessage:发送消息;
NetEventsInterface::InitializeNode:初始化节点;
维护节点信息,包括通信的套接字,发送缓冲区,接收缓冲区等等。
最后CConnman将调兵遣将,把任务交给几个线程去做:
net线程
// Send and receive from sockets, accept connections
threadSocketHandler = std::thread(&TraceThread >, "net", std::function(std::bind(&CConnman::ThreadSocketHandler, this)));
从注释上就知道,这个"net"线程的任务就是从套接字发送和接收数据,同时还要监听其他节点的连接请求。
(2) dnsseed线程
if (!gArgs.GetBoolArg("-dnsseed", true))
LogPrintf("DNS seeding disabled\n");
else
threadDNSAddressSeed = std::thread(&TraceThread >, "dnsseed", std::function(std::bind(&CConnman::ThreadDNSAddressSeed, this)));
这个线程名字就叫做"dnsseed",它的作用是通过dns查询解析出种子节点的地址,之后新启动的节点将要向这些种子节点发起连接。
(3)opencon线程
if (connOptions.m_use_addrman_outgoing || !connOptions.m_specified_outgoing.empty())
threadOpenConnections = std::thread(&TraceThread >, "opencon", std::function(std::bind(&CConnman::ThreadOpenConnections, this, connOptions.m_specified_outgoing)));
这个线程将负责向已发现的节点发起连接。
(4)msghand线程
// Process messages
threadMessageHandler = std::thread(&TraceThread >, "msghand", std::function(std::bind(&CConnman::ThreadMessageHandler, this)));
此线程将负责比特币P2P协议的消息处理。
接下来我们各个击破,对这四个线程进行逐一分析。
网友评论