作者 | VV一笑ヽ

责编 | Carol

出品 | 区块链大本营(blockchain_camp)

封面 | CSDN 付费下载于视觉中国

如果有一个P2P的Demo,我们要怎么才能应用到区块链当中?

今天就来一起尝试一下吧!

首先,我们需要模拟网络中的多个节点相互通讯,我们假设现在的情况是有AB两个节点整个过程如下图所示:

梳理流程

让我们来梳理一下整个流程,明确在P2P网络中需要做的事情。

  1. 启动节点A。A首先创建一个创世区块

  2. 创建钱包A1。调用节点A提供的API创建一个钱包,此时A1的球球币为0。

  3. A1挖矿。调用节点A提供的挖矿API,生成新的区块,同时为A1的钱包有了系统奖励的球球币。

  4. 启动节点B。节点B要向A同步信息,当前的区块链,当前的交易池,当前的所有钱包的公钥。

  5. 创建钱包B1、A2,调用节点A和B的API,要广播(通知每一个节点)出去创建的钱包(公钥),目前节点只有两个,因此A需要告诉B,A2的钱包。B需要告诉A,B1的钱包。

  6. A1转账给B1。调用A提供的API,同时广播交易

  7. A2挖矿记账。调用A提供的API,同时广播新生成的区块

总结一下,就是节点刚开始加入到区块链网络中,需要同步其他节点的

  • 区块链信息

  • 钱包信息

  • 交易信息

已经处于网络中的某个节点,在下述情况下需要通知网络中的其他节点

  • 发生新的交易

  • 创建新的钱包

  • 挖矿产生新区块

P2P的大致流程为下方几点,我们后边的实现会结合这个过程。

  1. client→server 发送消息,一般是请求数据;

  2. server收到消息后,向client发送消息 (调用service,处理后返回数据);

  3. client收到消息处理数据(调用service,对数据处理)。

相关代码

在实现的过程中,由于消息类型较多,封装了一个消息对象用来传输消息,对消息类型进行编码,统一处理,消息对象Message,实现了Serializable接口,使其对象可序列化:

public class Message implements Serializable {
/*** 消息内容,就是我们的区块链、交易池等所需要的信息,使用JSON.toString转化到的json字符串*/
private String data;
/*** 消息类型*/
private int type;
}

涉及到的消息类型(Type)有:

/*** 查询最新的区块*/
private final static int QUERY_LATEST_BLOCK = 0;
/*** 查询整个区块链*/
private final static int QUERY_BLOCK_CHAIN = 1;
/*** 查询交易集合*/
private final static int QUERY_TRANSACTION = 2;
/*** 查询已打包的交易集合*/
private final static int QUERY_PACKED_TRANSACTION = 3;
/*** 查询钱包集合*/
private final static int QUERY_WALLET = 4;
/*** 返回区块集合*/
private final static int RESPONSE_BLOCK_CHAIN = 5;
/*** 返回交易集合*/
private final static int RESPONSE_TRANSACTION = 6;
/*** 返回已打包交易集合*/
private final static int RESPONSE_PACKED_TRANSACTION = 7;
/*** 返回钱包集合*/
private final static int RESPONSE_WALLET = 8;

由于代码太多,就不全部粘在这里了,以Client同步其他节点钱包信息为例,结合上面的P2P网络交互的三个步骤,为大家介绍下相关的实现。

1、client→server 发送消息,一般是请求数据

在Client节点的启动类首先创建Client对象,调用Client内部方法,连接Server。

启动类Main方法中关键代码,(端口参数配置在Args中):

P2PClient p2PClient = new P2PClient();
String url = "ws://localhost:"+args[0]+"/test";
p2PClient.connectToPeer(url);

P2PClient中的connectToPeer方法:

public void connectToPeer(String url) throws IOException, DeploymentException {WebSocketContainer container = ContainerProvider.getWebSocketContainer();URI uri = URI.create(url);
this.session = container.connectToServer(P2PClient.class, uri);
}

P2PClient中,WebSocketContainer.connectToServer的时候会回调onOpen函数,假设我们只查询钱包公钥信息,此时服务端会接收到相应的请求。

@OnOpen
public void onOpen(Session session) {
this.session = session;p2PService.sendMsg(session, p2PService.queryWalletMsg());
}

注意:我把解析消息相关的操作封装到了一个Service 中,方便Server和Client的统一使用。给出相应的queryWalletMsg方法:

public String queryWalletMsg() {
return JSON.toJSONString(new Message(QUERY_WALLET));
}

以及之前提到的sendMsg方法:

@Override
public void sendMsg(Session session, String msg) {
session.getAsyncRemote().sendText(msg);
}

2、Server收到消息后,向Client发送消息(调用Service,处理后返回数据)

Server收到消息,进入P2PServerOnMessage方法

/*** 收到客户端发来消息* @param msg  消息对象*/
@OnMessage
public void onMessage(Session session, String msg) {p2PService.handleMessage(session, msg);
}

p2PService.handleMessage就是解析接收到的消息(Msg),根据类型的不同调用其他的方法(一个巨型Switch语句,这里就介绍一小部分),这里我们接收到了Client传来的信息码QUERY_WALLET

@Override
public void handleMessage(Session session, String msg) {Message message = JSON.parseObject(msg, Message.class);
switch (message.getType()){
case QUERY_WALLET:sendMsg(session, responseWallets());
break;
case RESPONSE_WALLET:handleWalletResponse(message.getData());
break;......}

根据信息码是QUERY_WALLET,调用 responseWallets()方法,得到数据。

private String responseWallets() {
String wallets = blockService.findAllWallets();
return JSON.toJSONString(new Message(RESPONSE_WALLET, wallets));
}

这里我把区块链的相关操作也封装到了一个Service中,下面给出findAllWallets的具体实现,其实就是遍历钱包集合,统计钱包公钥,没有什么难度。

@Override
public String findAllWallets() {List<Wallet> wallets = new ArrayList<>();myWalletMap.forEach((address, wallet) ->{wallets.add(Wallet.builder().publicKey(wallet.getPublicKey()).build());});otherWalletMap.forEach((address, wallet) ->{wallets.add(wallet);});
return JSON.toJSONString(wallets);
}

得到数据之后,返回给Client:

因此我们的 responseWallets()方法中,最后一句话新建了一个Message对象,并设置了信息码为RESPONSE_WALLET,在handleMessage中调用了sendmsg方法回传给Client。

case QUERY_WALLET:sendMsg(session, responseWallets());break;

3、Client收到消息处理数据(调用Service,对数据处理)

Client收到了请求得到的数据,进入P2PClient中的OnMessage方法:

@OnMessage
public void onMessage(String msg) {p2PService.handleMessage(this.session, msg);
}

同样进入我们上面提到的p2PService.handleMessage方法,此时收到的信息码为RESPONSE_WALLET,进入handleWalletResponse方法:

case RESPONSE_WALLET:handleWalletResponse(message.getData());break;

handleWalletResponse的实现, 解析接收到的钱包公钥信息,并存储到Client节点的blockService中。

private void handleWalletResponse(String msg) {List<Wallet> wallets = "\"[]\"".equals(msg)?new ArrayList<>():JSON.parseArray(msg, Wallet.class);wallets.forEach(wallet -> {blockService.addOtherWallet(walletService.getWalletAddress(wallet.getPublicKey()),wallet );});
}

在具体实现中,由于使用到了注入服务的方式,在向Server(@ServerEndpoint)和Client(@ClientEndpoint)中使用@Autowired 注解注入Bean的时候,由于Spring Boot单例的特点。

而Websocket每次都会创建一个新的对象,所以在使用服务的时候会导致出现空指针异常,因此,我们创建了一个工具类Spring til,每次需要服务时,都从Spring容器中获取到我们所需要的Bean,下面给出工具类代码。

public class SpringUtil implements ApplicationContextAware {
public static ApplicationContext applicationContext;@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if (SpringUtil.applicationContext != null) {SpringUtil.applicationContext = applicationContext;}}
/*** 获取applicationContext*/
public static ApplicationContext getApplicationContext() {
return applicationContext;}/*** 通过name获取 Bean.*/
public static Object getBean(String name) {
return getApplicationContext().getBean(name);}
/*** 通过class获取Bean.*/
public static <T> T getBean(Class<T> clazz) {
return getApplicationContext().getBean(clazz);}/*** 通过name,以及Clazz返回指定的Bean*/
public static <T> T getBean(String name, Class<T> clazz) {
return getApplicationContext().getBean(name, clazz);}
}

因此测试之前我们首先需要设定SpringUtil中的applicationContext,下面给出启动类(为了简单测试,两个节点共用一个启动类,根据Args的不同来分别处理)以及相关节点的配置。

public static void main(String[] args) {System.out.println("Hello world");SpringUtil.applicationContext  = SpringApplication.run(Hello.class, args);
if (args.length>0){P2PClient p2PClient = new P2PClient();String url = "ws://localhost:"+args[0]+"/test";
try {p2PClient.connectToPeer(url);} catch (Exception e) {e.printStackTrace();}}

使用时,我们需要手动获取Bean:

//之前是这样//@Autowired//private P2PService p2PService;//改正后,去掉Autowired,每次使用都手动获取beanprivate P2PService p2PService;@OnOpenpublic void onOpen(Session session) {//如果不使用那些,在这里会报空指针异常,p2PService 为 null p2PService = SpringUtil.getBean(P2PService.class);//新增这句话从IVO容器中获取bean p2PService.sendMsg(session, p2PService.queryWalletMsg());}

Hello节点,测试时作为Server:

Test节点,测试时作为Client。

到此,我们就实现了P2P网络中Server节点与Client节点的交互过程。建议你也可以尝试一下,然后在评论区和我们讨论哦!

推荐阅读 

☞不用掉一根头发!用 Flutter + Dart 快速构建一款绝美移动 App

☞看我发现了什么好东西? Java Optional,绝对值得一学 | 原力计划

☞腾讯提结合ACNet进行细粒度分类,效果达到最新SOTA | CVPR 2020

☞我最喜欢的云 IDE 推荐!

☞智能合约编写之Solidity的高级特性

☞返鄂复工人员自述:回武汉上班,要先飞合肥,再由公司包车接回去

你点的每一个在看,我认真当成了喜欢

编程小白模拟简易比特币系统,手把手带你写一波!(附代码)相关推荐

  1. 【NLP傻瓜式教程】手把手带你RCNN文本分类(附代码)

    继续之前的文本分类系列 [NLP傻瓜式教程]手把手带你CNN文本分类(附代码) [NLP傻瓜式教程]手把手带你RNN文本分类(附代码) [NLP傻瓜式教程]手把手带你fastText文本分类(附代码) ...

  2. 【NLP傻瓜式教程】手把手带你HAN文本分类(附代码)

    继续之前的文本分类系列 [NLP傻瓜式教程]手把手带你CNN文本分类(附代码) [NLP傻瓜式教程]手把手带你RNN文本分类(附代码) [NLP傻瓜式教程]手把手带你fastText文本分类(附代码) ...

  3. 【NLP傻瓜式教程】手把手带你fastText文本分类(附代码)

    写在前面 已经发布: [NLP傻瓜式教程]手把手带你CNN文本分类(附代码) [NLP傻瓜式教程]手把手带你RNN文本分类(附代码) 继续NLP傻瓜式教程系列,今天的教程是基于FAIR的Bag of ...

  4. 【NLP保姆级教程】手把手带你RNN文本分类(附代码)

    写在前面 这是NLP保姆级教程的第二篇----基于RNN的文本分类实现(Text RNN) 参考的的论文是来自2016年复旦大学IJCAI上的发表的关于循环神经网络在多任务文本分类上的应用:Recur ...

  5. 【NLP】保姆级教程:手把手带你CNN文本分类(附代码)

    分享一篇老文章,文本分类的原理和代码详解,非常适合NLP入门! 写在前面 本文是对经典论文<Convolutional Neural Networks for Sentence Classifi ...

  6. 【NLP傻瓜式教程】手把手带你RNN文本分类(附代码)

    文章来源于NewBeeNLP,作者kaiyuan 写在前面 这是NLP傻瓜式教程的第二篇----基于RNN的文本分类实现(Text RNN) 参考的的论文是来自2016年复旦大学IJCAI上的发表的关 ...

  7. 【NLP傻瓜式教程】手把手带你CNN文本分类(附代码)

    文章来源于NewBeeNLP,作者kaiyuan 写在前面 本文是对经典论文<Convolutional Neural Networks for Sentence Classification[ ...

  8. 【NLP保姆级教程】手把手带你CNN文本分类(附代码)

    分享一篇老文章,文本分类的原理和代码详解,非常适合NLP入门! 写在前面 本文是对经典论文<Convolutional Neural Networks for Sentence Classifi ...

  9. 【NLP】Pyhon+讯飞开放平台:​手把手带你写一个智能语音播报系统

    手把手带你写一个智能语音播报系统. 微信扫码登陆讯飞开放平台:https://www.xfyun.cn/ 完成个人认证. 在控制台创建应用,注意应用名称全库查重,很容易跟别人重复. 可查看到pytho ...

最新文章

  1. Django基础知识
  2. 【疯狂积累CSS】2:利用@media screen实现网页布局的自适应
  3. 成功解决The subservice has not been subscribed.
  4. oracle 提示i386,新手请教:RAC安装时检测i386软件包未安装,这个怎么处理?
  5. 2440 休眠唤醒的实现过程(作者:wogoyixikexie@gliet)
  6. unity5.x Translate平移移动 以及GetComponent获取组件
  7. javascript学习之数组的使用一 push pop shift unshift 方法
  8. [渝粤教育] 天水师范学院 GNSS测量原理及其应用 参考 资料
  9. 跟我从零基础学习Unity3D开发--NGUI入门基础
  10. 转载 elm中文手册
  11. jpa报错:Provided id of the wrong type for class
  12. 用scratch2.0编飞机大战
  13. 从Transformer、BERT到GPT2和XLNet:高端玩家如何用论文互怼
  14. PLL Simulink行为模型
  15. 本周内外盘行情回顾2022.2.27
  16. Chapter3:字符串编码和文件操作
  17. 关于MSTAR的IO初始化
  18. 半导体芯片产业无尘车间激光尘埃粒子计数器
  19. 计算机刚过国家线能调剂到哪些学校,本科985刚过国家线好调剂吗?
  20. C语言计算短路电流的软件,短路电流计算软件 Archives | Trace Software International

热门文章

  1. C++调用函数模仿数字钟表
  2. Qt绘制形状不规则窗口(一)
  3. 【cornerstoneTools】【vue】使用cornerstoneTools搭建标注系统相关代码
  4. mysql缓存怎么防止缓存击穿_带你搞明白什么是缓存穿透、缓存击穿、缓存雪崩...
  5. auto.js停止所有线程_使用多线程处理输入的数据
  6. 【机器学习】Andrew Ng——02单变量线性回归
  7. 理解Unix/Linux系统中的文件描述符
  8. C++11多线程------std::async
  9. Tensorflow卷积神经网络识别MINST手写数字
  10. Flutter进阶—质感设计之进度条