{"id":20917540,"url":"https://github.com/impact-eintr/enet","last_synced_at":"2026-05-26T14:02:42.683Z","repository":{"id":57648970,"uuid":"366571055","full_name":"impact-eintr/enet","owner":"impact-eintr","description":"模仿 zinx 写的 支持 TCP 通信的服务端框架","archived":false,"fork":false,"pushed_at":"2022-02-07T06:39:15.000Z","size":178,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-14T19:31:22.428Z","etag":null,"topics":["golang","socket","tcp-server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/impact-eintr.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2021-05-12T02:37:19.000Z","updated_at":"2022-12-01T02:12:51.000Z","dependencies_parsed_at":"2022-09-26T20:30:40.302Z","dependency_job_id":null,"html_url":"https://github.com/impact-eintr/enet","commit_stats":null,"previous_names":["impact-eintr/zinx"],"tags_count":20,"template":false,"template_full_name":null,"purl":"pkg:github/impact-eintr/enet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fenet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fenet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fenet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fenet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/impact-eintr","download_url":"https://codeload.github.com/impact-eintr/enet/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/impact-eintr%2Fenet/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33523669,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T03:12:49.672Z","status":"ssl_error","status_checked_at":"2026-05-26T03:12:47.976Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["golang","socket","tcp-server"],"created_at":"2024-11-18T16:34:01.338Z","updated_at":"2026-05-26T14:02:42.639Z","avatar_url":"https://github.com/impact-eintr.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# enet\nenet 跟着 zinx 写的 添加了UDP通信\n\n## 示例\n\n\u003e 下面的示例是一个心跳监听+资源定位的demo\n\n``` tcsh\n生产者1\n\t   \\\n\t     \u003c- 广播消息- -发送心跳/资源定位-\u003e udpServer \u003c-定位资源- -检查有多少节点-\u003e 消费者\n\t   /\n生产者2\n\n\n```\n\n\n### 消息服务器\n``` go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/impact-eintr/enet\"\n\t\"github.com/impact-eintr/enet/iface\"\n)\n\nvar m = make(map[string]time.Time, 0) // 计数器\n\n// 监听心跳的Router\ntype LHBRouter struct {\n\tenet.BaseRouter //一定要先基础BaseRouter\n}\n\n// 心跳监控\nfunc (this *LHBRouter) Handle(request iface.IRequest) {\n\t// 先更新\n\tlocker.Lock()\n\tm[string(request.GetData())] = time.Now()\n\tlocker.Unlock()\n}\n\n// 广播心跳的Router\ntype BHBRouter struct {\n\tenet.BaseRouter\n}\n\nfunc (this *BHBRouter) Handle(request iface.IRequest) {\n\t// 访问这个Router的都是API server\n\tvar s string\n\tlocker.RLock()\n\tfor k := range m {\n\t\ts += k + \" \" // A.A.A.A:a B.B.B.B:b\n\t}\n\tlocker.RUnlock()\n\n\terr := request.GetConnection().SendBuffUdpMsg(10,\n\t\t[]byte(s), request.GetRemoteAddr())\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\n// broadcast file location\ntype BFLRouter struct {\n\tenet.BaseRouter\n}\n\nvar bflm = make(map[string]chan []byte)\nvar bflLocker sync.RWMutex\n\nfunc (this *BFLRouter) Handle(request iface.IRequest) {\n\tbtest(request.GetData()) // 发一个定位广播\n\n\tbflm[string(request.GetData())] = make(chan []byte)\n\n\tselect {\n\tcase res := \u003c-bflm[string(request.GetData())]:\n\t\t// 把定位结果返回\n\t\trequest.GetConnection().SendBuffUdpMsg(20,\n\t\t\tres, request.GetRemoteAddr())\n\t\tdelete(bflm, string(request.GetData()))\n\tcase \u003c-time.Tick(1 * time.Second):\n\t\t// 这里可以倒计时:\n\t\trequest.GetConnection().SendBuffUdpMsg(20,\n\t\t\t[]byte(\"没有找到\"), request.GetRemoteAddr())\n\t\tdelete(bflm, string(request.GetData()))\n\t}\n\n}\n\nfunc btest(data []byte) error {\n\tconn, err := net.DialUDP(\"udp\", nil,\n\t\t\u0026net.UDPAddr{\n\t\t\tIP:   net.IPv4(172, 17, 255, 255),\n\t\t\tPort: 9000,\n\t\t}) // 协议, 发送者,接收者\n\tdefer conn.Close()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t_, err = conn.Write(data)\n\tif err != nil {\n\t\treturn err\n\t}\n\tfmt.Println(\"广播发送成功\")\n\n\treturn nil\n}\n\n// response file location\ntype RFLRouter struct {\n\tenet.BaseRouter\n}\n\nfunc (this *RFLRouter) Handle(request iface.IRequest) {\n\trtest(request.GetData())\n}\n\n// RFL 21\nfunc rtest(data []byte) {\n\ts := strings.Split(string(data), \"\\n\")\n\tselect {\n\tcase bflm[s[0]] \u003c- []byte(s[1]):\n\tcase \u003c-time.Tick(1 * time.Second):\n\t}\n\t//bflLocker.Lock()\n\t//if _, ok := bflm[s[0]]; ok {\n\t//\tbflm[s[0]] \u003c- data\n\t//}\n\t//bflLocker.Unlock()\n}\n\nfunc ListenHeartBeat() {\n\t//1 创建一个server 句柄 s\n\ts := enet.NewServer(\"udp\")\n\n\ts.AddRouter(10, \u0026LHBRouter{})\n\ts.AddRouter(11, \u0026BHBRouter{})\n\ts.AddRouter(20, \u0026BFLRouter{})\n\ts.AddRouter(21, \u0026RFLRouter{})\n\n\t//2 开启服务\n\ts.Serve()\n}\n\nvar locker sync.RWMutex\n\nfunc main() {\n\tgo ListenHeartBeat()\n\n\tfor {\n\t\tlocker.Lock()\n\t\tfor k, t := range m {\n\t\t\tif t.Add(2 * time.Second).Before(time.Now()) {\n\t\t\t\tdelete(m, k)\n\t\t\t\tlog.Printf(\"\u003c%s\u003e失效\\n\", k)\n\t\t\t}\n\t\t}\n\t\tlocker.Unlock()\n\t\ttime.Sleep(2 * time.Second)\n\t}\n\n}\n\n```\n\n### 消费端\n\n``` go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/impact-eintr/enet\"\n)\n\nfunc listenHeartBeat() {\n\tip := net.ParseIP(\"172.17.0.2\")\n\n\tsrcAddr := \u0026net.UDPAddr{IP: net.IPv4zero, Port: 0}\n\tdstAddr := \u0026net.UDPAddr{IP: ip, Port: 6430}\n\n\tconn, err := net.DialUDP(\"udp\", srcAddr, dstAddr)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tdefer conn.Close()\n\tfor {\n\t\treqMsg := enet.NewMsgPackage(11, []byte(\"让我访问!!!\"))\n\t\tbuf := enet.NewDataPack().Encode(reqMsg)\n\t\t_, err = conn.Write(buf[:])\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\n\t\tresp := make([]byte, 1024)\n\t\tn, err := conn.Read(resp)\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tcontinue\n\t\t}\n\t\tresp = resp[:n]\n\n\t\trespMsg := enet.NewDataPack().Decode(resp)\n\t\tfmt.Println(string(respMsg.GetData()))\n\n\t\ttime.Sleep(2000 * time.Millisecond)\n\t}\n\n}\n\nfunc main() {\n\tgo listenHeartBeat()\n\n\t// 每 3 秒发送一个文件定位信息\n\tip := net.ParseIP(\"172.17.0.2\")\n\n\tsrcAddr := \u0026net.UDPAddr{IP: net.IPv4zero, Port: 0}\n\tdstAddr := \u0026net.UDPAddr{IP: ip, Port: 6430}\n\n\tconn, err := net.DialUDP(\"udp\", srcAddr, dstAddr)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tdefer conn.Close()\n\n\tfor {\n\t\treqMsg := enet.NewMsgPackage(20, []byte(\"文件定位测试\"))\n\t\tbuf := enet.NewDataPack().Encode(reqMsg)\n\t\t_, err = conn.Write(buf[:])\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\n\t\tresp := make([]byte, 1024)\n\t\tn, err := conn.Read(resp)\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t\tresp = resp[:n]\n\n\t\trespMsg := enet.NewDataPack().Decode(resp)\n\t\tfmt.Println(\"文件位于:\", string(respMsg.GetData()))\n\n\t\ttime.Sleep(5 * time.Second)\n\t}\n}\n\n```\n\n### 生产端1(有资源)\n\n``` go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/impact-eintr/enet\"\n)\n\nfunc SendHeartBeat() {\n\tlocalhost := \"10.29.1.2:12345\"\n\tip := net.ParseIP(\"172.17.0.2\")\n\n\tsrcAddr := \u0026net.UDPAddr{IP: net.IPv4zero, Port: 0}\n\tdstAddr := \u0026net.UDPAddr{IP: ip, Port: 6430}\n\n\tfor {\n\t\tconn, err := net.DialUDP(\"udp\", srcAddr, dstAddr)\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\n\t\tmsg := enet.NewMsgPackage(10, []byte(localhost)) // LBH\n\t\tbuf := enet.NewDataPack().Encode(msg)\n\t\t_, err = conn.Write(buf[:])\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t\tconn.Close()\n\n\t\ttime.Sleep(1000 * time.Millisecond)\n\t}\n}\n\nfunc SendFileLocation(file []byte) {\n\tlocalhost := \"10.29.1.2:12345\"\n\tip := net.ParseIP(\"172.17.0.2\")\n\n\tsrcAddr := \u0026net.UDPAddr{IP: net.IPv4zero, Port: 0}\n\tdstAddr := \u0026net.UDPAddr{IP: ip, Port: 6430}\n\n\tconn, err := net.DialUDP(\"udp\", srcAddr, dstAddr)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n\tdefer conn.Close()\n\n\tfile = append(file, '\\n')\n\tfile = append(file, []byte(localhost)...)\n\tmsg := enet.NewMsgPackage(21, file) // RFL\n\tbuf := enet.NewDataPack().Encode(msg)\n\t_, err = conn.Write(buf[:])\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc main() {\n\tgo SendHeartBeat()\n\n\t// TODO 准备接受广播 每个dataNode 是一个server\n\t// 解析得到UDP地址\n\taddr, err := net.ResolveUDPAddr(\"udp\", \":9000\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// 在UDP地址上建立UDP监听,得到连接\n\tconn, err := net.ListenUDP(\"udp\", addr)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer conn.Close()\n\n\t// 建立缓冲区\n\tbuffer := make([]byte, 1024)\n\n\tfor {\n\t\t//从连接中读取内容,丢入缓冲区\n\t\ti, udpAddr, e := conn.ReadFromUDP(buffer)\n\t\t// 第一个是字节长度,第二个是udp的地址\n\t\tif e != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"来自%v,读到的内容是:%s\\n\", udpAddr, buffer[:i])\n\n\t\t// Node1 有这个文件\n\t\tSendFileLocation(buffer[:i])\n\t}\n}\n\n```\n\n### 生产端2(无资源)\n\n``` go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"time\"\n\n\t\"github.com/impact-eintr/enet\"\n)\n\nfunc SendHeartBeat() {\n\tlocalhost := \"10.29.1.3:12345\"\n\tip := net.ParseIP(\"172.17.0.2\")\n\n\tsrcAddr := \u0026net.UDPAddr{IP: net.IPv4zero, Port: 0}\n\tdstAddr := \u0026net.UDPAddr{IP: ip, Port: 6430}\n\n\tfor {\n\t\tconn, err := net.DialUDP(\"udp\", srcAddr, dstAddr)\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\n\t\tmsg := enet.NewMsgPackage(10, []byte(localhost))\n\t\tbuf := enet.NewDataPack().Encode(msg)\n\t\t_, err = conn.Write(buf[:])\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t}\n\t\tconn.Close()\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\n}\n\nfunc main() {\n\tgo SendHeartBeat()\n\n\t// TODO 准备接受广播 每个dataNode 是一个server\n\t// 解析得到UDP地址\n\taddr, err := net.ResolveUDPAddr(\"udp\", \":9000\")\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\t// 在UDP地址上建立UDP监听,得到连接\n\tconn, err := net.ListenUDP(\"udp\", addr)\n\tif err != nil {\n\t\tlog.Fatal(err)\n\t}\n\n\tdefer conn.Close()\n\n\t// 建立缓冲区\n\tbuffer := make([]byte, 1024)\n\n\tfor {\n\t\t//从连接中读取内容,丢入缓冲区\n\t\ti, udpAddr, e := conn.ReadFromUDP(buffer)\n\t\t// 第一个是字节长度,第二个是udp的地址\n\t\tif e != nil {\n\t\t\tcontinue\n\t\t}\n\t\tfmt.Printf(\"来自%v,读到的内容是:%s\\n\", udpAddr, buffer[:i])\n\t}\n}\n\n```\n\n## v0.2\n### 简单的连接封装和业务绑定\n\u003e 连接的模块\n- 方法\n    - 启动连接\n    - 停止连接\n    - 获取当前连接的conn对象(socket)\n    - 得到连接ID\n    - 得到客户端连接的地址和端口\n    - 发送数据的方法\n- 属性\n    - socket TCP套接字\n    - 连接的ID\n    - 当前连接的状态\n    - 与当前连接绑定的处理业务方法\n    - 等待连接被动退出的channel\n\n## v0.3\n\n\u003e 基础router模块\n\n- Ruquest请求封装(将链接和数据绑定在一起)\n    - 属性\n        - 连接IConnection\n        - 请求数据\n    - 方法\n        - 得到当前连接\n        - 得到当前数据\n- Router模块\n    - 抽象的IRouter\n        -  处理业务之前的方法\n        -  处理业务的方法\n        -  处理业务之后的方法\n    - 具体的BaseRouter(作为具体实现的基类)\n        -  处理业务之前的方法\n        -  处理业务的方法\n        -  处理业务之后的方法\n- zinx集成router模块\n    - Iserver增添路由功能\n    - Server类增添Router成员\n    - Commection类绑定一个Router成员\n    - 在Connection调用 已经注册的Router处理业务\n\n## v0.4\n\u003e 增添全局配置\n\n## v0.5\n\n\u003e 消息封装\n\n- 定义一个消息的结构\n    - 属性\n        - 消息的ID\n        - 消息长度\n        - 消息的内容\n    - 方法\n        - Setter\n        - Getter\n- 将消息封装机制集成到Zinx框架中\n    - 将Message添加到Request中\n    - 修改连接读取数据的机制 将之前的单纯读取byte改为拆包读取方式\n    - 连接的发包机制 将发送的消息进行打包 再发送\n\n## v0.6\n\n\u003e 消息管理模块\n\n- 属性\n    - 集合-消息ID和对应的router的关系 map\n- 方法\n    - 根据msgID来索引调度路由方法\n    - 添加路由方法到map集合中\n\u003e 将消息管理机制集成到Zinx框架中\n1. 将server模块中的Router属性 替换成MsgHandler属性\n2. 将server之前的AddRouter修改成AddRouter--AddRouter(msgId unit32, router ziface.IRouter)\n3. 将connection模块Router属性 替换成MsgHandler 修改Connection方法\n4. Connection的之前调度Router的业务替换成MsgHandler调度 修改StartReader方法\n\n## v0.7\n\u003e Zinx读写分离\n\n![Zinx读写分离](https://img.kancloud.cn/80/28/8028019d6bfce107ebc1bf5a15fd8940_1024x768.jpeg)\n\n1. 添加一个Reader与Write之间通信的channel\n2. 添加一个Writer Goroutine\n3. Reader由之前直接发送给客户端 改为发送给通信Channel\n4. 启动Reader和Writer一同工作\n\n\n## v0.8\n\u003e 消息队列以及多任务\n\n![消息队列](https://img.kancloud.cn/70/6c/706cb06abebcb8c1b7dd22c23d79cf48_1024x768.jpeg)\n\n1. 创建一个消息队列\n- MsgHandler消息管理模块\n    - 增加属性\n        - 消息队列\n        - worker工作池的数量\n2. 创建多任务worker的工作池并且启动\n- 创建一个Worker的工作池并且启动\n    - 根据Workerpoolsize的数量去创建Worker\n    - 每一个Worker都开启一个协程负载\n        - 阻塞等待与当前Worker对应的channel来消息\n        - 一旦有消息到来mworker应该处理当前消息对应的业务\n3. 将之前的发送消息，全部都改成发送给消息队列和Worker工作池来处理\n- 定义一个方法 将消息发送给消息队列工作池\n    - 保证每个worker所受的request任务书均衡(平均分配) \n    - 将消息发送给对应的队列 \n\n\u003e 将消息队列机制集成到Zinx框架中\n- 开启并调用消息队列\n- 将从客户端接收的数据 发送给当前的Worker工作池来处理\n\n## v0.9\n\u003e 创建一个链接管理模块 \n- 属性\n    - 已经创建的Connection集合\n    - 针对map的互斥锁\n- 方法\n    - 添加链接\n    - 删除链接\n    - 根据链接ID查找对应的链接\n    - 总链接个数\n    - 清除全部的链接\n\u003e 将链接管理模块集成到Zinx框架中\n\n\u003e 给Zinx框架提供 创建链接之后/销毁链接之前 的钩子函数\n\n\u003e\n\n## v1.0\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimpact-eintr%2Fenet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fimpact-eintr%2Fenet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fimpact-eintr%2Fenet/lists"}