基于java开发一套完整的区块链系统详细教程(附源码)
前言
近几年区块链概念越来越火,特别是区块链技术被纳入国家基础设施建设名单后,各大企业也开始招兵买马,对区块链技术进行研究,从各大招聘网站的区块链职位来看,薪资待遇都很不错,月薪30K到80K的都有,这对于我们程序员来说也是一种机遇,说明学习区块链技术刻不容缓。
我个人从2016年就开始在做区块链系统开发的相关工作,最近身边很多朋友都在找我,想让我给他们讲讲区块链技术开发的相关知识,介于此,索性我就手撸了一套简单的java区块链系统,并写了一份详细的开发教程,发布到博客,希望能够对热爱区块链技术的小伙伴学习入门有所帮助。
这套区块链系统代码非常简洁清晰,对于新手来说非常好理解,旨在告诉大家其实区块链技术并没有那么高深复杂。系统中除了springboot框架外,其他基本都是纯原生开发,就连P2P网络也是用的java socket来实现的。
文末有本文完整源码链接。
一、区块链技术理论基础
1、基本概念
(1)区块链
从技术层面来看,区块链是由包含交易信息的区块按照时间顺序从后向前有序链接起来的数据结构。
从应用层面来说,区块链是一个分布式的共享账本和数据库,具有去中心化、不可篡改、全程留痕、集体维护、公开透明等特点。基于这些特点,区块链技术可以开发出自带信任体系特征的系统,实现多个主体之间的协作信任与一致行动。
区块是区块链中的最小组成单位,主要有包含元数据的区块头和存放一条或者多条交易信息的区块体两部分组成,每个区块都记录着当前区块的哈希和上一个区块的哈希,通过两个哈希值的关联,让所有的区块以链式结构串起来,就形成了一个完整的区块链。
区块链中的第一个区块被称作为创世区块,无需关联上一个区块。以BTC网络为例,每个区块主要包含如下信息字段:
- 区块大小:用字节表示的区块数据大小
- 区块头:组成区块头的包括以下几个字段:
1. 区块头hash值
1. 父区块头hash值
1. 时间戳:区块产生的近似时间
1. Merkle根:该区块中交易的merkle树根的哈希值
1. 难度目标:该区块工作量证明算法的难度目标
1. Nonce:用于工作量证明算法的计数器 - 交易计数器:交易的数量
- 交易:记录在区块里的交易信息
区块链结构的简易模型,如下图所示:
区块中的交易集合记录的是一些特定的信息,在BTC网络中主要记录的是交易信息,在其他区块链网络中可以按照业务逻辑来保存相应的业务数据,如审计信息、版权信息、票据信息等,这也是区块链经常用来当做共享账本的原因。
打个比方,可以把区块链当做一个用来记账的笔记本,一个区块就相当于一页纸,上面记录了某一时间段內所有的账务信息,从第一页到最后一页,按照页码顺序排列起来就是一个完整的账本。
(2)区块链网络
实际的区块链系统由多个区块链节点组成,每个节点都运行着相同一套区块链主干网络的副本,且各个节点间通过P2P网络进行交互,并最终形成一个完整的区块链网络系统。
P2P网络具有可靠性、去中心化,以及开放性,各个节点之间交互运作、协同处理,每个节点在对外提供服务的同时也使用网络中其他节点所提供的服务。当某一个区块链节点产生新的区块时,会通过广播的方式告诉其他节点,其他节点通过网络接收到该区块信息时,会对这个区块信息进行验证,当有一定数量的节点都验证通过后,各个节点会把该区块更新到各自现有的区块链上,最终使得整个区块链网络中的各个节点信息保持一致,这也是区块链去中心化、可信任特性的体现。
区块链网络简易模型,如下图所示:
2、区块链分类
(1)公有链
公有区块链(Public Block Chains)是指:世界上任何个体或者团体都可以发送交易,且交易能够获得该区块链的有效确认,任何人都可以参与使用和维护该区块链,信息公开透明。公有区块链是最早的区块链,例如BTC、以太坊等虚拟数字货币均基于公有区块链。不过目前公有链实际应用价值不大,并没有产生特别合适的应用场景。
(2)联盟链
行业区块链(Consortium Block Chains):由某个群体内部指定多个预选的节点为记账人,每个块的生成由所有的预选节点共同决定(预选节点参与共识过程),其他接入节点可以参与交易,但有权限限制,信息受保护,如银联组织。目前联盟链是各个区块链技术团队主要研究的对象,由于联盟链拥有区块链技术的大部分特征,并且在权限管理、数据安全、监管方面更有优势,是企业优先考虑的区块链技术方案。
市面上也有一些比较主流的联盟链技术框架,让开发维护联盟链更加便捷。国内一些大的软件厂商也都有自己的企业区块链技术解决方案,例如蚂蚁金服区块链平台,腾讯的TrustSQL平台,东软的SaCa EchoTrust区块链应用平台以及京东区块链防伪追溯平台等等。
(3)私有链
私有区块链(Private Block Chains):仅仅使用区块链的总账技术进行记账,可以是一个公司,也可以是个人,独享该区块链的写入权限,利用区块链的不易篡改特性,把区块链作为账本数据库来使用。
3、关键技术与特性
(1)共识机制
共识机制被称作为区块链系统的灵魂,是区块链系统信任体系的基础。区块链系统作为一个多节点的分布式账本系统,当有新的信息需要记录时,哪个节点来负责记账,记账奖励发放给哪个节点,哪些节点负责验证记账结果,如何让各个节点达成最终一致,将记账结果被网络中所有节点以同样的顺序复制并记录下来,就是共识机制要做的事情。
而按照百度百科上的说法:
所谓“共识机制”是通过特殊节点的投票,在很短的时间内完成对交易的验证和确认,对一笔交易,如果利益不相干的若干个节点能够达成共识,我们就可以认为全网对此也能够达成共识。再通俗一点来讲,如果中国一名微博大V、美国一名虚拟币玩家、一名非洲留学生和一名欧洲旅行者互不相识,但他们都一致认为你是个好人,那么基本上就可以断定你这人还不坏。
目前,较为主流的共识算法有PoW、PoS、DPoS、PBFT等,在实际使用时,每种算法都有各自的优点和缺点。在应用于不同场景时,区块链项目将会采用不同的共识机制和算法。
(2)去中心化
去中心化,是互联网发展过程中形成的社会关系形态和内容产生形态,是相对于“中心化”而言的新型网络内容生产过程。在一个分布有众多节点的区块链系统中,每个节点都具有高度自治的特征。任何一个节点都可能成为阶段性的中心,但不具备强制性的中心控制功能。节点与节点之间的影响,会通过网络而形成关联关系。这种开放式、扁平化、平等性的系统现象或结构,我们称之为去中心化。
去中心化的系统具有容错力高、抗攻击力强的特征。中心化的系统一旦中心出现问题,整个系统都会崩溃,但是区块链系统中的任何一个节点出现问题,并不会对整个区块链网络产生太大的影响。
另外,去中介化并不代表着不接受监管,“去中心化”去的是中央控制方和中介方,而不是监管方。监管节点可以方便地接入任何一个区块链网络。并且由于区块链的公开透明特性,监管机构反而可以更加方便地监控整个系统的交易数据。
(3)智能合约
从技术层面讲,智能合约是一段部署在在区块链上的程序代码,当满足程序设定的条件时,它便会在区块链上运行,并得到相应的结果。这种情况有点类似于微信的小程序,区块链提供虚拟机和脚本语言,用户根据脚本语言的语法开发带有一定业务逻辑的程序,部署在区块链上,当满足执行的条件时,智能合约便会被区块链虚拟机解释并运行。
典型的应用便是以太坊平台的智能合约,在这个平台里可以支持用户通过简单的几行代码就能实现他们想要的合约,实现无需人为监督的、不可篡改、自动化运行的合约,买卖房子不需要再找中介、借钱不需要再找公证人……人们可以随时随地根据自身需求发起合约,它的执行不依赖某个人和组织,所有的信任完全基于以太坊区块链平台本身。
(4)不可逆转性
大部分人习惯称它为不可篡改性,但是从技术层面来说,我个人觉得叫做不可逆转性更贴切,既然是一个计算机系统,增删改查是基本的功能属性,只不过区块链系统删除和修改操作比较特殊一点。
区块链是由每个区块的哈希值串连起来的链式结构,而区块的哈希值=SHA256(“当前区块内容+上一个区块的哈希值”),任何一个区块的内容发生修改,都会引起哈希值的变化,而哈希值的变化也会引起子区块哈希值发生变化,进而引起整个区块链的改变。
因此任何人想要修改区块的数据几乎是不可能的,除非他把整个区块链中从创世区块到最新的区块的所有哈希值全部重新修改一遍,并且修改完之后,还得广播告诉网络中的其他所有节点,让其他所有节点接受修改。
不过按照目前计算机的算力,想要在短时间内从区块链头部到尾部全部修改一遍,是一件非常困难的事,并且即使修改完了,其他节点也不会接受修改,因为凭一己之力,没有能够让所有节点达成共识的条件。
4、流行的区块链框架与应用
(1)公有链应用:BTC网络
区块链1.0产品,对于比特币,中本聪是这样定义的:是一种完全通过点对点技术实现的电子现金系统,它使得在线支付能够直接由一方发起并支付给另外一方,中间不需要通过任何的金融机构。
与所有的货币不同,比特币不依靠特定货币机构发行,它依据特定算法,通过大量的计算产生,比特币经济使用整个P2P网络中众多节点构成的分布式数据库来确认并记录所有的交易行为,并使用密码学的设计来确保货币流通各个环节安全性。之后人们根据比特币网络技术整理出了区块链技术体系,去解决信任的问题,而比特币网络原理也成为了区块链技术初学者的经典教材。
(2)公有链应用:以太坊网络
区块链2.0产品的代表,以太坊是一个为去中心化应用(Dapp)而生的开源区块链平台,拥有着大部分区块链技术的特征,但与其它区块链不同的是,以太坊是可编程的,开发者可以用它来构建不同的应用程序,通过其专用加密货币以太币(简称“ETH”)提供去中心化的以太虚拟机(Ethereum Virtual Machine)来处理点对点合约(就是一些脚本程序代码)。如果把比特币网络看作是一套分布式的数据库,而以太坊则更进一步,它可以看作是一台分布式的计算机:区块链是计算机的ROM,合约是程序,而以太坊的矿工们则负责计算,担任CPU的角色。
以太坊的概念首次在2013至2014年间由程序员Vitalik Buterin受比特币启发后提出,大意为“下一代加密货币与去中心化应用平台”。虽然以太坊作为平台可以在其上开发新的应用,但是由于以太坊的运行和BTC网络一样,采用的是Token机制,且平台性能不足,经常出现网络拥堵的情况,平台用来学习开发与测试区块链技术还可以,用于实际生产的话不太现实。
(3)联盟链开发框架:Hyperledger Fabric
Hyperledger Fabric 也叫超级账本,它是 IBM 贡献给 Linux 基金会的商用分布式账本,是面向企业应用的全球最大的分布式开源项目。像其他区块链技术一样,它也有一个账本,可以使用智能合约。Fabric的智能合约可以有多种架构,它可以用主流语言编程,例如Go、Java和Javascript,此外也可以使用Solidity。
至今,Fabric已获得了阿里巴巴、AWS、Azure、百度、谷歌、华为、IBM、甲骨文、腾讯等互联网巨头的支持。许多企业的区块链平台都把Fabric作为底层框架来使用,例如甲骨文。不过由于IBM对区块链的定义强调了区块链的分布式和不可变两个元素,对共识机制进行了削弱,采用了Kafka和zookeeper的“排序服务”实现共识,因此部分业内人士也称超级账本是“伪区块链”,但是即便如此,也抵挡不了企业对超级账本的喜爱,目前Fabric 2.0版本已经正式发布。
(4)小结
目前公有链在实际应用中并没有太多的业务场景落地,大部分都是以挖矿为主题或者线上宠物饲养的游戏为主,并且由于数字货币的匿名性,有些不法分子利用这一特点,将数字货币用于洗钱、暗网买卖等违法行为,是各个国家的打击对象,我国政策法规也严厉禁止,因此对于技术人员来说,公有链可以作为研究学习的对象,其他方面暂时没有太多实际意义。
目前大部分区块链企业的研究方向主要是针对企业的联盟链和私有链,并且国家层面也在大力支持区块链技术的发展,特别是区块链底层核心技术的研发,倡导把区块链作为核心技术自主创新的重要突破口,明确主攻方向,加大投入力度,着力攻克一批关键核心技术,加快推动区块链技术和产业创新发展。不过现在市面上主流的区块链平台大部分还是以国外公司主导的为主,国内区块链底层核心技术的发展,还需要技术人员的加倍努力。
二、区块链技术Java实现
1、区块链技术架构
目前主流的区块链技术架构主要分为五层,数据层是最底层的技术,主要实现了数据存储、账户信息、交易信息等模块,数据存储主要基于Merkle树,通过区块的方式和链式结构实现,而账户和交易基于数字签名、哈希函数和非对称加密技术等多种密码学算法和技术,来保证区块链中数据的安全性。
网络层主要实现网络节点的连接和通讯,又称点对点技术,各个区块链节点通过网络进行通信。共识层是通过共识算法,让网络中的各个节点对全网所有的区块数据真实性正确性达成一致,防止出现拜占庭攻击、51攻击等区块链共识算法攻击。
激励层主要是实现区块链代币的发行和分配机制,是公有链的范畴,我们不做分析。应用层一般把区块链系统作为一个平台,在平台之上实现一些去中心化的应用程序或者智能合约,平台提供运行这些应用的虚拟机。
接下来我们基于Java语言来开发一套小型的区块链系统,来实现数据层、网络层、共识层的一些功能,用简单的代码来直观抽象的概念,以便加深对以上区块链技术基础理论的理解。
2、基于java的区块链系统开发
(1)开发环境
开发工具 | VSCode |
---|---|
开发语言 | Java |
JDK版本 | JDK1.8或者OpenJDK11 |
开发框架 | SpringBoot2.2.1 |
工程管理 | Maven3.6 |
测试工具 | Postman |
(2)基本模型构建
区块是区块链系统的最小单元,第一步我们先实现最简单的区块结构,新建Block.java类,主要包含以下几个字段:
Block.java
/*** 区块结构* * @author Jared Jia**/
public class Block implements Serializable {private static final long serialVersionUID = 1L;/*** 区块索引号(区块高度)*/private int index;/*** 当前区块的hash值,区块唯一标识*/private String hash;/*** 前一个区块的hash值*/private String previousHash;/*** 生成区块的时间戳*/private long timestamp;/*** 工作量证明,计算正确hash值的次数*/private int nonce;/*** 当前区块存储的业务数据集合(例如转账交易信息、票据信息、合同信息等)*/private List<Transaction> transactions;/*** 省略get set方法****/}
区块链是由区块按照区块哈希前后顺序串联起来的数据结构,哈希值通过散列算法对区块进行二次哈希计算而得到的数字摘要信息(不了解散列函数的,可以先百度了解一下SHA算法),用于保证区块的信息安全以及整条区块链的有效性。因此第二步我们新增计算区块Hash值的方法,采用SHA256算法,通过java实现:
CryptoUtil.java
/*** 密码学工具类* * @author Jared Jia**/
public class CryptoUtil {/*** SHA256散列函数* @param str* @return*/public static String SHA256(String str) {MessageDigest messageDigest;String encodeStr = "";try {messageDigest = MessageDigest.getInstance("SHA-256");messageDigest.update(str.getBytes("UTF-8"));encodeStr = byte2Hex(messageDigest.digest());} catch (Exception e) {System.out.println("getSHA256 is error" + e.getMessage());}return encodeStr;}private static String byte2Hex(byte[] bytes) {StringBuilder builder = new StringBuilder();String temp;for (int i = 0; i < bytes.length; i++) {temp = Integer.toHexString(bytes[i] & 0xFF);if (temp.length() == 1) {builder.append("0");}builder.append(temp);}return builder.toString();}
}
第三步,创建一个链式结构对象,按照先后顺序来保存区块对象,从来形成一个有序的区块链表,考虑到线程安全问题,采用CopyOnWriteArrayList来实现,为了方便测试,暂且把区块链结构保存在本地缓存中,实际的区块链网络最终会实现持久层的功能,把区块链数据保存至数据库中,例如BTC核心网络采用的是K-V数据库LevelDB:
BlockCache.java
public class BlockCache {/*** 当前节点的区块链结构*/private List<Block> blockChain = new CopyOnWriteArrayList<Block>();public List<Block> getBlockChain() {return blockChain;}public void setBlockChain(List<Block> blockChain) {this.blockChain = blockChain;}}
第四步,有了区块链结构后,需要新增向区块链中添加区块的方法,同时每次添加区块的时候,我们需要验证新区块的有效性,例如Hash值是否正确,新区块中上一区块的Hash属性的值,与上一区块的Hash值是否相等。
另外,区块链中必须有个创世区块,我们直接通过硬编码实现:
BlockService.java
/*** 区块链核心服务* * @author Jared Jia**/
@Service
public class BlockService {@AutowiredBlockCache blockCache;/*** 创建创世区块* @return*/public String createGenesisBlock() {Block genesisBlock = new Block();//设置创世区块高度为1genesisBlock.setIndex(1);genesisBlock.setTimestamp(System.currentTimeMillis());genesisBlock.setNonce(1);//封装业务数据List<Transaction> tsaList = new ArrayList<Transaction>();Transaction tsa = new Transaction();tsa.setId("1");tsa.setBusinessInfo("这是创世区块");tsaList.add(tsa);Transaction tsa2 = new Transaction();tsa2.setId("2");tsa2.setBusinessInfo("区块链高度为:1");tsaList.add(tsa2); genesisBlock.setTransactions(tsaList);//设置创世区块的hash值genesisBlock.setHash(calculateHash("",tsaList,1));//添加到已打包保存的业务数据集合中blockCache.getPackedTransactions().addAll(tsaList);//添加到区块链中blockCache.getBlockChain().add(genesisBlock);return JSON.toJSONString(genesisBlock);}/*** 创建新区块* @param nonce* @param previousHash* @param hash* @param blockTxs* @return*/public Block createNewBlock(int nonce, String previousHash, String hash, List<Transaction> blockTxs) {Block block = new Block();block.setIndex(blockCache.getBlockChain().size() + 1);//时间戳block.setTimestamp(System.currentTimeMillis());block.setTransactions(blockTxs);//工作量证明,计算正确hash值的次数block.setNonce(nonce);//上一区块的哈希block.setPreviousHash(previousHash);//当前区块的哈希block.setHash(hash);if (addBlock(block)) {return block;}return null;}/*** 添加新区块到当前节点的区块链中* * @param newBlock*/public boolean addBlock(Block newBlock) {//先对新区块的合法性进行校验if (isValidNewBlock(newBlock, blockCache.getLatestBlock())) {blockCache.getBlockChain().add(newBlock);// 新区块的业务数据需要加入到已打包的业务数据集合里去blockCache.getPackedTransactions().addAll(newBlock.getTransactions());return true;}return false;}/*** 验证新区块是否有效* * @param newBlock* @param previousBlock* @return*/public boolean isValidNewBlock(Block newBlock, Block previousBlock) {if (!previousBlock.getHash().equals(newBlock.getPreviousHash())) {System.out.println("新区块的前一个区块hash验证不通过");return false;} else {// 验证新区块hash值的正确性String hash = calculateHash(newBlock.getPreviousHash(), newBlock.getTransactions(), newBlock.getNonce());if (!hash.equals(newBlock.getHash())) {System.out.println("新区块的hash无效: " + hash + " " + newBlock.getHash());return false;}if (!isValidHash(newBlock.getHash())) {return false;}}return true;}}
以上关键代码实现之后,我们就构建了一个非常简单的区块链模型,包含一个基本的区块模型和一个区块链模型,并且能够生成新的区块并添加到区块链中,接下来我们进行测试。
第五步,我们编写一个Controller类进行调用:
BlockController.java
@Controller
public class BlockController {@ResourceBlockService blockService;@AutowiredBlockCache blockCache;/*** 查看当前节点区块链数据* @return*/@GetMapping("/scan")@ResponseBodypublic String scanBlock() {return JSON.toJSONString(blockCache.getBlockChain());}/*** 创建创世区块* @return*/@GetMapping("/create")@ResponseBodypublic String createFirstBlock() {blockService.createGenesisBlock();return JSON.toJSONString(blockCache.getBlockChain());}}
第六步,系统测试
首先系统启动后,先查看区块链中的数据,可以看到当前系统中的区块链为空:
然后我们调用创建创世区块的方法,查看返回结果:
我们把生成的创世区块添加到本地区块链中后,转换成JSON字符串返回,可以看到当前区块链中存储的有一个区块对象,至此我们已经实现了一个简单的区块链。实际的区块链系统模型要复杂的多,需要根据不同的业务场景扩展相应的字段,但是基本特征都是一样的。
(3)共识机制实现
在上章节中,我们实现了一个简单的区块链结构,并且能够生成并添加新的区块,但是问题来了,实际的区块链系统是一个多节点、分布式、去中心化的网络,每个节点通过网络交互,实时同步保存着同样的整条区块链数据,那么我们生成的区块,如何才能被其他节点认可,并同步添加到其他所有节点上呢,这个时候我们就需要一套规则,让所有网络节点的参与者达成能够达成共识,接纳并保存新的区块,也就是所谓的“共识机制”。
理论基础部分已经提到了,共识机制有很多种,各有各的优势与缺点,接下来我们就用java代码来模拟实现我们最为熟知的一种机制:工作量证明(Proof of Work),顾名思义就是对工作量的证明,在基于POW机制构建的区块链网络中,节点通过计算随机哈希散列的数值争夺记账权,求得正确的数值并生成区块的能力是节点算力的具体表现,计算的过程一般被形象地称为“挖矿”。
简单来说就是,区块链系统设定一套计算规则或者说是一套计算题,在新区块生成前,各个节点都投入到这道题的求解计算中,哪个节点先计算出结果,并得到其它节点的验证和认可,这个节点就会获得新区块的记账权,并获得系统相应的奖励,共识结束。
典型的PoW共识机制应用就是BTC网络,在BTC网络中,共识计算的目标是找到满足某个特定要求的区块Hash(哈希值)。这个区块哈希值就是工作结果的一个证明,计算工作的目的就是为了寻找到这个证明值,上一章节中,测试时我们已经见过这个Hash值:
[{"hash": "25931395e736653212f0258824df4222ae739ec2d5897310258b0857d4d3870c","index": 1,"nonce": 1,"timestamp": 1580970554734,"transactions": [{"businessInfo": "这是创世区块","id": "1"}]}
]
BTC网络PoW使用的Hashcash算法,大致逻辑如下:
- 获取某种公开可知的数据data(BTC网络中,指的是区块头);
- 添加一个计数器nonce,初始值设置为0;
- 计算data与nonce拼接字符串的哈希值;
- 检查上一步的哈希值是否满足某个条件,满足则停止计算,不满足则nonce加1,然后重复第3步和第4步,直到满足这个特定的条件为止。
接下来我们用Java代码实现这个算法,设定满足的特定条件为,Hash值前4位都是0,则计算成功(实际区块链网络中的特定条件要求更高,对计算的运算能力要求也高,并且系统随着计算难度动态调整满足的特定条件,来保证区块生成的速度)。
第一步,我们新建一个共识机制服务类,添加一个“挖矿”方法,计算成功后,获取记账权,调用添加区块的方法,把区块添加到区块链中:
PowService.java
/*** 共识机制* 采用POW即工作量证明实现共识* @author Administrator**/
@Service
public class PowService {@AutowiredBlockCache blockCache;@AutowiredBlockService blockService;/*** 通过“挖矿”进行工作量证明,实现节点间的共识* * @return*/public Block mine(){// 封装业务数据集合,记录区块产生的节点信息,临时硬编码实现List<Transaction> tsaList = new ArrayList<Transaction>();Transaction tsa1 = new Transaction();tsa1.setId("1");tsa1.setBusinessInfo("这是IP为:"+CommonUtil.getLocalIp()+",端口号为:"+blockCache.getP2pport()+"的节点挖矿生成的区块");tsaList.add(tsa1);Transaction tsa2 = new Transaction();tsa2.setId("2");tsa2.setBusinessInfo("区块链高度为:"+(blockCache.getLatestBlock().getIndex()+1));tsaList.add(tsa2);// 定义每次哈希函数的结果 String newBlockHash = "";int nonce = 0;long start = System.currentTimeMillis();System.out.println("开始挖矿");while (true) {// 计算新区块hash值newBlockHash = blockService.calculateHash(blockCache.getLatestBlock().getHash(), tsaList, nonce);// 校验hash值if (blockService.isValidHash(newBlockHash)) {System.out.println("挖矿完成,正确的hash值:" + newBlockHash);System.out.println("挖矿耗费时间:" + (System.currentTimeMillis() - start) + "ms");break;}System.out.println("第"+(nonce+1)+"次尝试计算的hash值:" + newBlockHash);nonce++;}// 创建新的区块Block block = blockService.createNewBlock(nonce, blockCache.getLatestBlock().getHash(), newBlockHash, tsaList);return block;}/*** 验证hash值是否满足系统条件* 暂定前4位是0则满足条件* @param hash* @return*/public boolean isValidHash(String hash) {//System.out.println("难度系数:"+blockCache.getDifficulty());return hash.startsWith("0000");}
}
第二步,编写测试共识机制服务的Controller类方法:
BlockController.java
/*** 工作量证明PoW* 挖矿生成新的区块 */@GetMapping("/mine")@ResponseBodypublic String createNewBlock() {powService.mine();return JSON.toJSONString(blockCache.getBlockChain());}
第三步,启动系统,进行测试。
首先执行http://localhost:8080/create方法,生成创世区块。
其次调用http://localhost:8080/mine方法进行工作量计算证明,生成新的区块,并添加到本地区块链中:
我们来看一下,系统后台计算的过程,此次计算共花费1048ms计算出满足条件的Hash值,共计算4850次:
至此,我们实现了一个简单的工作量证明机制,并在当前区块链系统节点上运行,完成了正确结果的计算,生成了一个新的区块。
接下来我们将会开发一个P2P网络,实现多个节点的同时运行,当一个节点挖矿完成后,通过P2P网络广播给其他节点,其他节点验证通过后,会把新产生的区块添加到自己的区块链上,进而保证整个区块链网络所有节点的数据一致性。
(4)P2P网络开发
前面我们已经实现了一个基本的区块链系统,并且实现了PoW工作量证明共识机制,通过挖矿计算出正确的结果同时生成一个新的区块添加到区块链中,但是这些都是基于单节点的运行,实际的区块链是有多个节点同时运行的分布式网络系统,所有节点同时计算抢夺记账权,共同维护一条完整的区块链。
接下来我们基于Java的WebSocket实现一个Peer-to-Peer网络,实现多个节点间的相互通信,通过本章节,我们将要实现以下功能:
- 创建一个基于java的p2p网络- 运行多个节点,且多个节点通过p2p网络自动同步区块信息- 一个节点挖矿生成新的区块后,自动广播给其他所有节点- 每个节点在接收到其他节点发送的区块内容后,进行验证,验证通过添加到本地区块链上- 在自我节点查看整个区块链内容
开发测试本章节的功能,我们最好准备两台电脑或者虚拟机,再或者Docker集群环境也可以,便于多节点的运行测试。如果只有一台电脑也可以,各个节点运行的端口号设置为不相同即可。
第一步,开发思路整理
目前我们已经实现了单节点的区块生成,那么我们接下来只需要实现各个节点的消息同步即可。
-
首先,通过java代码实现p2p网络的server端和client端,每个节点既是服务端也是客户端。
-
然后,一个节点启动时,会寻找区块链网络上的有效节点,并建立socket连接(BTC网络可以通过使用“DNS”种子方式获取BTC有效节点,DNS种子提供比特币节点的IP地址列表),我们直接把节点列表配置到application.yml文件中。
-
接着,从连接上的节点获取最新的区块信息,如果当前节点首次运行,则获取整个区块链信息,并替换到本地。
-
之后,各个节点同时挖矿计算,哪个节点先计算完成,就把生成的新区块全网广播给其他所有节点(实际的区块链网络一直在计算,我们为了便于测试,手动触发一个节点挖矿产生区块,然后全网广播给其他所有节点)。
-
最后,当一个节点收到广播内容后,对接收到的新区块进行验证,验证通过后添加到本地区块链上,验证的首要条件是新区块的高度必须比本地的区块链高度要高。
第二步,先实现P2P网络server端
新建一个P2PServer类,并添加一个初始化server端的方法:
P2PServer.java
/*** p2p服务端* * @author Jared Jia**/
@Component
public class P2PServer {@AutowiredP2PService p2pService;public void initP2PServer(int port) {WebSocketServer socketServer = new WebSocketServer(new InetSocketAddress(port)) {/*** 连接建立后触发*/@Overridepublic void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) {p2pService.getSockets().add(webSocket);}/*** 连接关闭后触发*/@Overridepublic void onClose(WebSocket webSocket, int i, String s, boolean b) {p2pService.getSockets().remove(webSocket);System.out.println("connection closed to address:" + webSocket.getRemoteSocketAddress());}/*** 接收到客户端消息时触发*/@Overridepublic void onMessage(WebSocket webSocket, String msg) {//作为服务端,业务逻辑处理p2pService.handleMessage(webSocket, msg, p2pService.getSockets());}/*** 发生错误时触发*/@Overridepublic void onError(WebSocket webSocket, Exception e) {p2pService.getSockets().remove(webSocket);System.out.println("connection failed to address:" + webSocket.getRemoteSocketAddress());}@Overridepublic void onStart() {}};socketServer.start();System.out.println("listening websocket p2p port on: " + port);}
}
第三步,实现P2P网络client端
P2PClient.java
/*** p2p客户端* * @author Jared Jia**/
@Component
public class P2PClient {@AutowiredP2PService p2pService;public void connectToPeer(String addr) {try {final WebSocketClient socketClient = new WebSocketClient(new URI(addr)) {@Overridepublic void onOpen(ServerHandshake serverHandshake) {//客户端发送请求,查询最新区块p2pService.write(this, p2pService.queryLatestBlockMsg());p2pService.getSockets().add(this);}/*** 接收到消息时触发* @param msg*/@Overridepublic void onMessage(String msg) {p2pService.handleMessage(this, msg, p2pService.getSockets());}@Overridepublic void onClose(int i, String msg, boolean b) {p2pService.getSockets().remove(this);System.out.println("connection closed");}@Overridepublic void onError(Exception e) {p2pService.getSockets().remove(this);System.out.println("connection failed");}};socketClient.connect();} catch (URISyntaxException e) {System.out.println("p2p connect is error:" + e.getMessage());}}
}
第四步,定义P2P网络同步的消息模型
同步的消息模型,我们定义为四类,分别是:
BlockConstant.java
// 查询最新的区块public final static int QUERY_LATEST_BLOCK = 1;// 返回最新的区块public final static int RESPONSE_LATEST_BLOCK = 2;// 查询整个区块链public final static int QUERY_BLOCKCHAIN = 3;// 返回整个区块链public final static int RESPONSE_BLOCKCHAIN = 4;
定义一个各个节点间传递的消息模型:
Message.java
/*** p2p通讯消息** @author Jared Jia* */
public class Message implements Serializable {private static final long serialVersionUID = 1L;/*** 消息类型*/private int type;/*** 消息内容*/private String data;/****set get方法省略****/}
**
第五步,实现向其他节点广播的方法
新建一个p2p网络服务类,向外发送消息,或者处理当前节点收到其他节点发送的请求。
P2PService.java
/*** 全网广播消息* @param message*/public void broatcast(String message) {List<WebSocket> socketsList = this.getSockets();if (CollectionUtils.isEmpty(socketsList)) {return;}System.out.println("======全网广播消息开始:");for (WebSocket socket : socketsList) {this.write(socket, message);}System.out.println("======全网广播消息结束");}
第六步,开发消息处理路由
第五步中已经实现了向外发送消息,本步骤实现接收消息。
首先设计一个服务端和客户端共用的消息路由,来分发消息给对应的处理单元。
P2PService.java
/*** 客户端和服务端共用的消息处理方法* @param webSocket* @param msg* @param sockets*/public void handleMessage(WebSocket webSocket, String msg, List<WebSocket> sockets) {try {Message message = JSON.parseObject(msg, Message.class);System.out.println("接收到IP地址为:" +webSocket.getRemoteSocketAddress().getAddress().toString()+",端口号为:"+ webSocket.getRemoteSocketAddress().getPort() + "的p2p消息:"+ JSON.toJSONString(message));switch (message.getType()) {//客户端请求查询最新的区块:1case BlockConstant.QUERY_LATEST_BLOCK:write(webSocket, responseLatestBlockMsg());//服务端调用方法返回最新区块:2break;//接收到服务端返回的最新区块:2case BlockConstant.RESPONSE_LATEST_BLOCK:handleBlockResponse(message.getData(), sockets);break;//客户端请求查询整个区块链:3case BlockConstant.QUERY_BLOCKCHAIN:write(webSocket, responseBlockChainMsg());//服务端调用方法返回最新区块:4break;//直接接收到其他节点发送的整条区块链信息:4case BlockConstant.RESPONSE_BLOCKCHAIN:handleBlockChainResponse(message.getData(), sockets);break;}} catch (Exception e) {System.out.println("处理IP地址为:" +webSocket.getRemoteSocketAddress().getAddress().toString()+",端口号为:"+ webSocket.getRemoteSocketAddress().getPort() + "的p2p消息错误:" + e.getMessage());}}
第七步,开发消息处理单元
有了消息路由之后,接着编写不同的处理单元,处理其他节点发送来的区块或者区块链信息,总体原则是:先校验其他节点发送来的区块或者区块链的有效性,然后判断它们的高度比当前节点的区块链高度要高,如果高则替换本地的区块链,或者把新区块添加到本地区块链上。
P2PService.java
/*** 处理其它节点发送过来的区块信息* @param blockData* @param sockets*/public synchronized void handleBlockResponse(String blockData, List<WebSocket> sockets) {//反序列化得到其它节点的最新区块信息Block latestBlockReceived = JSON.parseObject(blockData, Block.class);//当前节点的最新区块Block latestBlock = blockCache.getLatestBlock();if (latestBlockReceived != null) {if(latestBlock != null) {//如果接收到的区块高度比本地区块高度大的多if(latestBlockReceived.getIndex() > latestBlock.getIndex() + 1) {broatcast(queryBlockChainMsg());System.out.println("重新查询所有节点上的整条区块链");}else if (latestBlockReceived.getIndex() > latestBlock.getIndex() && latestBlock.getHash().equals(latestBlockReceived.getPreviousHash())) {if (blockService.addBlock(latestBlockReceived)) {broatcast(responseLatestBlockMsg());}System.out.println("将新接收到的区块加入到本地的区块链");}}else if(latestBlock == null) {broatcast(queryBlockChainMsg());System.out.println("重新查询所有节点上的整条区块链");}}}/*** 处理其它节点发送过来的区块链信息* @param blockData* @param sockets*/public synchronized void handleBlockChainResponse(String blockData, List<WebSocket> sockets) {//反序列化得到其它节点的整条区块链信息List<Block> receiveBlockchain = JSON.parseArray(blockData, Block.class);if(!CollectionUtils.isEmpty(receiveBlockchain) && blockService.isValidChain(receiveBlockchain)) {//根据区块索引先对区块进行排序Collections.sort(receiveBlockchain, new Comparator<Block>() {public int compare(Block block1, Block block2) {return block1.getIndex() - block2.getIndex();}});//其它节点的最新区块Block latestBlockReceived = receiveBlockchain.get(receiveBlockchain.size() - 1);//当前节点的最新区块Block latestBlock = blockCache.getLatestBlock();if(latestBlock == null) {//替换本地的区块链blockService.replaceChain(receiveBlockchain);}else {//其它节点区块链如果比当前节点的长,则处理当前节点的区块链if (latestBlockReceived.getIndex() > latestBlock.getIndex()) {if (latestBlock.getHash().equals(latestBlockReceived.getPreviousHash())) {if (blockService.addBlock(latestBlockReceived)) {broatcast(responseLatestBlockMsg());}System.out.println("将新接收到的区块加入到本地的区块链");} else {// 用长链替换本地的短链blockService.replaceChain(receiveBlockchain);}}}}}
第八步,打包生成测试用的可执行jar包
准备两台机器(虚拟机或者Docker集群都行),同时运行两个节点,节点信息如下:
Node1 | Node2 | |
---|---|---|
IP地址 | 192.168.0.104 | 192.168.0.112 |
http端口号 | 8080 | 8090 |
websocket服务端口号 | 7001 | 7002 |
websocket服务地址 | ws://192.168.0.104:7001 | ws://192.168.0.112:7002 |
通过mvn package -Dmaven.test.skip=true命令对工程进行打包,生成可直接运行的jar包,打包前对工程进行配置,配置信息如下图:
节点Node1打包:
节点Node2打包:
分别打包之后,生成两个节点的可执行jar包,如下:
把两个jar包分别放在对应IP的windows机器上,打开命令行模式,进入jar所在文件夹,分别执行以下命令运行两个节点:
java -jar dce-blockchain-node1.jar
java -jar dce-blockchain-node2.jar
启动节点2的时候,可以看到后台日志,已经连接上节点1,如下图所示:
第九步,对两个节点进行测试
首先,两个节点启动后,用postman分别执行http://192.168.0.104:8080/scan和http://192.168.0.112:8090/scan请求,可以看到两个节点的区块链内容都为空。
然后,在节点1上分别执行http://192.168.0.104:8080/create和http://192.168.0.104:8080/mine请求,来生成创世区块,以及通过挖矿产生第二个区块,执行后查看节点1的区块链信息如下:
执行http://192.168.0.104:8080/scan结果:
[{"hash": "5303d2990c139992bdb5a22aa1dac4f2719755304e45bac03ca4a1f1688c909e","index": 1,"nonce": 1,"timestamp": 1581064647736,"transactions": [{"businessInfo": "这是创世区块","id": "1"},{"businessInfo": "区块链高度为:1","id": "2"}]},{"hash": "0000de5eea0c20c2e7d06220bc023886e88dd8784eaa2fd2d1d6c5e581061d85","index": 2,"nonce": 4850,"previousHash": "5303d2990c139992bdb5a22aa1dac4f2719755304e45bac03ca4a1f1688c909e","timestamp": 1581064655139,"transactions": [{"businessInfo": "这是IP为:192.168.0.104,端口号为:7001的节点挖矿生成的区块","id": "1"},{"businessInfo": "区块链高度为:2","id": "2"}]}
]
最后,我们来验证节点2是否已经完成了节点1生成的区块链信息的网络同步,Postman执行http://192.168.0.112:8090/scan请求,查看返回结果:
从结果可以看到,区块链网络节点2已经接收到节点1发送的区块链信息,系统日志如下:
反过来,我们在节点2上再执行一次挖矿操作,可以看到节点1上,已经接收到节点2挖矿新产生的区块信息,并添加到节点1的区块链上:
至此,我们已经实现了一个完整的小型区块链网络,并实现各个节点间的通信,多个节点共同维护同一个区块链信息。
结语:
区块链系统非常庞大,涉及方方面面的技术,本人所演示的代码主要对区块链基础的一些概念进行了诠释,感兴趣的同学,还可以在此基础上继续开发,来实现例如持久层、消息的加密解密、系统账户模型、预言机、侧链技术以及智能合约等区块链系统功能。
写给每个区块链技术人员:
目前市面上流行的企业级区块链框架,例如超级账本Fabric都是国外人员在主导,而我们国内除了几家大厂外,其他很多区块链公司基本都是把人家的东西拿过来进行二次封装,然后对外声称自己公司已经掌握了区块链核心技术,并对企业提供服务,这是一种不好的现象。大家可以想想我们现在用的开发语言、框架有几个真正是国产的,我们再联想一下前段时间中兴、华为被人家核心技术卡脖子事件,就知道我们要做的事情有很多,我们需要去除浮躁,静下心来好好研究底层核心技术,这样才能实现真正的“弯道超车”!
三、源代码与参考文献
1、本文完整源代码地址
https://gitee.com/luckytuan/dce-blockchain
2、Java区块链技术交流QQ群
群文件中有大量区块链技术资料,欢迎进群交流学习:
3、参考书籍
《精通比特币》
《2018年中国区块链产业白皮书》
《超级账本Hyperledger白皮书》
4、参考资料
Fabric项目源码:
https://github.com/hyperledger/fabric
Fabric wiki:
https://wiki.hyperledger.org/projects/Fabric
Fabric官方文档:
https://hyperledger-fabric.readthedocs.io/en/latest/tutorials.html
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
相关文章
- 【瞬态动力学】斜齿轮齿面摩擦接触分析
目录引言摩擦接触斜齿轮摩擦接触实例材料属性定义接触网格设置边界条件结果分析 引言 瞬态动力学分析(亦称时间历程分析)是确定承受任意随时间变化载荷结构的动力学响应的一种方法。 瞬态动力学分析确定结构在稳态载荷、瞬态载荷和简谐载荷的随意组合作用下的随时间变化的位移…...
2024/4/27 9:46:43 - 使用ForkJoin框架实现归并排序
Fork/JoinFork/Join是一种采用“分治法”思想的框架,即将一个大问题拆分为无数个相同的小问题,然后各个击破再统一组装。jdk中的ForkJoinPool就是使用fork/join框架的多线程工具类(同其他线程池一样,扩展自抽象类AbstractExecutorService),我们只需要编写如何拆分任务的代…...
2024/4/15 5:03:20 - 10.非父子组件之间的通信-兄弟组件间的通信
兄弟组件间的传值一、单页面中实现效果二、单文件组件中1.在main.js中写入一个事件中心,并向外暴露这个事件。2.在组件 A 中 import 这个事件中心2.在组件 B 中 import 这个事件中心 一、单页面中 情景:实现点击组件A中的按钮触发组件B中的数据增加,点击组件B中的按钮触发组…...
2024/4/28 0:57:50 - LeetCode 559. N叉树的最大深度
原题目:https://leetcode-cn.com/problems/maximum-depth-of-n-ary-tree/思路:采用BFS,首先用size记录属于同一层的节点的个数,然后把这些节点的孩子加入到队列之中。代码;class Solution { public:int maxDepth(Node* root) {if(!root) return 0;queue<Node*> q;q.p…...
2024/4/28 3:53:21 - Java中Scanner的用法:单行/多行输入
Java的Scanner用法,主要用于算法笔试时的控制台输入1.多行输入以三行输入为例,第一行输入两个数字m,n,分别表示数组num1和num2的长度,第二行和第三行输入num1和num2的元素,以空格分隔。// 输入如下 3 4 10 2 3 11 4 5 6程序如下:import java.util.Arrays; import java.…...
2024/4/27 7:03:37 - day18笔记
1、集合 2、Iterator迭代器 3、增强for循环 4、泛型 ###01集合使用的回顾 *A:集合使用的回顾 *a.ArrayList集合存储5个int类型元素 public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(111); list.add(222); list.add(333); list.add(444);…...
2024/4/15 5:03:13 - PHP多进程 - 回收子进程pcntl_wait 与 pcntl_waitpid
前面已经讲到,当父进程是一个常驻进程的时候,子进程退出了,父进程没有回收它,那么该子进程会成为僵尸进程,且必须等到父进程退出了才会被销毁。所以,如果父进程只是个短暂的进程,那么也没必要去回收子进程,当然主动回收更好。 PHP中父进程回收子进程的函数有两个 1、pc…...
2024/4/28 7:11:18 - jupyter notebook导出含有中文的pdf文件
jupyter notebook中如果含有中文,直接导出pdf的话可能出现错误,或者是乱码。所以下面介绍一种解决方法。首先,把jupyter notebook下载为tex文件。之后,解压缩该zip文件,使用一个文本编辑器(比如notepad)编辑tex文件。加入以下三行:\usepackage{fontspec, xunicode, xlt…...
2024/4/28 4:52:12 - 检测来自无头Chrome的爬虫
原文:Detecting Chrome Headless 作者:Antoine Vastel 作者注:我在Github上建了一个可以利用浏览器指纹来检测机器人和爬虫的库,虽然仍在开发中,但各位看官已经可以开始使用了,飞机票在此。 何为无头浏览器 无头浏览器(Headless browser)是一种无需图形操作界面的浏览器。…...
2024/4/27 22:36:16 - Git和GitHub的使用
Git是Linux的创始人为了更好的管理Linux而设计的一个分布式版本控制系统。GitHub则是使用Git进行代码托管的一个网站。具体介绍可以查看Git简介。本文是根据B站视频整理的部分笔记。 1、使用GitHub GitHub的官网:https://github.com/ 1.1 注册 进入官网后,会看到以下界面:点…...
2024/4/24 11:34:05 - 爬虫之JS解析,Python模拟JS代码运行。(附带模拟人人网登录案例)
六、JS解析 1.定位js文件1.通过initiator定位到js文件 2.通过search搜索关键字定位到js文件 3.通过元素绑定的事件监听函数找到js文件,Event Listeners 注:三种方法不能保证每一种都能找到js文件,都试试2.js代码分析,掌握加密步骤可以加断点3.模拟重现1.通过第三方js加载模…...
2024/4/24 11:34:05 - 程序员学习的网站(持续收集中)
1.http://www.mamicode.com/ 码迷 (看技术文章)2.https://www.nowcoder.com/ 牛客(刷题)3.https://www.oschina.net/ 开源中国(技术文章)4.https://leetcode-cn.com/ 力扣(刷题)5.https://www.51zxw.net/ 51自学网6.http…...
2024/4/28 5:43:14 - 数码管点亮中几个常见三极管基极导通状态
9012是pnp三极管,当基极为低电平时,处于导通状态 8050,9013是npn 三极管,基极为高电平,处于导通状态...
2024/4/23 15:20:07 - Jenkins Tips:去掉pipeline中shell命令的调试信息
在Jenkins的Console Output中有时会看到‘+’开头的shell命令调试信息,看起来比较混乱。原因是Jenkins默认用‘-xe’的选项去运行‘sh’命令。例如如下pipeline会产生后续的输出。pipeline {agent nonestages {stage(Example) {steps {node(master) {sh dmesg | grep raspberr…...
2024/4/15 5:03:33 - webstorm中保存自动格式化
目录设置保存自动格式化和eslint修复1、打开文件2、启动宏任务录制3、录制宏任务4、结束宏任务录制5、对宏任务设置启动快捷键并覆盖原来的ctrl + s快捷键本人的格式化规则htmljavascriptstyle 如果你使用过vscode,那么你应该知道它可以借助插件来在保存时自动格式化。其实web…...
2024/4/28 9:37:40 - C语言笔记----2012年计算机联考真题
C语言笔记【2】 知识点: 1、结构体占字节数 2、小端地址与大段地址 3、 边界对齐方式存储【2012年计算机联考真题】 某计算机存储器按字节编址,采用小端方式存放数据,假定编译器规定int和short型长度分别为32位和16位,并且数据按边界对齐存储,其C语言程序段如下: struct{…...
2024/4/15 5:03:31 - 如何为你的硬件开发Simulink Toolbox(3)
在Simulink Toolbox中,需要为自定义开发的block写S-Function,我们使用的C语言编写,也叫C Mex S-Function,C语言编写的S-Function仿真速度快,也可以调用具体的硬件驱动和使用已有的C库,功能十分强大,我们的目标是为硬件编写Toolbox,S-Function的主要功能是将自定义Block…...
2024/4/28 6:30:10 - beego 实现 react、vue 应用发布
现代的 web 开发提倡前后端分离,当然 beego 原生支持 web应用开发,但是现在主流 web 开发都是通过 react 或者 vue 实现,和服务直接通过 REST API 交互。于是便想利用 beego 的静态文件发布能力发布 react 或 vue 开发的前端页面,这样,就可以实现简单 web 应用的快速部署和…...
2024/4/28 6:41:40 - 7-38 寻找大富翁 (25分)(堆排序+栈)
7-38 寻找大富翁 (25分) 胡润研究院的调查显示,截至2017年底,中国个人资产超过1亿元的高净值人群达15万人。假设给出N个人的个人资产值,请快速找出资产排前M位的大富翁。 输入格式: 输入首先给出两个正整数N(≤10 6 )和M(≤10),其中N为总人数,M为需要找出的大富…...
2024/4/27 22:48:05 - 用python实现(1.求输入的百倍,十位,个位数;2.输入a,b和ab间夹角,计算c边长;3.计算两点间曼哈顿距离)
import math print("1.获取输入整数的百位,十位,个位数(无位数限制)") print(*map(int, input("Please input number:"))) print("2.输入a,b和ab之间的夹角,求c边长") a, b, degree = map(float, input("Please input a,b,degree:&quo…...
2024/4/28 6:07:39
最新文章
- Blender笔记之基本操作
code review! —— 2024-04-27 杭州 Blender笔记…...
2024/4/28 12:44:37 - 梯度消失和梯度爆炸的一些处理方法
在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言,在此感激不尽。 权重和梯度的更新公式如下: w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...
2024/3/20 10:50:27 - [C++][算法基础]模拟队列(数组)
实现一个队列,队列初始为空,支持四种操作: push x – 向队尾插入一个数 x;pop – 从队头弹出一个数;empty – 判断队列是否为空;query – 查询队头元素。 现在要对队列进行 M 个操作,其中的每…...
2024/4/22 21:35:57 - 【Godot4自学手册】第三十五节摇杆控制开门
本节主要实现,在地宫墙壁上安装一扇门,在核实安装一个开门的摇杆,攻击摇杆,打开这扇门,但是只能攻击一次,效果如下: 一、添加完善节点 切换到underground场景,先将TileMap修改一下…...
2024/4/26 19:55:01 - 【外汇早评】美通胀数据走低,美元调整
原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...
2024/4/26 18:09:39 - 【原油贵金属周评】原油多头拥挤,价格调整
原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...
2024/4/28 3:28:32 - 【外汇周评】靓丽非农不及疲软通胀影响
原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...
2024/4/26 23:05:52 - 【原油贵金属早评】库存继续增加,油价收跌
原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...
2024/4/27 4:00:35 - 【外汇早评】日本央行会议纪要不改日元强势
原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...
2024/4/27 17:58:04 - 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响
原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...
2024/4/27 14:22:49 - 【外汇早评】美欲与伊朗重谈协议
原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...
2024/4/28 1:28:33 - 【原油贵金属早评】波动率飙升,市场情绪动荡
原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...
2024/4/27 9:01:45 - 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试
原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...
2024/4/27 17:59:30 - 【原油贵金属早评】市场情绪继续恶化,黄金上破
原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...
2024/4/25 18:39:16 - 【外汇早评】美伊僵持,风险情绪继续升温
原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...
2024/4/28 1:34:08 - 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势
原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...
2024/4/26 19:03:37 - 氧生福地 玩美北湖(上)——为时光守候两千年
原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...
2024/4/28 1:22:35 - 氧生福地 玩美北湖(中)——永春梯田里的美与鲜
原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...
2024/4/25 18:39:14 - 氧生福地 玩美北湖(下)——奔跑吧骚年!
原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...
2024/4/26 23:04:58 - 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!
原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...
2024/4/27 23:24:42 - 「发现」铁皮石斛仙草之神奇功效用于医用面膜
原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...
2024/4/28 5:48:52 - 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者
原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...
2024/4/26 19:46:12 - 广州械字号面膜生产厂家OEM/ODM4项须知!
原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...
2024/4/27 11:43:08 - 械字号医用眼膜缓解用眼过度到底有无作用?
原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...
2024/4/27 8:32:30 - 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...
解析如下:1、长按电脑电源键直至关机,然后再按一次电源健重启电脑,按F8健进入安全模式2、安全模式下进入Windows系统桌面后,按住“winR”打开运行窗口,输入“services.msc”打开服务设置3、在服务界面,选中…...
2022/11/19 21:17:18 - 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。
%读入6幅图像(每一幅图像的大小是564*564) f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...
2022/11/19 21:17:16 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...
win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面,在等待界面中我们需要等待操作结束才能关机,虽然这比较麻烦,但是对系统进行配置和升级…...
2022/11/19 21:17:15 - 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...
有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows,请勿关闭计算机”的提示,要过很久才能进入系统,有的用户甚至几个小时也无法进入,下面就教大家这个问题的解决方法。第一种方法:我们首先在左下角的“开始…...
2022/11/19 21:17:14 - win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...
置信有很多用户都跟小编一样遇到过这样的问题,电脑时发现开机屏幕显现“正在配置Windows Update,请勿关机”(如下图所示),而且还需求等大约5分钟才干进入系统。这是怎样回事呢?一切都是正常操作的,为什么开时机呈现“正…...
2022/11/19 21:17:13 - 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...
Win7系统开机启动时总是出现“配置Windows请勿关机”的提示,没过几秒后电脑自动重启,每次开机都这样无法进入系统,此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一:开机按下F8,在出现的Windows高级启动选…...
2022/11/19 21:17:12 - 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...
有不少windows10系统用户反映说碰到这样一个情况,就是电脑提示正在准备windows请勿关闭计算机,碰到这样的问题该怎么解决呢,现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法:1、2、依次…...
2022/11/19 21:17:11 - 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...
今天和大家分享一下win7系统重装了Win7旗舰版系统后,每次关机的时候桌面上都会显示一个“配置Windows Update的界面,提示请勿关闭计算机”,每次停留好几分钟才能正常关机,导致什么情况引起的呢?出现配置Windows Update…...
2022/11/19 21:17:10 - 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...
只能是等着,别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚,只能是考虑备份数据后重装系统了。解决来方案一:管理员运行cmd:net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...
2022/11/19 21:17:09 - 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?
原标题:电脑提示“配置Windows Update请勿关闭计算机”怎么办?win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢?一般的方…...
2022/11/19 21:17:08 - 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...
关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!关机提示 windows7 正在配…...
2022/11/19 21:17:05 - 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...
钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...
2022/11/19 21:17:05 - 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...
前几天班里有位学生电脑(windows 7系统)出问题了,具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面,长时间没反应,无法进入系统。这个问题原来帮其他同学也解决过,网上搜了不少资料&#x…...
2022/11/19 21:17:04 - 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...
本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法,并在最后教给你1种保护系统安全的好方法,一起来看看!电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中,添加了1个新功能在“磁…...
2022/11/19 21:17:03 - 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...
许多用户在长期不使用电脑的时候,开启电脑发现电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机。。.这要怎么办呢?下面小编就带着大家一起看看吧!如果能够正常进入系统,建议您暂时移…...
2022/11/19 21:17:02 - 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...
配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容,让我们赶快一起来看一下吧!配置windows update失败 还原更改 请勿关闭计算机&#x…...
2022/11/19 21:17:01 - 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...
不知道大家有没有遇到过这样的一个问题,就是我们的win7系统在关机的时候,总是喜欢显示“准备配置windows,请勿关机”这样的一个页面,没有什么大碍,但是如果一直等着的话就要两个小时甚至更久都关不了机,非常…...
2022/11/19 21:17:00 - 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...
当电脑出现正在准备配置windows请勿关闭计算机时,一般是您正对windows进行升级,但是这个要是长时间没有反应,我们不能再傻等下去了。可能是电脑出了别的问题了,来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...
2022/11/19 21:16:59 - 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...
我们使用电脑的过程中有时会遇到这种情况,当我们打开电脑之后,发现一直停留在一个界面:“配置Windows Update失败,还原更改请勿关闭计算机”,等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢࿰…...
2022/11/19 21:16:58 - 如何在iPhone上关闭“请勿打扰”
Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...
2022/11/19 21:16:57