以太坊系列之十八: 百行go代码构建p2p聊天室

简介: 百行go代码构建p2p聊天室百行go代码构建p2p聊天室1. 上手使用2. whisper 原理3. 源码解读3.1 参数说明3.1 连接主节点3.2 我的标识3.2 配置我的节点3.

百行go代码构建p2p聊天室

只需百行代码,就可以构建一个完整的p2p聊天室,并且消息加密,无法被追踪;并且不需要服务器,永不停机,是不是很酷.

系统实际上基于以太坊的whisper,它本来是为以太坊上的DAPPS通信构建的,这里直接拿来做聊天室一点问题都没有.

1. 上手使用

先说用法,来感受一下完全匿名的P2p聊天系统.
在终端运行p2pmessage.exe,然后等待出现.
Connected to peer,you can type message now.,这时候你就已经连接到whisper的p2p网络中了. 有可能你需要几分钟才能成功.
你可以收到来自别人的消息,也可以发送消息给别人.
你可以同时在不同的机器上启动程序来感受一下结果.

2017-09-11 11:31:18 <mine>: hello
input ~Q to quit>
2017-09-11 11:33:10 [182cbbaac94313b3b96b25d9c9a0a1adea4519e9]: who am i

第一行是收到了我发送的消息,第二行是提示输入,第三行是收到了182cbbaac94313b3b96b25d9c9a0a1adea4519e9发来的消息.
其中182cbbaac94313b3b96b25d9c9a0a1adea4519e9是结点标识.

2. whisper 原理

接入whisper网络中的节点,在收到任何消息会首先验证一下工作量(可以参考bitmessage),如果没问题然后就转发.
同时也会看看是不是发送给我的,如果是就告诉用户.
至于怎么知道是不是发送给我的,有多种方式,这里只使用主题以及密码匹配.
也就是说,必须是我感兴趣的主题,同时加密的密码也是我指定的.那就可以愉快的聊天了.
当然,我没办法知道对方是谁,除非他告诉我.

3. 源码解读

可以到github下载完整的源码.

3.1 参数说明

总共有三个参数
-verbosity 用来打印调试信息
-topic 聊天室的主题(任意四个字节), 你必须事先知道主题才能加入,如果随便写一个,那就是你自己创建一个聊天室了.
-password 聊天室的密码, 主题密码都一致,才能进入同一个聊天室.

3.1 连接主节点

虽然说p2p网络没有服务器,但是必须存在知名节点,否则无从启动网络.
首先就是连接以太坊的主节点.

    for _, node := range ethparams.MainnetBootnodes {
        peer := discover.MustParseNode(node)
        peers = append(peers, peer)
    }
    peer := discover.MustParseNode("enode://b89172e36cb79202dd0c0822d4238b7a7ddbefe8aa97489049c9afe68f71b10c5c9ce588ef9b5df58939f982c718c59243cc5add6cebf3321b88d752eac02626@182.254.155.208:33333")
    peers = append(peers, peer)

后面这个节点是我搭建的.方便国内的用户快速通信,因为基于主节点的通信可能会比较慢,延时比较长.

3.2 我的标识

每个节点都有自己的私钥,标识就是自己的公钥.
当然可以每次都使用相同的私钥,这里简单起见,每次都是自动生成了.

    asymKeyID, err = shh.NewKeyPair()
    if err != nil {
        utils.Fatalf("Failed to generate a new key pair: %s", err)
    }

    asymKey, err = shh.GetPrivateKey(asymKeyID)
    if err != nil {
        utils.Fatalf("Failed to retrieve a new key pair: %s", err)
    }

3.2 配置我的节点

一个节点就是不停的转发符合Pow的消息,如果是我这个聊天室的消息,就告诉用户.所以节点要和其他节点进行交互,交互的节点越多,消息传播的越快.
当然这些节点数量要有一个上限,这里是80. 其中peers变量就是3.1 连接主节点的主节点.

maxPeers := 80

    server = &p2p.Server{
        Config: p2p.Config{
            PrivateKey:     asymKey,
            MaxPeers:       maxPeers,
            Name:           common.MakeName("p2p chat group", "5.0"),
            Protocols:      shh.Protocols(),
            NAT:            nat.Any(),
            BootstrapNodes: peers,
            StaticNodes:    peers,
            TrustedNodes:   peers,
        },
    }

3.3 哪个聊天室

具有相同的主题和密码的就是同一个聊天室.
symKey关联到指定的密码,topic保存四个字节的指定主题.

func configureNode() {
    symKeyID, err := shh.AddSymKeyFromPassword(*argPass)
    if err != nil {
        utils.Fatalf("Failed to create symmetric key: %s", err)
    }
    symKey, err = shh.GetSymKey(symKeyID)
    if err != nil {
        utils.Fatalf("Failed to save symmetric key: %s", err)
    }
    copy(topic[:], common.FromHex(*argTopic))
    fmt.Printf("Filter is configured for the topic: %x \n", topic)
}

3.3 加入聊天室

我的节点可能会收到千百条各种消息,有些我能解密,有些我不能解密,但是其中只有极少一部分是我想看到的.
所以要告诉我的节点我只对这个聊天室感兴趣,如果有消息来就告诉我.
SubscribeMessage订阅指定主题和密码的消息,注意filterID,它相当于向系统订阅特定消息的句柄,后面还会用到.

func SubscribeMessage() {
    var err error

    filter := whisper.Filter{
        KeySym:   symKey,
        KeyAsym:  asymKey,
        Topics:   [][]byte{topic[:]},
        AllowP2P: true,
    }
    filterID, err = shh.Subscribe(&filter)
    if err != nil {
        utils.Fatalf("Failed to install filter: %s", err)
    }
}

3.4 群发消息

在p2p网络中群发消息反而是最简单的,如果要点对点发消息,限制反而要多. whisper提供有发送消息的api.
主要就是构造一个合法的消息结构,主要是指定topic以及加密的秘钥,还有就是消息体(payload)就可以了,asymKey主要是为了标识ID,不是用作非对称加密.

发送消息主要是按照指定的PoW要求(比特币,莱特币,以太币等等都是类似的思路),计算hash,然后把消息发送到网络上.

func sendMsg(payload []byte) common.Hash {
    params := whisper.MessageParams{
        Src:      asymKey,
        KeySym:   symKey,
        Payload:  payload,
        Topic:    topic,
        TTL:      whisper.DefaultTTL,
        PoW:      whisper.DefaultMinimumPoW,
        WorkTime: 5,
    }

    msg, err := whisper.NewSentMessage(&params)
    if err != nil {
        utils.Fatalf("failed to create new message: %s", err)
    }
    envelope, err := msg.Wrap(&params)
    if err != nil {
        fmt.Printf("failed to seal message: %v \n", err)
        return common.Hash{}
    }

    err = shh.Send(envelope)
    if err != nil {
        fmt.Printf("failed to send message: %v \n", err)
        return common.Hash{}
    }

    return envelope.Hash()
}

3.5 接收消息

系统实际上一直在不停的接收消息并转发,这里说的接收消息实际上就是把我们感兴趣的消息提取出来,也就是我们这个聊天室的消息.
注意这里的filterID就是 3.3 哪个聊天室提到的,这里可以认作是聊天室的ID了.
可以看出messageLoop就是不停的轮询有没有相关聊天室的消息,目前whisper还没有实现消息推送功能.

func messageLoop() {
    f := shh.GetFilter(filterID)
    if f == nil {
        utils.Fatalf("filter is not installed")
    }
    ticker := time.NewTicker(time.Millisecond * 50)
    for {
        select {
        case <-ticker.C:
            messages := f.Retrieve()
            for _, msg := range messages {
                printMessageInfo(msg)
            }
        case <-done:
            return
        }
    }
}

4. 再次使用p2pmessage

在主机1和主机2同时上运行p2pmessage -topic ffff0000 -password 7859931 ,并等待Connected to peer,you can type message now.

可以看到如下截图:
主机1:
主机1
主机2:
主机2

5. 二进制程序下载地址

win64版本
linux64版本

目录
相关文章
|
28天前
|
数据采集 数据挖掘 Go
踏入网页抓取的旅程:使用 grequests 构建 Go 视频下载器
使用 Go 和 grequests 构建 Bilibili 视频下载器,结合爬虫代理 IP 提高下载稳定性与速度。通过获取视频信息、构建下载链接、设置代理IP及异步请求,实现视频的本地保存。代码示例展示了如何运用 grequests 请求选项配置代理及处理请求。
踏入网页抓取的旅程:使用 grequests 构建 Go 视频下载器
|
1月前
|
Go 调度 开发者
CSP模型与Goroutine调度的协同作用:构建高效并发的Go语言世界
【2月更文挑战第17天】在Go语言的并发编程中,CSP模型与Goroutine调度机制相互协同,共同构建了高效并发的运行环境。CSP模型通过通道(channel)实现了进程间的通信与同步,而Goroutine调度机制则确保了并发任务的合理调度与执行。本文将深入探讨CSP模型与Goroutine调度的协同作用,分析它们如何共同促进Go语言并发性能的提升。
|
1月前
|
负载均衡 Java 中间件
使用Go语言构建高性能Web服务
Go语言作为一种快速、高效的编程语言,其在构建高性能Web服务方面具有独特优势。本文将探讨如何利用Go语言开发和优化Web服务,以实现更高的性能和可伸缩性。
|
1月前
|
网络协议 Go 开发者
Go语言网络编程基础:构建高效、可靠的网络应用
【2月更文挑战第12天】本文将深入探讨Go语言在网络编程领域的基础知识,包括其强大的并发模型、网络库的使用、TCP/IP和HTTP协议的理解等。通过本文,读者将能够理解Go语言在网络编程中的优势,并掌握构建高效、可靠网络应用的核心技能。
|
2月前
|
搜索推荐 Go 开发者
Go模块与依赖管理:构建稳定、可维护的项目生态
【2月更文挑战第9天】Go模块是Go语言从1.11版本开始引入的一个新的依赖管理工具,它改变了以往通过GOPATH管理项目依赖的方式,为Go开发者带来了更加灵活、高效的依赖管理方式。本文将深入探讨Go模块与依赖管理的概念、使用方法和最佳实践,帮助读者更好地理解和应用Go模块,构建稳定、可维护的项目生态。
|
2月前
|
Go 开发者
Go语言包管理基础:构建高效、可维护的代码库
【2月更文挑战第9天】Go语言以其简洁、高效和并发的特性受到了开发者的广泛欢迎。在Go中,包(package)是组织和管理代码的基本单位。本文将深入探讨Go语言包管理的基础知识,包括包的定义、组织结构、导入导出规则等,帮助读者更好地理解和应用Go语言中的包管理机制,构建高效、可维护的代码库。
|
2月前
|
Go API 数据安全/隐私保护
Go语言标准库概览:构建高效、可靠的软件开发基石
【2月更文挑战第8天】Go语言标准库是Go语言生态系统的重要组成部分,它为开发者提供了一系列高效、可靠的工具和函数,帮助开发者构建高质量的软件应用。本文将对Go语言标准库进行概览,介绍其组成特点、设计哲学以及使用场景与优势,帮助读者更好地理解和应用Go语言标准库。
|
3月前
|
编译器 Go API
go generate指南:代码自动生成
go generate指南:代码自动生成
465 0
|
2月前
|
编译器 Go 持续交付
Go语言模块导入的实践与技巧:提升代码重用与模块化开发效率
【2月更文挑战第9天】在Go语言中,模块导入是实现代码重用和模块化开发的关键环节。本文将介绍Go语言中模块导入的实践与技巧,包括本地模块的导入、远程模块的导入、导入路径的解析与重定向、导入别名与包的重命名等,旨在帮助读者更加高效地进行Go语言的项目开发。
|
2月前
|
算法 安全 Go
Go语言标准库核心模块详解:构建高效应用的基石
【2月更文挑战第8天】Go语言标准库中的核心模块为开发者提供了构建高效应用所需的基础功能和工具。本文将深入探讨Go语言标准库中的几个核心模块,包括容器与集合、并发与并行、输入输出、时间与日期、字符串处理以及加密与安全,帮助读者更好地理解和应用这些核心模块,为构建高效应用打下坚实基础。