本篇文章IT兄弟连GO语言学院小美
给读者们分享一下
对GO语言感兴趣想要学习Golang开发技术的小伙伴就随小编来了解一下吧。
**需求摘要**
- 实现一个分布式点对点的聊天系统,所有节点都是对等的,不需要中央服务器
- 实现注册节点名称,节点之间通过节点名称发起会话
**思路分析**
- 节点同时具备服务端和客户端的职能
- 服务端只负责接收其它节点主动发送过来的消息
- 客户端只负责主动向其它节点发送消息
- 通信都用短连接,服务端收完消息/客户端发完消息都断开conn——一方面是节约IO资源,另一方面是为了使逻辑清晰
- 节点名称注册到【注册服务器】(很像DNS),以便根据节点名称访问节点而不是监听端口
**节点代码实现**
peer.go代码实现如下
```
package main
import (
"fmt"
"net"
"os"
"time"
)
/*
·用一个可执行程序实现相互聊天
·实现注册节点名称,并通过名称发起会话
·实现群发消息
*/
/*
思路概要:
·节点同时具备服务端和客户端的职能
·服务端只负责接收其它节点主动发送过来的消息
·客户端只负责主动向其它节点发送消息
·通信都用短连接,服务端收完消息/客户端发完消息都断开conn——一方面是节约IO资源,另一方面是为了使逻辑清晰
·节点名称注册到【注册服务器】(很像DNS),以便根据节点名称访问节点而不是监听端口
*/
/*
节点注册服务器地址
提供节点注册和查询功能
*/
const registerAddress = "127.0.0.1:8888"
/*
节点的主业务逻辑
*/
func main() {
//初始化缓存表
cacheMap = make(map[string]string)
/*从命令行接收监听端口和节点名称*/
//从命令行上接收一个用于监听的端口:peer pa 1234
peerName = os.Args[1]
peerListeningPort = os.Args[2]
fmt.Println(peerListeningPort, peerName)
/*
向注册器注册自己
reg pa 1234
*/
peerAddress := RegOrGetPeerListeningAddress("reg " + peerName + " " + peerListeningPort)
fmt.Println("节点注册成功", peerName, peerAddress)
/*在独立并发任务中接收其它节点的消息*/
go StartServe()
/*在独立并发任务中向其它节点发送消息*/
go StartRequest()
//不能主协程会在此挂掉(如果主协程挂掉,子协程就跟着挂掉了)
for {
time.Sleep(1 * time.Second)
}
fmt.Println("GAME OVER")
}
/*错误处理*/
func HandleErr(err error, when string) {
if err != nil {
fmt.Println("err=", err, when)
os.Exit(1)
}
}
//节点监听端口,节点名称
var peerListeningPort, peerName string
//缓存其它通信节点的监听地址(如果已经查询过一回,就没必要每次都查询)
var cacheMap map[string]string
/*
向【注册器】注册/获取【节点的监听地址】
request 请求命令:
reg pa 1234 向注册机注册名为pa的节点,监听在1234端口
get pa 向注册机获取名为pa的节点的监听地址
返回值 pa节点的监听地址
*/
func RegOrGetPeerListeningAddress(request string) string {
//拨号【注册服务器】
conn, e := net.Dial("tcp", registerAddress)
HandleErr(e, "RegOrGetPeerListeningAddress")
//发送注册/查询命令
conn.Write([]byte(request))
//得到要注册/查询的节点的监听地址
buffer := make([]byte, 1024)
n, e := conn.Read(buffer)
HandleErr(e, "RegGetPeerAddressconn.Read(buffer)")
peerAddress := string(buffer[:n])
//返回这个监听地址
return peerAddress
}
/*
监听并接收其它节点发送过来的消息
这是节点【服务端】的一面
*/
func StartServe() {
//在配置和注册好的端口建立TCP监听
listener, e := net.Listen("tcp", ":"+peerListeningPort)
HandleErr(e, "net.Listen")
/*循环接入其它节点*/
for {
conn, e := listener.Accept()
HandleErr(e, "listener.Accept()")
//接收远程节点的消息
buffer := make([]byte, 1024)
n, err := conn.Read(buffer)
HandleErr(err, "conn.Read(buffer)")
msg := string(buffer[:n])
fmt.Println(conn.RemoteAddr(), ":", msg)
//接收完毕立即断开
conn.Close()
}
}
/*
主动向其它节点发起会话
这是节点【客户端】的一面
*/
func StartRequest() {
//目标节点名称,要发送的消息
var targetName, msg string
for {
//从控制台输入信息
fmt.Println("请输入对方名称:消息内容")
fmt.Scan(&targetName, &msg)
//看看缓存中是否有节点信息
var targetAddress string
if temp, ok := cacheMap[targetName]; !ok {
//向注册器查询节点的监听地址
fmt.Println("从注册服务器获得节点监听地址")
targetAddress = RegOrGetPeerListeningAddress("get " + targetName)
//将查询结果写入缓存
cacheMap[targetName] = targetAddress
} else {
//使用缓存中的监听地址
fmt.Println("从缓存获得节点监听地址")
targetAddress = temp
}
//向目标地址发送消息
conn, e := net.Dial("tcp", targetAddress)
HandleErr(e, "net.Dial")
conn.Write([]byte(msg))
//消息发送完毕,断开连接
conn.Close()
}
}
```
**节点注册服务器**
- 节点注册服务器作为基础设施,提供节点的注册和查询功能
registerer.go 代码实现如下
```
package main
import (
"fmt"
"net"
"os"
"strings"
)
/*
负责注册节点名称:节点运行端口
*/
func RHandleErr(err error, when string) {
if err != nil{
fmt.Println("注册器err=",err,when)
os.Exit(1)
}
}
//所有节点【名称-监听地址】映射表
var peerNameListeningAddressMap map[string]string
/*注册机的主业务*/
func main() {
//初始化注册表
peerNameListeningAddressMap = make(map[string]string)
//开启注册服务
listener, e := net.Listen("tcp", ":8888")
RHandleErr(e,"net.Listen")
buffer := make([]byte, 1024)
/*循环接受节点的注册和查询服务*/
for {
conn, e := listener.Accept()
RHandleErr(e,"listener.Accept()")
/*
接收节点消息
reg pa 1234 注册:节点名称pa,监听端口1234
get pa 查询:节点名称为pa的节点的监听地址
*/
n, e := conn.Read(buffer)
RHandleErr(e,"conn.Read(buffer)")
msg := string(buffer[:n])
//将消息炸碎为字符串,获取命令和节点名称
strs := strings.Split(msg, " ")
cmd := strs[0]
peerName := strs[1]
if cmd == "reg"{
//将节点名称和【节点-地址】写入全局映射表 reg pa 1234
runningAddress := conn.RemoteAddr().String()
//拼接节点的IP和监听端口,得到节点的监听地址
peerIP := strings.Split(runningAddress, ":")[0]
peerListeningPort := strs[2]
listenAddress := peerIP +":" + peerListeningPort
//节点名称为键,监听地址为值,写入map(下次就可以供别人查询了)
peerNameListeningAddressMap[peerName] = listenAddress
//将节点的名称和监听地址写入全局映射表
conn.Write([]byte(listenAddress))
} else if cmd=="get"{
//根据节点名称查询节点监听地址
listeningAddress := peerNameListeningAddressMap[peerName]
conn.Write([]byte(listeningAddress))
}
//断开会话,继续接受其他节点的注册或查询请求
conn.Close()
}
}
```
想要了解更多关于GO语言开发方面内容的小伙伴,
请关注IT兄弟连官网、公众号:GO语言研习社,
IT兄弟连教育有专业的微软、谷歌讲师为您指导,
此外IT兄弟连老师精心推出的GO语言教程定能让你快速掌握GO语言从入门到精通开发实战技能。
有疑问加站长微信联系(非本文作者)