另一个P2P Messenger
仅阅读评论和语言文档不足以学习如何在其上编写或多或少有用的应用程序。
确保合并,您需要创建一些有趣的东西,以便将这些开发成果用于其他任务。

本文面向对围棋语言和对等网络感兴趣的初学者。
对于可以提出合理想法或建设性批评的专业人员。
我在Java,PHP,JS,Python中使用不同程度的沉浸程度已经进行了一段时间的编程。
而且每种编程语言在其领域都是不错的。
Go的主要领域是创建分布式服务微服务。
通常,微服务是执行其高度专业化功能的小型程序。
但是微服务仍应能够相互通信,因此创建微服务的工具应允许轻松,轻松地进行网络连接。
为了测试这一点,我们将编写一个组织去中心化对等网络(Peer-To-Peer)的应用程序,最简单的是p2p Messenger(顺便说一句,这个单词有俄语同义词吗?)。
在代码中,我积极发明自行车并踩踏耙子,以感受戈朗,得到建设性的批评和合理的建议。
我们该怎么办
对等(peer)-Messenger的唯一实例。
我们的使者应该能够:
- 寻找附近的盛宴
- 与其他同伴建立联系
- 加密与对等方的数据交换
- 接收来自用户的消息
- 向用户显示消息
为了使任务更加有趣,让我们全部通过一个网络端口进行。

如果通过HTTP拉该端口,我们将获得一个React应用程序,该应用程序通过建立Web套接字连接来拉同一个端口。
如果您通过HTTP而不是从本地计算机拉端口,则我们将显示标语。
如果另一个对等方连接到该端口,则使用端到端加密建立永久连接。
确定传入连接的类型
首先,打开端口进行监听,我们将等待新的连接。
net.ListenTCP("tcp", tcpAddr)
在新连接上,读取前4个字节。
我们获取HTTP动词列表,然后将其4个字节进行比较。
现在,我们确定是否从本地计算机建立了连接,如果没有建立连接,我们将显示一条横幅并挂断。
buf, err := readWriter.Peek(4) if ItIsHttp(buf) { handleHttp(readWriter, conn, p) } else { peer := proto.NewPeer(conn) p.HandleProto(readWriter, peer) } if !strings.EqualFold(s, "127") && !strings.EqualFold(s, "[::") { response.Body = ioutil.NopCloser(strings.NewReader("Peer To Peer Messenger. see https://github.com/easmith/p2p-messenger")) }
如果连接是本地连接,那么我们将使用与请求相对应的文件进行响应。
然后,我决定自己编写处理程序,尽管我可以使用标准库中提供的处理程序。
如果请求路径/ws
,那么我们尝试建立一个websocket连接。
由于我在处理文件请求时组装了自行车,因此我将使用gorilla / websocket库来处理ws连接。
为此,创建MyWriter
并在其中实现方法以对应于接口http.ResponseWriter
和http.Hijacker
。
对等检测
为了在局域网中搜索对等体,我们将使用UDP多播。
我们会将带有有关我们自己的信息的数据包发送到多播IP地址。
func startMeow(address string, p *proto.Proto) { conn, err := net.DialUDP("udp", nil, addr) for { _, err := conn.Write([]byte(fmt.Sprintf("meow:%v:%v", hex.EncodeToString(p.PubKey), p.Port))) time.Sleep(1 * time.Second) } }
并与多播IP分开侦听所有UDP数据包。
func listenMeow(address string, p *proto.Proto, handler func(p *proto.Proto, peerAddress string)) { conn, err := net.ListenMulticastUDP("udp", nil, addr) _, src, err := conn.ReadFromUDP(buffer)
因此,我们宣布自己,并了解其他盛宴的出现。
可以在IP级别上进行组织,甚至在IPv4包的官方文档中,也仅将多播数据包作为代码示例给出。
对等交互协议
我们将同伴之间的所有通信都封装在一个信封(信封)中。
在任何信封上总是有一个发送者和一个接收者,为此我们将添加一条命令(他随身携带),一个标识符(到目前为止这是一个随机数,但可以作为内容的哈希值),内容的长度和信封本身的内容-消息或命令参数。

该命令(或内容的类型)已成功放置在信封的最开始,并且我们定义了一个4字节的命令列表,该列表与HTTP动词的名称不相交。
在传输过程中,整个信封被序列化为字节数组。
握手
建立连接后,盛宴立即伸出手进行握手,提供其名称,公共密钥和临时公共密钥以生成共享会话密钥。
作为响应,对等方收到一组相似的数据,注册在其列表中找到的对等方,并计算(CalcSharedSecret)公共会话密钥。
func handShake(p *proto.Proto, conn net.Conn) *proto.Peer { peer := proto.NewPeer(conn) p.SendName(peer) envelope, err := proto.ReadEnvelope(bufio.NewReader(conn)) }
盛宴交流
握手后,对等方交换其对等方列表=)
为此,将发送带有LIST命令的信封,并在其内容中放置一个JSON对等体列表。
作为回应,我们得到了类似的信封。
我们在新列表中找到,并尝试与每个列表建立联系,握手,交流盛宴等...
用户消息
自定义消息对我们来说是最大的价值,因此我们将对每个连接进行加密和签名。
关于加密
在来自crypto软件包的标准(google)golang库中,实现了许多不同的算法(没有GOST标准)。
我认为最方便的签名是Ed25519曲线。 我们将使用ed25519库对消息进行签名。
从一开始,我就考虑过使用从ed25519获得的密钥对不仅用于签名,还用于生成会话密钥。
但是,用于签名的密钥不适用于计算共享密钥-您仍然需要想到它们:
func CreateKeyExchangePair() (publicKey [32]byte, privateKey [32]byte) { pub, priv, err := ed25519.GenerateKey(nil) copy(publicKey[:], pub[:]) copy(privateKey[:], priv[:]) curve25519.ScalarBaseMult(&publicKey, &privateKey) }
因此,决定生成临时密钥,并且一般来说,这是正确的方法,不会使攻击者有机会获得通用密钥。
对于数学爱好者,以下是Wiki链接:
Diffie协议—椭圆曲线上的Hellman_
数字签名EdDSA
共享密钥的生成是非常标准的:首先,对于新连接,我们生成临时密钥,我们将带有公共密钥的信封发送到套接字。
另一侧的操作相同,但顺序不同:它接收带有公钥的信封,生成自己的对,然后将公钥发送到套接字。
现在,每个参与者都有别人的公共和私人临时密钥。
乘以它们,我们就得到了相同的密钥,我们将用它来加密消息。
我们将使用已建立的AES算法以块耦合模式(CBC)对消息进行加密。
所有这些实现都可以在golang文档中轻松找到。
唯一的改进是使用零字节自动填充消息,以确保其长度乘以加密块的长度(16字节)。
早在2013年,作为Pavel Durov竞赛的一部分,他实现了AES(与CBC类似的模式)来加密Telegram中的消息。
当时,电报中使用了最常见的Diffie-Hellman协议来生成临时密钥。
并且为了从假连接中排除负载,在每次密钥交换之前,客户端解决了分解问题。
图形用户界面
我们需要显示一个对等方的列表和与之相关的消息列表,还需要通过增加对等方名称旁边的计数器来响应新消息。
这里没有麻烦-ReactJS + websocket。
Web套接字消息本质上是唯一的信封,只有它们不包含密文。
它们都是WsCmd
类型的“继承人”,并且在传输时以JSON序列化。
因此,一个HTTP请求到达根目录(“ /”),现在显示其前端,在“ front / build”目录中查找并给出index.html
界面已经好了,现在用户可以选择:在浏览器或单独的窗口-WebView中运行它。
对于最后使用的选项zserge / webview
e := webview.Open("Peer To Peer Messenger", fmt.Sprintf("http://localhost:%v", initParams.Port), 800, 600, false)
要使用它构建应用程序,您需要安装另一个系统
sudo apt install libwebkit2gtk-4.0-dev
在考虑GUI的过程中,我发现许多用于GTK,QT的库,并且控制台界面看起来非常怪异-我认为这是一个非常有趣的主意-https: //github.com/jroimartin/gocui 。
信使发射
Golang安装
当然,您首先需要安装go。
为此,我强烈建议您使用golang.org/doc/install说明。
bash脚本的简化说明
在GOPATH中下载应用程序
如此安排,所有库甚至您的项目都应位于所谓的GOPATH中。
默认情况下,这是$ HOME / go。 Go允许您使用简单的命令从公共存储库中提取源代码:
go get github.com/easmith/p2p-messenger
现在,在您的$HOME/go/src/github.com/easmith/p2p-messenger
将显示master分支$HOME/go/src/github.com/easmith/p2p-messenger
源
Npm安装和前部组装
就像我在上面写的那样,我们的GUI是一个Web应用程序,在ReactJs上有一个前端,因此前端仍然需要组装。
Nodejs + npm-像往常一样。
以防万一,这是Ubuntu的说明
现在我们以标准的方式启动前部组件
cd front npm update npm run build
前面准备好了!
发射
让我们回到根源,启动我们的使者盛宴。
在启动时,我们可以指定对等体的名称,端口,文件以及其他对等体的地址以及一个标志,指示是否启动WebView。
缺省情况下, $USER@$HOSTNAME
用作对$USER@$HOSTNAME
名称和端口35035。
因此,我们开始与本地网络上的朋友聊天。
go run app.go -name Snowden
有关Golang编程的反馈
接下来是什么?
因此,实现了最简单的对等Messenger。
锥体塞满了,进一步可以改善用户功能:发送文件,图片,音频,表情符号等。
而且您不能发明协议,而使用Google协议缓冲区,
使用以太坊智能合约连接区块链并保护自己免受垃圾邮件的侵害。
在智能合约上,组织群聊,频道,名称系统,头像和用户个人资料。
还必须运行种子对等方,实现NAT绕过,并在对等方之间发送消息。
结果,您得到了一个很好的替代电报/电话,您只需要将所有朋友转移到那里=)
有用性