作者 | VV一笑ヽ
责编 | 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)有:/**
* 查询最新的区块
*/
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);中的方法:public void connectToPeer(String url) throws IOException, DeploymentException {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
URI uri = URI.create(url);
this.session = container.connectToServer(P2PClient.class, uri);
}中,- WebSocketContainer.connectToServer
复制代码 的时候会回调函数,假设我们只查询钱包公钥信息,此时服务端会接收到相应的请求。@OnOpen
public void onOpen(Session session) {
this.session = session;
p2PService.sendMsg(session, p2PService.queryWalletMsg());
}
注意:我把解析消息相关的操作封装到了一个Service 中,方便Server和Client的统一使用。给出相应的方法:public String queryWalletMsg() {
return JSON.toJSONString(new Message(QUERY_WALLET));
}
以及之前提到的方法:@Override
public void sendMsg(Session session, String msg) {
session.getAsyncRemote().sendText(msg);
}
2、Server收到消息后,向Client发送消息(调用Service,处理后返回数据)
Server收到消息,进入中方法/**
* 收到客户端发来消息
* @param msg 消息对象
*/
@OnMessage
public void onMessage(Session session, String msg) {
p2PService.handleMessage(session, msg);
}就是解析接收到的消息(Msg),根据类型的不同调用其他的方法(一个巨型Switch语句,这里就介绍一小部分),这里我们接收到了Client传来的信息码。@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;
......
}
根据信息码是,调用方法,得到数据。
private String responseWallets() {
String wallets = blockService.findAllWallets();
return JSON.toJSONString(new Message(RESPONSE_WALLET, wallets));
}
这里我把区块链的相关操作也封装到了一个Service中,下面给出的具体实现,其实就是遍历钱包集合,统计钱包公钥,没有什么难度。
@Override
public String findAllWallets() {
List 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:
因此我们的方法中,最后一句话新建了一个Message对象,并设置了信息码为,在中调用了方法回传给Client。case QUERY_WALLET:
sendMsg(session, responseWallets());
break;
3、Client收到消息处理数据(调用Service,对数据处理)
Client收到了请求得到的数据,进入中的方法:@OnMessage
public void onMessage(String msg) {
p2PService.handleMessage(this.session, msg);
}
同样进入我们上面提到的方法,此时收到的信息码为,进入方法:
case RESPONSE_WALLET:
handleWalletResponse(message.getData());
break;的实现, 解析接收到的钱包公钥信息,并存储到Client节点的中。private void handleWalletResponse(String msg) {
List 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 getBean(Class clazz) {
return getApplicationContext().getBean(clazz);
}
/**
* 通过name,以及Clazz返回指定的Bean
*/
public static T getBean(String name, Class clazz) {
return getApplicationContext().getBean(name, clazz);
}
}
因此测试之前我们首先需要设定中的,下面给出启动类(为了简单测试,两个节点共用一个启动类,根据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节点的交互过程。建议你也可以尝试一下,然后在评论区和我们讨论哦!推荐阅读
[/url][url=http://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=2650740638&idx=1&sn=bbc01a86dd890fd4e5908df56085d63c&chksm=bea7664d89d0ef5b9b8cdefdcd7581e245208c97e5493ac61e7bb8d64bba0e095b6fb9dd745e&scene=21#wechat_redirect]不用掉一根头发!用 Flutter + Dart 快速构建一款绝美移动 App
[/url][url=http://mp.weixin.qq.com/s?__biz=MjM5MjAwODM4MA==&mid=2650740638&idx=3&sn=53f2e85720607a88dfc9a8a3db3f91df&chksm=bea7664d89d0ef5b04cc39179a2faf964c6be85a8d97ef0608d89c60567ed8cda10599a183c7&scene=21#wechat_redirect]看我发现了什么好东西?Java Optional,绝对值得一学 | 原力计划
腾讯提结合ACNet进行细粒度分类,效果达到最新SOTA | CVPR 2020
[/url][url=http://mp.weixin.qq.com/s?__biz=MzA3MjY1MTQwNQ==&mid=2649834027&idx=3&sn=e7bdd6738953d51fb62864fa2cf86b67&chksm=871ee5c9b0696cdfef4c3245b0d4e1c48e010524b780c932a12cef7be3deec0f3ce5b0767a5f&scene=21#wechat_redirect]我最喜欢的云 IDE 推荐![/url][url=http://mp.weixin.qq.com/s?__biz=MzU2MTE1NDk2Mg==&mid=2247500249&idx=3&sn=c610713f0830288010cfdb5d7d2dc019&chksm=fc7f9f24cb0816321762ded03e4b1bcd375e2d8b64a639d0bd3a81cfb56152f78a837be628c1&scene=21#wechat_redirect]智能合约编写之Solidity的高级特性
返鄂复工人员自述:回武汉上班,要先飞合肥,再由公司包车接回去
你点的每一个在看,我认真当成了喜欢 |
|