作者| VV微笑
编辑|卡罗尔
出品|区块链大本营(区块链_营地)
封面| CSDN视觉中国付费下载
如果有一个P2P演示,我们如何将它应用到区块链?
今天就来试试吧!
首先,我们需要模拟网络中多个节点之间的通信。我们假设有两个节点AB,整个过程如下图所示:
梳理过程
我们来梳理一下整个流程,明确一下P2P网络需要做的事情。
开始节点a. A首先创建一个创建块。
创建钱包A1。调用节点A提供的API创建一个wallet。此时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的一般流程如下,后面的实现会结合这个流程。
客户端服务器发送消息,通常是请求数据;服务器收到消息后,将消息发送给客户端(调用服务,处理后返回数据);客户端接收消息处理数据(调用服务来处理数据)。相关代码
在实现过程中,由于消息类型较多,所以封装一个消息对象来传递消息,对消息类型进行统一编码和处理。message对象Message实现Serializable接口,使其对象可序列化:
公共类消息实现者serializable的消息内容{/* * *是我们的区块链和事务池需要的信息,json string */private String数据由JSON.toString转换;/* * *消息类型*/private int类型;}涉及的消息类型有:
/* * *查询最新块*/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;/* * *返回block set */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;因为代码太多,这里就不全卡了。以客户端同步其他节点钱包信息为例,结合以上P2P网络交互的三个步骤,为大家介绍相关实现。
1.客户端服务器发送消息,通常是请求数据。
客户机节点的启动类首先创建一个客户机对象,调用客户机的内部方法,并连接到服务器。
启动类的Main方法中的关键代码(端口参数在Args中配置):
P2PClient p2PClient=新的P2P client;string URL=' ws://localhost:' args[0]'/test 'P2P client . connectto peer(URL);P2PClient中的ConnectToPeer方法;
公共void连接到对等方(字符串URL)抛出IOException,部署异常{ WebSocketContainer container=容器提供者。getwebsocketcontainerURI uri=uri。创建(URL);这个。会话=容器。connecttoserver(P2P客户端。类,uri);}客户端中,websocketcontainer。connecttoserver的时候会回调奥诺彭函数,假设我们只查询钱包公钥信息,此时服务端会接收到相应的请求。
@ OnOpenpublic void on open(Session Session){ this。Session=会话;p2PService.sendMsg(会话,P2P服务。querywalletmsg);}注意:我把解析消息相关的操作封装到了一个服务中,方便计算机网络服务器和客户的统一使用。给出相应的queryWalletMsg方法:
公共字符串查询钱包消息{返回JSON。tojsonstring(新消息(QUERY _ WALLET));}以及之前提到的发送消息方法:
@ override public void send msg(Session Session,String msg){ Session。getasync远程。发送文本(msg);}2、服务器收到消息后,向客户发送消息(调用服务,处理后返回数据)
计算机网络服务器收到消息,进入对等网络服务器中OnMessage方法
/*** 收到客户端发来消息* @param msg消息对象*/@ on message public void on message(Session会话,String msg){ P2P服务。处理消息(会话,消息);}p2PService.handleMessage就是解析接收到的消息(味精),根据类型的不同调用其他的方法(一个巨型转换语句,这里就介绍一小部分),这里我们接收到了客户传来的信息码查询_钱包。
@ override public void handle Message(Session Session,String msg){ Message Message=JSON。解析对象(消息,消息。类);开关(消息。gettype){ case QUERY _ WALLET:send msg(session,response wallets);打破;case RESPONSE _ WALLET:handleWalletResponse(消息。获取数据);打破;}根据信息码是查询_钱包,调用回应华尔街方法,得到数据。
私有字符串响应钱包{字符串钱包=块服务。findall钱包;返回JSON.toJSONString(新消息(RESPONSE_WALLET,钱包));}这里我把区块链的相关操作也封装到了一个服务中,下面给出findAllWallets的具体实现,其实就是遍历钱包集合,统计钱包公钥,没有什么难度。
@ override public String find all wallet { list wallet wallets=new ArrayList;myWalletMap.forEach((地址,钱包)-{钱包。添加(钱包。建筑商。公钥(钱包。获取公钥).构建);});otherWalletMap.forEach(地址,钱包)-{wallets.add(钱包);});返回JSON.toJSONString(钱包);}得到数据之后,返回给客户:
因此我们的回应华尔街方法中,最后一句话新建了一个消息对象,并设置了信息码为回应_钱包,在处理消息中调用了发送消息方法回传给客户。
case QUERY _ WALLET:发送消息(session,response wallets);打破;3、客户收到消息处理数据(调用服务,对数据处理)
客户收到了请求得到的数据,进入对等网络客户端中的OnMessage方法:
@ on message public void on message(String msg){ P2P service。处理消息(这。session,msg);}同样进入我们上面提到的p2PService.handleMessage方法,此时收到的信息码为回应_钱包,进入handleWalletResponse方法:
case RESPONSE _ WALLET:handleWalletResponse(消息。获取数据);打破;handleWalletResponse的实现,解析接收到的钱包公钥信息,并存储到客户节点的区块服务中。
private void handleWalletResponse(String msg){ list wallet wallet=' '等于(味精)?新数组列表:JSON。解析数组(消息,钱包。类);钱包。foreach(wallet-{ block service。addotherwallet(钱包服务。getwalletaddress(钱包。获取公钥)、钱包);});}在具体实现中,由于使用到了注入服务的方式,在向服务器(@ServerEndpoint)和客户端(@ClientEndpoint)中使用@自动连线注解注入豆的时候,由于Spring Boot单例的特点。
而Websocket每次都会创建一个新的对象,所以在使用服务的时候会导致出现空指针异常,因此,我们创建了一个工具类春耕,每次需要服务时,都从春天容器中获取到我们所需要的豆子,下面给出工具类代码。
公共类斯普林古尔实现ApplicationContextAware {公共静态应用程序上下文应用程序上下文;@覆盖public void setApplicationContext(应用程序上下文应用程序上下文)抛出beans异常{ if(spring util。应用上下文!=){ spring util。应用上下文=应用上下文;}}/*** 获取应用程序上下文*/公共静态应用程序上下文getApplicationContext {返回应用程序上下文;}/*** 通过名字获取比恩。*/公共静态对象get bean(字符串名称){ return getapplicationcontext。获取bean(名称);}/*** 通过班级获取比恩/public static T T get bean(clazz){ return getapplicationcontext。get bean(clazz);}/*** 通过姓名,以及克拉兹返回指定的bean */public static T T get bean(String name,clazz){ return getapplicationcontext。get bean(名字,clazz);}}因此测试之前我们首先需要设定斯普林古尔中的应用程序上下文,下面给出启动类(为了简单测试,两个节点共用一个启动类,根据一个参数名的不同来分别处理)以及相关节点的配置。
公共静态void main(String[]args){ system。出去。println(“Hello world”);春天的效用。应用程序上下文=spring应用程序。跑(你好。class,args);if(args。长度0){ P2P客户端P2P客户端=新P2P客户端;string URL=' ws://localhost:' args[0]'/test '请尝试{ P2P client . connectto peer(URL):} catch(Exception e){ e . printstacktrace;}}使用时,我们需要手动获取Bean:
//之前是这样//@ Autowired//私有P2P服务P2P服务;//改正后,去掉自动连线,每次使用都手动获取bean私有P2P服务P2P服务;@ OnOpenpublic void on open(Session Session){//如果不使用那些,在这里会报空指针异常,P2P服务为P2P服务=spring util。获取bean(P2P服务。类);//新增这句话从口内下颌升支垂直切骨术容器中获取bean p2PService.sendMsg(会话,P2P服务。querywalletmsg);}你好节点,测试时作为服务器:
试验节点,测试时作为客户。
到此,我们就实现了对等网络网络中计算机网络服务器节点与客户节点的交互过程。建议你也可以尝试一下,然后在评论区和我们讨论哦!
不用掉一根头发!用颤动飞镖快速构建一款绝美移动应用
看我发现了什么好东西?Java 语言(一种计算机语言,尤用于创建网站)可选,绝对值得一学|原力计划
腾讯提结合ACNet进行细粒度分类,效果达到最新SOTA | CVPR 2020
我最喜欢的云集成驱动电子设备推荐!
智能合约编写之固态的高级特性
返鄂复工人员自述:回武汉上班,要先飞合肥,再由公司包车接回去