zoukankan      html  css  js  c++  java
  • NEO从入门到开窗(4)

    一、唠叨两句

    首先,我们都知道区块链是去中心化的,其中节点都是对等节点,每个节点都几乎有完整的区块链特性,CLI就是NEO的一个命令行对等节点,当然也有GUI这个项目,图形化的NEO节点。节点之间需要通信,互通有无,我们今天主要看看这部分。

    二、从入口开始

    CLI是一个Console程序,那我们就从它的Main开始把。

    static void Main(string[] args)
            {
                AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
                new MainService().Run(args);
            }
    Main

    这里除了注册一个未捕获异常的处理事件之外,就是这个MainService的Run方法了。MainService继承自ConsoleServiceBase,我们看下ConsoleServiceBase的代码吧,看一眼你就明白这个Service是在搞什么东东了

    using System;
    using System.Reflection;
    using System.Security;
    using System.Text;
    
    namespace Neo.Services
    {
        public abstract class ConsoleServiceBase
        {
            protected virtual string Prompt => "service";
    
            public abstract string ServiceName { get; }
    
            protected bool ShowPrompt { get; set; } = true;
    
            protected virtual bool OnCommand(string[] args)
            {
                switch (args[0].ToLower())
                {
                    case "clear":
                        Console.Clear();
                        return true;
                    case "exit":
                        return false;
                    case "version":
                        Console.WriteLine(Assembly.GetEntryAssembly().GetName().Version);
                        return true;
                    default:
                        Console.WriteLine("error");
                        return true;
                }
            }
    
            protected internal abstract void OnStart(string[] args);
    
            protected internal abstract void OnStop();
    
            public void Run(string[] args)
            {
                OnStart(args);
                RunConsole();
                OnStop();
            }
    
            private void RunConsole()
            {
                bool running = true;
    #if NET461
                Console.Title = ServiceName;
    #endif
                Console.OutputEncoding = Encoding.Unicode;
    
                Console.ForegroundColor = ConsoleColor.DarkGreen;
                Version ver = Assembly.GetEntryAssembly().GetName().Version;
                Console.WriteLine($"{ServiceName} Version: {ver}");
                Console.WriteLine();
    
                while (running)
                {
                    if (ShowPrompt)
                    {
                        Console.ForegroundColor = ConsoleColor.Green;
                        Console.Write($"{Prompt}> ");
                    }
    
                    Console.ForegroundColor = ConsoleColor.Yellow;
                    string line = Console.ReadLine().Trim();
                    Console.ForegroundColor = ConsoleColor.White;
    
                    string[] args = line.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                    if (args.Length == 0)
                        continue;
                    try
                    {
                        running = OnCommand(args);
                    }
                    catch (Exception ex)
                    {
    #if DEBUG
                        Console.WriteLine($"error: {ex.Message}");
    #else
                        Console.WriteLine("error");
    #endif
                    }
                }
    
                Console.ResetColor();
            }
        }
    }
    ConsoleServiceBase

    MainService肯定重写了方法OnStart,OnStop,里面干啥了后面再讲,除此之外,主要方法RunConsole里就是等输入命令,然后根据命令进行相应操作,然后循环继续。可以创建个钱包啊,创建个地址啊,blabla的。OnCommand里实现了一些界面上的操作,那MainService类里一定就是侦听CLI可以支持的各种指令咯,具体每个指令做了什么操作顺着这个线索看代码即可了解,后面我们会选几个关键的指令溜一下代码,现在我们就只关注这个OnStart,OnStop都干啥了。

    protected internal override void OnStart(string[] args)
            {
                Blockchain.RegisterBlockchain(new LevelDBBlockchain(Settings.Default.Paths.Chain));
                if (!args.Contains("--nopeers") && File.Exists(PeerStatePath))
                    using (FileStream fs = new FileStream(PeerStatePath, FileMode.Open, FileAccess.Read, FileShare.Read))
                    {
                        LocalNode.LoadState(fs);
                    }
                LocalNode = new LocalNode();
                Task.Run(() =>
                {
                    const string acc_path = "chain.acc";
                    const string acc_zip_path = acc_path + ".zip";
                    if (File.Exists(acc_path))
                    {
                        using (FileStream fs = new FileStream(acc_path, FileMode.Open, FileAccess.Read, FileShare.None))
                        {
                            ImportBlocks(fs);
                        }
                        File.Delete(acc_path);
                    }
                    else if (File.Exists(acc_zip_path))
                    {
                        using (FileStream fs = new FileStream(acc_zip_path, FileMode.Open, FileAccess.Read, FileShare.None))
                        using (ZipArchive zip = new ZipArchive(fs, ZipArchiveMode.Read))
                        using (Stream zs = zip.GetEntry(acc_path).Open())
                        {
                            ImportBlocks(zs);
                        }
                        File.Delete(acc_zip_path);
                    }
                    LocalNode.Start(Settings.Default.P2P.Port, Settings.Default.P2P.WsPort);
                    bool recordNotifications = false;
                    for (int i = 0; i < args.Length; i++)
                    {
                        switch (args[i])
                        {
                            case "/rpc":
                            case "--rpc":
                            case "-r":
                                if (rpc == null)
                                {
                                    rpc = new RpcServerWithWallet(LocalNode);
                                    rpc.Start(Settings.Default.RPC.Port, Settings.Default.RPC.SslCert, Settings.Default.RPC.SslCertPassword);
                                }
                                break;
                            case "--record-notifications":
                                recordNotifications = true;
                                break;
                        }
                    }
                    if (recordNotifications)
                        Blockchain.Notify += Blockchain_Notify;
                });
            }
    MainService.OnStart

    我们看到OnStart方法里做了如下的事情:

    1. 注册LevelDBBlockChain作为本地的链的存储

    2. 正常启动需要与其他节点通讯的情况,会先找一个叫做peers.dat的文件。这个文件里存储的是其他对等节点的地址,读取的到地址存储在LocalNode类的静态属性UnconnectedPeers里,拿到这些节点地址可以建立与其的通信。

    3. 新建了一个LocalNode实例,这个实例比较关键了,作为本地节点的通讯模块,与之对应的是RemoteNode,每个已连接上的对等节点都会有一个对应的RemoteNode实例,保存在LocalNode实例的connectedPeers列表里。

    4. 查看是否有chain.acc或者chain.acc.zip文件,这个文件是链的存储文件,如果不想同步节点,可以使用这个文件作为离线文件启动,免去同步数据的时间。

    5. 调用LocalNode.Start方法,该方法里做了啥?

    a. 启动两个线程connectThread和poolThread,这两个线程都干啥呢?

    I. connectThread: 如果前面讲述的unconnectedPeers列表里还有未连接的节点,就创建对应的任务去连接。如果没有未连接的节点了,就从connectedPeers列表里拿出来已连接的RemoteNode节点,发送一个getaddr消息。如果两个列表都是空,就拿出系统默认的feed节点去连接。

    在连接节点的时候,会创建一个RemoteNode实例,添加到connectedPeers列表里,并调用RemoteNode的StartProtocal,这个方法里表达出的是两个对等节点建立连接的过程:

                      给对方发送Version消息

                      接收到Version消息,就发送VerAck消息

                      接收VerAck消息,根据Version消息中的信息判断是不是对方持有更新的数据,如果是就发送getheaders和getblocks消息获取最新的数据

    II. poolThread: 首先先说下两个数据结构temp_pool和mem_pool,都是存储交易的,temp_pool存储的是最近接收到的交易,mem_pool是在内存中存储所有未验证交易。当节点接收到了交易消息,会把消息放在temp_pool里,然后在这个poolThread的一个loop中把mem_pool和temp_pool合并,对每笔交易进行验证,验证通过的交易都会发送一个inv消息,把消息发送到已连接上的RemoteNode中去。

    b. 开启TcpListener侦听其他对等节点的消息,开启WebSocket侦听WebSocket消息。

    6. 如果需要开启rpc,则开启RPC服务。

    protected internal override void OnStop()
            {
                if (consensus != null) consensus.Dispose();
                if (rpc != null) rpc.Dispose();
                LocalNode.Dispose();
                using (FileStream fs = new FileStream(PeerStatePath, FileMode.Create, FileAccess.Write, FileShare.None))
                {
                    LocalNode.SaveState(fs);
                }
                Blockchain.Default.Dispose();
            }
    MainService.OnStop

    OnStop方法就简单多了

    1. 析构共识服务和RPC服务

    2. 析构LocalNode,调用LocalNode.Dispose方法,停止TcpListener侦听,与所有的connectedPeers断开连接,并且把所有的connectedPeers放到unconnectedPeers队列里。

    3. 将unconnectedPeers写入peers.dat文件

    4. 析构链结构,LevelDBBlockchain关闭底层的LevelDB存储。

    三、小结

    好了,到这里简单介绍了一下NEO CLI,重点讲了启动和关闭时都搞了些什么,基本上也就是NEO网络层干的事情,剩下的就是处理互相通信的消息,处理CLI上输入的指令。到这里你应该已经建立了一个多对等节点建立网络的大概过程。

  • 相关阅读:
    目录和文件的权限设置方法
    logstash5 单实例多配置文件实现
    elasticsearch 使用快照方式迁移数据
    mysql 主库有数据通过锁库做主从
    mfs挂载
    web页面性能分析一些网址
    centos7 ffmpeg安装
    (转)在 Windows 上安装Rabbit MQ 指南
    (转)TeamCity配置笔记
    (转)分布式缓存GemFire架构介绍
  • 原文地址:https://www.cnblogs.com/DexterDi/p/8683286.html
Copyright © 2011-2022 走看看