5.8 Go语言项目实战:点对点聊天

Amiee7 · · 3353 次点击 · 开始浏览    置顶
这是一个创建于 的主题,其中的信息可能已经有所发展或是发生改变。

本篇文章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语言从入门到精通开发实战技能。

有疑问加站长微信联系(非本文作者)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3353 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传