• 只用120行Java代码写一个自己的区块链-2网络


    已经看完第一章的内容了吗,欢迎回来。

    上一章我们介绍了关于怎么去编写自己的区块链,完成哈希和新块的校验。但是它只是在一个终端(结点)上跑。我们怎么样来连接其他结点以及贡献新的块呢,怎么样广播到其他结点告诉他们要更新区块了呢?

    本章就是要告诉你这些。

    // 区块链的核心部分
    // 维护一个在启动时可以连接的对等节点列表。当一个完整的节点第一次启动时,它必须被自举(bootstrapped)到网络。
    // 自举过程完成后,节点向其对等节点发送一个包含其自身IP地址的addr消息。其对等的每个节点向它们自己的对等节点转发这个信息,以便进一步扩大连接池。
    // 块广播
    // 在与对等节点建立连接后,双方互发包含最新块哈希值的getblocks消息。
    // 如果某个节点坚信其拥有最新的块信息或者有更长的链,它将发送一个inv消息(invite),其中包含至多500个最新块的哈希值,以此来表明它的链更长。
    // 收到的节点使用getdata来请求块的详细信息,而远程的节点通过命令block来发送这些信息。
    // 在500个块的信息被处理完之后,节点可以通过getblocks请求更多的块信息。这些块在被接收节点认证之后得到确认。
    // 新块的确认也可通过矿工挖到并发布的块来发现。其扩散过程和上述类似。
    // 通过之前的连接,新块以inv消息发布出去,而接收节点可以通过getdata请求这些块的详细信息。

    我们会做什么

    1.建立第一个结点,作为tcp server监听

    2.打开一个终端,连接到第一个结点上来,模拟新产生区块

    3.结点将新的区块链以广播的形式传播到所有结点

    我们不会做什么

    与上一篇文章一样,本教程的目的是模拟节点网络,以便您可以直观的认识区块链网络。所以不会像真实的区块链网络一样去实现所有功能,对以上功能会进行简化。

    让我们开始编码吧!

    除了上一章讲过的区块内容,我们将去掉http的部分,今天讲的重点是TCP构建网络

    TCP 和 HTTP 有什么差别?

    这里我们不会详细讨论,但是您需要知道的是TCP是一个传输数据的基本协议。HTTP建立在TCP之上,以在web和浏览器上使用此数据传输。当您查看一个网站时,您使用的是HTTP,它是由一个名为TCP的底层数据传输协议支持的。

    在本教程中,我们将使用TCP,因为我们不需要在浏览器中查看任何内容。

    我们创建一个Node.java的文件,来开始我们的编写。

    Imports

    包声明我们需要的导入。

    import java.net.ServerSocket;
    import java.net.Socket;

    Review

    我们的块,以及块相关操作的内容都没有变,索性我们把这些内容放在一起,新建BlockUtils.java文件,内容如下:

     
    public class BlockUtils {
        /**
         * 计算区块的hash值
         * 
         * @param block
         *            区块
         * @return
         */
        public static String calculateHash(Block block) {
            String record = (block.getIndex()) + block.getTimestamp() + (block.getVac()) + block.getPrevHash();
            MessageDigest digest = DigestUtils.getSha256Digest();
            byte[] hash = digest.digest(StringUtils.getBytesUtf8(record));
            return  Hex.encodeHexString(hash);
        }
    
        /**
         * 区块的生成
         * 
         * @param oldBlock
         * @param vac
         * @return
         */
        public static Block generateBlock(Block oldBlock, int vac) {
            Block newBlock = new Block();
            newBlock.setIndex(oldBlock.getIndex() + 1);
            newBlock.setTimestamp(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
            newBlock.setVac(vac);
            newBlock.setPrevHash(oldBlock.getHash());
            newBlock.setHash(calculateHash(newBlock));
            return newBlock;
        }
    
        /**
         * 校验区块的合法性(有效性)
         * 
         * @param newBlock
         * @param oldBlock
         * @return
         */
        public static boolean isBlockValid(Block newBlock, Block oldBlock) {
            if (oldBlock.getIndex() + 1 != newBlock.getIndex()) {
                return false;
            }
            if (!oldBlock.getHash().equals(newBlock.getPrevHash())) {
                return false;
            }
            if (!calculateHash(newBlock).equals(newBlock.getHash())) {
                return false;
            }
            return true;
        }
    }

    好了!我们已经基本得到了所有的区块链相关函数,从第1章删除了所有HTTP相关的内容。我们现在可以进行联网了。

    网络

    在此之前,让我们声明一个名为bcServer的全局变量(blockchainServer的简称),它是接收传入块的队列。

    我们从main函数开始,我们需要把结点作为TCP server启动,监听端口8333。

    当网络中有链接进来时,我们接收链接,并开线程处理它。这样如果有很多结点连接过来,每个结点通讯都可以并行处理。

    接着,我们模拟广播到全网。
           // 建立TCP监听8333
                serverSocket = new ServerSocket(8333);
    
                LOGGER.info("*** Node is started,waiting for others ***");
                // 监听对等网络中的结点
                for(;;) {
                    final Socket socket = serverSocket.accept();
                    // 创建一个新的线程 ,和建立连接的结点通讯
                    new NodeThread(socket, blockChain).start();
    
                    // 模拟网络结点广播
                    new BroadcastThread(socket, blockChain).start();
                }

    让我们来写NodeThread的实现。当新的结点连上来,我们发出一条信息“请输入一个资产值”,新结点输入一个数值,创建一个新的块,发送回链,我们将这个新的块加入到链上。

    代码如下

         BufferedReader br = null;
            PrintWriter pw = null;
            try {
                //提示结点输入
                pw = new PrintWriter(socket.getOutputStream());
                pw.write("please enter a new number(vac):
    ");
                pw.flush();
                String info = null;
    
                // 读取结点发送的信息
                br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                while ((info = br.readLine()) != null) {
                    try {
                        int vac = Integer.parseInt(info);
                        // 根据vac创建区块
                        Block newBlock = BlockUtils.generateBlock(blockChain.get(blockChain.size() - 1), vac);
                        if (BlockUtils.isBlockValid(newBlock, blockChain.get(blockChain.size() - 1))) {
                            blockChain.add(newBlock);
                            pw.write("Success!
    ");
                            pw.write(gson.toJson(blockChain));
                        } else {
                            pw.write("HTTP 500: Invalid Block Error
    ");
                        }
                        LOGGER.info("add new block with vac:" + vac);
                    } catch (Exception e) {
                        LOGGER.error("not a number:", e);
                        pw.write("not a number! 
    ");
                    }
                    pw.write("Please enter a new number(vac):" + "
    ");
                    // 调用flush()方法将缓冲输出
                    pw.flush();
                }
                
            } catch (Exception e) {
                LOGGER.error("TCP i/o error Or client closed", e);
            } finally {
                LOGGER.info("node closed:" + address.getHostAddress() + ",port:" + socket.getPort());
                // 关闭资源
                try {
                    if (pw != null) {
                        pw.close();
                    }
                    if (br != null) {
                        br.close();
                    }
                } catch (IOException e) {
                    LOGGER.error("close error:", e);
                }
            }
     

    模拟广播

    我们需要把得到的新链告诉所有链接到我们TCPServer的结点,因此,我们将模拟数据如何传输到所有的其他结点。

    如何做呢,设置一个时间,定时把blockchain用json化写入连接,通知所有连接结点。

    代码如下:

    for (;;) {
                PrintWriter pw = null;
                try {
                    Thread.sleep(30000);
                    LOGGER.info("
    ------------broadcast-------------
    ");
                    LOGGER.info(gson.toJson(blockChain));
                    pw = new PrintWriter(socket.getOutputStream());
                    // 发送到其他结点
                    pw.write("------------broadcast-------------
    ");
                    pw.write(gson.toJson(blockChain));
                    pw.flush();
                } catch (InterruptedException e) {
                    LOGGER.error("error:", e);
                } catch (IOException e) {
                    LOGGER.error("error:", e);
                } 
            }

    可以到我的github查看完整的代码

    https://github.com/Mignet/blockchain

    实验步骤:

    启动第一个结点.Node

    打开一个终端,执行命令nc localhost 8333

    打开更多终端,执行命令nc localhost 8333

    在任意终端输入数字,看链的打印

    每过30秒,看广播到的链。

    如果你有真正的网络环境,想象一下,你的任意一台设备作为结点,都可能是第一个结点,它们直接通过tcp互相通讯,广播,是不是也一样类似上面的过程呢。

    下一章,我们将谈一谈工作量证明算法(Proof Of Work)。

  • 相关阅读:
    HTTP以及TCP协议
    分布式理论
    JAVA基础面试题
    JAVA基础面试题
    vue 中百度富文本初始化内容加载失败(编辑操作某列数据时,富文本中内容偶尔会为空)
    CodeMirror的使用方法
    JSON格式化,JSON.stringify()的用法
    promise与await的用法
    服务器端node.js
    数组扁平化
  • 原文地址:https://www.cnblogs.com/mignet/p/120_line_java_blockchain_2.html
Copyright © 2020-2023  润新知