责编 | Carol
出品 | 区块链大本营(blockchain_camp)
封面 | CSDN 付费下载于视觉中国
如果有一个P2P的Demo,我们要怎么才能应用到区块链当中?
今天就来一起尝试一下吧!
首先,我们需要模拟网络中的多个节点相互通讯,我们假设现在的情况是有AB两个节点整个过程如下图所示:
梳理流程
让我们来梳理一下整个流程,明确在P2P网络中需要做的事情。
启动节点A。A首先创建一个创世区块
创建钱包A1。调用节点A提供的API创建一个钱包,此时A1的球球币为0。
A1挖矿。调用节点A提供的挖矿API,生成新的区块,同时为A1的钱包有了系统奖励的球球币。
启动节点B。节点B要向A同步信息,当前的区块链,当前的交易池,当前的所有钱包的公钥。
创建钱包B1、A2,调用节点A和B的API,要广播(通知每一个节点)出去创建的钱包(公钥),目前节点只有两个,因此A需要告诉B,A2的钱包。B需要告诉A,B1的钱包。
A1转账给B1。调用A提供的API,同时广播交易。
A2挖矿记账。调用A提供的API,同时广播新生成的区块。
总结一下,就是节点刚开始加入到区块链网络中,需要同步其他节点的
区块链信息
钱包信息
交易信息
已经处于网络中的某个节点,在下述情况下需要通知网络中的其他节点
发生新的交易
创建新的钱包
挖矿产生新区块
P2P的大致流程为下方几点,我们后边的实现会结合这个过程。
client→server 发送消息,一般是请求数据;
server收到消息后,向client发送消息 (调用service,处理后返回数据);
client收到消息处理数据(调用service,对数据处理)。
相关代码
在实现的过程中,由于消息类型较多,封装了一个消息对象用来传输消息,对消息类型进行编码,统一处理,消息对象Message,实现了Serializable接口,使其对象可序列化:
public class Message implements Serializable {*** 消息内容,就是我们的区块链、交易池等所需要的信息,使用JSON.toString转化到的json字符串*private String data;*** 消息类型*private int type;}
涉及到的消息类型(Type)有:
由于代码太多,就不全部粘在这里了,以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
P2PClient
WebSocketContainer.connectToServer
onOpen
@OnOpenpublic 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
@Overridepublic void sendMsg(Session session,String msg) {session.getAsyncRemote.sendText(msg);}
2、Server收到消息后,向Client发送消息(调用Service,处理后返回数据)
Server收到消息,进入P2PServer
OnMessage
*** 收到客户端发来消息* @param msg 消息对象*@OnMessagepublic void onMessage(Session session,String msg) {p2PService.handleMessage(session,msg);}
p2PService.handleMessage
QUERY_WALLET
根据信息码是QUERY_WALLET
responseWallets
private String responseWallets {String wallets = blockService.findAllWallets;return JSON.toJSONString(new Message(RESPONSE_WALLET,wallets));}
这里我把区块链的相关操作也封装到了一个Service中,下面给出findAllWallets
@Overridepublic 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
RESPONSE_WALLET
handleMessage
sendmsg
case QUERY_WALLET:sendMsg(session,responseWallets);break;
3、Client收到消息处理数据(调用Service,对数据处理)
Client收到了请求得到的数据,进入P2PClient
OnMessage
@OnMessagepublic void onMessage(String msg) {p2PService.handleMessage(this.session,msg);}
同样进入我们上面提到的p2PService.handleMessage
RESPONSE_WALLET
handleWalletResponse
case RESPONSE_WALLET:handleWalletResponse(message.getData);break;
handleWalletResponse
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,下面给出工具类代码。
因此测试之前我们首先需要设定SpringUtil
applicationContext
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 为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的高级特性
☞返鄂复工人员自述:回武汉上班,要先飞合肥,再由公司包车接回去