热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java语言实现区块链(五)

Java语言实现区块链(五),Go语言社区,Golang程序员人脉社
一、集成WebSocket,实现P2P网络通信

WebSocket官方文档:https://github.com/TooTallNate/Java-WebSocket

(1)引入坐标。

compile "org.java-websocket:Java-WebSocket:1.3.8"

(2)在websocket包创建两个类,一个代表websocket客户端,一个代表websocket服务端。

public class MyClient extends WebSocketClient {
    // 客户端的名称
    private String name;

    // 在构造函数中传入连接服务端的地址,并指定客户端的名称
    public MyClient(URI serverUri, String name) {
        super(serverUri);
        this.name = name;
    }

    @Override
    public void onOpen(ServerHandshake handshakedata) {
        System.out.println("客户端" + name + "打开了连接...");
    }

    @Override
    public void onMessage(String message) {
        System.out.println("客户端" + name + "收到服务端发送过来的消息...");
    }

    @Override
    public void onClose(int code, String reason, boolean remote) {
        System.out.println("客户端" + name + "关闭了连接...");
    }

    @Override
    public void onError(Exception ex) {
        System.out.println("客户端" + name + "发生错误...");
    }
}

public class MyServer extends WebSocketServer {
    private int port;

    // 在构造函数中指定监听的端口号
    public MyServer(int port) {
        super(new InetSocketAddress(port));
        this.port = port;
    }

    @Override
    public void onOpen(WebSocket conn, ClientHandshake handshake) {
        System.out.println("WebSocket_" + port + "打开了连接...");
    }

    @Override
    public void onClose(WebSocket conn, int code, String reason, boolean remote) {
        System.out.println("WebSocket_" + port + "关闭了连接...");
    }

    @Override
    public void onMessage(WebSocket conn, String message) {
        System.out.println("WebSocket_" + port + "接收到消息...");
    }

    @Override
    public void onError(WebSocket conn, Exception ex) {
        System.out.println("WebSocket_" + port + "发送了错误...");
    }

    @Override
    public void onStart() {
        System.out.println("WebSocket_" + port + "启动成功...");
    }

    // 启动服务端
    public void startServer() {
        // 因为WebServerSocket继承了Runnable类,所以可以启动线程去执行它
        new Thread(this).start();
    }
}

(3)修改BlockChainApplication类,让springboot启动时候可以动态指定端口号。

@SpringBootApplication
public class BlockchainqsApplication {
    public static int port;

    public static void main(String[] args) {
        System.out.println("请输入端口号:");
        Scanner sc = new Scanner(System.in);
        port = sc.nextInt();
        SpringApplicationBuilder application = new SpringApplicationBuilder(BlockchainqsApplication.class)
                .properties("server.port=" + port);
        application.run(args);
    }

}

(4)在BlockController中定义以下几个方法:

init():启动websocket服务,端口号为springboot端口号加1。

regist():注册节点

conn():连接节点

broadcast():广播消息

// 记录已经注册了的节点(这里是端口号)
private HashSet nodes = new  HashSet<>();
private MyServer myServer;

// 初始化myServer
@PostConstruct
public void init() {
    // 启动服务端,端口号为springboot端口号加1
    myServer = new MyServer(BlockchainqsApplication.port + 1);
    myServer.startServer();
}

// 注册节点
@RequestMapping(value = "/regist")
public String regist(String node) {
    nodes.add(node);
    return "注册成功";
}

// 连接
@RequestMapping(value = "/conn")
public String conn() {
    try {
        // 遍历所有已经注册了的节点,然后创建对应客户端去连接这些节点
        // 相当于每个节点都对应着一个MyClient对象
        for (String node : nodes) {
            URI uri = new URI("ws://localhost:"+ node);
            MyClient myClient = new MyClient(uri, node);
            myClient.connect();
        }
        return "连接成功";
    } catch (URISyntaxException e) {
        return "连接失败:" + e.getMessage();
    }
 }

 // 广播消息
 @RequestMapping(value = "/broadcast")
 public String broadcast(String msg) {
     // 广播消息,其实就是向已注册该WebSocket服务的客户端发送消息
     myServer.broadcast(msg);
     return "广播成功";
 }

(5)修改页面,指定按钮组。




(6)定义事件函数。

// 注册节点
function regist() {
    // 获取用户输入的内容
    var node = $("#node").val();
    // 显示进度条
    loading.baosight.showPageLoadingMsg(false);
    // 发起请求
    $.post("regist", "node=" + node, function (data) {
        // 展示操作结果
        $("#result").html(data)
        // 清空输入框
        $("#node").val("");
        // 隐藏进度条
        loading.baosight.hidePageLoadingMsg();
    });
}

// 连接节点
function conn() {
    // 显示进度条
    loading.baosight.showPageLoadingMsg(false);
    // 发起请求
    $.post("conn", function (data) {
        // 展示操作结果
        $("#result").html(data)
        // 隐藏进度条
        loading.baosight.hidePageLoadingMsg();
    });
}

// 广播
function broadcast() {
    // 获取用户输入的内容
    var msg = $("#node").val();
    // 显示进度条
    loading.baosight.showPageLoadingMsg(false);
    // 发起请求
    $.post("broadcast", "msg=" + msg, function (data) {
        // 展示操作结果
        $("#result").html(data)
        // 清空输入框
        $("#node").val("");
        // 隐藏进度条
         loading.baosight.hidePageLoadingMsg();
    });
}

(7)启动服务器测试。

第一步:点击Edit Configuration,把Single instance only的勾取消掉。

第二步:启动两个sprintboot,一个监听7000端口号,一个监听8000端口号。

第三步:启动成功后,在浏览器分别打开localhost:7000和localhost:8000两个页面进行测试。

测试流程:

1)在localhost:7000页面中先注册节点8001,然后点击连接。

2)在localhost:8000页面中先注册节点7001,然后点击连接。

3)在任意一个页面中广播消息,比如在localhost:7000页面广播了一条消息,那么在8000的springboot控制台上可以看到该条广播消息。

结果如下图所示:

 

二、同步数据

(1)在页面上添加同步按钮。


(2)定义事件函数。

// 同步
function syncData() {
   // 显示进度条
   loading.baosight.showPageLoadingMsg(false);
   // 发起请求
   $.post("syncData", function (data) {
        // 展示操作结果
        $("#result").html(data)
        // 显示数据
        showList();
        // 隐藏进度条
        loading.baosight.hidePageLoadingMsg();
   });
}

(3)在BlockController中定义syncData方法,执行同步操作。

// 请求同步其他节点的区块链数据
@RequestMapping(value = "/syncData")
public String syncData() {
    for (MyClient client : clients) {
        client.send("兄弟,请把您的区块链数据给我一份");
    }
    return "同步成功";
}

(4)修改MyServer的onMessage函数,该函数把该节点的区块链数据广播给所有已注册的节点。

@Override
public void onMessage(WebSocket conn, String message) {
    System.out.println("WebSocket_" + port + "接收到消息...");
    try {
        if ("兄弟,请把您的区块链数据给我一份".equals(message)) {
            // 获取区块连数据
            NoteBook noteBooke = NoteBook.newInstance();
            List blocks = noteBooke.showList();
            // 把blocks转换成字符串
            ObjectMapper objectMapper = new ObjectMapper();
            String blockInfos = objectMapper.writeValueAsString(blocks);
            // 把数据封装到MessageBean
            MessageBean mb = new MessageBean(1, blockInfos);
            // 把MessageBean对象转换成字符串
            String msg = objectMapper.writeValueAsString(mb);
            // 广播消息
            broadcast(msg);
        }
    } catch (JsonProcessingException e) {
        e.printStackTrace();
    }
}

(5)修改MyClient的onMessage函数,该函数用于同步注册节点的区块链数据。

@Override
public void onMessage(String message) {
    System.out.println("客户端" + name + "收到服务端发送过来的消息...");
    try {
        ObjectMapper objectMapper = new ObjectMapper();
        // 把message转换成MessageBean对象
        MessageBean mb = objectMapper.readValue(message, MessageBean.class);
        // 判断消息类型
        if (mb.getType() == 1) {
            // 把消息转换成ArrayList对象
            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, Block.class);
            ArrayList newList = objectMapper.readValue(mb.getMsg(), javaType);
            // 比较本地blocks和得到的blocks的长度
            // 如果本地blocks的长度比得到blocks的长度小,代表需要同步。
            NoteBook noteBook = NoteBook.newInstance();
            noteBook.compareData(newList);
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

(6)定义MessageBean类,用于不同类型的数据。

public class MessageBean {
    private int type; // 1代表区块链数据 2代表交易数据
    private String msg; // 消息内容

    public MessageBean() {}

    public MessageBean(int type, String msg) {
        this.type = type;
        this.msg = msg;
    }

    ...
}

(7)修改NoteBook类,把NoteBook对象定义成单例模式。

private static NoteBook noteBook;

public static NoteBook newInstance() {
    if (noteBook == null) {
        synchronized (NoteBook.class) {
            if (noteBook == null) {
                noteBook = new NoteBook();
            }
        }
    }
    return noteBook;
}

(7)测试程序。

启动两个springboot,在浏览器打开两个页面。先执行注册和连接操作,然后在一个页面中添加区块, 在另外一个页面中执行同步操作。如果同步成功,可以在页面上看到刚才添加的区块数据。

 

三、广播交易数据

(1)修改BlockController的addBlock函数,添加区块的时候进行广播操作。

@RequestMapping(value = "/addBlock", method = RequestMethod.POST)
public String addBlock(Transaction tx) {
    try {
        if (tx.verify()) {
            // 把Transaction对象转换成字符串
            ObjectMapper objectMapper = new ObjectMapper();
            String txInfo = objectMapper.writeValueAsString(tx);
            // 把交易数据封装成MessageBean对象
            MessageBean mb = new MessageBean(2, txInfo);
            String msg = objectMapper.writeValueAsString(mb);
            broadcast(msg);
            // 执行添加操作
            noteBook.addBlock(txInfo);
            return "添加区块成功!";
        } else {
            throw new RuntimeException("交易数据校验失败!");
        }
    } catch (Exception e) {
        return "添加失败:" + e.getMessage();
    }
}

(2)修改MyClient的onMessage方法,同步交易数据。

@Override
public void onMessage(String message) {
    System.out.println("客户端" + name + "收到服务端发送过来的消息...");
    try {
        ObjectMapper objectMapper = new ObjectMapper();
        // 把message转换成MessageBean对象
        MessageBean mb = objectMapper.readValue(message, MessageBean.class);
        NoteBook noteBook = NoteBook.newInstance();
        // 判断消息类型
        if (mb.getType() == 1) {
            // 把消息转换成ArrayList对象
            JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, Block.class);
            ArrayList newList = objectMapper.readValue(mb.getMsg(), javaType);
            // 比较本地blocks和得到的blocks的长度
            // 如果本地blocks的长度比得到blocks的长度小,代表需要同步。
            noteBook.compareData(newList);
        } else if (mb.getType() == 2) {
            // 把msg数据转换成Transaction对象
            Transaction tx = objectMapper.readValue(mb.getMsg(), Transaction.class);
            // 交易交易
            if (tx.verify()) {
                noteBook.addBlock(mb.getMsg());
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
}

最后运行测试即可。同样也是启动两个springboot,在浏览器打开两个页面。先执行注册和连接操作。然后在一个页面中添加区块, 在另外一个页面刷新数据。这样就可以看到刚才添加的区块数据。

 


推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • ZSI.generate.Wsdl2PythonError: unsupported local simpleType restriction ... [详细]
  • 本文为Codeforces 1294A题目的解析,主要讨论了Collecting Coins整除+不整除问题。文章详细介绍了题目的背景和要求,并给出了解题思路和代码实现。同时提供了在线测评地址和相关参考链接。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了brain的意思、读音、翻译、用法、发音、词组、同反义词等内容,以及脑新东方在线英语词典的相关信息。还包括了brain的词汇搭配、形容词和名词的用法,以及与brain相关的短语和词组。此外,还介绍了与brain相关的医学术语和智囊团等相关内容。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
author-avatar
奶油泡芙2覀21
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有