[
  {
    "path": ".gitattributes",
    "content": "*.aspx linguist-language=Go\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: [aceld] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\notechie: # Replace with a single Otechie username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\n#custom: https://user-images.githubusercontent.com/7778936/179700249-a89a82e1-d851-4b3d-a43e-c5a91b7d128d.png # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/workflows/reviewdog.yml",
    "content": "name: reviewdog\non: [pull_request]\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  golangci-lint:\n    runs-on: ubuntu-latest\n    name: runner / golangci-lint\n    steps:\n      - name: Check out code into the Go module directory\n        uses: actions/checkout@v3\n      - name: golangci-lint\n        uses: reviewdog/action-golangci-lint@dd3fda91790ca90e75049e5c767509dc0ec7d99b\n        with:\n          github_token: ${{ secrets.github_token }}\n          # Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review].\n          reporter: github-pr-review\n          # Report all results.\n          filter_mode: nofilter\n          # Exit with 1 when it finds at least one finding.\n          fail_on_error: true\n          #golangci_lint_flags\n          golangci_lint_version: v1.64.7"
  },
  {
    "path": ".gitignore",
    "content": ".idea\n.vscode\n\n/zinx_app_demo/mmo_game/game_client/client_Data/\n/zinx_app_demt/mmo_game/mmo_game_log/\n*.log\n\n### bin\nexamples/zinx_server/zinx_server\nexamples/zinx_server/server\nexamples/zinx_client/client\n\n.DS_Store\n\n\nlog\n\n/examples/zinx_websocket/minicode/node_modules\n/examples/zinx_websocket/minicode/miniprogram_npm\n\nrebase\n"
  },
  {
    "path": ".golangci.yaml",
    "content": "run:\n  timeout: 30m\n  skip-dirs:\n  - examples\n\nlinters:\n  disable-all: true\n  enable:\n  #- unused\n  - ineffassign\n  - goimports\n  - gofmt\n  - misspell\n  - unparam\n  - unconvert\n  - govet\n  # - errcheck\n  - staticcheck\n\nlinters-settings:\n  staticcheck:\n    go: \"1.17\"\n    checks:\n    - \"all\"\n    - \"-SA1019\"\n\n  unused:\n    go: \"1.17\"\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2024 Aceld(刘丹冰)\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "\n.PHONY: build\n\nSERVICE := zinx\nCUR_PWD := $(shell pwd)\n\nSERVER_DEMO_PATH := $(CUR_PWD)/examples/zinx_server\nCLIENT_DEMO_PATH := $(CUR_PWD)/examples/zinx_client\nSERVER_DEMO_BIN := $(SERVER_DEMO_PATH)/server\nCLIENT_DEMO_BIN := $(CLIENT_DEMO_PATH)/client\n\nAUTHOR := $(shell git log --pretty=format:\"%an\"|head -n 1)\nVERSION := $(shell git rev-list HEAD | head -1)\nBUILD_INFO := $(shell git log --pretty=format:\"%s\" | head -1)\nBUILD_DATE := $(shell date +%Y-%m-%d\\ %H:%M:%S)\n\nexport GO111MODULE=on\n\nLD_FLAGS='-X \"$(SERVICE)/version.TAG=$(TAG)\" -X \"$(SERVICE)/version.VERSION=$(VERSION)\" -X \"$(SERVICE)/version.AUTHOR=$(AUTHOR)\" -X \"$(SERVICE)/version.BUILD_INFO=$(BUILD_INFO)\" -X \"$(SERVICE)/version.BUILD_DATE=$(BUILD_DATE)\"'\n\nTEST_FILES := znet/acceptdelay_test.go znet/acceptdelay.go\n\ndefault: build\n\nbuild:\n\tgo build  -ldflags $(LD_FLAGS) -gcflags \"-N\"  -o $(SERVER_DEMO_BIN) $(SERVER_DEMO_PATH)/main.go\n\tgo build  -ldflags $(LD_FLAGS) -gcflags \"-N\"  -o $(CLIENT_DEMO_BIN) $(CLIENT_DEMO_PATH)/main.go\n\ntest:\n\tgo test -v -cover $(TEST_FILES)\nclean:\n\trm $(SERVER_DEMO_BIN)\n\trm $(CLIENT_DEMO_BIN)\n"
  },
  {
    "path": "README-CN.md",
    "content": "# <img width=\"80px\" src=\"https://s2.ax1x.com/2019/10/09/u4yHo9.png\" /> \n\n[English](README.md) | 简体中文\n\n[![License](https://img.shields.io/badge/License-MIT-black.svg)](LICENSE)\n[![Discord](https://img.shields.io/badge/zinx-Discord在线社区-blue.svg)](https://discord.gg/xQ8Xxfyfcz)\n[![Gitter](https://img.shields.io/badge/zinx-Gitter在线交流-green.svg)](https://gitter.im/zinx_go/community)\n[![zinx tutorial](https://img.shields.io/badge/Zinx教程-YuQue-red.svg)](https://www.yuque.com/aceld/npyr8s/bgftov)\n[![Original Book of Zinx](https://img.shields.io/badge/原创书籍-YuQue-black.svg)](https://www.yuque.com/aceld)\n\nZinx 是一个基于Golang的轻量级并发服务器框架\n\n## 开发者文档\n\n[ < Zinx Wiki : English > ](https://github.com/aceld/zinx/wiki)\n\n[ < Zinx 文档 : 简体中文> ](https://www.yuque.com/aceld/tsgooa/sbvzgczh3hqz8q3l)\n\n> **说明**:目前zinx已经在很多企业进行开发使用，具体使用领域包括:后端模块的消息中转、长连接游戏服务器、Web框架中的消息处理插件等。zinx的定位是代码简洁，让更多的开发者迅速的了解框架的内脏细节并且可以快速基于zinx DIY(二次开发)一款适合自己企业场景的模块。\n\n---\n## zinx源码地址\n| platform | Entry | \n| ---- | ---- | \n|Github| https://github.com/aceld/zinx |\n|Gitcode|https://gitcode.com/aceld/zinx|\n|Gitee|https://gitee.com/Aceld/zinx|\n\n### 官网\nhttp://zinx.me\n\n---\n\n## 在线开发教程\n\n### 文字教程\n\n| platform | Entry | \n| ---- | ---- | \n| <img src=\"https://user-images.githubusercontent.com/7778936/236784004-b6d99e26-b1ab-4bc3-988e-7a46108b85fe.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [Zinx Framework tutorial-Lightweight server based on Golang](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)| \n|<img src=\"https://user-images.githubusercontent.com/7778936/236784168-6528a9b8-d37b-4b02-a37c-b9988d7508d8.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />|[《Golang轻量级并发服务器框架zinx》](https://www.yuque.com/aceld)|\n\n\n### 视频教程\n\n| platform | online video | \n| ---- | ---- | \n| <img src=\"https://s1.ax1x.com/2022/09/22/xFePUK.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.bilibili.com/video/av71067087)| \n| <img src=\"https://s1.ax1x.com/2022/09/22/xFesxJ.png\" width = \"100\" height = \"80\" alt=\"\" align=center />  | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.douyin.com/video/6983301202939333891) |\n| <img src=\"https://s1.ax1x.com/2022/09/23/xkQcng.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [![zinx-youtube](https://s2.ax1x.com/2019/10/14/KSurCR.jpg)](https://www.youtube.com/watch?v=U95iF-HMWsU&list=PL_GrAPKmuajzeNI8HBTi-k5NQO1g0rM-A)| \n\n    \n## 一、写在前面\n\n我们为什么要做Zinx，Golang目前在服务器的应用框架很多，但是应用在游戏领域或者其他长连接的领域的轻量级企业框架甚少。\n\n设计Zinx的目的是我们可以通过Zinx框架来了解基于Golang编写一个TCP服务器的整体轮廓，让更多的Golang爱好者能深入浅出的去学习和认识这个领域。\n\nZinx框架的项目制作采用编码和学习教程同步进行，将开发的全部递进和迭代思维带入教程中，而不是一下子给大家一个非常完整的框架去学习，让很多人一头雾水，不知道该如何学起。\n\n教程会一个版本一个版本迭代，每个版本的添加功能都是微小的，让一个服务框架小白，循序渐进的曲线方式了解服务器框架的领域。\n\n当然，最后希望Zinx会有更多的人加入，给我们提出宝贵的意见，让Zinx成为真正的解决企业的服务器框架！在此感谢您的关注！\n\n\n\n\n## 二、初探Zinx架构\n![Zinx框架](https://user-images.githubusercontent.com/7778936/220058446-0ad45112-2225-4b71-b0d8-69a7f3cee5ca.jpg)\n\n![流程图](https://github.com/wenyoufu/testaaaaaa/blob/abc8a50078a86aed37e8af6082d1d867bc165c32/%E6%9C%AA%E5%91%BD%E5%90%8D%E6%B5%81%E7%A8%8B%E5%9B%BE%20(1).jpg?raw=true)\n![zinx-start](https://user-images.githubusercontent.com/7778936/126594039-98dddd10-ec6a-4881-9e06-a09ec34f1af7.gif)\n\n\n\n## 三、Zinx开发接口文档\n\n\n### （1）快速开始\n\n[<Zinx的Tcp调试工具>](https://github.com/xxl6097/tcptest)\n\n**版本**\nGolang 1.17+\n\nDownLoad zinx Source\n\n```bash\n$go get github.com/aceld/zinx\n```\n\n> note: Golang Version 1.17+\n\n#### Zinx-Server\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// PingRouter MsgId=1的路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n//Ping Handle MsgId=1的路由处理方法\nfunc (r *PingRouter) Handle(request ziface.IRequest) {\n\t//读取客户端的数据\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n}\n\nfunc main() {\n\t//1 创建一个server服务\n\ts := znet.NewServer()\n\n\t//2 配置路由\n\ts.AddRouter(1, &PingRouter{})\n\n\t//3 启动服务\n\ts.Serve()\n}\n\n```\n\nRun Server\n\n```bash\n$ go run server.go\n```\n\n```bash\n                                        \n              ██                        \n              ▀▀                        \n ████████   ████     ██▄████▄  ▀██  ██▀ \n     ▄█▀      ██     ██▀   ██    ████   \n   ▄█▀        ██     ██    ██    ▄██▄   \n ▄██▄▄▄▄▄  ▄▄▄██▄▄▄  ██    ██   ▄█▀▀█▄  \n ▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀  ▀▀    ▀▀  ▀▀▀  ▀▀▀ \n                                        \n┌──────────────────────────────────────────────────────┐\n│ [Github] https://github.com/aceld                    │\n│ [tutorial] https://www.yuque.com/aceld/npyr8s/bgftov │\n└──────────────────────────────────────────────────────┘\n[Zinx] Version: V1.0, MaxConn: 12000, MaxPacketSize: 4096\n===== Zinx Global Config =====\nTCPServer: <nil>\nHost: 0.0.0.0\nTCPPort: 8999\nName: ZinxServerApp\nVersion: V1.0\nMaxPacketSize: 4096\nMaxConn: 12000\nWorkerPoolSize: 10\nMaxWorkerTaskLen: 1024\nMaxMsgChanLen: 1024\nConfFilePath: /Users/Aceld/go/src/zinx-usage/quick_start/conf/zinx.json\nLogDir: /Users/Aceld/go/src/zinx-usage/quick_start/log\nLogFile: \nLogIsolationLevel: 0\nHeartbeatMax: 10\n==============================\n2023/03/09 18:39:49 [INFO]msghandler.go:61: Add api msgID = 1\n2023/03/09 18:39:49 [INFO]server.go:112: [START] Server name: ZinxServerApp,listenner at IP: 0.0.0.0, Port 8999 is starting\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 0 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 1 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 3 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 2 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 4 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 6 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 7 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 8 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 9 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 5 is started.\n2023/03/09 18:39:49 [INFO]server.go:134: [START] start Zinx server  ZinxServerApp succ, now listenning...\n\n```\n\n\n\n#### Zinx-Client\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"time\"\n)\n\n//客户端自定义业务\nfunc pingLoop(conn ziface.IConnection) {\n\tfor {\n\t\terr := conn.SendMsg(1, []byte(\"Ping...Ping...Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\n//创建连接的时候执行\nfunc onClientStart(conn ziface.IConnection) {\n\tfmt.Println(\"onClientStart is Called ... \")\n\tgo pingLoop(conn)\n}\n\nfunc main() {\n\t//创建Client客户端\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\t//设置连接建立成功后的钩子函数\n\tclient.SetOnConnStart(onClientStart)\n\n\t//启动客户端\n\tclient.Start()\n\n\t//防止进程退出，等待中断信号\n\tselect {}\n}\n\n```\n\nRun Client\n\n```bash\n$ go run client.go \n2023/03/09 19:04:54 [INFO]client.go:73: [START] Zinx Client LocalAddr: 127.0.0.1:55294, RemoteAddr: 127.0.0.1:8999\n2023/03/09 19:04:54 [INFO]connection.go:354: ZINX CallOnConnStart....\n```\n\nTerminal of Zinx Print:\n```bash\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\n...\n```\n\n\n### （2）Zinx配置文件\n```json\n{\n  \"Name\":\"zinx v-0.10 demoApp\",\n  \"Host\":\"0.0.0.0\",\n  \"TCPPort\":9090,\n  \"MaxConn\":3,\n  \"WorkerPoolSize\":10,\n  \"LogDir\": \"./mylog\",\n  \"LogFile\":\"app.log\",\n  \"LogSaveDays\":15,\n  \"LogCons\": true,\n  \"LogIsolationLevel\":0\n}\n```\n\n`Name`:服务器应用名称\n\n`Host`:服务器IP\n\n`TcpPort`:服务器监听端口\n\n`MaxConn`:允许的客户端连接最大数量\n\n`WorkerPoolSize`:工作任务池最大工作Goroutine数量\n\n`LogDir`: 日志文件夹\n\n`LogFile`: 日志文件名称(如果不提供，则日志信息打印到Stderr)\n\n`LogIsolationLevel`: 日志隔离级别 0：全开, 1：关debug, 2：关debug/info, 3：关debug/info/warn \n\n---\n\n#### 开发者\n| **Zinx**                                               | **开发者**  |\n|--------------------------------------------------------| ----  | \n| [zinx](https://github.com/aceld/zinx)                  |刘丹冰([@aceld](https://github.com/aceld)) 张超([@zhngcho](https://github.com/zhngcho)) 高智辉Roger([@adsian](https://github.com/adsian)) 胡贵建([@huguijian](https://github.com/huguijian)) 张继瑀([@kstwoak](https://github.com/kstwoak)) 夏小力([@xxl6097](https://github.com/xxl6097)) 李志成([@clukboy](https://github.com/clukboy)）姚承政([@hcraM41](https://github.com/hcraM41)）李国杰([@LI-GUOJIE](https://github.com/LI-GUOJIE)）余喆宁([@YanHeDoki](https://github.com/YanHeDoki)）|\n| [moke-kit(微服务框架)](https://github.com/GStones/moke-kit) |GStones([@GStones](https://github.com/GStones))|\n| [zinx(C++)](https://github.com/marklion/zinx)          |刘洋([@marklion](https://github.com/marklion))|\n| [zinx(Lua)](https://github.com/huqitt/zinx-lua)        |胡琪([@huqitt](https://github.com/huqitt))|\n| [ginx(Java)](https://github.com/ModuleCode/ginx)       |ModuleCode([@ModuleCode](https://github.com/ModuleCode))|\n\n---\n\n感谢所有为zinx贡献的开发者\n\n\n<a href=\"https://github.com/aceld/zinx/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=aceld/zinx\" />\n</a>    \n\n\n---\n### 关于作者：\n\n作者：`Aceld(刘丹冰)`\n\n`mail`:\n[danbing.at@gmail.com](mailto:danbing.at@gmail.com)\n\n`github`:\n[https://github.com/aceld](https://github.com/aceld)\n\n`原创书籍`:\n[https://www.yuque.com/aceld](https://www.yuque.com/aceld)\n\n\n### 加入Zinx技术社区\n\n| platform | Entry | \n| ---- | ---- | \n| <img src=\"https://user-images.githubusercontent.com/7778936/236775008-6bd488e3-249a-4d43-8885-7e3889e11e2d.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| https://discord.gg/xQ8Xxfyfcz| \n| <img src=\"https://user-images.githubusercontent.com/7778936/236775137-5381f8a6-f534-49c4-8628-e52bf245c3bc.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />  | 加微信: `ace_ld`  或扫二维码，备注`zinx`即可。</br><img src=\"https://user-images.githubusercontent.com/7778936/236781258-2f0371bd-5797-49e8-a74c-680e9f15843d.png\" width = \"150\" height = \"150\" alt=\"\" align=center /> |\n|<img src=\"https://user-images.githubusercontent.com/7778936/236778547-9cdadfb6-0f62-48ac-851a-b940389038d0.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />|<img src=\"https://s1.ax1x.com/2020/07/07/UFyUdx.th.jpg\" height = \"150\"  alt=\"\" align=center /> **WeChat Public Account** |\n|<img src=\"https://user-images.githubusercontent.com/7778936/236779000-70f16c8f-0eec-4b5f-9faa-e1d5229a43e0.png\" width = \"100\" height = \"100\" alt=\"\" align=center />|<img src=\"https://s1.ax1x.com/2020/07/07/UF6Y9S.th.png\" width = \"150\" height = \"150\" alt=\"\" align=center /> **QQ Group** |\n"
  },
  {
    "path": "README.md",
    "content": "# <img width=\"80px\" src=\"https://s2.ax1x.com/2019/10/09/u4yHo9.png\" />\nEnglish | [简体中文](README-CN.md)\n\n[![License](https://img.shields.io/badge/License-MIT-black.svg)](LICENSE)\n[![Discord](https://img.shields.io/badge/zinx-Discord-blue.svg)](https://discord.gg/xQ8Xxfyfcz)\n[![Gitter](https://img.shields.io/badge/zinx-Gitter-green.svg)](https://gitter.im/zinx_go/community) \n[![zinx tutorial](https://img.shields.io/badge/ZinxTutorial-YuQue-red.svg)](https://www.yuque.com/aceld/npyr8s/bgftov) \n[![Original Book of Zinx](https://img.shields.io/badge/OriginalBook-YuQue-black.svg)](https://www.yuque.com/aceld)\n\nZinx is a lightweight concurrent server framework based on Golang.\n\n##  Document \n\n[ < Zinx Wiki : English > ](https://github.com/aceld/zinx/wiki)\n\n[ < Zinx 文档 : 简体中文> ](https://www.yuque.com/aceld/tsgooa/sbvzgczh3hqz8q3l)\n\n\n> **Note**: \n> Zinx has been widely used in many enterprises for development purposes, including message forwarding for backend modules, long-linked game servers, and message handling plugins for web frameworks. \n> Zinx is positioned as a framework with concise code that allows developers to quickly understand the internal details of the framework and easily customize it based on their own enterprise scenarios.\n\n---\n## Source of Zinx\n| platform | Entry | \n| ---- | ---- | \n|Github| https://github.com/aceld/zinx |\n|Gitcode|https://gitcode.com/aceld/zinx|\n|Gitee|https://gitee.com/Aceld/zinx|\n\n### Website\nhttp://zinx.me\n\n---\n## Online Tutorial\n\n| platform | Entry | \n| ---- | ---- | \n| <img src=\"https://user-images.githubusercontent.com/7778936/236784004-b6d99e26-b1ab-4bc3-988e-7a46108b85fe.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [Zinx Framework tutorial-Lightweight server based on Golang](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)| \n|<img src=\"https://user-images.githubusercontent.com/7778936/236784168-6528a9b8-d37b-4b02-a37c-b9988d7508d8.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />|[《Golang轻量级并发服务器框架zinx》](https://www.yuque.com/aceld)|\n\n\n## Online Tutorial Video\n\n| platform | online video | \n| ---- | ---- | \n| <img src=\"https://s1.ax1x.com/2022/09/22/xFePUK.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.bilibili.com/video/av71067087)| \n| <img src=\"https://github.com/user-attachments/assets/4c401abd-7807-432a-968e-36ce0e74349c\" width = \"100\" height = \"100\" alt=\"\" align=center />  | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.douyin.com/video/6983301202939333891) |\n| <img src=\"https://s1.ax1x.com/2022/09/23/xkQcng.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [![zinx-youtube](https://s2.ax1x.com/2019/10/14/KSurCR.jpg)](https://www.youtube.com/watch?v=U95iF-HMWsU&list=PL_GrAPKmuajzeNI8HBTi-k5NQO1g0rM-A)| \n\n\n\n\n\n## I. One word that has been said before\n\nWhy did we create Zinx? Although there are many Golang application frameworks for servers, there are few lightweight enterprise frameworks applied in the gaming or other long-linked fields.\n\nThe purpose of designing Zinx is to provide a complete outline of how to write a TCP server based on Golang, so that more Golang enthusiasts can learn and understand this field in a straightforward manner.\n\nThe development of the Zinx framework project is synchronized with the creation of learning tutorials, and all the incremental and iterative thinking involved in the development process is incorporated into the tutorials. This approach avoids overwhelming beginners with a complete framework that they may find difficult to grasp all at once.\n\nThe tutorials will be iterated version by version, with each version adding small increments of functionality, allowing a beginner to gradually and comprehensively learn about the field of server frameworks.\n\nOf course, we hope that more people will join Zinx and provide us with valuable feedback, enabling Zinx to become a truly enterprise-level server framework. Thank you for your attention!\n\n\n\n## II. Zinx architecture\n![Zinx框架](https://user-images.githubusercontent.com/7778936/220058446-0ad45112-2225-4b71-b0d8-69a7f3cee5ca.jpg)\n\n![流程图](https://raw.githubusercontent.com/wenyoufu/testaaaaaa/master/%E6%B5%81%E7%A8%8B%E5%9B%BE-en.jpg)\n![zinx-start](https://user-images.githubusercontent.com/7778936/126594039-98dddd10-ec6a-4881-9e06-a09ec34f1af7.gif)\n\n\n\n## III. Zinx development API documentation\n\n\n### (1) QuickStart\n\n[<Zinx's TCP Debugging Tool>](https://github.com/xxl6097/tcptest)\n\nDownLoad zinx Source\n\n```bash\n$go get github.com/aceld/zinx\n```\n\n> note: Golang Version 1.17+\n\n#### Zinx-Server\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// PingRouter MsgId=1 \ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n//Ping Handle MsgId=1\nfunc (r *PingRouter) Handle(request ziface.IRequest) {\n\t//read client data\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n}\n\nfunc main() {\n\t//1 Create a server service\n\ts := znet.NewServer()\n\n\t//2 configure routing\n\ts.AddRouter(1, &PingRouter{})\n\n\t//3 start service\n\ts.Serve()\n}\n```\n\nRun Server\n\n```bash\n$ go run server.go\n```\n\n```bash\n                                        \n              ██                        \n              ▀▀                        \n ████████   ████     ██▄████▄  ▀██  ██▀ \n     ▄█▀      ██     ██▀   ██    ████   \n   ▄█▀        ██     ██    ██    ▄██▄   \n ▄██▄▄▄▄▄  ▄▄▄██▄▄▄  ██    ██   ▄█▀▀█▄  \n ▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀  ▀▀    ▀▀  ▀▀▀  ▀▀▀ \n                                        \n┌──────────────────────────────────────────────────────┐\n│ [Github] https://github.com/aceld                    │\n│ [tutorial] https://www.yuque.com/aceld/npyr8s/bgftov │\n└──────────────────────────────────────────────────────┘\n[Zinx] Version: V1.0, MaxConn: 12000, MaxPacketSize: 4096\n===== Zinx Global Config =====\nHost: 0.0.0.0\nTCPPort: 8999\nName: ZinxServerApp\nVersion: V1.0\nMaxPacketSize: 4096\nMaxConn: 12000\nWorkerPoolSize: 10\nMaxWorkerTaskLen: 1024\nMaxMsgChanLen: 1024\nConfFilePath: /Users/Aceld/go/src/zinx-usage/quick_start/conf/zinx.json\nLogDir: /Users/Aceld/go/src/zinx-usage/quick_start/log\nLogFile: \nLogIsolationLevel: 0\nHeartbeatMax: 10\n==============================\n2023/03/09 18:39:49 [INFO]msghandler.go:61: Add api msgID = 1\n2023/03/09 18:39:49 [INFO]server.go:112: [START] Server name: ZinxServerApp,listenner at IP: 0.0.0.0, Port 8999 is starting\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 0 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 1 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 3 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 2 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 4 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 6 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 7 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 8 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 9 is started.\n2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 5 is started.\n2023/03/09 18:39:49 [INFO]server.go:134: [START] start Zinx server  ZinxServerApp succ, now listenning...\n\n```\n\n\n\n#### Zinx-Client\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"time\"\n)\n\n//Client custom business\nfunc pingLoop(conn ziface.IConnection) {\n\tfor {\n\t\terr := conn.SendMsg(1, []byte(\"Ping...Ping...Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\n//Executed when a connection is created\nfunc onClientStart(conn ziface.IConnection) {\n\tfmt.Println(\"onClientStart is Called ... \")\n\tgo pingLoop(conn)\n}\n\nfunc main() {\n\t//Create a client client\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\t//Set the hook function after the link is successfully established\n\tclient.SetOnConnStart(onClientStart)\n\n\t//start the client\n\tclient.Start()\n\n\t//Prevent the process from exiting, waiting for an interrupt signal\n\tselect {}\n}\n```\n\nRun Client\n\n```bash\n$ go run client.go \n2023/03/09 19:04:54 [INFO]client.go:73: [START] Zinx Client LocalAddr: 127.0.0.1:55294, RemoteAddr: 127.0.0.1:8999\n2023/03/09 19:04:54 [INFO]connection.go:354: ZINX CallOnConnStart....\n```\n\nTerminal of Zinx Print:\n```bash\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\nrecv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient]\n...\n```\n\n\n### (2) Zinx configuration file\n```json\n{\n  \"Name\":\"zinx v-0.10 demoApp\",\n  \"Host\":\"0.0.0.0\",\n  \"TCPPort\":9090,\n  \"MaxConn\":3,\n  \"WorkerPoolSize\":10,\n  \"LogDir\": \"./mylog\",\n  \"LogFile\":\"app.log\",\n  \"LogSaveDays\":15,\n  \"LogCons\": true,\n  \"LogIsolationLevel\":0\n}\n```\n\n`Name`:Server Application Name\n\n`Host`:Server IP\n\n`TcpPort`:Server listening port\n\n`MaxConn`:Maximum number of client links allowed\n\n`WorkerPoolSize`:Maximum number of working Goroutines in the work task pool\n\n`LogDir`: Log folder\n\n`LogFile`: Log file name (if not provided, log information is printed to Stderr)\n\n`LogIsolationLevel`: Log Isolation Level -0: Full On 1: Off debug 2: Off debug/info 3: Off debug/info/warn\n\n---\n\n\n#### Developers\n\n| **Zinx**                                                       | **Authors**  |\n|----------------------------------------------------------------| ----  | \n| [zinx](https://github.com/aceld/zinx)                          |刘丹冰([@aceld](https://github.com/aceld)) 张超([@zhngcho](https://github.com/zhngcho)) 高智辉Roger([@adsian](https://github.com/adsian)) 胡贵建([@huguijian](https://github.com/huguijian)) 张继瑀([@kstwoak](https://github.com/kstwoak)) 夏小力([@xxl6097](https://github.com/xxl6097)) 李志成([@clukboy](https://github.com/clukboy)）姚承政([@hcraM41](https://github.com/hcraM41)）李国杰([@LI-GUOJIE](https://github.com/LI-GUOJIE)）余喆宁([@YanHeDoki](https://github.com/YanHeDoki)）|\n| [moke-kit(Microservices)](https://github.com/GStones/moke-kit) |GStones([@GStones](https://github.com/GStones))|\n| [zinx(C++)](https://github.com/marklion/zinx)                  |刘洋([@marklion](https://github.com/marklion))|\n| [zinx(Lua)](https://github.com/huqitt/zinx-lua)                |胡琪([@huqitt](https://github.com/huqitt))|\n| [ginx(Java)](https://github.com/ModuleCode/ginx)               |ModuleCode([@ModuleCode](https://github.com/ModuleCode))|\n\n---\n\nThanks to all the developers who contributed to Zinx!\n\n<a href=\"https://github.com/aceld/zinx/graphs/contributors\">\n  <img src=\"https://contrib.rocks/image?repo=aceld/zinx\" />\n</a>    \n\n\n---\n\n### About the author\n\n`name`：`Aceld(刘丹冰)`\n\n`mail`:\n[danbing.at@gmail.com](mailto:danbing.at@gmail.com)\n\n`github`:\n[https://github.com/aceld](https://github.com/aceld)\n\n`original work`:\n[https://www.yuque.com/aceld](https://www.yuque.com/aceld)\n\n### Join the Zinx community \n\n| platform | Entry | \n| ---- | ---- | \n| <img src=\"https://user-images.githubusercontent.com/7778936/236775008-6bd488e3-249a-4d43-8885-7e3889e11e2d.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| https://discord.gg/xQ8Xxfyfcz| \n| <img src=\"https://user-images.githubusercontent.com/7778936/236775137-5381f8a6-f534-49c4-8628-e52bf245c3bc.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />  | 加微信: `ace_ld`  或扫二维码，备注`zinx`即可。</br><img src=\"https://user-images.githubusercontent.com/7778936/236781258-2f0371bd-5797-49e8-a74c-680e9f15843d.png\" width = \"150\" height = \"150\" alt=\"\" align=center /> |\n|<img src=\"https://user-images.githubusercontent.com/7778936/236778547-9cdadfb6-0f62-48ac-851a-b940389038d0.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />|<img src=\"https://s1.ax1x.com/2020/07/07/UFyUdx.th.jpg\" height = \"150\"  alt=\"\" align=center /> **WeChat Public Account** |\n|<img src=\"https://user-images.githubusercontent.com/7778936/236779000-70f16c8f-0eec-4b5f-9faa-e1d5229a43e0.png\" width = \"100\" height = \"100\" alt=\"\" align=center />|<img src=\"https://github.com/aceld/zinx/assets/7778936/461b409f-6337-48a8-826b-a7a746aaee31\" width = \"150\" height = \"150\" alt=\"\" align=center /> **QQ Group** |\n"
  },
  {
    "path": "examples/zinx_RequestPollMode/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/examples/zinx_client/c_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// Custom business logic of the client (客户端自定义业务)\nfunc business(conn ziface.IConnection) {\n\n\tfor i := 0; i < 100; i++ {\n\t\terr := conn.SendMsg(1, []byte(\"Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tzlog.Error(err)\n\t\t\tbreak\n\t\t}\n\n\t\terr = conn.SendMsg(2, []byte(\"Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tzlog.Error(err)\n\t\t\tbreak\n\t\t}\n\n\t}\n}\n\n// Function to execute when the connection is created (创建连接的时候执行)\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tzlog.Debug(\"DoConnectionBegin is Called ... \")\n\n\t// Set two connection properties after the connection is created (设置两个连接属性，在连接创建之后)\n\tconn.SetProperty(\"Name\", \"刘丹冰Aceld\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\tgo business(conn)\n}\n\n// Function to execute when the connection is lost (连接断开的时候执行)\nfunc DoClientConnectedLost(conn ziface.IConnection) {\n\t// Get the Name and Home properties of the connection before it is destroyed\n\t// (在连接销毁之前，查询conn的Name，Home属性)\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Name = \", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Home = \", home)\n\t}\n\n\tzlog.Debug(\"DoClientConnectedLost is Called ... \")\n}\n\nfunc main() {\n\t// Create a client handle using Zinx's Method (创建一个Client句柄，使用Zinx的方法)\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\t// Set the business logic to execute when the connection is created or lost\n\t// (添加首次建立连接时的业务)\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\tclient.SetOnConnStop(DoClientConnectedLost)\n\n\t// Register routers for the messages received from the server\n\t// (注册收到服务器消息业务路由)\n\tclient.AddRouter(2, &c_router.PingRouter{})\n\tclient.AddRouter(3, &c_router.HelloRouter{})\n\n\t// Start the client\n\tclient.Start()\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n\tclient.Stop()\n\ttime.Sleep(time.Second * 2)\n}\n"
  },
  {
    "path": "examples/zinx_RequestPollMode/no_pool_mode_server/NoPoolModeServer.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// 如果不使用对象池模式则可以直接传递但是产生大量的 Request 对象\n\nfunc NoPoll1(request ziface.IRequest) {\n\trequest.Set(\"num\", 1)\n\tgo NoPoll2(request)\n}\n\nfunc NoPoll2(request ziface.IRequest) {\n\ttime.Sleep(time.Second * 3)\n\tget, _ := request.Get(\"num\")\n\tfmt.Printf(\"num:%v \\n\", get)\n\n}\n\nfunc NoPoll4(request ziface.IRequest) {\n\t// 非对象池模式永远不会影响原本的 Request\n\trequest.Set(\"num\", 3)\n}\n\nfunc main() {\n\n\t// 关闭 Request 对象池模式\n\tserver := znet.NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: \"127.0.0.1\", RequestPoolMode: false})\n\tserver.AddRouterSlices(1, NoPoll1)\n\tserver.AddRouterSlices(2, NoPoll4)\n\tserver.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_RequestPollMode/pool_mode_server/PoolModeServer.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc Poll1(request ziface.IRequest) {\n\t// 如果需要连接信息\n\trequest.Set(\"conn\", request.GetConnection())\n\trequest.Set(\"num\", 1)\n\tfmt.Printf(\"request 1 addr:%p,conn:%p \\n\", &request, request.GetConnection())\n\n\t// 需要新线程同时也需要上下文的情况,则需要调用 copy 方法拷贝一份\n\tcp := request.Copy()\n\tgo Poll2(cp)\n\n\t// 如果不使用 copy 方法拷贝对象则会出现同一个对象但是信息可能不一致的问题,不启动 poll2 会更直接\n\tgo Poll3(request)\n}\n\nfunc Poll2(request ziface.IRequest) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\t// 接收一个panic\n\t\t\tfmt.Println(err)\n\t\t}\n\n\t}()\n\tget_conn, ok := request.Get(\"conn\")\n\tif ok {\n\t\t// 如果直接取用则会导致空指针\n\t\trequest.GetConnection().GetConnID()\n\t\t//  打印出的 Request 对象的地址是不一致的\n\t\tconn := get_conn.(ziface.IConnection)\n\t\tfmt.Printf(\"request copy addr:%p,conn:%p \\n\", &request, conn)\n\t\t// conn.sendMsg()\n\t}\n}\n\n// 如果请求的次数多,则开启对象池且直接传递不copy Request 就可能导致值不一致\nfunc Poll3(request ziface.IRequest) {\n\ttime.Sleep(time.Second * 3)\n\tget, _ := request.Get(\"num\")\n\t// 池化对象如果直接传递被影响可能随机打印被修改的值 3\n\tfmt.Printf(\"num:%v \\n\", get)\n\n}\n\nfunc Poll4(request ziface.IRequest) {\n\t// 影响原本的 request 对象\n\trequest.Set(\"num\", 3)\n}\n\nfunc main() {\n\n\t// 开启 Request 对象池模式\n\tserver := znet.NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: \"127.0.0.1\", RequestPoolMode: true})\n\tserver.AddRouterSlices(1, Poll1)\n\tserver.AddRouterSlices(2, Poll4)\n\tserver.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_async_op/async_op_apis/user_async_api.go",
    "content": "package async_op_apis\n\nimport (\n\t\"github.com/aceld/zinx/examples/zinx_async_op/db_model\"\n\t\"github.com/aceld/zinx/zasync_op\"\n\t\"github.com/aceld/zinx/ziface\"\n)\n\nfunc AsyncUserSaveData(request ziface.IRequest) *zasync_op.AsyncOpResult {\n\n\topId := 1 // player's unique identifier Id (玩家的唯一标识Id)\n\tasyncResult := zasync_op.NewAsyncOpResult(request.GetConnection())\n\n\tzasync_op.Process(\n\t\tint(opId),\n\t\tfunc() {\n\t\t\t// perform db operation (执行db操作)\n\t\t\tuser := db_model.SaveUserData()\n\n\t\t\t// set async return result (设置异步返回结果)\n\t\t\tasyncResult.SetReturnedObj(user)\n\n\t\t\t// test active exception (测试主动异常)\n\t\t\t/*\n\t\t\t\ta := 0\n\t\t\t\tb := 1\n\t\t\t\tc := b / a\n\t\t\t\tfmt.Println(c)\n\t\t\t*/\n\t\t},\n\t)\n\n\treturn asyncResult\n}\n"
  },
  {
    "path": "examples/zinx_async_op/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\n\t\"github.com/aceld/zinx/zpack\"\n)\n\nfunc main() {\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:8999\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\", err)\n\t\treturn\n\t}\n\n\tdp := zpack.NewDataPack()\n\tmsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(\"async_op_router test=========>\")))\n\t_, err = conn.Write(msg)\n\tif err != nil {\n\t\tfmt.Println(\"write error err \", err)\n\t\treturn\n\t}\n\n\tfor {\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client read head err: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client unpack head err: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"client unpack data err\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Printf(\"==> Client receive Msg: ID = %d, len = %d , data = %s\\n\", msg.ID, msg.DataLen, msg.Data)\n\t\t}\n\t}\n\n}\n"
  },
  {
    "path": "examples/zinx_async_op/db_model/user_dao.go",
    "content": "package db_model\n\nimport (\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zlog\"\n)\n\ntype UserModel struct {\n\tUserId     uint32\n\tUpdateTime int64\n\tName       string\n}\n\nfunc SaveUserData() *UserModel {\n\tzlog.Debug(\"SaveUserData IN=================>222\")\n\n\ttime.Sleep(time.Second * 2) // 模拟db操作需要2秒时间\n\tuser := &UserModel{1, time.Now().Unix(), \"14March\"}\n\n\tzlog.Debug(\"SaveUserData OUT==================>222\")\n\treturn user\n}\n"
  },
  {
    "path": "examples/zinx_async_op/msg_struct/user_login.go",
    "content": "package msg_struct\n\ntype MsgLoginResponse struct {\n\tUserId    uint32\n\tUserName  string\n\tErrorCode int\n}\n"
  },
  {
    "path": "examples/zinx_async_op/router/login.go",
    "content": "package router\n\nimport (\n\t\"encoding/json\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/examples/zinx_async_op/async_op_apis\"\n\t\"github.com/aceld/zinx/examples/zinx_async_op/db_model\"\n\t\"github.com/aceld/zinx/examples/zinx_async_op/msg_struct\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype LoginRouter struct {\n\tznet.BaseRouter\n}\n\nfunc (hr *LoginRouter) Handle(request ziface.IRequest) {\n\tzlog.Debug(\"AsyncOpRouter Handle IN ===>111\")\n\n\tasyncResult := async_op_apis.AsyncUserSaveData(request) // // Test DB asynchronous operation(测试DB异步操作)\n\n\t// 测试：执行了一大推业务逻辑,才设置回调函数\n\t// Test: A lot of business logic is executed before setting the callback function\n\ttime.Sleep(1 * time.Second)\n\n\t// Asynchronous callback (异步回调)\n\tasyncResult.OnComplete(func() {\n\t\tzlog.Debug(\"OnComplete IN===>333\")\n\t\treturnedObj := asyncResult.GetReturnedObj()\n\t\tif returnedObj == nil {\n\t\t\tzlog.Debug(\"The asynchronous result has not been set when registering the callback function.\")\n\t\t\treturn\n\t\t}\n\n\t\tuser := returnedObj.(*db_model.UserModel)\n\n\t\tuserLoginRsp := &msg_struct.MsgLoginResponse{\n\t\t\tUserId:    user.UserId,\n\t\t\tUserName:  user.Name,\n\t\t\tErrorCode: 0,\n\t\t}\n\n\t\tmarshalData, marErr := json.Marshal(userLoginRsp)\n\t\tif marErr != nil {\n\t\t\tzlog.Error(\"LoginRouter marErr\", marErr.Error())\n\t\t\treturn\n\t\t}\n\n\t\t// Send response to the client\n\t\tconn := request.GetConnection()\n\t\tif sendErr := conn.SendMsg(1, marshalData); sendErr != nil {\n\t\t\tzlog.Error(\"LoginRouter sendErr\", sendErr.Error())\n\t\t\treturn\n\t\t}\n\t\tzlog.Debug(\"OnComplete OUT===>333\")\n\n\t\t// Test actively throwing an exception (测试主动异常)\n\t\t/*\n\t\t\ta := 0\n\t\t\tb := 1\n\t\t\tc := b / a\n\t\t\tfmt.Println(c)\n\t\t*/\n\t})\n\n\t// Test: The original thread is blocked for 3 seconds, and the callback function is executed in the original thread,\n\t//       so it will be executed after 3 seconds\n\t// 测试：原来所属的线程阻塞3秒，回调函数因为是回到原来所属的线程里执行的，所以一定在3秒后执行.\n\ttime.Sleep(time.Second * 3)\n\n\tzlog.Debug(\"AsyncOpRouter Handle OUT ===>111\")\n}\n"
  },
  {
    "path": "examples/zinx_async_op/server/server.go",
    "content": "package main\n\nimport (\n\t\"github.com/aceld/zinx/examples/zinx_async_op/router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc OnConnectionAdd(conn ziface.IConnection) {\n\tzlog.Debug(\"zinx_async_op OnConnectionAdd ===>\")\n}\n\nfunc OnConnectionLost(conn ziface.IConnection) {\n\tzlog.Debug(\"zinx_async_op OnConnectionLost ===>\")\n}\n\nfunc main() {\n\ts := znet.NewServer()\n\n\ts.SetOnConnStart(OnConnectionAdd)\n\ts.SetOnConnStop(OnConnectionLost)\n\n\ts.AddRouter(1, &router.LoginRouter{})\n\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_client/Makefile",
    "content": "PROJECT_NAME:=zinx_client\nVERSION:=v1\n\n\n.PHONY: image run build clean\n\nbuild:\n\tbash build.sh ${PROJECT_NAME}\n\nimage:\n\tdocker build -t ${PROJECT_NAME}:${VERSION} .\n\nrun:\n\tdocker run  -itd \\\n\t-p 8999:8999 \\\n\t${PROJECT_NAME}:${VERSION}\n\n\nclean:\n\trm -rf ${PROJECT_NAME} \n\n"
  },
  {
    "path": "examples/zinx_client/build.sh",
    "content": "#!/bin/bash\n\nset -e\n\nAPP_NAME=$1\nAPP_VERSION=v$(cat version)\nBUILD_VERSION=$(git log -1 --oneline)\nBUILD_TIME=$(date \"+%FT%T%z\")\nGIT_REVISION=$(git rev-parse --short HEAD)\nGIT_BRANCH=$(git name-rev --name-only HEAD)\nGO_VERSION=$(go version)\n\n\ngo build -ldflags \" \\\n\t-X 'main.AppName=${APP_NAME}' \t\t\t\\\n\t-X 'main.AppVersion=${APP_VERSION}'     \\\n\t-X 'main.BuildVersion=${BUILD_VERSION//\\'/_}' \\\n\t-X 'main.BuildTime=${BUILD_TIME}'       \\\n\t-X 'main.GitRevision=${GIT_REVISION}'   \\\n\t-X 'main.GitBranch=${GIT_BRANCH}'       \\\n\t-X 'main.GoVersion=${GO_VERSION}'       \\\n\t\" -o $APP_NAME\n"
  },
  {
    "path": "examples/zinx_client/c_router/hello.go",
    "content": "package c_router\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype HelloRouter struct {\n\tznet.BaseRouter\n}\n\n// HelloZinxRouter Handle\nfunc (this *HelloRouter) Handle(request ziface.IRequest) {\n\tzlog.Debug(\"Call HelloZinxRouter Handle\")\n\n\tzlog.Debug(\"recv from server : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n}\n"
  },
  {
    "path": "examples/zinx_client/c_router/ping.go",
    "content": "package c_router\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// Ping test custom routing.\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Ping Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tzlog.Debug(\"Call PingRouter Handle\")\n\tzlog.Debug(\"recv from server : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\tif err := request.GetConnection().SendBuffMsg(1, []byte(\"Hello[from client]\")); err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_client/main.go",
    "content": "/**\n* @Author: Aceld\n* @Date: 2023/03/02\n* @Mail: danbing.at@gmail.com\n*    zinx client demo\n */\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/examples/zinx_client/c_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\n// Custom business logic of the client (客户端自定义业务)\nfunc business(conn ziface.IConnection) {\n\n\tfor {\n\t\terr := conn.SendMsg(100, []byte(\"Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tzlog.Error(err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\n// Function to execute when the connection is created (创建连接的时候执行)\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tzlog.Debug(\"DoConnectionBegin is Called ... \")\n\n\t// Set two connection properties after the connection is created (设置两个连接属性，在连接创建之后)\n\tconn.SetProperty(\"Name\", \"刘丹冰Aceld\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\tgo business(conn)\n}\n\n// Function to execute when the connection is lost (连接断开的时候执行)\nfunc DoClientConnectedLost(conn ziface.IConnection) {\n\t// Get the Name and Home properties of the connection before it is destroyed\n\t// (在连接销毁之前，查询conn的Name，Home属性)\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Name = \", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Home = \", home)\n\t}\n\n\tzlog.Debug(\"DoClientConnectedLost is Called ... \")\n}\n\nfunc main() {\n\t// Create a client handle using Zinx's Method (创建一个Client句柄，使用Zinx的方法)\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\t// Set the business logic to execute when the connection is created or lost\n\t// (添加首次建立连接时的业务)\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\tclient.SetOnConnStop(DoClientConnectedLost)\n\n\t// Register routers for the messages received from the server\n\t// (注册收到服务器消息业务路由)\n\tclient.AddRouter(2, &c_router.PingRouter{})\n\tclient.AddRouter(3, &c_router.HelloRouter{})\n\n\t// Start the client\n\tclient.Start()\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n\tclient.Stop()\n\ttime.Sleep(time.Second * 2)\n}\n"
  },
  {
    "path": "examples/zinx_client/version",
    "content": "v 1.0.0\n"
  },
  {
    "path": "examples/zinx_client_old/Makefile",
    "content": "PROJECT_NAME:=zinx_client\nVERSION:=v1\n\n\n.PHONY: image run build clean\n\nbuild:\n\tbash build.sh ${PROJECT_NAME}\n\nimage:\n\tdocker build -t ${PROJECT_NAME}:${VERSION} .\n\nrun:\n\tdocker run  -itd \\\n\t-p 8999:8999 \\\n\t${PROJECT_NAME}:${VERSION}\n\n\nclean:\n\trm -rf ${PROJECT_NAME} \n\n"
  },
  {
    "path": "examples/zinx_client_old/build.sh",
    "content": "#!/bin/bash\n\nset -e\n\nAPP_NAME=$1\nAPP_VERSION=v$(cat version)\nBUILD_VERSION=$(git log -1 --oneline)\nBUILD_TIME=$(date \"+%FT%T%z\")\nGIT_REVISION=$(git rev-parse --short HEAD)\nGIT_BRANCH=$(git name-rev --name-only HEAD)\nGO_VERSION=$(go version)\n\n\ngo build -ldflags \" \\\n\t-X 'main.AppName=${APP_NAME}' \t\t\t\\\n\t-X 'main.AppVersion=${APP_VERSION}'     \\\n\t-X 'main.BuildVersion=${BUILD_VERSION//\\'/_}' \\\n\t-X 'main.BuildTime=${BUILD_TIME}'       \\\n\t-X 'main.GitRevision=${GIT_REVISION}'   \\\n\t-X 'main.GitBranch=${GIT_BRANCH}'       \\\n\t-X 'main.GoVersion=${GO_VERSION}'       \\\n\t\" -o $APP_NAME\n"
  },
  {
    "path": "examples/zinx_client_old/c_router/hello.go",
    "content": "package c_router\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype HelloRouter struct {\n\tznet.BaseRouter\n}\n\n// HelloZinxRouter Handle\nfunc (this *HelloRouter) Handle(request ziface.IRequest) {\n\tzlog.Debug(\"Call HelloZinxRouter Handle\")\n\tzlog.Debug(\"recv from server : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n}\n"
  },
  {
    "path": "examples/zinx_client_old/c_router/ping.go",
    "content": "package c_router\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Ping Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tzlog.Debug(\"Call PingRouter Handle\")\n\n\tzlog.Debug(\"recv from server : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\tif err := request.GetConnection().SendBuffMsg(1, []byte(\"Hello[from client]\")); err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_client_old/main.go",
    "content": "/**\n* @Author: Aceld\n* @Date: 2023/03/02\n* @Mail: danbing.at@gmail.com\n*    zinx client demo\n */\npackage main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\nfunc main() {\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:8999\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\", err)\n\t\treturn\n\t}\n\n\tfor {\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(100, []byte(\"ZinxPing\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Test Router:[Ping] Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_client_old/version",
    "content": "v 1.0.0\n"
  },
  {
    "path": "examples/zinx_closecallback/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/examples/zinx_closecallback/router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// business handles the main business logic for sending ping messages\n// business 处理发送ping消息的主要业务逻辑\nfunc business(conn ziface.IConnection) {\n\tfor i := 0; i < 3; i++ {\n\t\terr := conn.SendMsg(1, []byte(fmt.Sprintf(\"Ping %d\", i+1)))\n\t\tif err != nil {\n\t\t\tfmt.Println(\"SendMsg error:\", err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n\n\t// Actively disconnect after sending is complete / 发送完成后主动断开连接\n\tfmt.Println(\"Client actively disconnects\")\n\tconn.Stop()\n}\n\n// DoClientConnectedBegin is the callback function when connection starts\n// DoClientConnectedBegin 是连接开始时的回调函数\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tfmt.Println(\"Client connection started\")\n\n\t// Set connection properties / 设置连接属性\n\tconn.SetProperty(\"StartTime\", time.Now())\n\n\t// Add close callback function - record connection statistics / 添加关闭回调函数 - 记录连接统计\n\tconn.AddCloseCallback(\"stats\", \"connection-stats\", func() {\n\t\tif startTime, err := conn.GetProperty(\"StartTime\"); err == nil {\n\t\t\tduration := time.Since(startTime.(time.Time))\n\t\t\tfmt.Printf(\"Client connection duration: %v\\n\", duration)\n\t\t}\n\t})\n\n\t// Start business processing / 启动业务处理\n\tgo business(conn)\n}\n\n// DoClientConnectedLost is the callback function when connection is lost\n// DoClientConnectedLost 是连接断开时的回调函数\nfunc DoClientConnectedLost(conn ziface.IConnection) {\n\tfmt.Println(\"Client connection lost\")\n}\n\nfunc main() {\n\t// Create client / 创建客户端\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\t// Set connection start and stop callbacks / 设置连接开始和断开的回调\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\tclient.SetOnConnStop(DoClientConnectedLost)\n\n\t// Add router / 添加路由\n\tclient.AddRouter(0, &router.PingRouter{})\n\n\t// Start client / 启动客户端\n\tfmt.Println(\"Client starting\")\n\tclient.Start()\n\n\t// Wait for interrupt signal / 等待中断信号\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\t<-c\n}\n"
  },
  {
    "path": "examples/zinx_closecallback/router/ping_router.go",
    "content": "package router\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// PingRouter handles ping messages\n// PingRouter 处理ping消息\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// PreHandle processes the request before the main handler\n// PreHandle 在主处理器之前处理请求\nfunc (r *PingRouter) PreHandle(req ziface.IRequest) {\n}\n\n// Handle processes the main ping message and sends pong response\n// Handle 处理主要的ping消息并发送pong响应\nfunc (r *PingRouter) Handle(req ziface.IRequest) {\n\t// Reply to client / 回复客户端\n\tif err := req.GetConnection().SendMsg(0, []byte(\"Pong\")); err != nil {\n\t\tfmt.Println(\"SendMsg error:\", err)\n\t}\n}\n\n// PostHandle processes the request after the main handler\n// PostHandle 在主处理器之后处理请求\nfunc (r *PingRouter) PostHandle(req ziface.IRequest) {\n}\n"
  },
  {
    "path": "examples/zinx_closecallback/server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/examples/zinx_closecallback/router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// DoConnectionBegin is the callback function when connection starts\n// DoConnectionBegin 是连接开始时的回调函数\nfunc DoConnectionBegin(conn ziface.IConnection) {\n\tfmt.Println(\"Server connection started\")\n\n\t// Set connection properties / 设置连接属性\n\tconn.SetProperty(\"StartTime\", time.Now())\n\n\t// Add close callback function - cleanup resources / 添加关闭回调函数 - 清理资源\n\tconn.AddCloseCallback(\"cleanup\", \"resources\", func() {\n\t\tfmt.Printf(\"Cleanup resources for connection %d\\n\", conn.GetConnID())\n\t})\n\n\t// Add close callback function - record statistics / 添加关闭回调函数 - 记录统计信息\n\tconn.AddCloseCallback(\"stats\", \"record\", func() {\n\t\tif startTime, err := conn.GetProperty(\"StartTime\"); err == nil {\n\t\t\tduration := time.Since(startTime.(time.Time))\n\t\t\tfmt.Printf(\"Connection %d duration: %v\\n\", conn.GetConnID(), duration)\n\t\t}\n\t})\n\n\t// Add close callback function - notify other components / 添加关闭回调函数 - 通知其他组件\n\tconn.AddCloseCallback(\"notification\", \"broadcast\", func() {\n\t\tfmt.Printf(\"Notify other components: connection %d disconnected\\n\", conn.GetConnID())\n\t})\n}\n\n// DoConnectionLost is the callback function when connection is lost\n// DoConnectionLost 是连接断开时的回调函数\nfunc DoConnectionLost(conn ziface.IConnection) {\n\tfmt.Println(\"Server connection lost\")\n}\n\nfunc main() {\n\t// Create server / 创建服务器\n\ts := znet.NewServer()\n\n\t// Set connection start and stop callbacks / 设置连接开始和断开的回调\n\ts.SetOnConnStart(DoConnectionBegin)\n\ts.SetOnConnStop(DoConnectionLost)\n\n\t// Add router / 添加路由\n\ts.AddRouter(1, &router.PingRouter{})\n\n\t// Start server / 启动服务器\n\tfmt.Println(\"Server starting\")\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_decoder/README.MD",
    "content": "# LengthFieldFrameDecoder使用详解\n\n\n>LengthFieldFrameDecoder是一个基于长度字段的解码器，比较难理解的解码器，它主要有5个核心的参数配置：\n\n>maxFrameLength：     数据包最大长度\n\n>lengthFieldOffset：  长度字段偏移量\n\n>lengthFieldLength：  长度字段所占的字节数\n\n>lengthAdjustment：   长度的调整值\n\n>initialBytesToStrip：解码后跳过的字节数\n\n\n\n\n## 示例讲解\n\n#### TLV格式协议\n\nTLV，即Tag(Type)—Length—Value，是一种简单实用的数据传输方案。在TLV的定义中，可以知道它包括三个域，分别为：标签域（Tag），长度域（Length），内容域（Value）。这里的长度域的值实际上就是内容域的长度。\n\n```\n解码前 (20 bytes)                                   解码后 (20 bytes)\n+------------+------------+-----------------+      +------------+------------+-----------------+\n|     Tag    |   Length   |     Value       |----->|     Tag    |   Length   |     Value       |\n| 0x00000001 | 0x0000000C | \"HELLO, WORLD\"  |      | 0x00000001 | 0x0000000C | \"HELLO, WORLD\"  |\n+------------+------------+-----------------+      +------------+------------+-----------------+\n```\n> Tag：   uint32类型，占4字节，Tag作为MsgId，暂定为1<br>\n> Length：uint32类型，占4字节，Length标记Value长度12(hex:0x0000000C)<br>\n> Value： 共12个字符，占12字节<br>\n\n```\n说明：\nlengthFieldOffset   = 4            (Length的字节位索引下标是4) 长度字段的偏差\nlengthFieldLength   = 4            (Length是4个byte) 长度字段占的字节数\nlengthAdjustment    = 0            (Length只表示Value长度，程序只会读取Length个字节就结束，后面没有来，故为0，若Value后面还有crc占2字节的话，那么此处就是2。若Length标记的是Tag+Length+Value总长度，那么此处是-8)\ninitialBytesToStrip = 0            (这个0表示返回完整的协议内容Tag+Length+Value，如果只想返回Value内容，去掉Tag的4字节和Length的4字节，此处就是8) 从解码帧中第一次去除的字节数\nmaxFrameLength      = 2^32 + 4 + 4 (Length为uint类型，故2^32次方表示Value最大长度，此外Tag和Length各占4字节)\n```\n\n\n#### HTLV+CRC格式协议\n\nHTLV+CRC，H头码，T功能码，L数据长度，V数据内容\n\n\n```\n\n+------+-------+---------+--------+--------+\n| 头码  | 功能码 | 数据长度 | 数据内容 | CRC校验 |\n| 1字节 | 1字节  | 1字节   | N字节   |  2字节  |\n+------+-------+---------+--------+--------+\n\n```\n\n数据示例\n\n```\n头码   功能码 数据长度      Body                         CRC\nA2      10     0E        0102030405060708091011121314 050B\n```\n\n```\n\n说明：\n   1.数据长度len是14(0E),这里的len仅仅指Body长度;\n\n   lengthFieldOffset   = 2   (len的索引下标是2，下标从0开始) 长度字段的偏差\n   lengthFieldLength   = 1   (len是1个byte) 长度字段占的字节数\n   lengthAdjustment    = 2   (len只表示Body长度，程序只会读取len个字节就结束，但是CRC还有2byte没读呢，所以为2)\n   initialBytesToStrip = 0   (这个0表示完整的协议内容，如果不想要A2，那么这里就是1) 从解码帧中第一次去除的字节数\n   maxFrameLength      = 255 + 4(起始码、功能码、CRC) (len是1个byte，所以最大长度是无符号1个byte的最大值)\n       \n```\n\n\n## 案例分析\n以下7种案例足以满足所有协议，只处理断粘包，并不能处理错包，包的完整性需要依靠协议自身定义CRC来校验\n\n#### 案例1：\n```\nlengthFieldOffset  =0 长度字段从0开始\nlengthFieldLength  =2 长度字段本身占2个字节\nlengthAdjustment   =0 需要调整0字节\ninitialBytesToStrip=0 解码后跳过0字节\n\n\n\n解码前 (14 bytes)                 解码后 (14 bytes)\n+--------+----------------+      +--------+----------------+\n| Length | Actual Content |----->| Length | Actual Content |\n| 0x000C | \"HELLO, WORLD\" |      | 0x000C | \"HELLO, WORLD\" |\n+--------+----------------+      +--------+----------------+\n```\n\n> Length为0x000C，这个是十六进制，0x000C转化十进制就是14\n\n\n#### 案例2：\n```\nlengthFieldOffset  =0 长度字段从0开始\nlengthFieldLength  =2 长度字段本身占2个字节\nlengthAdjustment   =0 需要调整0字节\ninitialBytesToStrip=2 解码后跳过2字节\n\n解码前 (14 bytes)                 解码后 (12 bytes)\n+--------+----------------+      +----------------+\n| Length | Actual Content |----->| Actual Content |\n| 0x000C | \"HELLO, WORLD\" |      | \"HELLO, WORLD\" |\n+--------+----------------+      +----------------+\n```\n>这时initialBytesToStrip字段起作用了，在解码后会将前面的2字节跳过，所以解码后就只剩余了数据部分。\n\n#### 案例3：\n```\nlengthFieldOffset  =0 长度字段从0开始\nlengthFieldLength  =2 长度字段本身占2个字节\nlengthAdjustment   =-2 需要调整 -2 字节\ninitialBytesToStrip=0 解码后跳过2字节\n\n\n解码前 (14 bytes)                 解码后 (14 bytes)\n+--------+----------------+      +--------+----------------+\n| Length | Actual Content |----->| Length | Actual Content |\n| 0x000E | \"HELLO, WORLD\" |      | 0x000E | \"HELLO, WORLD\" |\n+--------+----------------+      +--------+----------------+\n```\n\n>这时lengthAdjustment起作用了，因为长度字段的值包含了长度字段本身的2字节，\n如果要获取数据的字节数，需要加上lengthAdjustment的值，就是 14+（-2）=12，这样才算出来数据的长度。\n\n\n#### 案例4：\n\n```\nlengthFieldOffset  =2 长度字段从第2个字节开始\nlengthFieldLength  =3 长度字段本身占3个字节\nlengthAdjustment   =0 需要调整0字节\ninitialBytesToStrip=0 解码后跳过0字节\n\n\n解码前 (17 bytes)                              解码后 (17 bytes)\n+----------+----------+----------------+      +----------+----------+----------------+\n| Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |\n|  0xCAFE  | 0x00000C | \"HELLO, WORLD\" |      |  0xCAFE  | 0x00000C | \"HELLO, WORLD\" |\n+----------+----------+----------------+      +----------+----------+----------------+\n```\n>由于数据包最前面加了2个字节的Header，所以lengthFieldOffset为2，\n说明长度字段是从第2个字节开始的。然后lengthFieldLength为3，说明长度字段本身占了3个字节。\n\n\n#### 案例5：\n```\nlengthFieldOffset  =0 长度字段从第0个字节开始\nlengthFieldLength  =3 长度字段本身占3个字节\nlengthAdjustment   =2 需要调整2字节\ninitialBytesToStrip=0 解码后跳过0字节\n\n\n解码前 (17 bytes)                              解码后 (17 bytes)\n+----------+----------+----------------+      +----------+----------+----------------+\n|  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |\n| 0x00000C |  0xCAFE  | \"HELLO, WORLD\" |      | 0x00000C |  0xCAFE  | \"HELLO, WORLD\" |\n+----------+----------+----------------+      +----------+----------+----------------+\n```\n>lengthFieldOffset为0，所以长度字段从0字节开始。lengthFieldLength为3，长度总共占3字节。\n因为长度字段后面还剩余14字节的总数据，但是长度字段的值为12，只表示了数据的长度，不包含头的长度，\n所以lengthAdjustment为2，就是12+2=14，计算出Header+Content的总长度。\n\n\n#### 案例6：\n\n```\nlengthFieldOffset  =1 长度字段从第1个字节开始\nlengthFieldLength  =2 长度字段本身占2个字节\nlengthAdjustment   =1 需要调整1字节\ninitialBytesToStrip=3 解码后跳过3字节\n\n解码前 (16 bytes)                               解码后 (13 bytes)\n+------+--------+------+----------------+      +------+----------------+\n| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |\n| 0xCA | 0x000C | 0xFE | \"HELLO, WORLD\" |      | 0xFE | \"HELLO, WORLD\" |\n+------+--------+------+----------------+      +------+----------------+\n```\n>这一次将Header分为了两个1字节的部分，lengthFieldOffset为1表示长度从第1个字节开始，lengthFieldLength为2表示长度字段占2个字节。\n因为长度字段的值为12，只表示了数据的长度，所以lengthAdjustment为1，12+1=13，\n表示Header的第二部分加上数据的总长度为13。因为initialBytesToStrip为3，所以解码后跳过前3个字节。\n\n\n#### 案例7：\n```\nlengthFieldOffset  =1 长度字段从第1个字节开始\nlengthFieldLength  =2 长度字段本身占2个字节\nlengthAdjustment   =-3 需要调整 -3 字节\ninitialBytesToStrip=3 解码后跳过3字节\n\n解码前 (16 bytes)                               解码后 (13 bytes)\n+------+--------+------+----------------+      +------+----------------+\n| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |\n| 0xCA | 0x0010 | 0xFE | \"HELLO, WORLD\" |      | 0xFE | \"HELLO, WORLD\" |\n+------+--------+------+----------------+      +------+----------------+\n```\n>这一次长度字段的值为16，表示包的总长度，所以lengthAdjustment为 -3 ，16+ (-3)=13，\n表示Header的第二部分加数据部分的总长度为13字节。initialBytesToStrip为3，解码后跳过前3个字节。\n\n"
  },
  {
    "path": "examples/zinx_decoder/bili/README.MD",
    "content": "# 一款水机设备服务端模拟程序\n\n\n```shell\nHTLV+CRC，H头码，T功能码，L数据长度，V数据内容\n+------+-------+---------+--------+--------+\n| 头码  | 功能码 | 数据长度 | 数据内容 | CRC校验 |\n| 1字节 | 1字节  | 1字节   | N字节   |  2字节  |\n+------+-------+---------+--------+--------+\n\n头码   功能码 数据长度      Body                         CRC\nA2    10    0E           0102030405060708091011121314 050B\n\n\n说明：\n  1.数据长度len是14(0E),这里的len仅仅指Body长度;\n   \n   lengthFieldOffset   = 2   (len的索引下标是2，下标从0开始) 长度字段的偏差\n   lengthFieldLength   = 1   (len是1个byte) 长度字段占的字节数\n   lengthAdjustment    = 2   (len只表示Body长度，程序只会读取len个字节就结束，但是CRC还有2byte没读呢，所以为2)\n   initialBytesToStrip = 0   (这个0表示完整的协议内容，如果不想要A2，那么这里就是1) 从解码帧中第一次去除的字节数\n   maxFrameLength      = 255 + 4(起始码、功能码、CRC) (len是1个byte，所以最大长度是无符号1个byte的最大值)\n```\n"
  },
  {
    "path": "examples/zinx_decoder/bili/main.go",
    "content": "package main\n\nimport (\n\t\"github.com/aceld/zinx/examples/zinx_decoder/bili/router\"\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc DoConnectionBegin(conn ziface.IConnection) {\n}\n\nfunc DoConnectionLost(conn ziface.IConnection) {\n}\n\nfunc main() {\n\tserver := znet.NewServer(func(s *znet.Server) {\n\t\ts.Port = 9090\n\t\t/*\n\t\t\ts.LengthField = ziface.LengthField{\n\t\t\t\tMaxFrameLength:      math.MaxUint8 + 4,\n\t\t\t\tLengthFieldOffset:   2,\n\t\t\t\tLengthFieldLength:   1,\n\t\t\t\tLengthAdjustment:    2,\n\t\t\t\tInitialBytesToStrip: 0,\n\t\t\t}\n\t\t*/\n\t})\n\tserver.SetOnConnStart(DoConnectionBegin)\n\tserver.SetOnConnStop(DoConnectionLost)\n\tserver.AddInterceptor(zdecoder.NewHTLVCRCDecoder())\n\tserver.AddRouter(0x10, &router.Data0x10Router{})\n\tserver.AddRouter(0x13, &router.Data0x13Router{})\n\tserver.AddRouter(0x14, &router.Data0x14Router{})\n\tserver.AddRouter(0x15, &router.Data0x15Router{})\n\tserver.AddRouter(0x16, &router.Data0x16Router{})\n\tserver.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_decoder/bili/router/bili0x10router.go",
    "content": "package router\n\nimport (\n\t\"bytes\"\n\t\"encoding/hex\"\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype Data0x10Router struct {\n\tznet.BaseRouter\n}\n\nfunc (this *Data0x10Router) Handle(request ziface.IRequest) {\n\tzlog.Ins().DebugF(\"Data0x10Router Handle %s \\n\", hex.EncodeToString(request.GetMessage().GetData()))\n\t_response := request.GetResponse()\n\tif _response != nil {\n\t\tswitch _response.(type) {\n\t\tcase zdecoder.HtlvCrcDecoder:\n\t\t\t_data := _response.(zdecoder.HtlvCrcDecoder)\n\t\t\t//zlog.Ins().DebugF(\"Data0x10Router %v \\n\", _data)\n\t\t\tbuffer := pack10(_data)\n\t\t\trequest.GetConnection().Send(buffer)\n\t\t}\n\t}\n}\n\n// 头码   功能码 数据长度      Body                         CRC\n// A2      10     0E        0102030405060708091011121314 050B\n// HeadCode FuncCode DataLen   Body                         CRC\n// A2       10       0E        0102030405060708091011121314 050B\nfunc pack10(_data zdecoder.HtlvCrcDecoder) []byte {\n\tbuffer := bytes.NewBuffer([]byte{})\n\tbuffer.WriteByte(0xA1)\n\tbuffer.WriteByte(_data.Funcode)\n\tbuffer.WriteByte(0x1E)\n\t//3~9:唯一设备码\t将IMEI码转换为16进制\n\t//3~9: Unique device code: Convert the IMEI code to hexadecimal\n\tbuffer.Write(_data.Body[:7])\n\t//10~14：园区代码\t后台根据幼儿园生成的唯一代码\n\t//10~14: Park code: A unique code generated by the background according to the kindergarten\n\tbuffer.Write([]byte{10, 11, 12, 13, 14})\n\t//15~18：时间戳\t实际当前北京时间的时间戳，转换为16进制\n\t//15~18: Timestamp: The actual current Beijing time stamp, converted to hexadecimal\n\tbuffer.Write([]byte{15, 16, 17, 18})\n\t//19：RFID模块工作模式\t0x01-离线工作模式（默认工作模式）0x02-在线工作模式\n\t//19: RFID module working mode: 0x01-offline working mode (default working mode) 0x02-online working mode\n\tbuffer.WriteByte(0x02)\n\t//20~27：通讯密匙\t预留，全填0x00\n\t//20~27: Communication key: Reserved, fill with 0x00\n\tbuffer.Write([]byte{20, 21, 22, 23, 24, 25, 26, 27})\n\t//28：出水方式\t0x00-放杯出水，取杯停止出水 0x01-刷一下卡出水，再刷停止出水【数联默认】\n\t//28: Water outlet mode: 0x00-put cup and stop water, 0x01-swipe card for water, swipe card again to stop water [Suliandu default]\n\tbuffer.WriteByte(0x01)\n\t//29~32：预留\t全填0x00\n\t//29~32: Reserved: Fill with 0x00\n\tbuffer.Write([]byte{29, 30, 31, 32})\n\tcrc := zdecoder.GetCrC(buffer.Bytes())\n\tbuffer.Write(crc)\n\treturn buffer.Bytes()\n\n}\n"
  },
  {
    "path": "examples/zinx_decoder/bili/router/bili0x13router.go",
    "content": "package router\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype Data0x13Router struct {\n\tznet.BaseRouter\n}\n\nfunc (this *Data0x13Router) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Data0x13Router Handle\", request.GetMessage().GetData())\n\t_response := request.GetResponse()\n\tif _response != nil {\n\t\tswitch _response.(type) {\n\t\tcase zdecoder.HtlvCrcDecoder:\n\t\t\t_data := _response.(zdecoder.HtlvCrcDecoder)\n\t\t\tfmt.Println(\"Data0x13Router\", _data)\n\t\t\tbuffer := pack13(_data)\n\t\t\trequest.GetConnection().Send(buffer)\n\t\t}\n\t}\n}\n\n// 头码   功能码 数据长度       Body                         CRC\n// A2      10     0E         0102030405060708091011121314 050B\n// HeadCode FuncCode DataLen Body                         CRC\n// A2       10       0E      0102030405060708091011121314 050B\nfunc pack13(_data zdecoder.HtlvCrcDecoder) []byte {\n\tbuffer := bytes.NewBuffer([]byte{})\n\tbuffer.WriteByte(0xA1)\n\tbuffer.WriteByte(_data.Funcode)\n\tbuffer.WriteByte(0x0E)\n\t//3~9:3~6：用户卡号\t用户IC卡卡号\n\t//3~9:3~6: User card number: User IC card number\n\tbuffer.Write(_data.Body[:4])\n\t//7：卡状态：\t0x00-未绑定（如服务器未查询到该IC卡时）\n\t//0x01-已绑定\n\t//0x02-解除绑定（如服务器查询到该IC卡解除绑定时下发）\n\t//7: Card Status: 0x00-Unbound (when the card is not found in the server)\n\t//0x01-Bound\n\t//0x02-Unbound (when the server sends a command to unbind the card)\n\tbuffer.WriteByte(0x01)\n\t//8~9：剩余使用天数\t该用户的剩余流量天数\n\t//8~9: Remaining usage days: the remaining number of days of usage for the user's data plan.\n\tbuffer.Write([]byte{8, 9})\n\t//10~11：每次最大出水量\t单位mL，实际出水量\n\t//10~11: Maximum dispensing amount per use, unit: mL, actual dispensing amount.\n\tbuffer.Write([]byte{10, 11})\n\t//12~16：预留\t全填0x00\n\t//12~16: Reserved, all filled with 0x00.\n\tbuffer.Write([]byte{12, 13, 14, 15, 16})\n\tcrc := zdecoder.GetCrC(buffer.Bytes())\n\tbuffer.Write(crc)\n\treturn buffer.Bytes()\n\n}\n"
  },
  {
    "path": "examples/zinx_decoder/bili/router/bili0x14router.go",
    "content": "package router\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype Data0x14Router struct {\n\tznet.BaseRouter\n}\n\nfunc (this *Data0x14Router) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Data0x14Router Handle\", request.GetMessage().GetData())\n\t_response := request.GetResponse()\n\tif _response != nil {\n\t\tswitch _response.(type) {\n\t\tcase zdecoder.HtlvCrcDecoder:\n\t\t\t_data := _response.(zdecoder.HtlvCrcDecoder)\n\t\t\tfmt.Println(\"Data0x14Router\", _data)\n\t\t\tbuffer := pack14(_data)\n\t\t\trequest.GetConnection().Send(buffer)\n\t\t}\n\t}\n}\n\n// 头码   功能码 数据长度       Body                         CRC\n// A2      10     0E         0102030405060708091011121314 050B\n// HeadCode FuncCode DataLen Body                         CRC\n// A2       10       0E      0102030405060708091011121314 050B\nfunc pack14(_data zdecoder.HtlvCrcDecoder) []byte {\n\t_data.Data[0] = 0xA1\n\tbuffer := bytes.NewBuffer(_data.Data[:len(_data.Data)-2])\n\tcrc := zdecoder.GetCrC(buffer.Bytes())\n\tbuffer.Write(crc)\n\treturn buffer.Bytes()\n\n}\n"
  },
  {
    "path": "examples/zinx_decoder/bili/router/bili0x15router.go",
    "content": "package router\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype Data0x15Router struct {\n\tznet.BaseRouter\n}\n\nfunc (this *Data0x15Router) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Data0x15Router Handle\", request.GetMessage().GetData())\n\t_response := request.GetResponse()\n\tif _response != nil {\n\t\tswitch _response.(type) {\n\t\tcase zdecoder.HtlvCrcDecoder:\n\t\t\t_data := _response.(zdecoder.HtlvCrcDecoder)\n\t\t\tfmt.Println(\"Data0x15Router\", _data)\n\t\t\tbuffer := pack15(_data)\n\t\t\trequest.GetConnection().Send(buffer)\n\t\t}\n\t}\n}\n\n// 头码   功能码 数据长度      Body                         CRC\n// A2      10     0E        0102030405060708091011121314 050B\n// HeaderCode FunctionCode DataLength Body                         CRC\n// A2         10           0E         0102030405060708091011121314 050B\nfunc pack15(_data zdecoder.HtlvCrcDecoder) []byte {\n\tbuffer := bytes.NewBuffer([]byte{})\n\tbuffer.WriteByte(0xA1)\n\tbuffer.WriteByte(_data.Funcode)\n\tbuffer.WriteByte(0x26)\n\t//3~9: Device Code Convert IMEI code to hex (3~9：设备代码, 将IMEI码转换为16进制)\n\tbuffer.Write(_data.Body[:7])\n\t//10: Model Code A8 (Hot Type Kindergarten Machine) , 10: 机型代码\tA8（即热式幼儿园机）\n\tbuffer.WriteByte(0xA8)\n\t//11：主机状态1\n\t//Bit0：0-待机中，1-运行中\n\t//Bit1：0-非智控，1-智控【本设备按智控】\n\t//Bit2：0-不能饮用，1-可以饮用\n\t//Bit3：0-无人用水，1-有人用水\n\t//Bit4：0-上电进水中，1-正常工作中\n\t//Bit5：0-消毒未启动，1-消毒进行中\n\t//Bit6：0-低压开关断开（无水），1-低压开关接通（有水）\n\t//Bit7：0-主机不带RO，1-主机带RO\n\t// 11: Host Status 1\n\t// Bit0: 0-Standby, 1-Running\n\t// Bit1: 0-Non-intelligent control, 1-Intelligent control [This device follows intelligent control]\n\t// Bit2: 0-Cannot be drunk, 1-Can be drunk\n\t// Bit3: 0-No one uses water, 1-Someone uses water\n\t// Bit4: 0-Entering water at power-on, 1-Working normally\n\t// Bit5: 0-Disinfection not started, 1-Disinfection in progress\n\t// Bit6: 0-Low voltage switch off (no water), 1-Low voltage switch on (with water)\n\t// Bit7: 0-Master without RO, 1-Master with RO\n\tbuffer.WriteByte(0x01)\n\t//12：主机状态2\tBit0：0－RO机不允许启动水泵，1－RO机允许启动水泵\n\t//Bit1：0－开水无人用，1－开水有人用\n\t//Bit2：0－高压开关断开（满水），1－高压开关接通（缺水）\n\t//Bit3：0－冰水无人用，1－冰水有人用【本设备无意义】\n\t//Bit4：0－无漏水信号，1－有漏水信号\n\t//Bit5：0－紫外灯未启动，1－紫外线灯杀菌中\n\t//Bit6：预留\n\t//Bit7：预留\n\t// 12: Host Status 2\n\t// Bit0: 0-RO machine does not allow pump to start, 1-RO machine allows pump to start\n\t// Bit1: 0-No one uses hot water, 1-Someone uses hot water\n\t// Bit2: 0-High-voltage switch off (full water), 1-High-voltage switch on (lack of water)\n\t// Bit3: 0-No one uses ice water, 1-Someone uses ice water [meaningless for this device]\n\t// Bit4: 0-No water leakage signal, 1-Water leakage signal\n\t// Bit5: 0-Ultraviolet lamp not started, 1-Ultraviolet lamp sterilizing\n\t// Bit6: Reserved\n\t// Bit7: Reserved\n\tbuffer.WriteByte(0x01)\n\t//13：水位状态\n\t//（即热水位）\tBit0：低水位，0-代表无水，1-代表有水【本设备低水位有水即表示水满】\n\t//Bit1：中水位，0-代表无水，1-代表有水【本设备无意义】\n\t//Bit2：高水位，0-代表无水，1-代表有水【本设备无意义】\n\t//Bit3：溢出水位，0-代表无水，1-代表有水【本设备无意义】\n\t//Bit4：预留\n\t//Bit5：预留\n\t//Bit6：预留\n\t//Bit7：预留\n\t// 13: Water level status (i.e. hot water level)\n\t// Bit0: Low water level, 0-represents no water, 1-represents water [Low water level with water indicates full water for this device]\n\t// Bit1: Medium water level, 0-represents no water, 1-represents water [Meaningless for this device]\n\t// Bit2: High water level, 0-represents no water, 1-represents water [Meaningless for this device]\n\t// Bit3: Overflow water level, 0-represents no water, 1-represents water [Meaningless for this device]\n\t// Bit4: Reserved\n\t// Bit5: Reserved\n\t// Bit6: Reserved\n\t// Bit7: Reserved\n\tbuffer.WriteByte(0x01)\n\tbuffer.WriteByte(0x01)\n\t// 14: Hot water temperature 0℃100℃, indicating the current hot water temperature\n\t//14：开水温度\t0℃~100℃，表示当前开水温度\n\tbuffer.WriteByte(0x1A)\n\t// 15: Stop heating temperature of current system 30~98℃, actual value\n\t// 15：当前系统的停止加热温度\t30~98℃，实际数值\n\tbuffer.WriteByte(0x27)\n\t//16：负载状态\n\t//Bit0：加热，0－未加热，1－加热中\n\t//Bit1：进水，0－未进水，1－进水中\n\t//Bit2：换水或消毒，0－未换水，1－换水或消毒\n\t//Bit3：冲洗，0－未冲洗，1－冲洗中\n\t//Bit4：增压泵和RO进水阀，0－未启动，1－启动增压泵和RO进水阀\n\t//Bit5：RO进水阀2，0－未启动，1－启动中【本设备无意义】\n\t//Bit6：开水出水阀1，0－未启动，1－启动中\n\t//Bit7：净化水出水阀1，0－未启动，1－启动中【本设备无意义】\n\t// 16: Load status\n\t// Bit0: Heating, 0 - Not heating, 1 - Heating\n\t// Bit1: Inlet water, 0 - No inlet water, 1 - Inlet water\n\t// Bit2: Water change or disinfection, 0 - No change, 1 - Water change or disinfection\n\t// Bit3: Flushing, 0 - Not flushing, 1 - Flushing\n\t// Bit4: Booster pump and RO inlet valve, 0 - Not started, 1 - Started booster pump and RO inlet valve\n\t// Bit5: RO inlet valve 2, 0 - Not started, 1 - Started 【Irrelevant to this device】\n\t// Bit6: Hot water outlet valve 1, 0 - Not started, 1 - Started\n\t// Bit7: Purified water outlet valve 1, 0 - Not started, 1 - Started 【Irrelevant to this device】\n\tbuffer.WriteByte(0x01)\n\t//17：负载状态2\t预留，填0x00\n\t//17: Load State 2, reserved, fill with 0x00.\n\tbuffer.WriteByte(0x00)\n\t//18：故障状态\n\t//Bit0：故障1，0－无故障，1－有故障\n\t//Bit1：故障2，0－无故障，1－有故障\n\t//Bit2：障保3，0－无故障，1－有故障\n\t//Bit3：故障4 ，0－无故障，1－有故障\n\t//Bit4：故障5 ，0－无故障，1－有故障\n\t//Bit5：故障6，0－无故障，1－有故障\n\t//Bit6：故障7，0－无故障，1－有故障\n\t//Bit7：故障8，0－无故障，1－有故障\n\t//18: Fault status\n\t//Bit0: Fault 1, 0-no fault, 1-fault\n\t//Bit1: Fault 2, 0-no fault, 1-fault\n\t//Bit2: Fault 3, 0-no fault, 1-fault\n\t//Bit3: Fault 4, 0-no fault, 1-fault\n\t//Bit4: Fault 5, 0-no fault, 1-fault\n\t//Bit5: Fault 6, 0-no fault, 1-fault\n\t//Bit6: Fault 7, 0-no fault, 1-fault\n\t//Bit7: Fault 8, 0-no fault, 1-fault\n\tbuffer.WriteByte(0x00)\n\t//19：故障状态2\n\t//Bit0：故障A，0－无故障，1－有故障\n\t//Bit1：故障B，0－无故障，1－有故障\n\t//Bit2：障保C，0－无故障，1－有故障\n\t//Bit3：故障9，0－无故障，1－有故障\n\t//Bit4：故障D，0－无故障，1－有故障\n\t//Bit5：故障E，0－无故障，1－有故障\n\t//Bit6：预留\n\t//Bit7：预留\n\t//19: Fault status 2\n\t//Bit0: Fault A, 0 - no fault, 1 - there is a fault\n\t//Bit1: Fault B, 0 - no fault, 1 - there is a fault\n\t//Bit2: Fault C, 0 - no fault, 1 - there is a fault\n\t//Bit3: Fault 9, 0 - no fault, 1 - there is a fault\n\t//Bit4: Fault D, 0 - no fault, 1 - there is a fault\n\t//Bit5: Fault E, 0 - no fault, 1 - there is a fault\n\t//Bit6: Reserved\n\t//Bit7: Reserved\n\tbuffer.WriteByte(0x00)\n\t//20：主板软件版本\t实际数值1~255\n\t//20: Mainboard software version, actual value 1~255.\n\tbuffer.WriteByte(0x01)\n\t//21：水位状态2\n\t//Bit0：纯水箱低水位，0-代表无水，1-代表有水【本设备无意义】\n\t//Bit1：纯水箱中水位，0-代表无水，1-代表有水【本设备无意义】\n\t//Bit2：纯水箱高水位，0-代表无水，1-代表有水【本设备无意义】\n\t//Bit3：保温箱低水位，0-代表无水，1-代表有水\n\t//Bit4：保温箱中水位，0-代表无水，1-代表有水【本设备无意义】\n\t//Bit5：保温箱高水位，0-代表无水，1-代表有水\n\t//Bit6：保温箱溢水位，0-代表无水，1-代表有水\n\t//Bit7：预留\n\t//21: Water level status 2\n\t//Bit0: Low water level in pure water tank, 0 - no water, 1 - water present (not applicable for this device)\n\t//Bit1: Medium water level in pure water tank, 0 - no water, 1 - water present (not applicable for this device)\n\t//Bit2: High water level in pure water tank, 0 - no water, 1 - water present (not applicable for this device)\n\t//Bit3: Low water level in insulation box, 0 - no water, 1 - water present\n\t//Bit4: Medium water level in insulation box, 0 - no water, 1 - water present (not applicable for this device)\n\t//Bit5: High water level in insulation box, 0 - no water, 1 - water present\n\t//Bit6: Overflow water level in insulation box, 0 - no water, 1 - water present\n\t//Bit7: Reserved\n\tbuffer.WriteByte(0x01)\n\t//22：温开水温度\t0~100℃\n\t//22: Hot water temperature in Celsius degree, range from 0 to 100℃.\n\tbuffer.WriteByte(0x30)\n\t//23~24：剩余滤芯寿命\t单位：小时，实际数值\n\t//23~24: Remaining filter life, unit: hours, actual numerical value\n\tbuffer.Write([]byte{23, 24})\n\t//25~26：剩余紫外线灯寿命\n\t//25~26: Remaining UV lamp life, measured in hours, actual numerical value.\n\tbuffer.Write([]byte{25, 26})\n\t//27~28：源水TDS值\t0x0000－无此功能 ,实际数值，单位，ppm\n\t//27~28: Source water TDS value, 0x0000 - no such function, actual value in ppm\n\tbuffer.Write([]byte{27, 28})\n\t//29：净水TDS值\t0x00－无此功能, 实际数值，单位，ppm\n\t//29: TDS value of purified water 0x00 - No such function, actual value, unit: ppm\n\tbuffer.WriteByte(0x00)\n\t//30~33：耗电量\t0xFFFFFFFF－无此功能, 实际数值，高位在前，低位在后，单位wh\n\t//30~33: Power consumption, 0xFFFFFFFF - not supported, actual value, high byte first, low byte last, unit is Wh.\n\tbuffer.Write([]byte{30, 31, 32, 33})\n\t//34：信号强度\t0x01~0x28\n\t//0x01~0x0A对应:-81~-90dbm=极差\n\t//0x0B~0x14对应：-71~-80dbm=差\n\t//0x15~0x1E对应-61~-70dbm=好\n\t//0x1F~0x28对应：-41以上~-50dbm=良好\n\t//34: Signal strength 0x01~0x28\n\t//0x010x0A correspond to -81~-90dbm=poor\n\t//0x0B0x14 correspond to -71-80dbm=weak\n\t//0x150x1E correspond to -61-70dbm=good\n\t//0x1F0x28 correspond to -41 and above-50dbm=excellent\n\tbuffer.WriteByte(0x30)\n\t//35~40：预留\t全填0x00\n\t//35~40: Reserved. All filled with 0x00.\n\tbuffer.Write([]byte{0x00, 0x00})\n\tcrc := zdecoder.GetCrC(buffer.Bytes())\n\tbuffer.Write(crc)\n\treturn buffer.Bytes()\n\n}\n"
  },
  {
    "path": "examples/zinx_decoder/bili/router/bili0x16router.go",
    "content": "package router\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype Data0x16Router struct {\n\tznet.BaseRouter\n}\n\nfunc (this *Data0x16Router) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Data0x16Router Handle\", request.GetMessage().GetData())\n\t_response := request.GetResponse()\n\tif _response != nil {\n\t\tswitch _response.(type) {\n\t\tcase zdecoder.HtlvCrcDecoder:\n\t\t\t_data := _response.(zdecoder.HtlvCrcDecoder)\n\t\t\tfmt.Println(\"Data0x16Router\", _data)\n\t\t\tbuffer := pack16(_data)\n\t\t\trequest.GetConnection().Send(buffer)\n\t\t}\n\t}\n}\n\n// Pack a complete 0x16 protocol data\n// Format:\n// HeadCode FuncCode DataLen Body                         CRC\n// A2       10       0E      0102030405060708091011121314 050B\n// 头码      功能码    数据长度  Body                         CRC\n// A2       10        0E     0102030405060708091011121314 050B\nfunc pack16(_data zdecoder.HtlvCrcDecoder) []byte {\n\t_data.Data[0] = 0xA1\n\tbuffer := bytes.NewBuffer(_data.Data[:len(_data.Data)-2])\n\tcrc := zdecoder.GetCrC(buffer.Bytes())\n\tbuffer.Write(crc)\n\treturn buffer.Bytes()\n\n}\n"
  },
  {
    "path": "examples/zinx_decoder/client/client.go",
    "content": "package main\n\nimport (\n\t\"encoding/binary\"\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// Use this method to generate mock data. (使用该方法生成模拟数据)\nfunc getTLVPackData() []byte {\n\tmsgID := 1\n\ttag := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(tag, uint32(msgID))\n\n\tstr := \"HELLO, WORLD\"\n\tvar value = []byte(str)\n\n\tlength := make([]byte, 4)\n\tbinary.BigEndian.PutUint32(length, uint32(len(value)))\n\n\t_data := make([]byte, 0)\n\t_data = append(_data, tag...)\n\t_data = append(_data, length...)\n\t_data = append(_data, value...)\n\tfmt.Println(\"--->\", len(_data), hex.EncodeToString(_data))\n\treturn _data\n}\n\nfunc getTLVData(index int) []byte {\n\t// Get a complete TLV simulated data package by using the getTLVPackData() method: 000000010000000c48454c4c4f2c20574f524c44.\n\t// (通过 getTLVPackData()方法，获得一段完整的TLV模拟数据包:000000010000000c48454c4c4f2c20574f524c44)\n\ttlvPackData := []string{\n\t\t\"000000010000000c48454c4c4f2c20574f524c44000000010000000c\",                         //one and a half packets(一包半)\n\t\t\"48454c4c4f2c20574f524c44\",                                                         //the remaining half of the packet(剩下的半包)\n\t\t\"000000010000000c48454c4c4f2c20574f524c44000000010000000c48454c4c4f2c20574f524c44\", //two packets(两包)\n\t}\n\n\t// The simulation sequence here is: two complete packages, one half package, and the remaining half package.\n\t// (此处模拟顺序如:两包一包半剩下的半包)\n\tindex = index % 3\n\tif index == 0 {\n\t\tfmt.Println(\"Simulation-Data - Sticking (粘包)\")\n\t\tindex = 2 //Simulate a situation of packet sticking, where two packets of data are combined together. (模拟粘包情况，两包数据一起)\n\t} else {\n\t\t// Simulate the situation of message fragmentation, with one and a half packages and the remaining half package\n\t\t// (模拟断包情况，一包半+剩下的半包)\n\t\tindex = index / 2 % 2\n\t\tfmt.Println(\"Simulation-Data - Fragmentation(断包)\")\n\t}\n\tarr, _ := hex.DecodeString(tlvPackData[index])\n\treturn arr\n}\n\nfunc getHTLVCRCData(index int) []byte {\n\t// A complete HTLVCRC simulation data packet: A2100E0102030405060708091011121314050B\n\t// (一段完整的HTLVCRC模拟数据包:A2100E0102030405060708091011121314050B)\n\ttlvPackData := []string{\n\t\t\"a21018686574000004d30000000000000000000000000000000000e7a2a2130e686574000004d300000001\", //one and a half packets(一包半)\n\t\t\"00000040c3\", //剩下的半包\n\t\t\"a21018686574000004d30000000000000000000000000000000000e7a2a2130e686574000004d30000000100000040c3\", //two packets(两包)\n\t}\n\n\t// Simulated sequence here: two complete packages, one half package, and the remaining half package.\n\t// (此处模拟顺序如:两包一包半剩下的半包)\n\tindex = index % 3\n\tif index == 0 {\n\t\tfmt.Println(\"Simulation-Data - Sticking (粘包)\")\n\t\tindex = 2 //Simulate a situation of packet sticking, where two packets of data are combined together. (模拟粘包情况，两包数据一起)\n\t} else {\n\t\t// Simulate the situation of message fragmentation, with one and a half packages and the remaining half package\n\t\t// (模拟断包情况，一包半+剩下的半包)\n\t\tindex = index / 2 % 2\n\t\tfmt.Println(\"Simulation-Data - Fragmentation(断包)\")\n\t}\n\tarr, _ := hex.DecodeString(tlvPackData[index])\n\treturn arr\n}\n\nfunc business(conn ziface.IConnection) {\n\tvar i int\n\tfor {\n\t\t//buffer := getTLVData(i)\n\t\tbuffer := getHTLVCRCData(i)\n\t\tconn.Send(buffer)\n\t\ti++\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tzlog.Debug(\"DoConnectionBegin is Called ... \")\n\tgo business(conn)\n}\n\nfunc main() {\n\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\n\tclient.Start()\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n\n}\n"
  },
  {
    "path": "examples/zinx_decoder/router/htlvcrcbusinessrouter.go",
    "content": "package router\n\nimport (\n\t\"encoding/hex\"\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype HtlvCrcBusinessRouter struct {\n\tznet.BaseRouter\n}\n\nfunc (this *HtlvCrcBusinessRouter) Handle(request ziface.IRequest) {\n\n\t//MsgID\n\tmsgID := request.GetMessage().GetMsgID()\n\tzlog.Ins().DebugF(\"Call HtlvCrcBusinessRouter Handle %d %s\\n\", msgID, hex.EncodeToString(request.GetMessage().GetData()))\n\n\tresp := request.GetResponse()\n\tif resp == nil {\n\t\treturn\n\t}\n\n\ttlvData := resp.(zdecoder.HtlvCrcDecoder)\n\n\tzlog.Ins().DebugF(\"do msgid=0x10 data business %+v\\n\", tlvData)\n}\n"
  },
  {
    "path": "examples/zinx_decoder/router/tlvbusinessrouter.go",
    "content": "package router\n\nimport (\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype TLVBusinessRouter struct {\n\tznet.BaseRouter\n}\n\nfunc (this *TLVBusinessRouter) Handle(request ziface.IRequest) {\n\n\tmsgID := request.GetMessage().GetMsgID()\n\tzlog.Ins().DebugF(\"Call TLVRouter Handle %d %+v\\n\", msgID, request.GetMessage().GetData())\n\n\tresp := request.GetResponse()\n\tif resp == nil {\n\t\treturn\n\t}\n\n\ttlvData := resp.(zdecoder.TLVDecoder)\n\tzlog.Ins().DebugF(\"do msgid=0x00000001 data business %+v\\n\", tlvData)\n}\n"
  },
  {
    "path": "examples/zinx_decoder/server/server.go",
    "content": "package main\n\nimport (\n\t\"github.com/aceld/zinx/examples/zinx_decoder/router\"\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc DoConnectionBegin(conn ziface.IConnection) {\n\tzlog.Ins().InfoF(\"DoConnectionBegin is Called ...\")\n}\n\nfunc DoConnectionLost(conn ziface.IConnection) {\n\tzlog.Ins().InfoF(\"Conn is Lost\")\n}\n\nfunc main() {\n\t//zlog.SetLogFile(\"./logs\", \"app.log\")\n\ts := znet.NewServer()\n\n\ts.SetOnConnStart(DoConnectionBegin)\n\ts.SetOnConnStop(DoConnectionLost)\n\n\t// TLV protocol corresponding to business function\n\t// TLV协议对应业务功能\n\ts.AddRouter(0x00000001, &router.TLVBusinessRouter{})\n\n\t// Process HTLVCRC protocol data\n\t// 处理HTLVCRC协议数据\n\ts.SetDecoder(zdecoder.NewHTLVCRCDecoder())\n\n\t// TLV protocol corresponding to business function, because the funcode field in client.go is 0x10\n\t// TLV协议对应业务功能，因为client.go中模拟数据funcode字段为0x10\n\ts.AddRouter(0x10, &router.HtlvCrcBusinessRouter{})\n\n\t// TLV protocol corresponding to business function, because the funcode field in client.go is 0x13\n\t// TLV协议对应业务功能，因为client.go中模拟数据funcode字段为0x13\n\ts.AddRouter(0x13, &router.HtlvCrcBusinessRouter{})\n\n\t//开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_dynamic_bind/client/client.go",
    "content": "package main\n\nimport (\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nconst (\n\tPingType = 1\n\tPongType = 2\n)\n\n// ping response router\ntype PongRouter struct {\n\tznet.BaseRouter\n\tclient string\n}\n\n// Hash 工作模式下，需要等待接受到client1的pong后，才会收到client2和client3的pong\n// DynamicBind工作模式下，client2, client3 都会立马收到pong, 但client1的pong会被阻塞十秒后才收到\nfunc (p *PongRouter) Handle(request ziface.IRequest) {\n\t//read server pong data\n\tzlog.Infof(\"---------client:%s, recv from server:%s, msgId=%d, data=%s ----------\\n\",\n\t\tp.client, request.GetConnection().RemoteAddr(), request.GetMsgID(), string(request.GetData()))\n}\n\nfunc onClient1Start(conn ziface.IConnection) {\n\tzlog.Infof(\"client1 connection start, %s->%s\\n\", conn.LocalAddrString(), conn.RemoteAddrString())\n\t//send ping\n\terr := conn.SendMsg(PingType, []byte(\"Ping From Client1\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\nfunc onClient2Start(conn ziface.IConnection) {\n\tzlog.Infof(\"client2 connection start, %s->%s\\n\", conn.LocalAddrString(), conn.RemoteAddrString())\n\t//send ping\n\terr := conn.SendMsg(PingType, []byte(\"Ping From Client2\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\nfunc onClient3Start(conn ziface.IConnection) {\n\tzlog.Infof(\"client3 connection start, %s->%s\\n\", conn.LocalAddrString(), conn.RemoteAddrString())\n\t//send ping\n\terr := conn.SendMsg(PingType, []byte(\"Ping From Client3\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\nfunc main() {\n\t//Create a client client\n\tclient1 := znet.NewClient(\"127.0.0.1\", 8999)\n\tclient1.SetOnConnStart(onClient1Start)\n\tclient1.AddRouter(PongType, &PongRouter{client: \"client1\"})\n\tclient1.Start()\n\n\ttime.Sleep(time.Second)\n\n\tclient2 := znet.NewClient(\"127.0.0.1\", 8999)\n\tclient2.SetOnConnStart(onClient2Start)\n\tclient2.AddRouter(PongType, &PongRouter{client: \"client2\"})\n\tclient2.Start()\n\n\ttime.Sleep(time.Second)\n\n\tclient3 := znet.NewClient(\"127.0.0.1\", 8999)\n\tclient3.SetOnConnStart(onClient3Start)\n\tclient3.AddRouter(PongType, &PongRouter{client: \"client3\"})\n\tclient3.Start()\n\n\t//Prevent the process from exiting, waiting for an interrupt signal\n\tsignalChan := make(chan os.Signal, 1)\n\tsignal.Notify(signalChan, os.Interrupt)\n\t<-signalChan\n\tclient1.Stop()\n\tclient2.Stop()\n\tclient3.Stop()\n\n\ttime.Sleep(time.Second)\n}\n"
  },
  {
    "path": "examples/zinx_dynamic_bind/server/conf/zinx.json",
    "content": "{\n    \"Name\":\"zinx server DynamicBind Mode Demo\",\n    \"Host\":\"127.0.0.1\",\n    \"TCPPort\":8999,\n    \"MaxConn\":12000,\n    \"WorkerPoolSize\":1,\n    \"MaxWorkerTaskLen\":50,\n    \"WorkerMode\":\"DynamicBind\"\n  }"
  },
  {
    "path": "examples/zinx_dynamic_bind/server/server.go",
    "content": "package main\n\nimport (\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc OnConnectionAdd(conn ziface.IConnection) {\n\tzlog.Debug(\"OnConnectionAdd:\", conn.GetConnection().RemoteAddr())\n}\n\nfunc OnConnectionLost(conn ziface.IConnection) {\n\tzlog.Debug(\"OnConnectionLost:\", conn.GetConnection().RemoteAddr())\n}\n\ntype blockRouter struct {\n\tznet.BaseRouter\n}\n\nvar Block = int32(1)\n\n// 模拟阻塞操作\nfunc (r *blockRouter) Handle(request ziface.IRequest) {\n\t//read client data\n\tzlog.Infof(\"recv from client:%s, msgId=%d, data=%s\\n\", request.GetConnection().RemoteAddr(), request.GetMsgID(), string(request.GetData()))\n\n\t// 第一次处理时，模拟任务阻塞操作, Hash 模式下，后面的连接的任务得不到处理\n\t// DynamicBind 模式下，看后面的连接的任务会得到即使处理，不会因为前面连接的任务阻塞而得不到处理\n\t// 这里只模拟一次阻塞操作。\n\tif atomic.CompareAndSwapInt32(&Block, 1, 0) {\n\t\tzlog.Infof(\"blockRouter handle start, msgId=%d, remote:%v\\n\", request.GetMsgID(), request.GetConnection().RemoteAddr())\n\t\ttime.Sleep(time.Second * 10)\n\t\t//阻塞操作结束\n\t\tzlog.Infof(\"blockRouter handle end, msgId=%d, remote:%v\\n\", request.GetMsgID(), request.GetConnection().RemoteAddr())\n\t}\n\n\terr := request.GetConnection().SendMsg(2, []byte(\"pong from server\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t\treturn\n\t}\n\tzlog.Infof(\"send pong over, client:%s\\n\", request.GetConnection().RemoteAddr())\n}\n\nfunc main() {\n\ts := znet.NewServer()\n\n\ts.SetOnConnStart(OnConnectionAdd)\n\ts.SetOnConnStop(OnConnectionLost)\n\n\ts.AddRouter(1, &blockRouter{})\n\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_heartbeat/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"time\"\n)\n\n// User-defined heartbeat message processing method\n// 用户自定义的心跳检测消息处理方法\nfunc myClientHeartBeatMsg(conn ziface.IConnection) []byte {\n\treturn []byte(\"heartbeat, I am Client, I am alive\")\n}\n\n// User-defined handling method for remote connection not alive.\n// 用户自定义的远程连接不存活时的处理方法\nfunc myClientOnRemoteNotAlive(conn ziface.IConnection) {\n\tfmt.Println(\"myClientOnRemoteNotAlive is Called, connID=\", conn.GetConnID(), \"remoteAddr = \", conn.RemoteAddr())\n\t//关闭连接\n\tconn.Stop()\n}\n\n// 用户自定义的心跳检测消息处理方法\ntype myClientHeartBeatRouter struct {\n\tznet.BaseRouter\n}\n\nfunc (r *myClientHeartBeatRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"in myClientHeartBeatRouter Handle, recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n}\n\nfunc main() {\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\tmyHeartBeatMsgID := 88888\n\n\t// Start heartbeating detection. (启动心跳检测)\n\tclient.StartHeartBeatWithOption(3*time.Second, &ziface.HeartBeatOption{\n\t\tMakeMsg:          myClientHeartBeatMsg,\n\t\tOnRemoteNotAlive: myClientOnRemoteNotAlive,\n\t\tRouter:           &myClientHeartBeatRouter{},\n\t\tHeartBeatMsgID:   uint32(myHeartBeatMsgID),\n\t})\n\n\tclient.Start()\n\n\tselect {}\n}\n"
  },
  {
    "path": "examples/zinx_heartbeat/client_default/client_default.go",
    "content": "package main\n\nimport (\n\t\"time\"\n\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc main() {\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\t// Start heartbeating detection.\n\tclient.StartHeartBeat(3 * time.Second)\n\n\tclient.Start()\n\n\t// wait\n\tselect {}\n}\n"
  },
  {
    "path": "examples/zinx_heartbeat/server/conf/zinx.json",
    "content": "{\n  \"Name\":\"Zinx Heartbeat\",\n  \"Host\":\"127.0.0.1\",\n  \"TcpPort\":8999,\n  \"MaxConn\":3,\n  \"WorkerPoolSize\":10,\n  \"LogIsolationLevel\": 1,\n  \"HeartbeatMax\": 10\n}\n"
  },
  {
    "path": "examples/zinx_heartbeat/server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"time\"\n)\n\n// User-defined heartbeat message processing method\n// 用户自定义的心跳检测消息处理方法\nfunc myHeartBeatMsg(conn ziface.IConnection) []byte {\n\treturn []byte(\"heartbeat, I am server, I am alive\")\n}\n\n// User-defined handling method for remote connection not alive.\n// 用户自定义的远程连接不存活时的处理方法\nfunc myOnRemoteNotAlive(conn ziface.IConnection) {\n\tfmt.Println(\"myOnRemoteNotAlive is Called, connID=\", conn.GetConnID(), \"remoteAddr = \", conn.RemoteAddr())\n\t//关闭连接\n\tconn.Stop()\n}\n\n// User-defined method for handling heartbeat messages (用户自定义的心跳检测消息处理方法)\ntype myHeartBeatRouter struct {\n\tznet.BaseRouter\n}\n\nfunc (r *myHeartBeatRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"in MyHeartBeatRouter Handle, recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n}\n\nfunc main() {\n\ts := znet.NewServer()\n\n\tmyHeartBeatMsgID := 88888\n\n\t// Start heartbeating detection. (启动心跳检测)\n\ts.StartHeartBeatWithOption(1*time.Second, &ziface.HeartBeatOption{\n\t\tMakeMsg:          myHeartBeatMsg,\n\t\tOnRemoteNotAlive: myOnRemoteNotAlive,\n\t\tRouter:           &myHeartBeatRouter{},\n\t\tHeartBeatMsgID:   uint32(myHeartBeatMsgID),\n\t})\n\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_heartbeat/server_default/conf/zinx.json",
    "content": "{\n  \"Name\":\"Zinx Heartbeat\",\n  \"Host\":\"127.0.0.1\",\n  \"TcpPort\":8999,\n  \"MaxConn\":3,\n  \"WorkerPoolSize\":10,\n  \"LogIsolationLevel\": 1,\n  \"HeartbeatMax\": 10\n}\n"
  },
  {
    "path": "examples/zinx_heartbeat/server_default/server_default.go",
    "content": "package main\n\nimport (\n\t\"github.com/aceld/zinx/znet\"\n\t\"time\"\n)\n\nfunc main() {\n\ts := znet.NewServer()\n\n\t// Start heartbeating detection.\n\ts.StartHeartBeat(5 * time.Second)\n\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_interceptor/client/client.go",
    "content": "package main\n\nimport (\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc main() {\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\tclient.SetOnConnStart(func(connection ziface.IConnection) {\n\t\t_ = connection.SendMsg(1, []byte(\"hello zinx\"))\n\t})\n\n\tclient.Start()\n\n\ttime.Sleep(time.Second)\n}\n"
  },
  {
    "path": "examples/zinx_interceptor/interceptors/interceptor_1.go",
    "content": "package interceptors\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n)\n\n// Custom Interceptor 1\n\ntype MyInterceptor struct{}\n\nfunc (m *MyInterceptor) Intercept(chain ziface.IChain) ziface.IcResp {\n\trequest := chain.Request()\n\t// This layer is the custom interceptor processing logic, which simply prints the input.\n\t// (这一层是自定义拦截器处理逻辑，这里只是简单打印输入)\n\tiRequest := request.(ziface.IRequest)\n\tzlog.Ins().InfoF(\"MyInterceptor, Recv：%s\", iRequest.GetData())\n\treturn chain.Proceed(chain.Request())\n}\n"
  },
  {
    "path": "examples/zinx_interceptor/router/route.go",
    "content": "package router\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype HelloRouter struct {\n\tznet.BaseRouter\n}\n\nfunc (hr *HelloRouter) Handle(request ziface.IRequest) {\n\tzlog.Ins().InfoF(string(request.GetData()))\n}\n"
  },
  {
    "path": "examples/zinx_interceptor/server/server.go",
    "content": "package main\n\nimport (\n\t\"github.com/aceld/zinx/examples/zinx_interceptor/interceptors\"\n\t\"github.com/aceld/zinx/examples/zinx_interceptor/router\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc main() {\n\tserver := znet.NewServer()\n\n\tserver.AddRouter(1, &router.HelloRouter{})\n\n\t// Add Custom Interceptor\n\tserver.AddInterceptor(&interceptors.MyInterceptor{})\n\n\tserver.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_kcp/client/kcp_client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"github.com/xtaci/kcp-go\"\n\t\"io\"\n\t\"time\"\n)\n\n// 模拟客户端\nfunc main() {\n\tfmt.Println(\"Client Test ... start\")\n\t// Replace net.Dial with kcp.DialWithOptions\n\tconn, err := kcp.Dial(\"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tdp := zpack.Factory().NewPack(ziface.ZinxDataPack)\n\tsendMsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(\"client test message\")))\n\t_, err = conn.Write(sendMsg)\n\tif err != nil {\n\t\tfmt.Println(\"client write err: \", err)\n\t\treturn\n\t}\n\n\tfor {\n\t\t// Read the \"head\" section from the stream first. (先读出流中的head部分)\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client read head err: \", err)\n\t\t\treturn\n\t\t}\n\n\t\t// Unpack the headData byte stream into msg. (将headData字节流 拆包到msg中)\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client unpack head err: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t// Read the \"data\" section from the stream. (再读出流中的data部分)\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t// read from io.Reader into msg.Data (根据dataLen从io中读取字节流)\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"client unpack data err\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Printf(\"==> Client receive Msg: ID = %d, len = %d , data = %s\\n\", msg.ID, msg.DataLen, msg.Data)\n\n\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t_, err = conn.Write(sendMsg)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"client write err: \", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_kcp/server/server.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zconf\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype TestRouter struct {\n\tznet.BaseRouter\n}\n\nvar dealTimes = 0\n\n// PreHandle -\nfunc (t *TestRouter) PreHandle(req ziface.IRequest) {\n\tstart := time.Now()\n\n\tfmt.Println(\"--> Call PreHandle\")\n\tif err := req.GetConnection().SendMsg(0, []byte(\"test1\")); err != nil {\n\t\tfmt.Println(err)\n\t}\n\telapsed := time.Since(start)\n\tfmt.Println(\"cost time：\", elapsed)\n}\n\n// Handle -\nfunc (t *TestRouter) Handle(req ziface.IRequest) {\n\tfmt.Println(\"--> Call Handle\")\n\n\tif err := Err(); err != nil {\n\t\treq.Abort()\n\t\tfmt.Println(\"Insufficient permission\")\n\t}\n\n\tdealTimes++\n\treq.GetConnection().AddCloseCallback(nil, nil, func() {\n\t\tfmt.Println(\"run close callback\")\n\t})\n\n\tif err := req.GetConnection().SendMsg(0, []byte(\"test2\")); err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\tif dealTimes == 5 {\n\t\treq.GetConnection().Stop()\n\t}\n\n\ttime.Sleep(1 * time.Millisecond)\n}\n\n// PostHandle -\nfunc (t *TestRouter) PostHandle(req ziface.IRequest) {\n\tfmt.Println(\"--> Call PostHandle\")\n\tif err := req.GetConnection().SendMsg(0, []byte(\"test3\")); err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc Err() error {\n\t//Specific Business Operation (具体业务操作)\n\treturn errors.New(\"Test\")\n}\n\nfunc main() {\n\ts := znet.NewUserConfServer(&zconf.Config{\n\t\tMode:               \"kcp\",\n\t\tKcpPort:            7777,\n\t\tKcpRecvWindow:      128,\n\t\tKcpSendWindow:      128,\n\t\tKcpStreamMode:      true,\n\t\tKcpACKNoDelay:      false,\n\t\tLogDir:             \"./\",\n\t\tLogFile:            \"test.log\",\n\t\tKcpFecDataShards:   10, //代表每10个原始数据块 发3个校验数据块\n\t\tKcpFecParityShards: 3,\n\t})\n\ts.AddRouter(1, &TestRouter{})\n\ts.SetOnConnStart(func(conn ziface.IConnection) {\n\t\tfmt.Println(\"--> OnConnStart\")\n\t})\n\ts.SetOnConnStop(func(conn ziface.IConnection) {\n\t\tfmt.Println(\"--> OnConnStop\")\n\t})\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_logger/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/examples/zinx_client/c_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\nfunc business(conn ziface.IConnection) {\n\n\tfor {\n\t\terr := conn.SendMsg(1, []byte(\"Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tzlog.Error(err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tzlog.Debug(\"DoConnectionBegin is Called ... \")\n\n\tconn.SetProperty(\"Name\", \"刘丹冰\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\tgo business(conn)\n}\n\nfunc DoClientConnectedLost(conn ziface.IConnection) {\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Error(\"Conn Property Name = \", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Error(\"Conn Property Home = \", home)\n\t}\n\n\tzlog.Debug(\"DoClientConnectedLost is Called ... \")\n}\n\nfunc main() {\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\tclient.SetOnConnStop(DoClientConnectedLost)\n\n\tclient.AddRouter(0, &c_router.PingRouter{})\n\n\tclient.Start()\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n}\n"
  },
  {
    "path": "examples/zinx_logger/server/my_logger.go",
    "content": "package main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n)\n\n// User-defined logging method\n// The internal engine logging method of zinx can be reset by the logging method of its own business.\n// In this example, fmt.Println is used.\n// 用户自定义日志方式，\n// 可以通过自身业务的日志方式，来重置zinx内部引擎的日志打印方式\n// 本例以fmt.Println为例\ntype MyLogger struct{}\n\n// Without context logging interface\nfunc (l *MyLogger) InfoF(format string, v ...interface{}) {\n\tfmt.Printf(format, v...)\n}\n\nfunc (l *MyLogger) ErrorF(format string, v ...interface{}) {\n\tfmt.Printf(format, v...)\n}\n\nfunc (l *MyLogger) DebugF(format string, v ...interface{}) {\n\tfmt.Printf(format, v...)\n}\n\n// Logging interface with context\nfunc (l *MyLogger) InfoFX(ctx context.Context, format string, v ...interface{}) {\n\tfmt.Println(ctx)\n\tfmt.Printf(format, v...)\n}\n\nfunc (l *MyLogger) ErrorFX(ctx context.Context, format string, v ...interface{}) {\n\tfmt.Println(ctx)\n\tfmt.Printf(format, v...)\n}\n\nfunc (l *MyLogger) DebugFX(ctx context.Context, format string, v ...interface{}) {\n\tfmt.Println(ctx)\n\tfmt.Printf(format, v...)\n}\n"
  },
  {
    "path": "examples/zinx_logger/server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype TestRouter struct {\n\tznet.BaseRouter\n}\n\n// PreHandle -\nfunc (t *TestRouter) PreHandle(req ziface.IRequest) {\n\tstart := time.Now()\n\n\tfmt.Println(\"--> Call PreHandle\")\n\tif err := req.GetConnection().SendMsg(0, []byte(\"test1\")); err != nil {\n\t\tfmt.Println(err)\n\t}\n\telapsed := time.Since(start)\n\tfmt.Println(\"elapsed：\", elapsed)\n}\n\n// Handle -\nfunc (t *TestRouter) Handle(req ziface.IRequest) {\n\tfmt.Println(\"--> Call Handle\")\n\n\tif err := req.GetConnection().SendMsg(0, []byte(\"test2\")); err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\n// PostHandle -\nfunc (t *TestRouter) PostHandle(req ziface.IRequest) {\n\tfmt.Println(\"--> Call PostHandle\")\n\tif err := req.GetConnection().SendMsg(0, []byte(\"test3\")); err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc main() {\n\ts := znet.NewServer()\n\ts.AddRouter(1, &TestRouter{})\n\tzlog.SetLogger(new(MyLogger))\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_metrics/client/c1/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/examples/zinx_client/c_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\nfunc business(conn ziface.IConnection) {\n\n\tfor {\n\t\terr := conn.SendMsg(100, []byte(\"Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tzlog.Error(err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tzlog.Debug(\"DoConnectionBegin is Called ... \")\n\n\t//设置两个连接属性，在连接创建之后\n\tconn.SetProperty(\"Name\", \"刘丹冰Aceld\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\tgo business(conn)\n}\n\nfunc DoClientConnectedLost(conn ziface.IConnection) {\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Name = \", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Home = \", home)\n\t}\n\n\tzlog.Debug(\"DoClientConnectedLost is Called ... \")\n}\n\nfunc main() {\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\tclient.SetOnConnStop(DoClientConnectedLost)\n\n\tclient.AddRouter(2, &c_router.PingRouter{})\n\tclient.AddRouter(3, &c_router.HelloRouter{})\n\n\tclient.Start()\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n\n\tclient.Stop()\n\ttime.Sleep(time.Second * 2)\n}\n"
  },
  {
    "path": "examples/zinx_metrics/client/c2/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/examples/zinx_client/c_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\nfunc business(conn ziface.IConnection) {\n\n\tfor {\n\t\terr := conn.SendMsg(100, []byte(\"Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tzlog.Error(err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tzlog.Debug(\"DoConnectionBegin is Called ... \")\n\n\tconn.SetProperty(\"Name\", \"刘丹冰Aceld\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\tgo business(conn)\n}\n\nfunc DoClientConnectedLost(conn ziface.IConnection) {\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Name = \", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Home = \", home)\n\t}\n\n\tzlog.Debug(\"DoClientConnectedLost is Called ... \")\n}\n\nfunc main() {\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\tclient.SetOnConnStop(DoClientConnectedLost)\n\n\tclient.AddRouter(2, &c_router.PingRouter{})\n\tclient.AddRouter(3, &c_router.HelloRouter{})\n\n\tclient.Start()\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n\n\tclient.Stop()\n\ttime.Sleep(time.Second * 2)\n}\n"
  },
  {
    "path": "examples/zinx_metrics/server/conf/zinx.json",
    "content": "{\n  \"Name\":\"MyZinxServer001\",\n  \"Host\":\"0.0.0.0\",\n  \"TCPPort\":8999,\n  \"MaxConn\":3,\n  \"WorkerPoolSize\":10,\n  \"LogIsolationLevel\": 0,\n  \"PrometheusMetricsEnable\": true,\n  \"PrometheusServer\": true,\n  \"PrometheusListen\": \"0.0.0.0:20004\"\n}\n"
  },
  {
    "path": "examples/zinx_metrics/server/server.go",
    "content": "package main\n\nimport (\n\t\"github.com/aceld/zinx/examples/zinx_server/s_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc DoConnectionBegin(conn ziface.IConnection) {\n\tzlog.Ins().InfoF(\"DoConnectionBegin is Called ...\")\n\n\tconn.SetProperty(\"Name\", \"Aceld\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\terr := conn.SendMsg(2, []byte(\"DoConnection BEGIN...\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\nfunc DoConnectionLost(conn ziface.IConnection) {\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Ins().InfoF(\"Conn Property Name = %v\", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Ins().InfoF(\"Conn Property Home = %v\", home)\n\t}\n\n\tzlog.Ins().InfoF(\"Conn is Lost\")\n}\n\n// usage:$  curl 0.0.0.0:20004/metrics\n// to get Metrics\nfunc main() {\n\ts := znet.NewServer()\n\n\ts.SetOnConnStart(DoConnectionBegin)\n\ts.SetOnConnStop(DoConnectionLost)\n\n\ts.AddRouter(100, &s_router.PingRouter{})\n\ts.AddRouter(1, &s_router.HelloZinxRouter{})\n\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_mutiport/client8999/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/examples/zinx_client/c_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\nfunc business(conn ziface.IConnection) {\n\n\tfor {\n\t\terr := conn.SendMsg(100, []byte(\"Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tzlog.Error(err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tzlog.Debug(\"DoConnectionBegin is Called ... \")\n\n\t//设置两个连接属性，在连接创建之后\n\tconn.SetProperty(\"Name\", \"刘丹冰Aceld\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\tgo business(conn)\n}\n\nfunc DoClientConnectedLost(conn ziface.IConnection) {\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Name = \", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Home = \", home)\n\t}\n\n\tzlog.Debug(\"DoClientConnectedLost is Called ... \")\n}\n\nfunc main() {\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\tclient.SetOnConnStop(DoClientConnectedLost)\n\n\tclient.AddRouter(2, &c_router.PingRouter{})\n\tclient.AddRouter(3, &c_router.HelloRouter{})\n\n\tclient.Start()\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n\n\tclient.Stop()\n\ttime.Sleep(time.Second * 2)\n}\n"
  },
  {
    "path": "examples/zinx_mutiport/client9000/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/examples/zinx_client/c_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\nfunc business(conn ziface.IConnection) {\n\n\tfor {\n\t\terr := conn.SendMsg(100, []byte(\"Ping...[FromClient]\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tzlog.Error(err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tzlog.Debug(\"DoConnectionBegin is Called ... \")\n\n\tconn.SetProperty(\"Name\", \"刘丹冰Aceld\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\tgo business(conn)\n}\n\nfunc DoClientConnectedLost(conn ziface.IConnection) {\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Name = \", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Debug(\"Conn Property Home = \", home)\n\t}\n\n\tzlog.Debug(\"DoClientConnectedLost is Called ... \")\n}\n\nfunc main() {\n\tclient := znet.NewClient(\"127.0.0.1\", 9000)\n\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\tclient.SetOnConnStop(DoClientConnectedLost)\n\n\tclient.AddRouter(2, &c_router.PingRouter{})\n\tclient.AddRouter(3, &c_router.HelloRouter{})\n\n\tclient.Start()\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n\n\tclient.Stop()\n\ttime.Sleep(time.Second * 2)\n}\n"
  },
  {
    "path": "examples/zinx_mutiport/server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/examples/zinx_server/s_router\"\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"os\"\n\t\"os/signal\"\n)\n\n// Execute when creating a connection (创建连接的时候执行)\nfunc DoConnectionBegin(conn ziface.IConnection) {\n\tzlog.Ins().InfoF(\"DoConnectionBegin is Called ...\")\n\n\t//设置两个连接属性，在连接创建之后\n\tconn.SetProperty(\"Name\", \"Aceld\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/@aceld\")\n\n\terr := conn.SendMsg(2, []byte(\"DoConnection BEGIN...\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\n// Execute when connection lost (连接断开的时候执行)\nfunc DoConnectionLost(conn ziface.IConnection) {\n\t// Query the Name and Home properties of the conn before destroying the connectio\n\t// 在连接销毁之前，查询conn的Name，Home属性\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Ins().InfoF(\"Conn Property Name = %v\", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Ins().InfoF(\"Conn Property Home = %v\", home)\n\t}\n\n\tzlog.Ins().InfoF(\"Conn is Lost\")\n}\n\nfunc main() {\n\n\tvar i = 0\n\n\tfor i < 2 {\n\n\t\tport := 8999 + i\n\t\ts := znet.NewUserConfServer(&zconf.Config{\n\t\t\tTCPPort: port,\n\t\t\tName:    fmt.Sprintf(\"MyZinxServer-port:%d\", port),\n\t\t})\n\n\t\ts.SetOnConnStart(DoConnectionBegin)\n\t\ts.SetOnConnStop(DoConnectionLost)\n\n\t\ts.AddRouter(100, &s_router.PingRouter{})\n\t\ts.AddRouter(1, &s_router.HelloZinxRouter{})\n\n\t\tgo s.Serve()\n\n\t\ti++\n\t}\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n}\n"
  },
  {
    "path": "examples/zinx_new_router/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n// 模拟客户端\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t// Send a test request after 3 seconds to give the server a chance to start the service. (3秒之后发起测试请求，给服务端开启服务的机会)\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tdp := zpack.Factory().NewPack(ziface.ZinxDataPack)\n\tmsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(\"client test message\")))\n\t_, err = conn.Write(msg)\n\tif err != nil {\n\t\tfmt.Println(\"client write err: \", err)\n\t\treturn\n\t}\n\n\tfor {\n\t\t// Read the \"head\" section from the stream first. (先读出流中的head部分)\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client read head err: \", err)\n\t\t\treturn\n\t\t}\n\n\t\t// Unpack the headData byte stream into msg. (将headData字节流 拆包到msg中)\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client unpack head err: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t// Read the \"data\" section from the stream. (再读出流中的data部分)\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t// read from io.Reader into msg.Data (根据dataLen从io中读取字节流)\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"client unpack data err\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Printf(\"==> Client receive Msg: ID = %d, len = %d , data = %s\\n\", msg.ID, msg.DataLen, msg.Data)\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_new_router/server/conf/zinx.json",
    "content": "{\n  \"Name\":\"zinx v-0.10 demoApp\",\n  \"Host\":\"127.0.0.1\",\n  \"TCPPort\":7777,\n  \"MaxConn\":3,\n  \"WorkerPoolSize\":10,\n  \"LogDir\": \"./mylog\",\n  \"LogFile\":\"zinx.log\"\n}\n"
  },
  {
    "path": "examples/zinx_new_router/server/server.go",
    "content": "package main\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"time\"\n)\n\ntype TestRouter struct {\n\tznet.BaseRouter\n}\n\n// PreHandle -\nfunc (t *TestRouter) PreHandle(req ziface.IRequest) {\n\tstart := time.Now()\n\n\tfmt.Println(\"--> Call PreHandle\")\n\tif err := req.GetConnection().SendMsg(0, []byte(\"test1\")); err != nil {\n\t\tfmt.Println(err)\n\t}\n\telapsed := time.Since(start)\n\tfmt.Println(\"cost time：\", elapsed)\n}\n\n// Handle -\nfunc (t *TestRouter) Handle(req ziface.IRequest) {\n\tfmt.Println(\"--> Call Handle\")\n\n\t// Simulated scenario - In the event of an expected error such as incorrect permissions or incorrect information,\n\t// subsequent function execution will be stopped, but this function will be fully executed.\n\t// 模拟场景- 出现意料之中的错误 如权限不对或者信息错误 则停止后续函数执行，但是次函数会执行完毕\n\tif err := Err(); err != nil {\n\t\treq.Abort()\n\t\tfmt.Println(\"Insufficient permission\")\n\t}\n\n\t// Simulation scenario - In case of a certain situation, repeat the above operation.\n\t// 模拟场景- 出现某种情况，重复上面的操作\n\t/*\n\t\tif err := Err(); err != nil {\n\t\t\treq.Goto(znet.PRE_HANDLE)\n\t\t\tfmt.Println(\"repeat\")\n\t\t}\n\t*/\n\n\tif err := req.GetConnection().SendMsg(0, []byte(\"test2\")); err != nil {\n\t\tfmt.Println(err)\n\t}\n\n\ttime.Sleep(1 * time.Millisecond)\n}\n\n// PostHandle -\nfunc (t *TestRouter) PostHandle(req ziface.IRequest) {\n\tfmt.Println(\"--> Call PostHandle\")\n\tif err := req.GetConnection().SendMsg(0, []byte(\"test3\")); err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc Err() error {\n\t//Specific Business Operation (具体业务操作)\n\treturn errors.New(\"Test\")\n}\n\nfunc main() {\n\ts := znet.NewServer()\n\ts.AddRouter(1, &TestRouter{})\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_protobuf/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/pb\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"github.com/golang/protobuf/proto\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\ntype PositionClientRouter struct {\n\tznet.BaseRouter\n}\n\nfunc (this *PositionClientRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Handle....\")\n\n\tmsg := &pb.Position{}\n\terr := proto.Unmarshal(request.GetData(), msg)\n\tif err != nil {\n\t\tfmt.Println(\"Position Unmarshal error \", err, \" data = \", request.GetData())\n\t\treturn\n\t}\n\n\tfmt.Printf(\"recv from server : msgId=%+v, data=%+v\\n\", request.GetMsgID(), msg)\n}\n\n// 客户端自定义业务\nfunc business(conn ziface.IConnection) {\n\n\tfor {\n\n\t\tmsg := &pb.Position{}\n\t\tmsg.X = 1\n\t\tmsg.Y = 2\n\t\tmsg.Z = 3\n\t\tmsg.V = 4\n\n\t\tdata, err := proto.Marshal(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"proto Marshal error = \", err, \" msg = \", msg)\n\t\t\tbreak\n\t\t}\n\n\t\terr = conn.SendMsg(0, data)\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\t\t\tbreak\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\tconn.SetProperty(\"Name\", \"刘丹冰Aceld\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\tgo business(conn)\n}\n\nfunc wait() {\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n}\n\nfunc main() {\n\tclient := znet.NewClient(\"127.0.0.1\", 8999)\n\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\n\tclient.AddRouter(0, &PositionClientRouter{})\n\n\tclient.Start()\n\n\twait()\n}\n"
  },
  {
    "path": "examples/zinx_protobuf/server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/pb\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\ntype PositionServerRouter struct {\n\tznet.BaseRouter\n}\n\n// Ping Handle\nfunc (this *PositionServerRouter) Handle(request ziface.IRequest) {\n\n\tmsg := &pb.Position{}\n\terr := proto.Unmarshal(request.GetData(), msg)\n\tif err != nil {\n\t\tfmt.Println(\"Position Unmarshal error \", err, \" data = \", request.GetData())\n\t\treturn\n\t}\n\n\tfmt.Printf(\"recv from client : msgId=%+v, data=%+v\\n\", request.GetMsgID(), msg)\n\n\tmsg.X += 1\n\tmsg.Y += 1\n\tmsg.Z += 1\n\tmsg.V += 1\n\n\tdata, err := proto.Marshal(msg)\n\tif err != nil {\n\t\tfmt.Println(\"proto Marshal error = \", err, \" msg = \", msg)\n\t\treturn\n\t}\n\n\terr = request.GetConnection().SendMsg(0, data)\n\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\nfunc main() {\n\ts := znet.NewServer()\n\n\ts.AddRouter(0, &PositionServerRouter{})\n\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_routerSlices/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"net\"\n)\n\nfunc main() {\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:8999\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\", err)\n\t\treturn\n\t}\n\n\tdp := zpack.NewDataPack()\n\tmsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(\"ZinxPing\")))\n\t_, err = conn.Write(msg)\n\tif err != nil {\n\t\tfmt.Println(\"write error err \", err)\n\t\treturn\n\t}\n\n}\n"
  },
  {
    "path": "examples/zinx_routerSlices/default_func_server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc DefaultTest1(request ziface.IRequest) {\n\tfmt.Println(\"test1\")\n}\nfunc DefaultTest2(request ziface.IRequest) {\n\ttime.Sleep(1)\n\tarr := make([]int, 1)\n\tfmt.Println(arr[1])\n}\n\nfunc main() {\n\ts := znet.NewDefaultRouterSlicesServer()\n\ts.AddRouterSlices(1, DefaultTest1, DefaultTest2)\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_routerSlices/router_func_server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc Auth1(request ziface.IRequest) {\n\n\t// Verify business, default to pass. (验证业务 默认固定放行)\n\tfmt.Println(\"I am the Auth1, I will always pass.\")\n\t// I am validation handler 1, and I must pass.\n\t// Note that the next function will start executing and return here after all functions are executed.\n\t// (注意是进入下一个函数开始执行，全部执行完后 回到此处)\n\trequest.RouterSlicesNext()\n}\n\nfunc Auth2(request ziface.IRequest) {\n\t// I am the validation handler 2, and by default, I do not allow the request to pass.(验证业务 默认固定不放行)\n\n\t// Terminate execution function, no more handlers will be executed after this one.(终结执行函数，再这个处理器结束后不会在执行后面的处理器)\n\trequest.Abort()\n\tfmt.Println(\"I am the Auth2, I will definitely not pass.\")\n\tfmt.Println(\"The business terminates here and the subsequent handlers will not be executed.\")\n}\n\nfunc Auth3(request ziface.IRequest) {\n\n\tfmt.Println(\"I am the group validation function.\")\n}\n\n// I am a business function.\nfunc TestFunc(request ziface.IRequest) {\n\tfmt.Println(\"I am a business function.\")\n}\n\nfunc main() {\n\n\t// New version usage and explanation.(新版本使用方法以及说明)\n\tserver := znet.NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: \"127.0.0.1\"})\n\n\t// Simulation scenario 1: A normal business that only executes a single operation function separately.\n\t// 模拟场景 1，普通业务单独只执行一个操作函数\n\t//server.AddRouterSlices(1, TestFunc)\n\n\t// Simulated scenario 2: All operations below require verification of request permissions, so a verification function is needed.\n\t// the verification component has been added to all the routes under the use method, such as 1 and 2.\n\t// 模拟场景 2, 以下所有操作都需要验证请求权限，所以需要一个验证函数\n\t// 将验证组件添加到了所有再use方法下的所有路由中了 如1,2 中都会带有\n\t//routerSlices := server.Use(Auth1)\n\t//routerSlices.AddHandler(1, TestFunc)\n\t//routerSlices.AddHandler(2, TestFunc)\n\n\t// Equivalent to the following:(等价于下面:)\n\t//routerSlices.AddHandler(1, Auth1, TestFunc)\n\n\t// Simulated scenario 3: Authorization is required, but some route operations require additional verification.\n\t// 模拟场景3 需要权限，但是某些路由操作需要更多额外验证\n\tserver.Use(Auth1)\n\tgroup1 := server.Group(1, 2, Auth3)\n\t{\n\t\t// MsgId=1, there will be Auth3 and Auth1. (1中就会有Auth3和Auth1)\n\t\tgroup1.AddHandler(1, TestFunc)\n\n\t\t// More specific scenario: Some operations within the group require an additional validation process.\n\t\t// 更特殊的情况，组内另一些操作还需要另一道校验处理\n\t\tgroup1.Use(Auth2)\n\t\t// MsgId=2, Auth3 and Auth1 will be added to all routes under the use method.\n\t\t// 2中就会有Auth1和Auth3以及Auth2\n\t\tgroup1.AddHandler(2, TestFunc)\n\n\t}\n\t// MsgId=3, Auth3 will not be included. (3中就不会有Auth3)\n\tserver.AddRouterSlices(3, TestFunc)\n\n\tserver.Serve()\n\n}\n"
  },
  {
    "path": "examples/zinx_routerSlices/router_group_server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc Test1(request ziface.IRequest) {\n\tfmt.Println(\"test1\")\n}\nfunc Test2(request ziface.IRequest) {\n\tfmt.Println(\"Test2\")\n}\nfunc Test3(request ziface.IRequest) {\n\tfmt.Println(\"Test3\")\n}\nfunc Test4(request ziface.IRequest) {\n\tfmt.Println(\"Test4\")\n}\nfunc Test5(request ziface.IRequest) {\n\tfmt.Println(\"Test5\")\n}\nfunc Test6(request ziface.IRequest) {\n\tfmt.Println(\"Test6\")\n}\n\ntype router struct {\n\tznet.BaseRouter\n}\n\nfunc (r *router) PreHandle(req ziface.IRequest) {\n\tfmt.Println(\" hello router1\")\n}\nfunc (r *router) Handle(req ziface.IRequest) {\n\treq.Abort()\n\tfmt.Println(\" hello router2\")\n}\nfunc (r *router) PostHandle(req ziface.IRequest) {\n\tfmt.Println(\" hello router3\")\n}\n\nfunc main() {\n\n\t// Old version router method (旧版本路由方法)\n\t//{\n\t//\tserver := znet.NewUserConfServer(&zconf.Config{TCPPort: 8999, Host: \"127.0.0.1\"})\n\t//\n\t//\t// Even without manually calling the router mode, the default is 1 (old version) 即使不手动调路由模式也可以,默认是1（旧版本）\n\t//\t//server := znet.NewServer()\n\t//\n\t//\t// Old version runs normally(旧版正常执行)\n\t//\tr := &router{}\n\t//\tserver.AddRouter(1, r)\n\t//\tserver.Serve()\n\t//}\n\t//{\n\n\t// New version usage and explanation(新版本使用方法以及说明)\n\t{\n\t\tserver := znet.NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: \"127.0.0.1\"})\n\t\t// Grouping(分组)\n\t\tgroup := server.Group(3, 10, Test1)\n\n\t\t// Add router. Will panic if not within the group range.(添加路由 如果不在组范围会直接panic)\n\t\t//group.AddHandler(11, Test2)\n\n\t\t// Within the group, not affected by Use, has processors 1 and 2.(在组中 不受Use影响 有 1 2 处理器)\n\t\tgroup.AddHandler(3, Test2)\n\n\t\t// Not within the group and before Use, only has its own processor 3.(既不在组里也在Use之前只会有自己的处理器 3)\n\t\tserver.AddRouterSlices(1, Test3)\n\n\t\t// If you want the group processor to have priority, you should do the following before Use.\n\t\t// You can manually add it via group.AddHandler(5, Test4, Test5,Test2, Test3, Test6)\n\t\t// or use the Group's Use method as shown below, which would have the order of 1 4 5 6 and not be affected by Use.\n\t\t// 如果希望group处理器优先，应当在Use之前如下操作\n\t\t// 可以手动添加 入 group.AddHandler(5, Test4, Test5,Test2, Test3, Test6)\n\t\t// 或者如下使用Group的Use方法 那么就是 1 4 5 6的顺序 不被use影响\n\t\tgroup.Use(Test2, Test3)\n\t\tgroup.AddHandler(5, Test4, Test5, Test6)\n\n\t\t// Common components, but not affected by the groups or routers before Use. (公共组件，但是，在使用Use之前的组或者路由不会影响到)\n\t\trouter := server.Use(Test4, Test5)\n\t\t// Add router. Not within the group but is affected by Use, has processors 4, 5, and 6.\n\t\t// (添加路由 不在组中但是收Use影响 有4 5 6处理器)\n\t\trouter.AddHandler(2, Test6)\n\n\t\t// Within the group and affected by Use, has all processors in the order of 4, 5, 1, 2, 3, 6 because the processors in Use are always at the forefront.\n\t\t// (在组里也受到Use影响 有所有处理器 且顺序应该是 4 5 1 2 3 6 因为use中的处理器始终在最前端)\n\t\tgroup.AddHandler(4, Test6)\n\n\t\tserver.Serve()\n\t}\n\n}\n"
  },
  {
    "path": "examples/zinx_server/Makefile",
    "content": "PROJECT_NAME:=zinx_server\nVERSION:=v1\n\n\n\n.PHONY: image run build clean\n\nbuild:\n\tbash build.sh ${PROJECT_NAME}\n\nimage:\n\tdocker build -t ${PROJECT_NAME}:${VERSION} .\n\nrun:\n\tdocker run  -itd \\\n\t-p 8999:8999 \\\n\t${PROJECT_NAME}:${VERSION}\n\n\nclean:\n\trm -rf ${PROJECT_NAME} \n\n"
  },
  {
    "path": "examples/zinx_server/build.sh",
    "content": "#!/bin/bash\n\nset -e\n\nAPP_NAME=$1\nAPP_VERSION=v$(cat version)\nBUILD_VERSION=$(git log -1 --oneline)\nBUILD_TIME=$(date \"+%FT%T%z\")\nGIT_REVISION=$(git rev-parse --short HEAD)\nGIT_BRANCH=$(git name-rev --name-only HEAD)\nGO_VERSION=$(go version)\n\n\ngo build -ldflags \" \\\n\t-X 'main.AppName=${APP_NAME}' \t\t\t\\\n\t-X 'main.AppVersion=${APP_VERSION}'     \\\n\t-X 'main.BuildVersion=${BUILD_VERSION//\\'/_}' \\\n\t-X 'main.BuildTime=${BUILD_TIME}'       \\\n\t-X 'main.GitRevision=${GIT_REVISION}'   \\\n\t-X 'main.GitBranch=${GIT_BRANCH}'       \\\n\t-X 'main.GoVersion=${GO_VERSION}'       \\\n\t\" -o $APP_NAME\n"
  },
  {
    "path": "examples/zinx_server/conf/zinx.json",
    "content": "{\r\n  \"Name\":\"zinx server Demo\",\r\n  \"Host\":\"127.0.0.1\",\r\n  \"TCPPort\":8999,\r\n  \"MaxConn\":3,\r\n  \"WorkerMode\": \"\",\r\n  \"WorkerPoolSize\":10,\r\n  \"LogDir\": \"./mylog\",\r\n  \"LogFile\":\"zinx.log\",\r\n  \"LogIsolationLevel\": 0,\r\n  \"Mode\":\"kcp\"\r\n}\r\n"
  },
  {
    "path": "examples/zinx_server/dockerfile",
    "content": "FROM centos:8\nCOPY zinx_server /zinx-server\nCOPY /conf/zinx.json /conf/zinx.json\nWORKDIR /\nEXPOSE  8999\n\nENTRYPOINT [ \"/zinx-server\" ]\n\n"
  },
  {
    "path": "examples/zinx_server/main.go",
    "content": "/**\n* @Author: Aceld\n* @Date: 2020/12/24 00:24\n* @Mail: danbing.at@gmail.com\n*    zinx server demo\n */\npackage main\n\nimport (\n\t\"github.com/aceld/zinx/examples/zinx_server/s_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// DoConnectionBegin Executed when creating a connection.\n// 创建连接的时候执行\nfunc DoConnectionBegin(conn ziface.IConnection) {\n\tzlog.Ins().InfoF(\"DoConnectionBegin is Called ...\")\n\n\t//设置两个连接属性，在连接创建之后\n\tconn.SetProperty(\"Name\", \"Aceld\")\n\tconn.SetProperty(\"Home\", \"https://www.kancloud.cn/@aceld\")\n\n\terr := conn.SendMsg(2, []byte(\"DoConnection BEGIN...\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\n// 连接断开的时候执行\n// DoConnectionLost Executed when the connection is closed.\nfunc DoConnectionLost(conn ziface.IConnection) {\n\t//在连接销毁之前，查询conn的Name，Home属性\n\t// Query the Name and Home properties of conn before destroying the connection.\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Ins().InfoF(\"Conn Property Name = %v\", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Ins().InfoF(\"Conn Property Home = %v\", home)\n\t}\n\n\tzlog.Ins().InfoF(\"Conn is Lost\")\n}\n\nfunc main() {\n\t// Create a server\n\ts := znet.NewServer()\n\n\t// Register a hook callback function for the connection\n\ts.SetOnConnStart(DoConnectionBegin)\n\ts.SetOnConnStop(DoConnectionLost)\n\n\t// Configure routing.\n\ts.AddRouter(100, &s_router.PingRouter{})\n\ts.AddRouter(1, &s_router.HelloZinxRouter{})\n\n\t// Start Service\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_server/s_router/hello.go",
    "content": "package s_router\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype HelloZinxRouter struct {\n\tznet.BaseRouter\n}\n\n// HelloZinxRouter Handle\nfunc (this *HelloZinxRouter) Handle(request ziface.IRequest) {\n\tzlog.Ins().DebugF(\"Call HelloZinxRouter Handle\")\n\t// Read the data from the client first, then send back \"ping...ping...ping\"\n\tzlog.Ins().DebugF(\"recv from client : msgId=%d, data=%+v, len=%d\", request.GetMsgID(), string(request.GetData()), len(request.GetData()))\n\n\terr := request.GetConnection().SendBuffMsg(3, []byte(\"Hello Zinx Router[FromServer]\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_server/s_router/ping.go",
    "content": "package s_router\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Ping Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\n\tzlog.Ins().DebugF(\"Call PingRouter Handle\")\n\t// Read the data from the client first, then send back \"ping...ping...ping\".\n\tzlog.Ins().DebugF(\"recv from client : msgId=%d, data=%+v, len=%d\", request.GetMsgID(), string(request.GetData()), len(request.GetData()))\n\n\terr := request.GetConnection().SendMsg(2, []byte(\"pong-server\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_server/version",
    "content": "v 1.0.0\n"
  },
  {
    "path": "examples/zinx_tls/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\n// PongRouter pong test 自定义路由\ntype PongRouter struct {\n\tznet.BaseRouter\n}\n\n// Handle Pong Handle\nfunc (this *PongRouter) Handle(request ziface.IRequest) {\n\n\tzlog.Debug(\"Call PongRouter Handle\")\n\t//先读取服务器返回的数据\n\tzlog.Debug(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n}\n\nfunc wait() {\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n}\n\nfunc main() {\n\t// Create a TLS client.\n\tc := znet.NewTLSClient(\"127.0.0.1\", 8899)\n\n\tc.SetOnConnStart(func(connection ziface.IConnection) {\n\t\tgo func() {\n\t\t\tfor {\n\t\t\t\terr := connection.SendMsg(1, []byte(\"Ping with TLS\"))\n\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Println(err)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t\ttime.Sleep(1 * time.Second)\n\t\t\t}\n\t\t}()\n\n\t})\n\n\tc.AddRouter(2, &PongRouter{})\n\n\tc.Start()\n\n\twait()\n}\n"
  },
  {
    "path": "examples/zinx_tls/server/server.go",
    "content": "package main\n\nimport (\n\t\"crypto/ecdsa\"\n\t\"crypto/elliptic\"\n\t\"crypto/rand\"\n\t\"crypto/x509\"\n\t\"crypto/x509/pkix\"\n\t\"encoding/pem\"\n\t\"math/big\"\n\t\"os\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// PingRouter ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Handle Ping Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\n\tzlog.Debug(\"Call PingRouter Handle\")\n\tzlog.Debug(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendBuffMsg(2, []byte(\"Pong with TLS\"), ziface.WithSendMsgTimeout(time.Millisecond*10))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\n// genExampleCrtAndKeyFile\n// Generate certificate and key files for testing purposes only! Please customize this function or use openssl to generate them for actual use.\n// (仅测试时生成证书和密钥文件！！实际使用请自定义该函数或者用openssl自行生成)\n// Reference for generating certificate and private key using openssl : https://blog.csdn.net/qq_44637753/article/details/124152315\n// (openssl生成证书和私钥方法参考 https://blog.csdn.net/qq_44637753/article/details/124152315)\nfunc genExampleCrtAndKeyFile(crtFileName, KeyFileName string) (err error) {\n\t// If already exists, regenerate.(如果已存在则重新生成)\n\t_ = os.Remove(crtFileName)\n\t_ = os.Remove(KeyFileName)\n\n\tdefer func() {\n\t\tif err != nil {\n\t\t\t// If there is an error during the process, delete the generated certificate and private key files.\n\t\t\t// (如果期间发生错误，删除以及生成的证书和私钥文件)\n\t\t\t_ = os.Remove(crtFileName)\n\t\t\t_ = os.Remove(KeyFileName)\n\t\t}\n\t}()\n\t// Generating a private key.(生成私钥)\n\tprivateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)\n\n\t// Creating a certificate template.(创建证书模板)\n\tserialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)\n\tserialNumber, err := rand.Int(rand.Reader, serialNumberLimit)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\ttemplate := x509.Certificate{\n\t\tSerialNumber: serialNumber,\n\t\tSubject: pkix.Name{\n\t\t\tOrganization: []string{\"Beijing University of Post and Telecommunication\"},\n\t\t},\n\n\t\tNotBefore: time.Now(),\n\t\tNotAfter:  time.Now().Add(24 * time.Hour * 365 * 10), // The certificate is valid for ten years. (证书十年之内有效)\n\n\t\tKeyUsage:              x509.KeyUsageDigitalSignature,\n\t\tExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},\n\t\tBasicConstraintsValid: true,\n\t}\n\n\t// Generating a certificate.(生成证书)\n\tderBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// serialize the certificate file.(序列化证书文件)\n\tpemCert := pem.EncodeToMemory(&pem.Block{Type: \"CERTIFICATE\", Bytes: derBytes})\n\tif pemCert == nil {\n\t\treturn err\n\t}\n\tif err := os.WriteFile(crtFileName, pemCert, 0644); err != nil {\n\t\treturn err\n\t}\n\n\t// Generating private key file(生成私钥文件)\n\tprivateBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)\n\tif err != nil {\n\t\treturn err\n\t}\n\tpemKey := pem.EncodeToMemory(&pem.Block{Type: \"PRIVATE KEY\", Bytes: privateBytes})\n\tif pemKey == nil {\n\t\treturn err\n\t}\n\tif err := os.WriteFile(KeyFileName, pemKey, 0600); err != nil {\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc main() {\n\t// Generate certificate and key files for testing purposes only!! Please customize this function or use openssl to generate them yourself in actual use.\n\t// Refer to this link for how to generate certificates and private keys using openssl: https://blog.csdn.net/qq_44637753/article/details/124152315\n\t// 生成测试用的证书和密钥文件！！仅测试时生成证书和密钥文件！！实际使用请自定义该函数或者用openssl自行生成\n\t// openssl生成证书和私钥方法参考 https://blog.csdn.net/qq_44637753/article/details/124152315\n\tcertFile := \"cert.pem\"\n\tkeyFile := \"key.pem\"\n\terr := genExampleCrtAndKeyFile(certFile, keyFile)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tdefer func() {\n\t\t// example中的证书和私钥文件仅作测试时使用 测试结束后删除\n\t\t// The certificate and private key files in the example are only used for testing purposes. Please delete them after the test is completed.\n\t\t_ = os.Remove(certFile)\n\t\t_ = os.Remove(keyFile)\n\t}()\n\n\t// Create a server, and if CertFile and PrivateKeyFile are specified, the server will start in TLS mode.\n\t// 创建一个server，当指定了CertFile和PrivateKeyFile时服务器开启TLS模式\n\ts := znet.NewUserConfServer(&zconf.Config{\n\t\tTCPPort:        8899,\n\t\tCertFile:       certFile, // 证书文件\n\t\tPrivateKeyFile: keyFile,  // 密钥文件\n\t})\n\n\ts.AddRouter(1, &PingRouter{})\n\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.10Test/client0/Client0.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte(\"Zinx V0.8 Client0 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.10Test/client1/Client1.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor n := 3; n >= 0; n-- {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(\"Zinx V0.8 Client1 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.10Test/server/Server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Ping Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call PingRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendBuffMsg(0, []byte(\"ping...ping...ping\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\ntype HelloZinxRouter struct {\n\tznet.BaseRouter\n}\n\n// HelloZinxRouter Handle\nfunc (this *HelloZinxRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call HelloZinxRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendBuffMsg(1, []byte(\"Hello Zinx Router V0.10\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\n// 创建连接的时候执行\nfunc DoConnectionBegin(conn ziface.IConnection) {\n\tfmt.Println(\"DoConnectionBegin is Called ... \")\n\n\t//设置两个连接属性，在连接创建之后\n\tfmt.Println(\"Set conn Name, Home done!\")\n\tconn.SetProperty(\"Name\", \"Aceld\")\n\tconn.SetProperty(\"Home\", \"https://www.jianshu.com/u/35261429b7f1\")\n\n\terr := conn.SendMsg(2, []byte(\"DoConnection BEGIN...\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\n// 连接断开的时候执行\nfunc DoConnectionLost(conn ziface.IConnection) {\n\t//在连接销毁之前，查询conn的Name，Home属性\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tfmt.Println(\"Conn Property Name = \", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tfmt.Println(\"Conn Property Home = \", home)\n\t}\n\n\tfmt.Println(\"DoConneciotnLost is Called ... \")\n}\n\nfunc main() {\n\t//创建一个server句柄\n\ts := znet.NewServer()\n\n\t//注册连接hook回调函数\n\ts.SetOnConnStart(DoConnectionBegin)\n\ts.SetOnConnStop(DoConnectionLost)\n\n\t//配置路由\n\ts.AddRouter(0, &PingRouter{})\n\ts.AddRouter(1, &HelloZinxRouter{})\n\n\t//开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.10Test/server/conf/zinx.json",
    "content": "{\r\n  \"Name\":\"zinx v-0.10 demoApp\",\r\n  \"Host\":\"127.0.0.1\",\r\n  \"TcpPort\":7777,\r\n  \"MaxConn\":3,\r\n  \"WorkerPoolSize\":10\r\n}\r\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.11Test/client0/Client0.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte(\"Zinx V0.8 Client0 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.11Test/client1/Client1.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor n := 3; n >= 0; n-- {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(\"Zinx V0.8 Client1 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.11Test/server/Server.go",
    "content": "/**\n* @Author: Aceld\n* @Date: 2019/4/30 17:42\n* @Mail: danbing.at@gmail.com\n*  ZinxV0.11测试，测试Zinx 日志模块功能 zlog模块\n */\npackage main\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Ping Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\n\tzlog.Debug(\"Call PingRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tzlog.Debug(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendBuffMsg(0, []byte(\"ping...ping...ping\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\ntype HelloZinxRouter struct {\n\tznet.BaseRouter\n}\n\n// HelloZinxRouter Handle\nfunc (this *HelloZinxRouter) Handle(request ziface.IRequest) {\n\tzlog.Debug(\"Call HelloZinxRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tzlog.Debug(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendBuffMsg(1, []byte(\"Hello Zinx Router V0.10\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\n// 创建连接的时候执行\nfunc DoConnectionBegin(conn ziface.IConnection) {\n\tzlog.Debug(\"DoConnectionBegin is Called ... \")\n\n\t//设置两个连接属性，在连接创建之后\n\tzlog.Debug(\"Set conn Name, Home done!\")\n\tconn.SetProperty(\"Name\", \"Aceld\")\n\tconn.SetProperty(\"Home\", \"https://www.jianshu.com/u/35261429b7f1\")\n\n\terr := conn.SendMsg(2, []byte(\"DoConnection BEGIN...\"))\n\tif err != nil {\n\t\tzlog.Error(err)\n\t}\n}\n\n// 连接断开的时候执行\nfunc DoConnectionLost(conn ziface.IConnection) {\n\t//在连接销毁之前，查询conn的Name，Home属性\n\tif name, err := conn.GetProperty(\"Name\"); err == nil {\n\t\tzlog.Error(\"Conn Property Name = \", name)\n\t}\n\n\tif home, err := conn.GetProperty(\"Home\"); err == nil {\n\t\tzlog.Error(\"Conn Property Home = \", home)\n\t}\n\n\tzlog.Debug(\"DoConneciotnLost is Called ... \")\n}\n\nfunc main() {\n\t//创建一个server句柄\n\ts := znet.NewServer()\n\n\t//注册连接hook回调函数\n\ts.SetOnConnStart(DoConnectionBegin)\n\ts.SetOnConnStop(DoConnectionLost)\n\n\t//配置路由\n\ts.AddRouter(0, &PingRouter{})\n\ts.AddRouter(1, &HelloZinxRouter{})\n\n\t//开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.11Test/server/conf/zinx.json",
    "content": "{\r\n  \"Name\":\"zinx v-0.10 demoApp\",\r\n  \"Host\":\"127.0.0.1\",\r\n  \"TcpPort\":7777,\r\n  \"MaxConn\":3,\r\n  \"WorkerPoolSize\":10,\r\n  \"LogDir\": \"./mylog\",\r\n  \"LogFile\":\"zinx.log\"\r\n}\r\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.1Test/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t_, err := conn.Write([]byte(\"hahaha\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\tbuf := make([]byte, 512)\n\t\tcnt, err := conn.Read(buf)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read buf error \")\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Printf(\" server call back : %s, cnt = %d\\n\", buf, cnt)\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.1Test/server/server.go",
    "content": "package main\n\nimport (\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// Server 模块的测试函数\nfunc main() {\n\n\t/*\n\t\t服务端测试\n\t*/\n\t//1 创建一个server 句柄 s\n\t// s := znet.NewServer(\"[zinx V0.1]\")\n\ts := znet.NewServer()\n\n\t//2 开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.2Test/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t_, err := conn.Write([]byte(\"hahaha\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\tbuf := make([]byte, 512)\n\t\tcnt, err := conn.Read(buf)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read buf error \")\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Printf(\" server call back : %s, cnt = %d\\n\", buf, cnt)\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.2Test/server/server.go",
    "content": "package main\n\nimport (\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// Server 模块的测试函数\nfunc main() {\n\n\t/*\n\t\t服务端测试\n\t*/\n\t//1 创建一个server 句柄 s\n\t// s := znet.NewServer(\"[zinx V0.2]\")\n\n\ts := znet.NewServer()\n\n\t//2 开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.3Test/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t_, err := conn.Write([]byte(\"Zinx V0.3\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\tbuf := make([]byte, 512)\n\t\tcnt, err := conn.Read(buf)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read buf error \")\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Printf(\" server call back : %s, cnt = %d\\n\", buf, cnt)\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.3Test/server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Test PreHandle\nfunc (this *PingRouter) PreHandle(request ziface.IRequest) {\n\tfmt.Println(\"Call Router PreHandle\")\n\t_, err := request.GetConnection().GetTCPConnection().Write([]byte(\"before ping ....\\n\"))\n\tif err != nil {\n\t\tfmt.Println(\"call back ping ping ping error\")\n\t}\n}\n\n// Test Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call PingRouter Handle\")\n\t_, err := request.GetConnection().GetTCPConnection().Write([]byte(\"ping...ping...ping\\n\"))\n\tif err != nil {\n\t\tfmt.Println(\"call back ping ping ping error\")\n\t}\n}\n\n// Test PostHandle\nfunc (this *PingRouter) PostHandle(request ziface.IRequest) {\n\tfmt.Println(\"Call Router PostHandle\")\n\t_, err := request.GetConnection().GetTCPConnection().Write([]byte(\"After ping .....\\n\"))\n\tif err != nil {\n\t\tfmt.Println(\"call back ping ping ping error\")\n\t}\n}\n\nfunc main() {\n\t//创建一个server句柄\n\t// s := znet.NewServer(\"[zinx V0.3]\")\n\ts := znet.NewServer()\n\n\t// s.AddRouter(&PingRouter{})\n\ts.AddRouter(3, &PingRouter{})\n\t//2 开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.4Test/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t_, err := conn.Write([]byte(\"Zinx V0.3\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\tbuf := make([]byte, 512)\n\t\tcnt, err := conn.Read(buf)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read buf error \")\n\t\t\treturn\n\t\t}\n\n\t\tfmt.Printf(\" server call back : %s, cnt = %d\\n\", buf, cnt)\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.4Test/server/conf/zinx.json",
    "content": "{\r\n  \"Name\":\"zinx v-0.4 demoApp\",\r\n  \"Host\":\"127.0.0.1\",\r\n  \"TcpPort\":7777,\r\n  \"MaxConn\":3\r\n}\r\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.4Test/server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Test PreHandle\nfunc (this *PingRouter) PreHandle(request ziface.IRequest) {\n\tfmt.Println(\"Call Router PreHandle\")\n\t_, err := request.GetConnection().GetTCPConnection().Write([]byte(\"before ping ....\\n\"))\n\tif err != nil {\n\t\tfmt.Println(\"call back ping ping ping error\")\n\t}\n}\n\n// Test Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call PingRouter Handle\")\n\t_, err := request.GetConnection().GetTCPConnection().Write([]byte(\"ping...ping...ping\\n\"))\n\tif err != nil {\n\t\tfmt.Println(\"call back ping ping ping error\")\n\t}\n}\n\n// Test PostHandle\nfunc (this *PingRouter) PostHandle(request ziface.IRequest) {\n\tfmt.Println(\"Call Router PostHandle\")\n\t_, err := request.GetConnection().GetTCPConnection().Write([]byte(\"After ping .....\\n\"))\n\tif err != nil {\n\t\tfmt.Println(\"call back ping ping ping error\")\n\t}\n}\n\nfunc main() {\n\t//创建一个server句柄\n\ts := znet.NewServer()\n\n\t//配置路由\n\t// s.AddRouter(&PingRouter{})\n\ts.AddRouter(4, &PingRouter{})\n\n\t//开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.5Test/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte(\"Zinx V0.5 Client Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.5Test/server/conf/zinx.json",
    "content": "{\r\n  \"Name\":\"zinx v-0.5 demoApp\",\r\n  \"Host\":\"127.0.0.1\",\r\n  \"TcpPort\":7777,\r\n  \"MaxConn\":3\r\n}\r\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.5Test/server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Test Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call PingRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\t//回写数据\n\t/*\n\t\t_, err := request.GetConnection().GetTCPConnection().Write([]byte(\"ping...ping...ping\\n\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(\"call back ping ping ping error\")\n\t\t}\n\t*/\n\terr := request.GetConnection().SendMsg(1, []byte(\"ping...ping...ping\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc main() {\n\t//创建一个server句柄\n\ts := znet.NewServer()\n\n\t//配置路由\n\ts.AddRouter(5, &PingRouter{})\n\n\t//开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.6Test-V0.7Test/client0/Client0.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte(\"Zinx V0.6 Client0 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.6Test-V0.7Test/client1/Client1.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(\"Zinx V0.6 Client1 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.6Test-V0.7Test/server/Server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Ping Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call PingRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendMsg(0, []byte(\"ping...ping...ping\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\ntype HelloZinxRouter struct {\n\tznet.BaseRouter\n}\n\n// HelloZinxRouter Handle\nfunc (this *HelloZinxRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call HelloZinxRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendMsg(1, []byte(\"Hello Zinx Router V0.6\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc main() {\n\t//创建一个server句柄\n\ts := znet.NewServer()\n\n\t//配置路由\n\ts.AddRouter(0, &PingRouter{})\n\ts.AddRouter(1, &HelloZinxRouter{})\n\n\t//开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.6Test-V0.7Test/server/conf/zinx.json",
    "content": "{\r\n  \"Name\":\"zinx v-0.6 demoApp\",\r\n  \"Host\":\"127.0.0.1\",\r\n  \"TcpPort\":7777,\r\n  \"MaxConn\":3\r\n}\r\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.8Test/client0/Client0.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte(\"Zinx V0.8 Client0 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.8Test/client1/Client1.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(\"Zinx V0.8 Client1 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.8Test/server/Server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Ping Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call PingRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendMsg(0, []byte(\"ping...ping...ping\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\ntype HelloZinxRouter struct {\n\tznet.BaseRouter\n}\n\n// HelloZinxRouter Handle\nfunc (this *HelloZinxRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call HelloZinxRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendMsg(1, []byte(\"Hello Zinx Router V0.8\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc main() {\n\t//创建一个server句柄\n\ts := znet.NewServer()\n\n\t//配置路由\n\ts.AddRouter(0, &PingRouter{})\n\ts.AddRouter(1, &HelloZinxRouter{})\n\n\t//开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.8Test/server/conf/zinx.json",
    "content": "{\r\n  \"Name\":\"zinx v-0.8 demoApp\",\r\n  \"Host\":\"127.0.0.1\",\r\n  \"TcpPort\":7777,\r\n  \"MaxConn\":3,\r\n  \"WorkerPoolSize\":10\r\n}\r\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.9Test/client0/Client0.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte(\"Zinx V0.8 Client0 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.9Test/client1/Client1.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n\t\"time\"\n)\n\n/*\n模拟客户端\n*/\nfunc main() {\n\n\tfmt.Println(\"Client Test ... start\")\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor n := 3; n >= 0; n-- {\n\t\t//发封包message消息\n\t\tdp := zpack.NewDataPack()\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(\"Zinx V0.8 Client1 Test Message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"write error err \", err)\n\t\t\treturn\n\t\t}\n\n\t\t//先读出流中的head部分\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\tif err != nil {\n\t\t\tfmt.Println(\"read head error\")\n\t\t\tbreak\n\t\t}\n\t\t//将headData字节流 拆包到msg中\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t//根据dataLen从io中读取字节流\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t}\n\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.9Test/server/Server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\n// ping test 自定义路由\ntype PingRouter struct {\n\tznet.BaseRouter\n}\n\n// Ping Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call PingRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendBuffMsg(0, []byte(\"ping...ping...ping\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\ntype HelloZinxRouter struct {\n\tznet.BaseRouter\n}\n\n// HelloZinxRouter Handle\nfunc (this *HelloZinxRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call HelloZinxRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgId=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendBuffMsg(1, []byte(\"Hello Zinx Router V0.8\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\n// 创建连接的时候执行\nfunc DoConnectionBegin(conn ziface.IConnection) {\n\tfmt.Println(\"DoConnectionBegin is Called ... \")\n\terr := conn.SendMsg(2, []byte(\"DoConnection BEGIN...\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\n// 连接断开的时候执行\nfunc DoConnectionLost(conn ziface.IConnection) {\n\tfmt.Println(\"DoConneciotnLost is Called ... \")\n}\n\nfunc main() {\n\t//创建一个server句柄\n\ts := znet.NewServer()\n\n\t//注册连接hook回调函数\n\ts.SetOnConnStart(DoConnectionBegin)\n\ts.SetOnConnStop(DoConnectionLost)\n\n\t//配置路由\n\ts.AddRouter(0, &PingRouter{})\n\ts.AddRouter(1, &HelloZinxRouter{})\n\n\t//开启服务\n\ts.Serve()\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/ZinxV0.9Test/server/conf/zinx.json",
    "content": "{\r\n  \"Name\":\"zinx v-0.8 demoApp\",\r\n  \"Host\":\"127.0.0.1\",\r\n  \"TcpPort\":7777,\r\n  \"MaxConn\":3,\r\n  \"WorkerPoolSize\":10\r\n}\r\n"
  },
  {
    "path": "examples/zinx_version_ex/datapackDemo/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"net\"\n)\n\nfunc main() {\n\t//客户端goroutine，负责模拟粘包的数据，然后进行发送\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"client dial err:\", err)\n\t\treturn\n\t}\n\n\t//创建一个封包对象 dp\n\tdp := zpack.NewDataPack()\n\n\t//封装一个msg1包\n\tmsg1 := &zpack.Message{\n\t\tID:      0,\n\t\tDataLen: 5,\n\t\tData:    []byte{'h', 'e', 'l', 'l', 'o'},\n\t}\n\n\tsendData1, err := dp.Pack(msg1)\n\tif err != nil {\n\t\tfmt.Println(\"client pack msg1 err:\", err)\n\t\treturn\n\t}\n\n\tmsg2 := &zpack.Message{\n\t\tID:      1,\n\t\tDataLen: 7,\n\t\tData:    []byte{'w', 'o', 'r', 'l', 'd', '!', '!'},\n\t}\n\tsendData2, err := dp.Pack(msg2)\n\tif err != nil {\n\t\tfmt.Println(\"client temp msg2 err:\", err)\n\t\treturn\n\t}\n\n\t//将sendData1，和 sendData2 拼接一起，组成粘包\n\tsendData1 = append(sendData1, sendData2...)\n\n\t//向服务器端写数据\n\tconn.Write(sendData1)\n\n\t//客户端阻塞\n\tselect {}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/datapackDemo/server/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"io\"\n\t\"net\"\n)\n\n// 只是负责测试datapack拆包，封包功能\nfunc main() {\n\t//创建socket TCP Server\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"server listen err:\", err)\n\t\treturn\n\t}\n\n\t//创建服务器gotoutine，负责从客户端goroutine读取粘包的数据，然后进行解析\n\n\tfor {\n\t\tconn, err := listener.Accept()\n\t\tif err != nil {\n\t\t\tfmt.Println(\"server accept err:\", err)\n\t\t}\n\n\t\t//处理客户端请求\n\t\tgo func(conn net.Conn) {\n\t\t\t//创建封包拆包对象dp\n\t\t\tdp := zpack.NewDataPack()\n\t\t\tfor {\n\t\t\t\t//1 先读出流中的head部分\n\t\t\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t\t\t_, err := io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Println(\"read head error\")\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t\t//将headData字节流 拆包到msg中\n\t\t\t\tmsgHead, err := dp.Unpack(headData)\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\n\t\t\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t\t\t//msg 是有data数据的，需要再次读取data数据\n\t\t\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t\t\t//根据dataLen从io中读取字节流\n\t\t\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t\t\t}\n\t\t\t}\n\t\t}(conn)\n\t}\n\n\t//阻塞\n\tselect {}\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/protoDemo/main.go",
    "content": "package main\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"github.com/aceld/zinx/examples/zinx_version_ex/protoDemo/pb\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\nfunc main() {\n\tperson := &pb.Person{\n\t\tName:   \"XiaoYuer\",\n\t\tAge:    16,\n\t\tEmails: []string{\"xiao_yu_er@sina.com\", \"yu_er@sina.cn\"},\n\t\tPhones: []*pb.PhoneNumber{\n\t\t\t{\n\t\t\t\tNumber: \"13113111311\",\n\t\t\t\tType:   pb.PhoneType_MOBILE,\n\t\t\t},\n\t\t\t{\n\t\t\t\tNumber: \"14141444144\",\n\t\t\t\tType:   pb.PhoneType_HOME,\n\t\t\t},\n\t\t\t{\n\t\t\t\tNumber: \"19191919191\",\n\t\t\t\tType:   pb.PhoneType_WORK,\n\t\t\t},\n\t\t},\n\t}\n\n\tdata, err := proto.Marshal(person)\n\tif err != nil {\n\t\tfmt.Println(\"marshal err:\", err)\n\t}\n\n\tfmt.Println(hex.EncodeToString(data))\n\n\tnewdata := &pb.Person{}\n\terr = proto.Unmarshal(data, newdata)\n\tif err != nil {\n\t\tfmt.Println(\"unmarshal err:\", err)\n\t}\n\tfmt.Println(newdata)\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/protoDemo/pb/Person.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// source: Person.proto\n\npackage pb\n\nimport (\n\tfmt \"fmt\"\n\tproto \"github.com/golang/protobuf/proto\"\n\tmath \"math\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package\n\n// enum为关键字，作用为定义一种枚举类型\ntype PhoneType int32\n\nconst (\n\tPhoneType_MOBILE PhoneType = 0\n\tPhoneType_HOME   PhoneType = 1\n\tPhoneType_WORK   PhoneType = 2\n)\n\nvar PhoneType_name = map[int32]string{\n\t0: \"MOBILE\",\n\t1: \"HOME\",\n\t2: \"WORK\",\n}\n\nvar PhoneType_value = map[string]int32{\n\t\"MOBILE\": 0,\n\t\"HOME\":   1,\n\t\"WORK\":   2,\n}\n\nfunc (x PhoneType) String() string {\n\treturn proto.EnumName(PhoneType_name, int32(x))\n}\n\nfunc (PhoneType) EnumDescriptor() ([]byte, []int) {\n\treturn fileDescriptor_841ab6396175eaf3, []int{0}\n}\n\n// message为关键字，作用为定义一种消息类型\ntype Person struct {\n\tName                 string         `protobuf:\"bytes,1,opt,name=name,proto3\" json:\"name,omitempty\"`\n\tAge                  int32          `protobuf:\"varint,2,opt,name=age,proto3\" json:\"age,omitempty\"`\n\tEmails               []string       `protobuf:\"bytes,3,rep,name=emails,proto3\" json:\"emails,omitempty\"`\n\tPhones               []*PhoneNumber `protobuf:\"bytes,4,rep,name=phones,proto3\" json:\"phones,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}       `json:\"-\"`\n\tXXX_unrecognized     []byte         `json:\"-\"`\n\tXXX_sizecache        int32          `json:\"-\"`\n}\n\nfunc (m *Person) Reset()         { *m = Person{} }\nfunc (m *Person) String() string { return proto.CompactTextString(m) }\nfunc (*Person) ProtoMessage()    {}\nfunc (*Person) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_841ab6396175eaf3, []int{0}\n}\n\nfunc (m *Person) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_Person.Unmarshal(m, b)\n}\nfunc (m *Person) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_Person.Marshal(b, m, deterministic)\n}\nfunc (m *Person) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Person.Merge(m, src)\n}\nfunc (m *Person) XXX_Size() int {\n\treturn xxx_messageInfo_Person.Size(m)\n}\nfunc (m *Person) XXX_DiscardUnknown() {\n\txxx_messageInfo_Person.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Person proto.InternalMessageInfo\n\nfunc (m *Person) GetName() string {\n\tif m != nil {\n\t\treturn m.Name\n\t}\n\treturn \"\"\n}\n\nfunc (m *Person) GetAge() int32 {\n\tif m != nil {\n\t\treturn m.Age\n\t}\n\treturn 0\n}\n\nfunc (m *Person) GetEmails() []string {\n\tif m != nil {\n\t\treturn m.Emails\n\t}\n\treturn nil\n}\n\nfunc (m *Person) GetPhones() []*PhoneNumber {\n\tif m != nil {\n\t\treturn m.Phones\n\t}\n\treturn nil\n}\n\n// message为关键字，作用为定义一种消息类型可以被另外的消息类型嵌套使用\ntype PhoneNumber struct {\n\tNumber               string    `protobuf:\"bytes,1,opt,name=number,proto3\" json:\"number,omitempty\"`\n\tType                 PhoneType `protobuf:\"varint,2,opt,name=type,proto3,enum=pb.PhoneType\" json:\"type,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *PhoneNumber) Reset()         { *m = PhoneNumber{} }\nfunc (m *PhoneNumber) String() string { return proto.CompactTextString(m) }\nfunc (*PhoneNumber) ProtoMessage()    {}\nfunc (*PhoneNumber) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_841ab6396175eaf3, []int{1}\n}\n\nfunc (m *PhoneNumber) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_PhoneNumber.Unmarshal(m, b)\n}\nfunc (m *PhoneNumber) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_PhoneNumber.Marshal(b, m, deterministic)\n}\nfunc (m *PhoneNumber) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_PhoneNumber.Merge(m, src)\n}\nfunc (m *PhoneNumber) XXX_Size() int {\n\treturn xxx_messageInfo_PhoneNumber.Size(m)\n}\nfunc (m *PhoneNumber) XXX_DiscardUnknown() {\n\txxx_messageInfo_PhoneNumber.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_PhoneNumber proto.InternalMessageInfo\n\nfunc (m *PhoneNumber) GetNumber() string {\n\tif m != nil {\n\t\treturn m.Number\n\t}\n\treturn \"\"\n}\n\nfunc (m *PhoneNumber) GetType() PhoneType {\n\tif m != nil {\n\t\treturn m.Type\n\t}\n\treturn PhoneType_MOBILE\n}\n\nfunc init() {\n\tproto.RegisterEnum(\"pb.PhoneType\", PhoneType_name, PhoneType_value)\n\tproto.RegisterType((*Person)(nil), \"pb.Person\")\n\tproto.RegisterType((*PhoneNumber)(nil), \"pb.PhoneNumber\")\n}\n\nfunc init() { proto.RegisterFile(\"Person.proto\", fileDescriptor_841ab6396175eaf3) }\n\nvar fileDescriptor_841ab6396175eaf3 = []byte{\n\t// 209 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8f, 0xcd, 0x4a, 0xc4, 0x30,\n\t0x14, 0x85, 0x4d, 0x13, 0x83, 0xbd, 0xe3, 0x4f, 0xb8, 0x0b, 0xc9, 0x32, 0xce, 0xc6, 0xa0, 0xd0,\n\t0xc5, 0xf8, 0x06, 0xc2, 0xc0, 0x88, 0x8e, 0x1d, 0x82, 0xe0, 0xba, 0x81, 0xa0, 0x82, 0x4d, 0x42,\n\t0x3b, 0x2e, 0xfa, 0xf6, 0x92, 0x34, 0x94, 0xd9, 0x7d, 0xf7, 0x3b, 0x70, 0x0e, 0x17, 0x2e, 0x0f,\n\t0x6e, 0x18, 0x83, 0x6f, 0xe2, 0x10, 0x8e, 0x01, 0xab, 0x68, 0xd7, 0x01, 0xf8, 0xec, 0x10, 0x81,\n\t0xf9, 0xae, 0x77, 0x92, 0x28, 0xa2, 0x6b, 0x93, 0x19, 0x05, 0xd0, 0xee, 0xcb, 0xc9, 0x4a, 0x11,\n\t0x7d, 0x6e, 0x12, 0xe2, 0x2d, 0x70, 0xd7, 0x77, 0x3f, 0xbf, 0xa3, 0xa4, 0x8a, 0xea, 0xda, 0x94,\n\t0x0b, 0xef, 0x81, 0xc7, 0xef, 0xe0, 0xdd, 0x28, 0x99, 0xa2, 0x7a, 0xb5, 0xb9, 0x69, 0xa2, 0x6d,\n\t0x0e, 0xc9, 0xbc, 0xff, 0xf5, 0xd6, 0x0d, 0xa6, 0xc4, 0xeb, 0x1d, 0xac, 0x4e, 0x74, 0xea, 0xf3,\n\t0x99, 0xca, 0x6e, 0xb9, 0xf0, 0x0e, 0xd8, 0x71, 0x8a, 0xf3, 0xf4, 0xf5, 0xe6, 0x6a, 0x69, 0xfb,\n\t0x98, 0xa2, 0x33, 0x39, 0x7a, 0x78, 0x84, 0x7a, 0x51, 0x08, 0xc0, 0xf7, 0xed, 0xf3, 0xcb, 0xdb,\n\t0x56, 0x9c, 0xe1, 0x05, 0xb0, 0x5d, 0xbb, 0xdf, 0x0a, 0x92, 0xe8, 0xb3, 0x35, 0xaf, 0xa2, 0xb2,\n\t0x3c, 0xbf, 0xfc, 0xf4, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xdf, 0x3c, 0xf1, 0xb8, 0x02, 0x01, 0x00,\n\t0x00,\n}\n"
  },
  {
    "path": "examples/zinx_version_ex/protoDemo/pb/Person.proto",
    "content": "syntax = \"proto3\"; \t\t\t\t\t\t//指定版本信息，不指定会报错\r\npackage pb;\t\t\t\t\t\t//后期生成go文件的包名\r\n\r\n//message为关键字，作用为定义一种消息类型\r\nmessage Person {\r\n\tstring\tname = 1;\t\t\t\t\t//姓名\r\n    int32\tage = 2;\t\t\t\t\t//年龄\r\n\trepeated string emails = 3; \t\t//电子邮件（repeated表示字段允许重复）\r\n\trepeated PhoneNumber phones = 4;\t//手机号\r\n}\r\n\r\n//enum为关键字，作用为定义一种枚举类型\r\nenum PhoneType {\r\n\tMOBILE = 0;\r\n    HOME = 1;\r\n    WORK = 2;\r\n}\r\n\r\n//message为关键字，作用为定义一种消息类型可以被另外的消息类型嵌套使用\r\nmessage PhoneNumber {\r\n    string number = 1;\r\n    PhoneType type = 2;\r\n}"
  },
  {
    "path": "examples/zinx_websocket/client/client.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"github.com/aceld/zinx/examples/zinx_client/c_router\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"os\"\n\t\"os/signal\"\n\t\"time\"\n)\n\ntype PositionClientRouter struct {\n\tznet.BaseRouter\n}\n\nfunc (this *PositionClientRouter) Handle(request ziface.IRequest) {\n\n}\n\n// 客户端自定义业务\nfunc business(conn ziface.IConnection) {\n\n\tfor {\n\t\terr := conn.SendMsg(1, []byte(\"ping ping ping ...\"))\n\t\tif err != nil {\n\t\t\tfmt.Println(err)\n\n\t\t}\n\t\ttime.Sleep(1 * time.Second)\n\t}\n}\n\n// 创建连接的时候执行\nfunc DoClientConnectedBegin(conn ziface.IConnection) {\n\t//设置两个连接属性，在连接创建之后\n\tconn.SetProperty(\"Name\", \"刘丹冰\")\n\tconn.SetProperty(\"Home\", \"https://yuque.com/aceld\")\n\n\tgo business(conn)\n}\n\nfunc wait() {\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n}\n\nfunc main() {\n\t// Create a Client.\n\tclient := znet.NewWsClient(\"127.0.0.1\", 9000)\n\n\t// Add business logic for when the connection is first established.(添加首次建立连接时的业务)\n\tclient.SetOnConnStart(DoClientConnectedBegin)\n\t// Register business routing for receiving messages from the server.(注册收到服务器消息业务路由)\n\tclient.AddRouter(2, &c_router.PingRouter{})\n\tclient.AddRouter(3, &c_router.HelloRouter{})\n\t// Start the client.\n\tclient.Start()\n\tselect {\n\tcase err := <-client.GetErrChan():\n\t\t// Handle the errors returned by the client.(处理客户端返回的错误)\n\t\tzlog.Ins().ErrorF(\"client err:%v\", err)\n\t}\n\n\t// close\n\tc := make(chan os.Signal, 1)\n\tsignal.Notify(c, os.Interrupt, os.Kill)\n\tsig := <-c\n\tfmt.Println(\"===exit===\", sig)\n\t// Clean up the client.(清理客户端)\n\tclient.Stop()\n\ttime.Sleep(time.Second * 2)\n}\n"
  },
  {
    "path": "examples/zinx_websocket/minicode/.eslintrc.js",
    "content": "/*\n * Eslint config file\n * Documentation: https://eslint.org/docs/user-guide/configuring/\n * Install the Eslint extension before using this feature.\n */\nmodule.exports = {\n  env: {\n    es6: true,\n    browser: true,\n    node: true,\n  },\n  ecmaFeatures: {\n    modules: true,\n  },\n  parserOptions: {\n    ecmaVersion: 2018,\n    sourceType: 'module',\n  },\n  globals: {\n    wx: true,\n    App: true,\n    Page: true,\n    getCurrentPages: true,\n    getApp: true,\n    Component: true,\n    requirePlugin: true,\n    requireMiniProgram: true,\n  },\n  // extends: 'eslint:recommended',\n  rules: {},\n}\n"
  },
  {
    "path": "examples/zinx_websocket/minicode/app.js",
    "content": "const Buffer = require(\"buffer\").Buffer;\nApp({\n    onLaunch() {\n        const socket = wx.connectSocket({\n            url: 'ws://localhost:9000',\n            protocols:[\"12321\",\"321321321\",32132121]\n        })\n        wx.onSocketOpen((result) => {\n            console.log(\"连接成功\")\n            wx.sendSocketMessage({\n                data: this.encodeTLV(1, \"hello\"),\n\n\n            })\n        })\n        socket.onMessage(result => {\n            let message = this.decodeTLV(result.data)\n            console.log(message)\n        })\n    },\n\n    globalData: {\n        TYPE_LENGTH: 4,\n        LENGTH_LENGTH: 4,\n    },\n    // 将数据编码为 TLV 格式\n    encodeTLV(type, value) {\n        const length = value.length;\n        const typeBuffer = Buffer.alloc(this.globalData.TYPE_LENGTH);\n        const lengthBuffer = Buffer.alloc(this.globalData.LENGTH_LENGTH);\n        const valueBuffer = Buffer.from(value);\n        typeBuffer.writeUInt32BE(type, 0);\n        lengthBuffer.writeUInt32BE(length, 0);\n        return Buffer.concat([typeBuffer, lengthBuffer, valueBuffer], this.globalData.TYPE_LENGTH + this.globalData.LENGTH_LENGTH + length);\n    },\n\n    // 从 TLV 格式解码数据\n    decodeTLV(buffer) {\n        // 解析包头长度\n        let data = Buffer.from(buffer)\n        const tag = Buffer.alloc(this.globalData.TYPE_LENGTH);\n        let offset = 0;\n        data.copy(tag, 0, offset, 4)\n        const dataLen = Buffer.alloc(this.globalData.LENGTH_LENGTH);\n        offset += 4;\n        data.copy(dataLen, 0, offset, offset + 4)\n        // 解析数据包内容\n        const body = new Buffer(dataLen.readInt32BE())\n        offset += 4\n        data.copy(body, 0, offset, offset + dataLen.readInt32BE());\n        let message = {\n            tag: tag.readUInt32BE(),\n            dataLen: dataLen.readUInt32BE(),\n            data: body\n        }\n        return message\n    }\n})"
  },
  {
    "path": "examples/zinx_websocket/minicode/app.json",
    "content": "{\n  \"pages\": [\n    \"index/index\"\n  ],\n  \"window\": {\n    \"backgroundTextStyle\": \"light\",\n    \"navigationBarBackgroundColor\": \"#fff\",\n    \"navigationBarTitleText\": \"Weixin\",\n    \"navigationBarTextStyle\": \"black\"\n  },\n  \"style\": \"v2\",\n  \"sitemapLocation\": \"sitemap.json\",\n  \"lazyCodeLoading\": \"requiredComponents\"\n}\n"
  },
  {
    "path": "examples/zinx_websocket/minicode/app.wxss",
    "content": ""
  },
  {
    "path": "examples/zinx_websocket/minicode/index/index.js",
    "content": "const app = getApp()\n\nPage({\n  data: {\n\n  },\n  onLoad() {\n    console.log('代码片段是一种迷你、可分享的小程序或小游戏项目，可用于分享小程序和小游戏的开发经验、展示组件和 API 的使用、复现开发问题和 Bug 等。可点击以下连接查看代码片段的详细文档：')\n    console.log('https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/devtools.html')\n  },\n})\n"
  },
  {
    "path": "examples/zinx_websocket/minicode/index/index.json",
    "content": "{\n  \"usingComponents\": {}\n}"
  },
  {
    "path": "examples/zinx_websocket/minicode/index/index.wxml",
    "content": "<view class=\"intro\">欢迎使用代码片段，可在控制台查看代码片段的说明和文档</view>"
  },
  {
    "path": "examples/zinx_websocket/minicode/index/index.wxss",
    "content": ".intro {\n  margin: 30px;\n  text-align: center;\n}"
  },
  {
    "path": "examples/zinx_websocket/minicode/package.json",
    "content": "{\n  \"dependencies\": {\n    \"buffer\": \"^6.0.3\"\n  }\n}\n"
  },
  {
    "path": "examples/zinx_websocket/minicode/project.config.json",
    "content": "{\n    \"appid\": \"wx0b3c079df9f406b6\",\n    \"compileType\": \"miniprogram\",\n    \"libVersion\": \"2.30.4\",\n    \"packOptions\": {\n        \"ignore\": [],\n        \"include\": []\n    },\n    \"setting\": {\n        \"coverView\": true,\n        \"es6\": true,\n        \"postcss\": true,\n        \"minified\": true,\n        \"enhance\": true,\n        \"showShadowRootInWxmlPanel\": true,\n        \"packNpmRelationList\": [],\n        \"babelSetting\": {\n            \"ignore\": [],\n            \"disablePlugins\": [],\n            \"outputPath\": \"\"\n        },\n        \"condition\": false\n    },\n    \"condition\": {},\n    \"editorSetting\": {\n        \"tabIndent\": \"insertSpaces\",\n        \"tabSize\": 4\n    }\n}"
  },
  {
    "path": "examples/zinx_websocket/minicode/project.private.config.json",
    "content": "{\n    \"description\": \"项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档：https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html\",\n    \"projectname\": \"minicode-15\",\n    \"setting\": {\n        \"compileHotReLoad\": true,\n        \"urlCheck\": false\n    }\n}"
  },
  {
    "path": "examples/zinx_websocket/minicode/sitemap.json",
    "content": "{\n  \"desc\": \"关于本文件的更多信息，请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html\",\n  \"rules\": [{\n  \"action\": \"allow\",\n  \"page\": \"*\"\n  }]\n}"
  },
  {
    "path": "examples/zinx_websocket/server/server.go",
    "content": "package main\n\nimport (\n\t\"github.com/aceld/zinx/examples/zinx_server/s_router\"\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\nfunc main() {\n\t// Set up as WebSocket before starting. (在启动之前设置为 websocket)\n\tzconf.GlobalObject.Mode = \"\"\n\tzconf.GlobalObject.LogFile = \"\"\n\n\ts := znet.NewServer()\n\n\ts.AddRouter(100, &s_router.PingRouter{})\n\ts.AddRouter(1, &s_router.HelloZinxRouter{})\n\n\ts.Serve()\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/aceld/zinx\n\ngo 1.23.0\n\nrequire (\n\tgithub.com/gorilla/websocket v1.5.0\n\tgithub.com/stretchr/testify v1.8.1\n\tgithub.com/xtaci/kcp-go v5.4.20+incompatible\n\tgolang.org/x/net v0.38.0 // indirect\n\tgoogle.golang.org/protobuf v1.33.0 // indirect\n)\n\nrequire github.com/golang/protobuf v1.5.0\n\nrequire (\n\tgithub.com/davecgh/go-spew v1.1.1 // indirect\n\tgithub.com/klauspost/cpuid/v2 v2.1.1 // indirect\n\tgithub.com/klauspost/reedsolomon v1.11.8 // indirect\n\tgithub.com/pkg/errors v0.9.1 // indirect\n\tgithub.com/pmezard/go-difflib v1.0.0 // indirect\n\tgithub.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect\n\tgithub.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect\n\tgithub.com/tjfoc/gmsm v1.4.1 // indirect\n\tgithub.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect\n\tgolang.org/x/crypto v0.36.0 // indirect\n\tgolang.org/x/sys v0.31.0 // indirect\n\tgopkg.in/yaml.v3 v3.0.1 // indirect\n)\n"
  },
  {
    "path": "go.sum",
    "content": "cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=\ngithub.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=\ngithub.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=\ngithub.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=\ngithub.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=\ngithub.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=\ngithub.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=\ngithub.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=\ngithub.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=\ngithub.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=\ngithub.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=\ngithub.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=\ngithub.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=\ngithub.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=\ngithub.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=\ngithub.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=\ngithub.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=\ngithub.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=\ngithub.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=\ngithub.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=\ngithub.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4=\ngithub.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=\ngithub.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=\ngithub.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=\ngithub.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=\ngithub.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=\ngithub.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=\ngithub.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=\ngithub.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0=\ngithub.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=\ngithub.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY=\ngithub.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A=\ngithub.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=\ngithub.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngithub.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=\ngithub.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=\ngithub.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=\ngithub.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=\ngithub.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=\ngithub.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=\ngithub.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=\ngithub.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=\ngithub.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=\ngithub.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=\ngithub.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU=\ngithub.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU=\ngithub.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI=\ngithub.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4=\ngithub.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho=\ngithub.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE=\ngithub.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg=\ngithub.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=\ngithub.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E=\ngithub.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0=\ngolang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=\ngolang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=\ngolang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=\ngolang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=\ngolang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=\ngolang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=\ngolang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=\ngolang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=\ngolang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=\ngolang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=\ngolang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=\ngolang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=\ngolang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=\ngolang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=\ngolang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\ngolang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=\ngolang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=\ngolang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=\ngolang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=\ngolang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=\ngolang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=\ngolang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=\ngolang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=\ngolang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=\ngolang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=\ngolang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=\ngoogle.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=\ngoogle.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=\ngoogle.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=\ngoogle.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=\ngoogle.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=\ngoogle.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=\ngoogle.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=\ngoogle.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=\ngoogle.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=\ngoogle.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=\ngoogle.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=\ngoogle.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=\ngoogle.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=\ngoogle.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=\ngoogle.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=\ngoogle.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=\ngoogle.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=\ngopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=\ngopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\ngopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=\ngopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=\nhonnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\nhonnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=\n"
  },
  {
    "path": "logo/zinxlogo.go",
    "content": "package logo\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/zconf\"\n)\n\nvar zinxLogo = `                                        \n              ██                        \n              ▀▀                        \n ████████   ████     ██▄████▄  ▀██  ██▀ \n     ▄█▀      ██     ██▀   ██    ████   \n   ▄█▀        ██     ██    ██    ▄██▄   \n ▄██▄▄▄▄▄  ▄▄▄██▄▄▄  ██    ██   ▄█▀▀█▄  \n ▀▀▀▀▀▀▀▀  ▀▀▀▀▀▀▀▀  ▀▀    ▀▀  ▀▀▀  ▀▀▀ \n                                        `\nvar topLine = `┌──────────────────────────────────────────────────────┐`\nvar borderLine = `│`\nvar bottomLine = `└──────────────────────────────────────────────────────┘`\n\nfunc PrintLogo() {\n\tfmt.Println(zinxLogo)\n\tfmt.Println(topLine)\n\tfmt.Println(fmt.Sprintf(\"%s [Github] https://github.com/aceld                    %s\", borderLine, borderLine))\n\tfmt.Println(fmt.Sprintf(\"%s [tutorial] https://www.yuque.com/aceld/npyr8s/bgftov %s\", borderLine, borderLine))\n\tfmt.Println(fmt.Sprintf(\"%s [document] https://www.yuque.com/aceld/tsgooa        %s\", borderLine, borderLine))\n\tfmt.Println(bottomLine)\n\tfmt.Printf(\"[Zinx] Version: %s, MaxConn: %d, MaxPacketSize: %d\\n\",\n\t\tzconf.GlobalObject.Version,\n\t\tzconf.GlobalObject.MaxConn,\n\t\tzconf.GlobalObject.MaxPacketSize)\n}\n"
  },
  {
    "path": "zasync_op/async_op.go",
    "content": "/*\nPackage zasync_op\n@Author：14March\n@File：async_op.go\n*/\npackage zasync_op\n\nimport (\n\t\"sync\"\n)\n\n/*\n\t<异步IO模块简介>\n\n1.业务线程执行业务操作，发送一个IO请求，由IO线程来完成写库，如果写完库之后，还有其他操作呢？\n\ta.接下来的逻辑就在 IO 线程里执行了；\n\tb.回到不是原来的业务线程，而是另一个业务线程执行；\n\t这2种情况，就相当于一部分业务逻辑在 A 线程里，一部分业务逻辑在 B 线程了；两个线程同时操作一块内存区域，会出现脏读写问题。\n\n2.因此，必须回到原本所属的业务线程里执行,意思就是说，业务逻辑原先是由谁来执行的，那么 IO 操作完成之后，继续交还给原来的人去执行。\n\n3.使用：\n\ta.调用 Process 选择一个异步worker进行异步IO操作逻辑；\n\tb.在异步IO逻辑中设置需要共享的变量，及异步返回结果：asyncResult.SetReturnedObj\n\tc.注册设置异步回调，即回到原本的业务线程里继续进行后续的操作：asyncResult.OnComplete\n*/\n\n/*\n\t<Asynchronous IO Module Introduction>\n\n1.Business threads execute business operations, send an IO request, and IO threads complete the write to the database. What if there are other operations after the write is complete?\n\ta. The next logic will be executed in the IO thread;\n\tb. Back to a different business thread instead of the original one.\n    These two situations mean that some business logic is in thread A and some in thread B. When two threads operate on the same memory area, dirty reads and writes occur.\n\n2.Therefore, the logic must return to the original business thread for execution, which means that the business logic was originally executed by whom, and after the IO operation is completed, it is returned to the original person to continue execution.\n\n3.Usage:\n\ta. Call Process to select an asynchronous worker for asynchronous IO operation logic;\n\tb. Set the variables that need to be shared in the asynchronous IO logic and the asynchronous return result: asyncResult.SetReturnedObj\n\tc. Register and set the asynchronous callback, that is, return to the original business thread to continue subsequent operations: asyncResult.OnComplete\n*/\n\n// Asynchronous worker group (异步worker组)\nvar asyncWorkerArray = [2048]*AsyncWorker{}\nvar onceArray = [2048]sync.Once{} // 每个元素对应一个 worker 的 Once\n\nfunc Process(opId int, asyncOp func()) {\n\tif asyncOp == nil {\n\t\treturn\n\t}\n\n\tcurWorker := getCurWorker(opId)\n\n\tif curWorker != nil {\n\t\tcurWorker.process(asyncOp)\n\t}\n\n}\n\nfunc getCurWorker(opId int) *AsyncWorker {\n\t// If opId is less than 0, convert it to its absolute value to ensure opId is positive\n\t// (如果 opId 小于 0，则取其绝对值，确保 opId 为正数)\n\tif opId < 0 {\n\t\topId = -opId\n\t}\n\n\t// 使用 opId 对工作者数组长度取模，确保根据 opId 获取到一个有效的工作者索引\n\t// (Use opId % len(asyncWorkerArray) to calculate a valid worker index)\n\tworkerIndex := opId % len(asyncWorkerArray)\n\n\t// Use sync.Once to ensure initialization happens only once for each worker\n\t// (使用 sync.Once 确保对每个工作者的初始化操作只会执行一次)\n\tonceArray[workerIndex].Do(func() {\n\t\t// Initialization: create a task queue for the worker at the current index and start a goroutine to execute tasks\n\t\t// (初始化操作：为当前索引的工作者创建任务队列，并启动一个 goroutine 执行任务)\n\t\tasyncWorkerArray[workerIndex] = &AsyncWorker{\n\t\t\ttaskQ: make(chan func(), 2048), // Create a task queue with a capacity of 2048\n\t\t}\n\n\t\t// Start a goroutine to repeatedly execute tasks for the worker\n\t\tgo asyncWorkerArray[workerIndex].loopExecTask()\n\t})\n\n\t// Return the worker at the corresponding index\n\treturn asyncWorkerArray[workerIndex]\n}\n"
  },
  {
    "path": "zasync_op/async_op_result.go",
    "content": "/*\n\tPackage zasync_op\n\t@Author：14March\n\t@File：async_op_result.go\n*/\n\npackage zasync_op\n\nimport (\n\t\"sync/atomic\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n)\n\ntype AsyncOpResult struct {\n\t// Player connection (玩家连接)\n\tconn ziface.IConnection\n\t// Returned object (已返回对象)\n\treturnedObj interface{}\n\t// Completion callback function(完成回调函数)\n\tcompleteFunc atomic.Value\n\t// Whether the return value has been set(是否已有返回值)\n\thasReturnedObj int32\n\t// Whether the completion callback function has been set(是否已有回调函数)\n\thasCompleteFunc int32\n\t// Whether the completion function has already been called(是否已经调用过完成函数)\n\tcompleteFuncHasAlreadyBeenCalled int32 // Default value = 0, not yet called (默认值 = 0, 没被调用过)\n}\n\n// GetReturnedObj returns the return value (获取返回值)\nfunc (aor *AsyncOpResult) GetReturnedObj() interface{} {\n\treturn aor.returnedObj\n}\n\n// SetReturnedObj sets the return value(设置返回值)\nfunc (aor *AsyncOpResult) SetReturnedObj(val interface{}) {\n\tif atomic.CompareAndSwapInt32(&aor.hasReturnedObj, 0, 1) {\n\t\taor.returnedObj = val\n\t\t// **** 防止未调用回调函数问题: 设置处理结果时，直接调用回调函数：1.回调函数未绑定，调用空；2.回调函数已绑定,立马调用 ****\n\t\t// **** Prevent the problem of not calling the completion callback function:\n\t\t// Call the callback function directly when setting the processing result:\n\t\t// 1. If the callback function is not bound, it will be called with nil;\n\t\t// 2. If the callback function is bound, it will be called immediately. ****\n\t\taor.doComplete()\n\t}\n}\n\n// OnComplete sets the completion callback function(完成回调函数)\nfunc (aor *AsyncOpResult) OnComplete(val func()) {\n\tif atomic.CompareAndSwapInt32(&aor.hasCompleteFunc, 0, 1) {\n\t\taor.completeFunc.Store(val)\n\n\t\t// **** 防止未调用回调函数问题:设置回调函数时，发现已经有处理结果了，直接调用 ****\n\t\t// **** Prevent the problem of not calling the completion callback function:\n\t\t// If a processing result already exists when setting the callback function,\n\t\t// call the callback function directly. ****\n\t\tif atomic.LoadInt32(&aor.hasReturnedObj) == 1 {\n\t\t\taor.doComplete()\n\t\t}\n\t}\n}\n\n// doComplete executes the completion callback function, double-checking when setting the processing result and callback function to prevent the possibility of not calling the callback function\n// (执行完成回调,在设置处理结果和回调函数的时候双重检查，杜绝未调用回调函数的可能性)\nfunc (aor *AsyncOpResult) doComplete() {\n\tv := aor.completeFunc.Load()\n\tif v == nil {\n\t\treturn\n\t}\n\n\tcf := v.(func())\n\n\t// Prevent re-entry problems (防止可重入问题)\n\tif atomic.CompareAndSwapInt32(&aor.completeFuncHasAlreadyBeenCalled, 0, 1) {\n\t\t// Prevent cross-thread calling problems,\n\t\t// throw it to the corresponding business thread to execute\n\t\t// (防止跨线程调用问题,扔到所属业务线程里去执行)\n\t\trequest := znet.NewFuncRequest(aor.conn, cf)\n\t\taor.conn.GetMsgHandler().SendMsgToTaskQueue(request)\n\t}\n}\n\n// NewAsyncOpResult creates a new asynchronous result(新建异步结果)\nfunc NewAsyncOpResult(conn ziface.IConnection) *AsyncOpResult {\n\tresult := &AsyncOpResult{}\n\tresult.conn = conn\n\treturn result\n}\n"
  },
  {
    "path": "zasync_op/async_worker.go",
    "content": "/*\n\tPackage zasync_op\n\t@Author：14March\n\t@File：async_worker.go\n*/\n\npackage zasync_op\n\nimport \"github.com/aceld/zinx/zlog\"\n\ntype AsyncWorker struct {\n\ttaskQ chan func()\n}\n\nfunc (aw *AsyncWorker) process(asyncOp func()) {\n\tif asyncOp == nil {\n\t\tzlog.Error(\"Async operation is empty.\")\n\t\treturn\n\t}\n\n\tif aw.taskQ == nil {\n\t\tzlog.Error(\"Task queue has not been initialized.\")\n\t\treturn\n\t}\n\n\taw.taskQ <- func() {\n\t\tdefer func() {\n\t\t\tif err := recover(); err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\"async process panic: %v\", err)\n\t\t\t}\n\t\t}()\n\n\t\t// Execute async operation.(执行异步操作)\n\t\tasyncOp()\n\t}\n}\n\nfunc (aw *AsyncWorker) loopExecTask() {\n\tif aw.taskQ == nil {\n\t\tzlog.Error(\"The task queue has not been initialized.\")\n\t\treturn\n\t}\n\n\tfor {\n\t\ttask := <-aw.taskQ\n\t\tif task != nil {\n\t\t\ttask()\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "zconf/env.go",
    "content": "/**\n * User: coder.sdp@gmail.com\n * Date: 2023/9/8\n * Time: 14:18\n */\n\npackage zconf\n\nimport (\n\t\"os\"\n\t\"path/filepath\"\n)\n\nconst (\n\t// EnvConfigFilePathKey (Set configuration file path export ZINX_CONFIG_FILE_PATH = xxxxxxzinx.json)\n\t// (设置配置文件路径 export ZINX_CONFIG_FILE_PATH = xxx/xxx/zinx.json)\n\tEnvConfigFilePathKey     = \"ZINX_CONFIG_FILE_PATH\"\n\tEnvDefaultConfigFilePath = \"/conf/zinx.json\"\n)\n\nvar env = new(zEnv)\n\ntype zEnv struct {\n\tconfigFilePath string\n}\n\nfunc init() {\n\tconfigFilePath := os.Getenv(EnvConfigFilePathKey)\n\tif configFilePath == \"\" {\n\t\tpwd, err := os.Getwd()\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t\tconfigFilePath = filepath.Join(pwd, EnvDefaultConfigFilePath)\n\t}\n\tvar err error\n\tconfigFilePath, err = filepath.Abs(configFilePath)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\tenv.configFilePath = configFilePath\n}\n\nfunc GetConfigFilePath() string {\n\treturn env.configFilePath\n}\n"
  },
  {
    "path": "zconf/userconf.go",
    "content": "package zconf\n\nimport \"github.com/aceld/zinx/zlog\"\n\n// UserConfToGlobal, Note that if UserConf is used,\n// the method should be called to synchronize with GlobalConfObject\n// because other parameters are called from this structure parameter.\n// (注意如果使用UserConf应该调用方法同步至 GlobalConfObject 因为其他参数是调用的此结构体参数)\nfunc UserConfToGlobal(config *Config) {\n\n\t// Server\n\tif config.Name != \"\" {\n\t\tGlobalObject.Name = config.Name\n\t}\n\tif config.Host != \"\" {\n\t\tGlobalObject.Host = config.Host\n\t}\n\tif config.TCPPort != 0 {\n\t\tGlobalObject.TCPPort = config.TCPPort\n\t}\n\n\t// Zinx\n\tif config.Version != \"\" {\n\t\tGlobalObject.Version = config.Version\n\t}\n\tif config.MaxPacketSize != 0 {\n\t\tGlobalObject.MaxPacketSize = config.MaxPacketSize\n\t}\n\tif config.MaxConn != 0 {\n\t\tGlobalObject.MaxConn = config.MaxConn\n\t}\n\tif config.WorkerPoolSize != 0 {\n\t\tGlobalObject.WorkerPoolSize = config.WorkerPoolSize\n\t}\n\tif config.MaxWorkerTaskLen != 0 {\n\t\tGlobalObject.MaxWorkerTaskLen = config.MaxWorkerTaskLen\n\t}\n\tif config.WorkerMode != \"\" {\n\t\tGlobalObject.WorkerMode = config.WorkerMode\n\t}\n\n\tif config.MaxMsgChanLen != 0 {\n\t\tGlobalObject.MaxMsgChanLen = config.MaxMsgChanLen\n\t}\n\tif config.IOReadBuffSize != 0 {\n\t\tGlobalObject.IOReadBuffSize = config.IOReadBuffSize\n\t}\n\n\t// logger\n\t// By default, it is False. If the config is not initialized, the default configuration will be used.\n\t// (默认是False, config没有初始化即使用默认配置)\n\tGlobalObject.LogIsolationLevel = config.LogIsolationLevel\n\tif GlobalObject.LogIsolationLevel > zlog.LogDebug {\n\t\tzlog.SetLogLevel(GlobalObject.LogIsolationLevel)\n\t}\n\n\t// Different from the required fields mentioned above, the logging module should use the default configuration if it is not configured.\n\t// (不同于上方必填项 日志目前如果没配置应该使用默认配置)\n\tif config.LogDir != \"\" {\n\t\tGlobalObject.LogDir = config.LogDir\n\t}\n\n\tif config.LogFile != \"\" {\n\t\tGlobalObject.LogFile = config.LogFile\n\t\tzlog.SetLogFile(GlobalObject.LogDir, GlobalObject.LogFile)\n\t}\n\tif config.LogSaveDays > 0 {\n\t\tGlobalObject.LogSaveDays = config.LogSaveDays\n\t}\n\tif config.LogFileSize > 0 {\n\t\tGlobalObject.LogFileSize = config.LogFileSize\n\t}\n\tif config.LogCons {\n\t\tGlobalObject.LogCons = config.LogCons\n\t}\n\n\t// Keepalive\n\tif config.HeartbeatMax != 0 {\n\t\tGlobalObject.HeartbeatMax = config.HeartbeatMax\n\t}\n\n\t// TLS\n\tif config.CertFile != \"\" {\n\t\tGlobalObject.CertFile = config.CertFile\n\t}\n\tif config.PrivateKeyFile != \"\" {\n\t\tGlobalObject.PrivateKeyFile = config.PrivateKeyFile\n\t}\n\n\tif config.Mode != \"\" {\n\t\tGlobalObject.Mode = config.Mode\n\t}\n\tif config.WsPort != 0 {\n\t\tGlobalObject.WsPort = config.WsPort\n\t}\n\tif config.WsPath != \"\" {\n\t\tGlobalObject.WsPath = config.WsPath\n\t}\n\n\tif config.RouterSlicesMode {\n\t\tGlobalObject.RouterSlicesMode = config.RouterSlicesMode\n\t}\n\n\tif config.RequestPoolMode {\n\t\tGlobalObject.RequestPoolMode = config.RequestPoolMode\n\t}\n\n\tif config.KcpPort != 0 {\n\t\tGlobalObject.KcpPort = config.KcpPort\n\t}\n\n\tif config.KcpACKNoDelay {\n\t\tGlobalObject.KcpACKNoDelay = config.KcpACKNoDelay\n\t}\n\n\tif !config.KcpStreamMode {\n\t\tGlobalObject.KcpStreamMode = config.KcpStreamMode\n\t}\n\n\tif config.KcpNoDelay != 0 {\n\t\tGlobalObject.KcpNoDelay = config.KcpNoDelay\n\t}\n\n\tif config.KcpInterval != 0 {\n\t\tGlobalObject.KcpInterval = config.KcpInterval\n\t}\n\n\tif config.KcpResend != 0 {\n\t\tGlobalObject.KcpResend = config.KcpResend\n\t}\n\n\tif config.KcpNc != 0 {\n\t\tGlobalObject.KcpNc = config.KcpNc\n\t}\n\n\tif config.KcpSendWindow != 0 {\n\t\tGlobalObject.KcpSendWindow = config.KcpSendWindow\n\t}\n\n\tif config.KcpRecvWindow != 0 {\n\t\tGlobalObject.KcpRecvWindow = config.KcpRecvWindow\n\t}\n\n\tif config.KcpFecDataShards != 0 {\n\t\tGlobalObject.KcpFecDataShards = config.KcpFecDataShards\n\t}\n\n\tif config.KcpFecParityShards != 0 {\n\t\tGlobalObject.KcpFecParityShards = config.KcpFecParityShards\n\t}\n\n}\n"
  },
  {
    "path": "zconf/zconf.go",
    "content": "// @Title  globalobj.go\n// @Description  相关配置文件定义及加载方式\n// defines a configuration structure named \"Config\" along with its methods.\n// The package is named \"zconf\", and the file is named \"globalobj.go\".\n// @Author  Aceld - Thu Mar 11 10:32:29 CST 2019\npackage zconf\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"reflect\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zlog\"\n)\n\nconst (\n\tServerModeTcp       = \"tcp\"\n\tServerModeWebsocket = \"websocket\"\n\tServerModeKcp       = \"kcp\"\n)\n\nconst (\n\tWorkerModeHash = \"Hash\" // By default, the round-robin average allocation rule is used.(默认使用取余的方式)\n\tWorkerModeBind = \"Bind\" // Bind a worker to each connection.(为每个连接分配一个worker)\n\n\t//Hash 模式有阻塞的问题(虽然有异步的方式可解决)。\n\t//Bind 模式下，如果配置MaxConn值比较大的话 就会后台就会起很多worker在等着，当服务器接入连接较少时, 很多worker都是空闲; MaxConn 设置小的话，服务器能接入的连接就会受限。\n\n\t//WorkerModeDynamicBind 也是一个连接对应一个worker, 给连接分配worker时, 如果工作池里初始化的worker已经用完了，就动态创建一个worker绑定到每个连接。这个临时创建的worker, 会在连接断开后销毁。\n\t//跟WorkerModeHash的区别是，如果业务层回调有阻塞操作的话，也不影响其他连接的业务层处理。\n\t//跟WorkerModeBind的区别是，不需要像Bind模式那样一开始就创建很多worker,而是根据连接数动态创建worker，这样可以避免闲置worker数量过多导致的资源浪费。\n\tWorkerModeDynamicBind = \"DynamicBind\" // Dynamic binding of a worker to each connection when there is no worker in worker pool.(临时动态创建一个worker绑定到每个连接)\n)\n\n/*\n\t   Store all global parameters related to the Zinx framework for use by other modules.\n\t   Some parameters can also be configured by the user based on the zinx.json file.\n\t\t(存储一切有关Zinx框架的全局参数，供其他模块使用\n\t\t一些参数也可以通过 用户根据 zinx.json来配置)\n*/\ntype Config struct {\n\t/*\n\t\tServer\n\t*/\n\tHost    string // The IP address of the current server. (当前服务器主机IP)\n\tTCPPort int    // The port number on which the server listens for TCP connections.(当前服务器主机监听端口号)\n\tWsPort  int    // The port number on which the server listens for WebSocket connections.(当前服务器主机websocket监听端口)\n\tWsPath  string // The path of the WebSocket connection.(websocket连接路径)\n\tName    string // The name of the current server.(当前服务器名称)\n\tKcpPort int    // he port number on which the server listens for KCP connections.(当前服务器主机监听端口号)\n\n\t/*\n\t\tServerConfig\n\t*/\n\tKcpACKNoDelay      bool // changes ack flush option, set true to flush ack immediately,\n\tKcpStreamMode      bool // toggles the stream mode on/off\n\tKcpNoDelay         int  // Whether nodelay mode is enabled, 0 is not enabled; 1 enabled.\n\tKcpInterval        int  // Protocol internal work interval, in milliseconds, such as 10 ms or 20 ms.\n\tKcpResend          int  // Fast retransmission mode, 0 represents off by default, 2 can be set (2 ACK spans will result in direct retransmission)\n\tKcpNc              int  // Whether to turn off flow control, 0 represents “Do not turn off” by default, 1 represents “Turn off”.\n\tKcpSendWindow      int  // SND_BUF, this unit is the packet, default 32.\n\tKcpRecvWindow      int  // RCV_BUF, this unit is the packet, default 32.\n\tKcpFecDataShards   int  // The number of data shards in the FEC.(FEC数据分片), default 0.\n\tKcpFecParityShards int  // The number of parity shards in the FEC.(FEC校验分片) default 0.\n\n\t/*\n\t\tZinx\n\t*/\n\tVersion          string // The version of the Zinx framework.(当前Zinx版本号)\n\tMaxPacketSize    uint32 // The maximum size of the packets that can be sent or received.(读写数据包的最大值)\n\tMaxConn          int    // The maximum number of connections that the server can handle.(当前服务器主机允许的最大连接个数)\n\tWorkerPoolSize   uint32 // The number of worker pools in the business logic.(业务工作Worker池的数量)\n\tMaxWorkerTaskLen uint32 // The maximum number of tasks that a worker pool can handle.(业务工作Worker对应负责的任务队列最大任务存储数量)\n\tWorkerMode       string // The way to assign workers to connections.(为连接分配worker的方式)\n\tMaxMsgChanLen    uint32 // The maximum length of the send buffer message queue.(SendBuffMsg发送消息的缓冲最大长度)\n\tIOReadBuffSize   uint32 // The maximum size of the read buffer for each IO operation.(每次IO最大的读取长度)\n\n\t//The server mode, which can be \"tcp\" or \"websocket\". If it is empty, both modes are enabled.\n\t//\"tcp\":tcp监听, \"websocket\":websocket 监听 为空时同时开启\n\tMode string\n\n\t// A boolean value that indicates whether the new or old version of the router is used. The default value is false.\n\t// 路由模式 false为旧版本路由，true为启用新版本的路由 默认使用旧版本\n\tRouterSlicesMode bool\n\n\t// 是否开启 Request 对象池模式\n\tRequestPoolMode bool\n\t/*\n\t\tlogger\n\t*/\n\tLogDir string // The directory where log files are stored. The default value is \"./log\".(日志所在文件夹 默认\"./log\")\n\n\t// The name of the log file. If it is empty, the log information will be printed to stderr.\n\t// (日志文件名称   默认\"\"  --如果没有设置日志文件，打印信息将打印至stderr)\n\tLogFile string\n\n\tLogSaveDays int   // 日志最大保留天数\n\tLogFileSize int64 // 日志单个日志最大容量 默认 64MB,单位：字节，记得一定要换算成MB（1024 * 1024）\n\tLogCons     bool  // 日志标准输出  默认 false\n\n\t// The level of log isolation. The values can be 0 (all open), 1 (debug off), 2 (debug/info off), 3 (debug/info/warn off), and so on.\n\t// 日志隔离级别  -- 0：全开 1：关debug 2：关debug/info 3：关debug/info/warn ...\n\tLogIsolationLevel int\n\n\t/*\n\t\tKeepalive\n\t*/\n\t// The maximum interval for heartbeat detection in seconds.\n\t// 最长心跳检测间隔时间(单位：秒),超过改时间间隔，则认为超时，从配置文件读取\n\tHeartbeatMax int\n\n\t/*\n\t\tTLS\n\t*/\n\tCertFile       string // The name of the certificate file. If it is empty, TLS encryption is not enabled.(证书文件名称 默认\"\")\n\tPrivateKeyFile string // The name of the private key file. If it is empty, TLS encryption is not enabled.(私钥文件名称 默认\"\" --如果没有设置证书和私钥文件，则不启用TLS加密)\n}\n\n// GlobalObject Define a global object.(定义一个全局的对象)\nvar GlobalObject *Config\n\n// PathExists Check if a file exists.(判断一个文件是否存在)\nfunc PathExists(path string) (bool, error) {\n\t_, err := os.Stat(path)\n\tif err == nil {\n\t\treturn true, nil\n\t}\n\tif os.IsNotExist(err) {\n\t\treturn false, nil\n\t}\n\treturn false, err\n}\n\n// Reload 读取用户的配置文件\n// This method is used to reload the configuration file.\n// It reads the configuration file specified in the command-line arguments,\n// and updates the fields of the \"Config\" structure accordingly.\n// If the configuration file does not exist, it prints an error message to the log and returns.\nfunc (g *Config) Reload() {\n\tconfFilePath := GetConfigFilePath()\n\tif confFileExists, _ := PathExists(confFilePath); confFileExists != true {\n\n\t\t// The configuration file may not exist,\n\t\t// in which case the default parameters should be used to initialize the logging module configuration.\n\t\t// (配置文件不存在也需要用默认参数初始化日志模块配置)\n\t\tg.InitLogConfig()\n\n\t\tzlog.Ins().ErrorF(\"Config File %s is not exist!! \\n You can set configFile by setting the environment variable %s, like export %s = xxx/xxx/zinx.conf \", confFilePath, EnvConfigFilePathKey, EnvConfigFilePathKey)\n\t\treturn\n\t}\n\n\tdata, err := os.ReadFile(confFilePath)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\terr = json.Unmarshal(data, g)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\n\tg.InitLogConfig()\n}\n\n// Show Zinx Config Info\nfunc (g *Config) Show() {\n\tobjVal := reflect.ValueOf(g).Elem()\n\tobjType := reflect.TypeOf(*g)\n\n\tfmt.Println(\"===== Zinx Global Config =====\")\n\tfor i := 0; i < objVal.NumField(); i++ {\n\t\tfield := objVal.Field(i)\n\t\ttypeField := objType.Field(i)\n\n\t\tfmt.Printf(\"%s: %v\\n\", typeField.Name, field.Interface())\n\t}\n\tfmt.Println(\"==============================\")\n}\n\nfunc (g *Config) HeartbeatMaxDuration() time.Duration {\n\treturn time.Duration(g.HeartbeatMax) * time.Second\n}\n\nfunc (g *Config) InitLogConfig() {\n\tif g.LogFile != \"\" {\n\t\tzlog.SetLogFile(g.LogDir, g.LogFile)\n\t\tzlog.SetCons(g.LogCons)\n\t}\n\tif g.LogSaveDays > 0 {\n\t\tzlog.SetMaxAge(g.LogSaveDays)\n\t}\n\tif g.LogFileSize > 0 {\n\t\tzlog.SetMaxSize(g.LogFileSize)\n\t}\n\tif g.LogIsolationLevel > zlog.LogDebug {\n\t\tzlog.SetLogLevel(g.LogIsolationLevel)\n\t}\n}\n\n/*\ninit, set default value\n*/\nfunc init() {\n\tpwd, err := os.Getwd()\n\tif err != nil {\n\t\tpwd = \".\"\n\t}\n\n\t// Note: Prevent errors like \"flag provided but not defined: -test.paniconexit0\" from occurring in go test.\n\t// (防止 go test 出现\"flag provided but not defined: -test.paniconexit0\"等错误)\n\ttesting.Init()\n\n\t// Initialize the GlobalObject variable and set some default values.\n\t// (初始化GlobalObject变量，设置一些默认值)\n\tGlobalObject = &Config{\n\t\tName:              \"ZinxServerApp\",\n\t\tVersion:           \"V1.0\",\n\t\tTCPPort:           8999,\n\t\tWsPort:            9000,\n\t\tWsPath:            \"/\",\n\t\tKcpPort:           9001,\n\t\tHost:              \"0.0.0.0\",\n\t\tMaxConn:           12000,\n\t\tMaxPacketSize:     4096,\n\t\tWorkerPoolSize:    10,\n\t\tMaxWorkerTaskLen:  1024,\n\t\tWorkerMode:        \"\",\n\t\tMaxMsgChanLen:     1024,\n\t\tLogDir:            pwd + \"/log\",\n\t\tLogFile:           \"\", // if set \"\", print to Stderr(默认日志文件为空，打印到stderr)\n\t\tLogIsolationLevel: 0,\n\t\tHeartbeatMax:      10, // The default maximum interval for heartbeat detection is 10 seconds. (默认心跳检测最长间隔为10秒)\n\t\tIOReadBuffSize:    1024,\n\t\tCertFile:          \"\",\n\t\tPrivateKeyFile:    \"\",\n\t\tMode:              ServerModeTcp,\n\t\tRouterSlicesMode:  false,\n\t\tRequestPoolMode:   false,\n\t\tKcpACKNoDelay:     false,\n\t\tKcpStreamMode:     true,\n\t\t//Normal Mode: ikcp_nodelay(kcp, 0, 40, 0, 0);\n\t\t//Turbo Mode： ikcp_nodelay(kcp, 1, 10, 2, 1);\n\t\tKcpNoDelay:         1,\n\t\tKcpInterval:        10,\n\t\tKcpResend:          2,\n\t\tKcpNc:              1,\n\t\tKcpRecvWindow:      32,\n\t\tKcpSendWindow:      32,\n\t\tKcpFecDataShards:   0,\n\t\tKcpFecParityShards: 0,\n\t}\n\n\t// Note: Load some user-configured parameters from the configuration file.\n\t// (从配置文件中加载一些用户配置的参数)\n\tGlobalObject.Reload()\n}\n"
  },
  {
    "path": "zdecoder/crc.go",
    "content": "package zdecoder\n\nvar crc16_h = []byte{\n\t0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,\n\t0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n\t0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,\n\t0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n\t0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,\n\t0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,\n\t0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,\n\t0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n\t0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,\n\t0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,\n\t0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,\n\t0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n\t0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,\n\t0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,\n\t0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,\n\t0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n\t0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,\n\t0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n\t0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,\n\t0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n\t0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,\n\t0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,\n\t0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,\n\t0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,\n\t0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,\n\t0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,\n}\n\nvar crc16_l = []byte{\n\t0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,\n\t0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,\n\t0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,\n\t0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,\n\t0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,\n\t0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,\n\t0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,\n\t0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,\n\t0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,\n\t0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,\n\t0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,\n\t0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,\n\t0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,\n\t0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,\n\t0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,\n\t0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,\n\t0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,\n\t0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,\n\t0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,\n\t0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,\n\t0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,\n\t0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,\n\t0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,\n\t0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,\n\t0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,\n\t0x43, 0x83, 0x41, 0x81, 0x80, 0x40,\n}\n\nfunc GetCrC(buff []byte) []byte {\n\tvar hi uint16 = 0x00ff\n\tvar low uint16 = 0x00ff\n\n\tfor i := 0; i < len(buff); i++ {\n\t\tpos := (low ^ uint16(buff[i])) & 0x00ff\n\t\tlow = hi ^ uint16(crc16_h[pos])\n\t\thi = uint16(crc16_l[pos])\n\t}\n\n\tvar d_crc = ((hi & 0x00ff) << 8) | (low&0x00ff)&0xffff\n\t//var d_crcArr = [2]byte{byte((d_crc & 0xff)), byte((d_crc >> 8) & 0xff)}\n\td_crcArr := make([]byte, 0)\n\td_crcArr = append(d_crcArr, byte((d_crc & 0xff)), byte((d_crc>>8)&0xff))\n\treturn d_crcArr\n}\n\nfunc IsComplete(src []byte, dst []byte) bool {\n\tif src != nil && dst != nil {\n\t\tif len(src) == 2 && len(dst) == 2 {\n\t\t\tif src[0] == dst[0] && src[1] == dst[1] {\n\t\t\t\treturn true\n\t\t\t}\n\t\t}\n\t}\n\treturn false\n}\n\nfunc CheckCRC(src []byte, crc []byte) bool {\n\treturn IsComplete(GetCrC(src), crc)\n}\n"
  },
  {
    "path": "zdecoder/htlvcrcdecoder.go",
    "content": "// HTLV+CRC, H header code, T function code, L data length, V data content\n//+------+-------+-------+--------+--------+\n//| H    | T     | L     | V      | CRC    |\n//| 1Byte| 1Byte | 1Byte | NBytes | 2Bytes |\n//+------+-------+-------+--------+--------+\n\n// HeaderCode FunctionCode DataLength Body                         CRC\n// A2         10           0E         0102030405060708091011121314 050B\n//\n//\n// Explanation:\n// 1. The data length len is 14 (0E), where len only refers to the length of the Body.\n//\n//\n// lengthFieldOffset = 2 (the index of len is 2, starting from 0) The offset of the length field\n// lengthFieldLength = 1 (len is 1 byte) The length of the length field in bytes\n// lengthAdjustment = 2 (len only represents the length of the Body, the program will only read len bytes and end, but there are still 2 bytes of CRC to read, so it's 2)\n// initialBytesToStrip = 0 (this 0 represents the complete protocol content. If you don't want A2, then it's 1) The number of bytes to strip from the decoding frame for the first time\n// maxFrameLength = 255 + 4 (starting code, function code, CRC) (len is 1 byte, so the maximum length is the maximum value of an unsigned byte plus 4 bytes)\n\n// [简体中文]\n//\n// HTLV+CRC，H头码，T功能码，L数据长度，V数据内容\n//+------+-------+---------+--------+--------+\n//| 头码  | 功能码 | 数据长度 | 数据内容 | CRC校验 |\n//| 1字节 | 1字节  | 1字节   | N字节   |  2字节  |\n//+------+-------+---------+--------+--------+\n\n//头码   功能码 数据长度      Body                         CRC\n//A2      10     0E        0102030405060708091011121314 050B\n//\n//\n//   说明：\n//   1.数据长度len是14(0E),这里的len仅仅指Body长度;\n//\n//\n//   lengthFieldOffset   = 2   (len的索引下标是2，下标从0开始) 长度字段的偏差\n//   lengthFieldLength   = 1   (len是1个byte) 长度字段占的字节数\n//   lengthAdjustment    = 2   (len只表示Body长度，程序只会读取len个字节就结束，但是CRC还有2byte没读呢，所以为2)\n//   initialBytesToStrip = 0   (这个0表示完整的协议内容，如果不想要A2，那么这里就是1) 从解码帧中第一次去除的字节数\n//   maxFrameLength      = 255 + 4(起始码、功能码、CRC) (len是1个byte，所以最大长度是无符号1个byte的最大值)\n\npackage zdecoder\n\nimport (\n\t\"encoding/hex\"\n\t\"math\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n)\n\nconst HEADER_SIZE = 5\n\ntype HtlvCrcDecoder struct {\n\tHead    byte   //HeaderCode(头码)\n\tFuncode byte   //FunctionCode(功能码)\n\tLength  byte   //DataLength(数据长度)\n\tBody    []byte //BodyData(数据内容)\n\tCrc     []byte //CRC校验\n\tData    []byte //// Original data content(原始数据内容)\n}\n\nfunc NewHTLVCRCDecoder() ziface.IDecoder {\n\treturn &HtlvCrcDecoder{}\n}\n\nfunc (hcd *HtlvCrcDecoder) GetLengthField() *ziface.LengthField {\n\t//+------+-------+---------+--------+--------+\n\t//| 头码  | 功能码 | 数据长度 | 数据内容 | CRC校验 |\n\t//| 1字节 | 1字节  | 1字节   | N字节   |  2字节  |\n\t//+------+-------+---------+--------+--------+\n\t//头码   功能码 数据长度      Body                         CRC\n\t//A2      10     0E        0102030405060708091011121314 050B\n\t//说明：\n\t//   1.数据长度len是14(0E),这里的len仅仅指Body长度;\n\t//\n\t//   lengthFieldOffset   = 2   (len的索引下标是2，下标从0开始) 长度字段的偏差\n\t//   lengthFieldLength   = 1   (len是1个byte) 长度字段占的字节数\n\t//   lengthAdjustment    = 2   (len只表示Body长度，程序只会读取len个字节就结束，但是CRC还有2byte没读呢，所以为2)\n\t//   initialBytesToStrip = 0   (这个0表示完整的协议内容，如果不想要A2，那么这里就是1) 从解码帧中第一次去除的字节数\n\t//   maxFrameLength      = 255 + 4(起始码、功能码、CRC) (len是1个byte，所以最大长度是无符号1个byte的最大值)\n\treturn &ziface.LengthField{\n\t\tMaxFrameLength:      math.MaxUint8 + 4,\n\t\tLengthFieldOffset:   2,\n\t\tLengthFieldLength:   1,\n\t\tLengthAdjustment:    2,\n\t\tInitialBytesToStrip: 0,\n\t}\n}\n\nfunc (hcd *HtlvCrcDecoder) decode(data []byte) *HtlvCrcDecoder {\n\tdatasize := len(data)\n\n\thtlvData := HtlvCrcDecoder{\n\t\tData: data,\n\t}\n\n\t// Parse the header\n\thtlvData.Head = data[0]\n\thtlvData.Funcode = data[1]\n\thtlvData.Length = data[2]\n\thtlvData.Body = data[3 : datasize-2]\n\thtlvData.Crc = data[datasize-2 : datasize]\n\n\t// CRC\n\tif !CheckCRC(data[:datasize-2], htlvData.Crc) {\n\t\tzlog.Ins().DebugF(\"crc check error %s %s\\n\", hex.EncodeToString(data), hex.EncodeToString(htlvData.Crc))\n\t\treturn nil\n\t}\n\n\t//zlog.Ins().DebugF(\"2htlvData %s \\n\", hex.EncodeToString(htlvData.data))\n\t//zlog.Ins().DebugF(\"HTLVCRC-DecodeData size:%d data:%+v\\n\", unsafe.Sizeof(htlvData), htlvData)\n\n\treturn &htlvData\n}\n\nfunc (hcd *HtlvCrcDecoder) Intercept(chain ziface.IChain) ziface.IcResp {\n\t//1. Get the IMessage of zinx\n\tiMessage := chain.GetIMessage()\n\tif iMessage == nil {\n\t\t// Go to the next layer in the chain of responsibility\n\t\treturn chain.ProceedWithIMessage(iMessage, nil)\n\t}\n\n\t//2. Get Data\n\tdata := iMessage.GetData()\n\t//zlog.Ins().DebugF(\"HTLVCRC-RawData size:%d data:%s\\n\", len(data), hex.EncodeToString(data))\n\n\t//3. If the amount of data read is less than the length of the header, proceed to the next layer directly.\n\t// (读取的数据不超过包头，直接进入下一层)\n\tif len(data) < HEADER_SIZE {\n\t\treturn chain.ProceedWithIMessage(iMessage, nil)\n\t}\n\n\t//4. HTLV+CRC Decode\n\thtlvData := hcd.decode(data)\n\n\t//5. Set the decoded data back to the IMessage, the Zinx Router needs MsgID for addressing\n\t// (将解码后的数据重新设置到IMessage中, Zinx的Router需要MsgID来寻址)\n\tiMessage.SetMsgID(uint32(htlvData.Funcode))\n\n\t//6. Pass the decoded data to the next layer.\n\t// (将解码后的数据进入下一层)\n\treturn chain.ProceedWithIMessage(iMessage, *htlvData)\n}\n"
  },
  {
    "path": "zdecoder/ltvdecoder_little.go",
    "content": "// LTV, which stands for Length-Tag(Type)-Value, is a simple and practical data transmission scheme.\n// In the definition of LTV, there are three fields: Tag field, Length field, and Value field.\n// The value of the Length field is actually the length of the Value field.\n//\n// Before decoding (20 bytes) After decoding (20 bytes)\n//+------------+------------+-----------------+      +------------+------------+-----------------+\n//| Length     |        Tag | Value           |----->| Length     | Tag        | Value           |\n//| 0x0000000C | 0x00000001 | \"HELLO, WORLD\"  |      | 0x0000000C | 0x00000001 | \"HELLO, WORLD\"  |\n//+------------+------------+-----------------+      +------------+------------+-----------------+\n// Length: uint32 type, 4 bytes, indicating the length of Value is 12 (hex: 0x0000000C).\n// Tag: uint32 type, 4 bytes, serves as MsgId, temporarily set to 1.\n// Value: 12 characters in total, 12 bytes in length.\n//\n// Explanation:\n// lengthFieldOffset = 0 (The index of Length byte is 0) Length field offset.\n// lengthFieldLength = 4 (Length takes up 4 bytes) Length field length.\n// lengthAdjustment = 4 (Length only represents the length of Value. The program will only read Length bytes and end.\n//                       If there is a crc of 2 bytes after Value, then it is 2 here. If Length represents the total\n//                       length of Tag+Length+Value, then it is -8 here.)\n// initialBytesToStrip = 0 (0 means returning the complete protocol content Tag+Length+Value. If you only want to\n//                          return the Value content and remove the 4 bytes of Tag and 4 bytes of Length, it is 8 here.)\n//                          The number of bytes removed from the decoded frame for the first time.\n// maxFrameLength = 2^32 + 4 + 4 (Since Length is of uint type, 2^32 represents the maximum length of Value.\n//                                In addition, Tag and Length each take up 4 bytes.)\n\n// [简体中文]\n// LTV，即Length-Tag(Type)-Value，是一种简单实用的数据传输方案。\n//在LTV的定义中，可以知道它包括三个域，分别为：标签域（Tag），长度域（Length），内容域（Value）。这里的长度域的值实际上就是内容域的长度。\n//\n//解码前 (20 bytes)                                   解码后 (20 bytes)\n//+------------+------------+-----------------+      +------------+------------+-----------------+\n//|   Length   |     Tag    |     Value       |----->|  Length    |     Tag    |     Value       |\n//| 0x0000000C | 0x00000001 | \"HELLO, WORLD\"  |      | 0x0000000C | 0x00000001 | \"HELLO, WORLD\"  |\n//+------------+------------+-----------------+      +------------+------------+-----------------+\n// Length：uint32类型，占4字节，Length标记Value长度12(hex:0x0000000C)\n// Tag：   uint32类型，占4字节，Tag作为MsgId，暂定为1\n// Value： 共12个字符，占12字节\n//\n//   说明：\n//   lengthFieldOffset   = 0            (Length的字节位索引下标是0) 长度字段的偏差\n//   lengthFieldLength   = 4            (Length是4个byte) 长度字段占的字节数\n//   lengthAdjustment    = 4            (Length只表示Value长度，程序只会读取Length个字节就结束，后面没有来，故为0，\n//                                        若Value后面还有crc占2字节的话，那么此处就是2。若Length标记的是Tag+Length+Value总长度，\n//                                        那么此处是-8)\n//   initialBytesToStrip = 0            (这个0表示返回完整的协议内容Tag+Length+Value，如果只想返回Value内容，去掉Tag的4字节和Length\n//                                        的4字节，此处就是8) 从解码帧中第一次去除的字节数\n//   maxFrameLength      = 2^32 + 4 + 4 (Length为uint类型，故2^32次方表示Value最大长度，此外Tag和Length各占4字节)\n\npackage zdecoder\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"math\"\n\n\t\"github.com/aceld/zinx/ziface\"\n)\n\nconst LTV_HEADER_SIZE = 8 //表示TLV空包长度\n\ntype LTV_Little_Decoder struct {\n\tLength uint32 //L\n\tTag    uint32 //T\n\tValue  []byte //V\n}\n\nfunc NewLTV_Little_Decoder() ziface.IDecoder {\n\treturn &LTV_Little_Decoder{}\n}\n\nfunc (ltv *LTV_Little_Decoder) GetLengthField() *ziface.LengthField {\n\t// +---------------+---------------+---------------+\n\t// |    Length     |     Tag       |     Value     |\n\t// | uint32(4byte) | uint32(4byte) |     n byte    |\n\t// +---------------+---------------+---------------+\n\t// Length：uint32类型，占4字节，Length标记Value长度\n\t// Tag：   uint32类型，占4字节\n\t// Value： 占n字节\n\t//\n\t//说明:\n\t//    lengthFieldOffset   = 0            (Length的字节位索引下标是4) 长度字段的偏差\n\t//    lengthFieldLength   = 4            (Length是4个byte) 长度字段占的字节数\n\t//    lengthAdjustment    = 4            (Length只表示Value长度，程序只会读取Length个字节就结束，后面没有来，故为0，若Value后面还有crc占2字节的话，那么此处就是2。若Length标记的是Tag+Length+Value总长度，那么此处是-8)\n\t//    initialBytesToStrip = 0            (这个0表示返回完整的协议内容Tag+Length+Value，如果只想返回Value内容，去掉Tag的4字节和Length的4字节，此处就是8) 从解码帧中第一次去除的字节数\n\t//    maxFrameLength      = 2^32 + 4 + 4 (Length为uint32类型，故2^32次方表示Value最大长度，此外Tag和Length各占4字节)\n\t//默认使用TLV封包方式\n\treturn &ziface.LengthField{\n\t\tMaxFrameLength:      math.MaxUint32 + 4 + 4,\n\t\tLengthFieldOffset:   0,\n\t\tLengthFieldLength:   4,\n\t\tLengthAdjustment:    4,\n\t\tInitialBytesToStrip: 0,\n\t\t//注意现在默认是大端，使用小端需要指定编码方式\n\t\tOrder: binary.LittleEndian,\n\t}\n}\n\nfunc (ltv *LTV_Little_Decoder) decode(data []byte) *LTV_Little_Decoder {\n\tltvData := LTV_Little_Decoder{}\n\n\t//Get L\n\tltvData.Length = binary.LittleEndian.Uint32(data[0:4])\n\t//Get T\n\tltvData.Tag = binary.LittleEndian.Uint32(data[4:8])\n\t//Determine the length of V. (确定V的长度)\n\tltvData.Value = make([]byte, ltvData.Length)\n\n\t//5. Get V\n\tbinary.Read(bytes.NewBuffer(data[8:8+ltvData.Length]), binary.LittleEndian, ltvData.Value)\n\n\treturn &ltvData\n}\n\nfunc (ltv *LTV_Little_Decoder) Intercept(chain ziface.IChain) ziface.IcResp {\n\t//1. Get the IMessage of zinx\n\tiMessage := chain.GetIMessage()\n\tif iMessage == nil {\n\t\t// Go to the next layer in the chain of responsibility\n\t\treturn chain.ProceedWithIMessage(iMessage, nil)\n\t}\n\n\t//2. Get Data\n\tdata := iMessage.GetData()\n\t//zlog.Ins().DebugF(\"LTV-RawData size:%d data:%s\\n\", len(data), hex.EncodeToString(data))\n\n\t//3. If the amount of data read is less than the length of the header, proceed to the next layer directly.\n\t// (读取的数据不超过包头，直接进入下一层)\n\tif len(data) < LTV_HEADER_SIZE {\n\t\treturn chain.ProceedWithIMessage(iMessage, nil)\n\t}\n\n\t//4. LTV Decode\n\tltvData := ltv.decode(data)\n\n\t//5. Set the decoded data back to the IMessage, the Zinx Router needs MsgID for addressing\n\t// (将解码后的数据重新设置到IMessage中, Zinx的Router需要MsgID来寻址)\n\tiMessage.SetDataLen(ltvData.Length)\n\tiMessage.SetMsgID(ltvData.Tag)\n\tiMessage.SetData(ltvData.Value)\n\n\t//6. Pass the decoded data to the next layer.\n\t// (将解码后的数据进入下一层)\n\treturn chain.ProceedWithIMessage(iMessage, *ltvData)\n}\n"
  },
  {
    "path": "zdecoder/tlvdecoder.go",
    "content": "// TLV, which stands for Tag(Type)-Length-Value, is a simple and practical data transmission scheme.\n// In the definition of TLV, it can be seen that it consists of three fields: the tag field (Tag), the length\n// field (Length), and the value field (Value).\n// The value of the length field is actually the length of the content field.\n//\n// Before decoding (20 bytes) After decoding (20 bytes)\n// +------------+------------+-----------------+       +------------+------------+-----------------+\n// | Tag        | Length     | Value           |-----> | Tag        | Length     | Value           |\n// | 0x00000001 | 0x0000000C | \"HELLO, WORLD\"  |       | 0x00000001 | 0x0000000C | \"HELLO, WORLD\"  |\n// +------------+------------+-----------------+       +------------+------------+-----------------+\n// Tag: uint32 type, occupies 4 bytes, Tag is set as MsgId, temporarily set to 1\n// Length: uint32 type, occupies 4 bytes, Length marks the length of Value, which is 12(hex:0x0000000C)\n// Value: 12 characters in total, occupies 12 bytes\n//\n// Explanation:\n// lengthFieldOffset = 4 (The byte index of Length is 4) Length field offset\n// lengthFieldLength = 4 (Length is 4 bytes) Length field length in bytes\n// lengthAdjustment = 0 (Length only represents the length of Value. The program will read only Length bytes and end.\n// \t                     If there is a crc of 2 bytes after Value, then this is 2. If Length marks the total length of\n//\t                     Tag+Length+Value, then this is -8)\n// initialBytesToStrip = 0 (This 0 means that the complete protocol content Tag+Length+Value is returned. If you only\n//                          want to return the Value content, remove the 4 bytes of Tag and 4 bytes of Length, and\n//                          this is 8) Number of bytes to strip from the decoded frame\n// maxFrameLength = 2^32 + 4 + 4 (Since Length is of uint type, 2^32 represents the maximum length of Value. In addition,\n//                                Tag and Length each occupy 4 bytes.)\n\n// [简体中文]\n// TLV，即Tag(Type)—Length—Value，是一种简单实用的数据传输方案。\n//在TLV的定义中，可以知道它包括三个域，分别为：标签域（Tag），长度域（Length），内容域（Value）。这里的长度域的值实际上就是内容域的长度。\n//\n//解码前 (20 bytes)                                   解码后 (20 bytes)\n//+------------+------------+-----------------+      +------------+------------+-----------------+\n//|     Tag    |   Length   |     Value       |----->|     Tag    |   Length   |     Value       |\n//| 0x00000001 | 0x0000000C | \"HELLO, WORLD\"  |      | 0x00000001 | 0x0000000C | \"HELLO, WORLD\"  |\n//+------------+------------+-----------------+      +------------+------------+-----------------+\n// Tag：   uint32类型，占4字节，Tag作为MsgId，暂定为1\n// Length：uint32类型，占4字节，Length标记Value长度12(hex:0x0000000C)\n// Value： 共12个字符，占12字节\n//\n//   说明：\n//   lengthFieldOffset   = 4            (Length的字节位索引下标是4) 长度字段的偏差\n//   lengthFieldLength   = 4            (Length是4个byte) 长度字段占的字节数\n//   lengthAdjustment    = 0            (Length只表示Value长度，程序只会读取Length个字节就结束，后面没有来，故为0，\n//                                       若Value后面还有crc占2字节的话，那么此处就是2。若Length标记的是Tag+Length+Value总长度，\n//                                       那么此处是-8)\n//   initialBytesToStrip = 0            (这个0表示返回完整的协议内容Tag+Length+Value，如果只想返回Value内容，去掉Tag的4字节和Length\n//                                       的4字节，此处就是8) 从解码帧中第一次去除的字节数\n//   maxFrameLength      = 2^32 + 4 + 4 (Length为uint类型，故2^32次方表示Value最大长度，此外Tag和Length各占4字节)\n\npackage zdecoder\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"math\"\n\n\t\"github.com/aceld/zinx/ziface\"\n)\n\nconst TLV_HEADER_SIZE = 8 //表示TLV空包长度\n\ntype TLVDecoder struct {\n\tTag    uint32 //T\n\tLength uint32 //L\n\tValue  []byte //V\n}\n\nfunc NewTLVDecoder() ziface.IDecoder {\n\treturn &TLVDecoder{}\n}\n\nfunc (tlv *TLVDecoder) GetLengthField() *ziface.LengthField {\n\t// +---------------+---------------+---------------+\n\t// |    Tag        |     Length    |     Value     |\n\t// | uint32(4byte) | uint32(4byte) |     n byte    |\n\t// +---------------+---------------+---------------+\n\t// Length：uint32类型，占4字节，Length标记Value长度\n\t// Tag：   uint32类型，占4字节\n\t// Value： 占n字节\n\t//\n\t//说明:\n\t//    lengthFieldOffset   = 4            (Length的字节位索引下标是4) 长度字段的偏差\n\t//    lengthFieldLength   = 4            (Length是4个byte) 长度字段占的字节数\n\t//    lengthAdjustment    = 0            (Length只表示Value长度，程序只会读取Length个字节就结束，后面没有来，故为0，若Value后面还有crc占2字节的话，那么此处就是2。若Length标记的是Tag+Length+Value总长度，那么此处是-8)\n\t//    initialBytesToStrip = 0            (这个0表示返回完整的协议内容Tag+Length+Value，如果只想返回Value内容，去掉Tag的4字节和Length的4字节，此处就是8) 从解码帧中第一次去除的字节数\n\t//    maxFrameLength      = 2^32 + 4 + 4 (Length为uint32类型，故2^32次方表示Value最大长度，此外Tag和Length各占4字节)\n\t//默认使用TLV封包方式\n\treturn &ziface.LengthField{\n\t\tMaxFrameLength:      math.MaxUint32 + 4 + 4,\n\t\tLengthFieldOffset:   4,\n\t\tLengthFieldLength:   4,\n\t\tLengthAdjustment:    0,\n\t\tInitialBytesToStrip: 0,\n\t}\n}\n\nfunc (tlv *TLVDecoder) decode(data []byte) *TLVDecoder {\n\ttlvData := TLVDecoder{}\n\t//Get T\n\ttlvData.Tag = binary.BigEndian.Uint32(data[0:4])\n\t//Get L\n\ttlvData.Length = binary.BigEndian.Uint32(data[4:8])\n\t//Determine the length of V. (确定V的长度)\n\ttlvData.Value = make([]byte, tlvData.Length)\n\n\t//Get V\n\tbinary.Read(bytes.NewBuffer(data[8:8+tlvData.Length]), binary.BigEndian, tlvData.Value)\n\n\t//zlog.Ins().DebugF(\"TLV-DecodeData size:%d data:%+v\\n\", unsafe.Sizeof(data), tlvData)\n\treturn &tlvData\n}\n\nfunc (tlv *TLVDecoder) Intercept(chain ziface.IChain) ziface.IcResp {\n\n\t//1. Get the IMessage of zinx\n\tiMessage := chain.GetIMessage()\n\tif iMessage == nil {\n\t\t// Go to the next layer in the chain of responsibility\n\t\treturn chain.ProceedWithIMessage(iMessage, nil)\n\t}\n\n\t//2. Get Data\n\tdata := iMessage.GetData()\n\t//zlog.Ins().DebugF(\"TLV-RawData size:%d data:%s\\n\", len(data), hex.EncodeToString(data))\n\n\t//3. If the amount of data read is less than the length of the header, proceed to the next layer directly.\n\t// (读取的数据不超过包头，直接进入下一层)\n\tif len(data) < TLV_HEADER_SIZE {\n\t\treturn chain.ProceedWithIMessage(iMessage, nil)\n\t}\n\n\t//4. TLV Decode\n\ttlvData := tlv.decode(data)\n\n\t//5. Set the decoded data back to the IMessage, the Zinx Router needs MsgID for addressing\n\t// (将解码后的数据重新设置到IMessage中, Zinx的Router需要MsgID来寻址)\n\tiMessage.SetMsgID(tlvData.Tag)\n\tiMessage.SetData(tlvData.Value)\n\tiMessage.SetDataLen(tlvData.Length)\n\n\t//6. Pass the decoded data to the next layer.\n\t// (将解码后的数据进入下一层)\n\treturn chain.ProceedWithIMessage(iMessage, *tlvData)\n}\n"
  },
  {
    "path": "ziface/iclient.go",
    "content": "// @Title  iclient.go\n// @Description  Provides all interface declarations for the Client abstraction layer.\n// @Author  Aceld - 2023-2-28\n\npackage ziface\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n)\n\ntype IClient interface {\n\tRestart()\n\tStart()\n\tStop()\n\tAddRouter(msgID uint32, router IRouter)\n\tConn() IConnection\n\n\t// SetOnConnStart Set the Hook function to be called when a connection is created for this Client\n\t// (设置该Client的连接创建时Hook函数)\n\tSetOnConnStart(func(IConnection))\n\n\t// SetOnConnStop Set the Hook function to be called when a connection is closed for this Client\n\t// (设置该Client的连接断开时的Hook函数)\n\tSetOnConnStop(func(IConnection))\n\n\t// GetOnConnStart Get the Hook function that is called when a connection is created for this Client\n\t// (获取该Client的连接创建时Hook函数)\n\tGetOnConnStart() func(IConnection)\n\n\t// GetOnConnStop Get the Hook function that is called when a connection is closed for this Client\n\t// (设置该Client的连接断开时的Hook函数)\n\tGetOnConnStop() func(IConnection)\n\n\t// GetPacket Get the data protocol packet binding method for this Client\n\t// (获取Client绑定的数据协议封包方式)\n\tGetPacket() IDataPack\n\n\t// SetPacket Set the data protocol packet binding method for this Client\n\t// (设置Client绑定的数据协议封包方式)\n\tSetPacket(IDataPack)\n\n\t// GetMsgHandler Get the message handling module bound to this Client\n\t// (获取Client绑定的消息处理模块)\n\tGetMsgHandler() IMsgHandle\n\n\t// StartHeartBeat Start heartbeat detection(启动心跳检测)\n\tStartHeartBeat(time.Duration)\n\n\t// StartHeartBeatWithOption Start heartbeat detection with custom callbacks 启动心跳检测(自定义回调)\n\tStartHeartBeatWithOption(time.Duration, *HeartBeatOption)\n\n\t// GetLengthField Get the length field of this Client\n\tGetLengthField() *LengthField\n\n\t// SetDecoder Set the decoder for this Client 设置解码器\n\tSetDecoder(IDecoder)\n\n\t// AddInterceptor Add an interceptor for this Client 添加拦截器\n\tAddInterceptor(IInterceptor)\n\n\t// Get the error channel for this Client 获取客户端错误管道\n\tGetErrChan() <-chan error\n\n\t// Set the name of this Clien\n\t// 设置客户端Client名称\n\tSetName(string)\n\n\t// Get the name of this Client\n\t// 获取客户端Client名称\n\tGetName() string\n\n\tSetUrl(url *url.URL)\n\n\tGetUrl() *url.URL\n\n\t// Set custom headers for WebSocket connection\n\t// 设置WebSocket连接的自定义请求头\n\tSetWsHeader(http.Header)\n\n\t// Get custom headers for WebSocket connection\n\t// 获取WebSocket连接的自定义请求头\n\tGetWsHeader() http.Header\n}\n"
  },
  {
    "path": "ziface/iconnection.go",
    "content": "// @Title  iconnection.go\n// @Description  Declaration of all connection-related methods\n// @Author  Aceld - Thu Mar 11 10:32:29 CST 2019\npackage ziface\n\nimport (\n\t\"context\"\n\t\"net\"\n\n\t\"github.com/gorilla/websocket\"\n)\n\n// IConnection Define connection interface\ntype IConnection interface {\n\t// Start the connection, make the current connection start working\n\t// (启动连接，让当前连接开始工作)\n\tStart()\n\t// Stop the connection and end the current connection state\n\t// (停止连接，结束当前连接状态)\n\tStop()\n\n\t// Returns ctx, used by user-defined go routines to obtain connection exit status\n\t// (返回ctx，用于用户自定义的go程获取连接退出状态)\n\tContext() context.Context\n\n\tGetName() string            // Get the current connection name (获取当前连接名称)\n\tGetConnection() net.Conn    // Get the original socket from the current connection(从当前连接获取原始的socket)\n\tGetWsConn() *websocket.Conn // Get the original websocket connection from the current connection(从当前连接中获取原始的websocket连接)\n\t// Deprecated: use GetConnection instead\n\tGetTCPConnection() net.Conn // Get the original socket TCPConn from the current connection (从当前连接获取原始的socket TCPConn)\n\tGetConnID() uint64          // Get the current connection ID (获取当前连接ID)\n\tGetConnIdStr() string       // Get the current connection ID for string (获取当前字符串连接ID)\n\tGetMsgHandler() IMsgHandle  // Get the message handler (获取消息处理器)\n\tGetWorkerID() uint32        // Get Worker ID（获取workerid）\n\tRemoteAddr() net.Addr       // Get the remote address information of the connection (获取连接远程地址信息)\n\tLocalAddr() net.Addr        // Get the local address information of the connection (获取连接本地地址信息)\n\tLocalAddrString() string    // Get the local address information of the connection as a string\n\tRemoteAddrString() string   // Get the remote address information of the connection as a string\n\n\tSend(data []byte) error                               // Send data directly to the remote TCP client (without buffering)\n\tSendToQueue(data []byte, opts ...MsgSendOption) error // Send data to the message queue to be sent to the remote TCP client later\n\n\t// Send Message data directly to the remote TCP client (without buffering)\n\t// 直接将Message数据发送数据给远程的TCP客户端(无缓冲)\n\tSendMsg(msgID uint32, data []byte) error\n\n\t// Send Message data to the message queue to be sent to the remote TCP client later (with buffering)\n\t// 直接将Message数据发送给远程的TCP客户端(有缓冲)\n\tSendBuffMsg(msgID uint32, data []byte, opts ...MsgSendOption) error\n\n\tSetProperty(key string, value interface{})   // Set connection property\n\tGetProperty(key string) (interface{}, error) // Get connection property\n\tRemoveProperty(key string)                   // Remove connection property\n\tIsAlive() bool                               // Check if the current connection is alive(判断当前连接是否存活)\n\tSetHeartBeat(checker IHeartbeatChecker)      // Set the heartbeat detector (设置心跳检测器)\n\n\tAddCloseCallback(handler, key interface{}, callback func()) // Add a close callback function (添加关闭回调函数)\n\tRemoveCloseCallback(handler, key interface{})               // Remove a close callback function (删除关闭回调函数)\n\tInvokeCloseCallbacks()                                      // Trigger the close callback function (触发关闭回调函数，独立协程完成)\n}\n"
  },
  {
    "path": "ziface/iconnmanager.go",
    "content": "// @Title iconnmanager.go\n// @Description Connection management related operations, including adding, removing, getting a connection object by a connection ID, methods to get the current number of connections and clear all connections.\n// @Author Aceld - Thu Mar 11 10:32:29 CST 2019\npackage ziface\n\n// IConnManager Connection Management Abstract Layer\ntype IConnManager interface {\n\tAdd(IConnection)                                                        // Add connection\n\tRemove(IConnection)                                                     // Remove connection\n\tGet(uint64) (IConnection, error)                                        // Get a connection by ConnID\n\tGet2(string) (IConnection, error)                                       // Get a connection by string ConnID\n\tLen() int                                                               // Get current number of connections\n\tClearConn()                                                             // Remove and stop all connections\n\tGetAllConnID() []uint64                                                 // Get all connection IDs\n\tGetAllConnIdStr() []string                                              // Get all string connection IDs\n\tRange(func(uint64, IConnection, interface{}) error, interface{}) error  // Traverse all connections\n\tRange2(func(string, IConnection, interface{}) error, interface{}) error // Traverse all connections 2\n}\n"
  },
  {
    "path": "ziface/idatapack.go",
    "content": "// @Title idatapack.go\n// @Description Message packing and unpacking methods\n// @Author Aceld - Thu Mar 11 10:32:29 CST 2019\npackage ziface\n\n// IDataPack Package and unpack data.\n// Operating on the data stream of TCP connections, add header information to transfer data, and solve TCP sticky packets.\n// (封包数据和拆包数据, 直接面向TCP连接中的数据流,为传输数据添加头部信息，用于处理TCP粘包问题。)\ntype IDataPack interface {\n\tGetHeadLen() uint32                // Get the length of the message header(获取包头长度方法)\n\tPack(msg IMessage) ([]byte, error) // Package message (封包方法)\n\tUnpack([]byte) (IMessage, error)   // Unpackage message(拆包方法)\n}\n\nconst (\n\t// Zinx standard packing and unpacking method (Zinx 标准封包和拆包方式)\n\tZinxDataPack    string = \"zinx_pack_tlv_big_endian\"\n\tZinxDataPackOld string = \"zinx_pack_ltv_little_endian\"\n\n\t//...(+)\n\t//// Custom packing method can be added here(自定义封包方式在此添加)\n)\n\nconst (\n\t// Zinx default standard message protocol format(Zinx 默认标准报文协议格式)\n\tZinxMessage string = \"zinx_message\"\n)\n"
  },
  {
    "path": "ziface/idecoder.go",
    "content": "package ziface\n\ntype IDecoder interface {\n\tIInterceptor\n\tGetLengthField() *LengthField\n}\n"
  },
  {
    "path": "ziface/iheartbeat.go",
    "content": "package ziface\n\ntype IHeartbeatChecker interface {\n\tSetOnRemoteNotAlive(OnRemoteNotAlive)\n\tSetHeartbeatMsgFunc(HeartBeatMsgFunc)\n\tSetHeartbeatFunc(HeartBeatFunc)\n\tBindRouter(uint32, IRouter)\n\tBindRouterSlices(uint32, ...RouterHandler)\n\tStart()\n\tStop()\n\tSendHeartBeatMsg() error\n\tBindConn(IConnection)\n\tClone() IHeartbeatChecker\n\tMsgID() uint32\n\tRouter() IRouter\n\tRouterSlices() []RouterHandler\n}\n\n// User-defined method for handling heartbeat detection messages\n// (用户自定义的心跳检测消息处理方法)\ntype HeartBeatMsgFunc func(IConnection) []byte\n\n// HeartBeatFunc User-defined heartbeat function\n// (用户自定义心跳函数)\ntype HeartBeatFunc func(IConnection) error\n\n// OnRemoteNotAlive User-defined method for handling remote connections that are not alive\n// 用户自定义的远程连接不存活时的处理方法\ntype OnRemoteNotAlive func(IConnection)\n\ntype HeartBeatOption struct {\n\tMakeMsg          HeartBeatMsgFunc // User-defined method for handling heartbeat detection messages(用户自定义的心跳检测消息处理方法)\n\tOnRemoteNotAlive OnRemoteNotAlive // User-defined method for handling remote connections that are not alive(用户自定义的远程连接不存活时的处理方法)\n\tHeartBeatMsgID   uint32           // User-defined ID for heartbeat detection messages(用户自定义的心跳检测消息ID)\n\tRouter           IRouter          // User-defined business processing route for heartbeat detection messages(用户自定义的心跳检测消息业务处理路由)\n\tRouterSlices     []RouterHandler  //新版本的路由处理函数的集合\n}\n\nconst (\n\tHeartBeatDefaultMsgID uint32 = 99999\n)\n"
  },
  {
    "path": "ziface/iinterceptor.go",
    "content": "/**\n * @author uuxia\n * @date 15:54 2023/3/10\n * @description\n **/\n\npackage ziface\n\n// Input data for interceptor\n// (拦截器输入数据)\ntype IcReq interface{}\n\n// Output data for interceptor\n// (拦截器输出数据)\ntype IcResp interface{}\n\n// Interceptor\n// (拦截器)\ntype IInterceptor interface {\n\tIntercept(IChain) IcResp\n\t// The interception method of the interceptor (defined by the developer)\n\t// (拦截器的拦截处理方法,由开发者定义)\n}\n\n// Responsibility chain\n// (责任链)\ntype IChain interface {\n\tRequest() IcReq        // Get the request data in the current chain (current interceptor)-获取当前责任链中的请求数据(当前拦截器)\n\tGetIMessage() IMessage // Get IMessage from Chain (从Chain中获取IMessage)\n\tProceed(IcReq) IcResp  // Enter and execute the next interceptor, and pass the request data to the next interceptor (进入并执行下一个拦截器，且将请求数据传递给下一个拦截器)\n\tProceedWithIMessage(IMessage, IcReq) IcResp\n}\n"
  },
  {
    "path": "ziface/ilengthfield.go",
    "content": "package ziface\n\nimport \"encoding/binary\"\n\ntype IFrameDecoder interface {\n\tDecode(buff []byte) [][]byte\n}\n\n// ILengthField Basic attributes possessed by ILengthField\n// (具备的基础属性)\ntype LengthField struct {\n\t/*\n\t\tNote:\n\t\t   Big-endian: the most significant byte (the \"big end\") of a word is placed at the byte with the lowest address;\n\t\t   the rest of the bytes are placed in order of decreasing significance towards the byte with the highest address.\n\t\t   Little-endian: the least significant byte (the \"little end\") of a word is placed at the byte with the lowest address;\n\t\t   the rest of the bytes are placed in order of increasing significance towards the byte with the highest address.\n\t\t(大端模式：是指数据的高字节保存在内存的低地址中，而数据的低字节保存在内存的高地址中，地址由小向大增加，而数据从高位往低位放；\n\t\t小端模式：是指数据的高字节保存在内存的高地址中，而数据的低字节保存在内存的低地址中，高地址部分权值高，低地址部分权值低，和我们的日常逻辑方法一致。)\n\t*/\n\tOrder               binary.ByteOrder //The byte order: BigEndian or LittleEndian(大小端)\n\tMaxFrameLength      uint64           //The maximum length of a frame(最大帧长度)\n\tLengthFieldOffset   int              //The offset of the length field(长度字段偏移量)\n\tLengthFieldLength   int              //The length of the length field in bytes(长度域字段的字节数)\n\tLengthAdjustment    int              //The length adjustment(长度调整)\n\tInitialBytesToStrip int              //The number of bytes to strip from the decoded frame(需要跳过的字节数)\n}\n"
  },
  {
    "path": "ziface/ilogger.go",
    "content": "package ziface\n\nimport \"context\"\n\ntype ILogger interface {\n\t//without context\n\tInfoF(format string, v ...interface{})\n\tErrorF(format string, v ...interface{})\n\tDebugF(format string, v ...interface{})\n\n\t//with context\n\tInfoFX(ctx context.Context, format string, v ...interface{})\n\tErrorFX(ctx context.Context, format string, v ...interface{})\n\tDebugFX(ctx context.Context, format string, v ...interface{})\n}\n"
  },
  {
    "path": "ziface/imessage.go",
    "content": "// @Title imessage.go\n// @Description Provides basic methods for messages\n// @Author Aceld - Thu Mar 11 10:32:29 CST 2019\npackage ziface\n\n// IMessage Package ziface defines an abstract interface for encapsulating a request message into a message\ntype IMessage interface {\n\tGetDataLen() uint32 // Gets the length of the message data segment(获取消息数据段长度)\n\tGetMsgID() uint32   // Gets the ID of the message(获取消息ID)\n\tGetData() []byte    // Gets the content of the message(获取消息内容)\n\tGetRawData() []byte // Gets the raw data of the message(获取原始数据)\n\n\tSetMsgID(uint32)   // Sets the ID of the message(设计消息ID)\n\tSetData([]byte)    // Sets the content of the message(设计消息内容)\n\tSetDataLen(uint32) // Sets the length of the message data segment(设置消息数据段长度)\n}\n"
  },
  {
    "path": "ziface/imsghandler.go",
    "content": "// @Title imsghandler.go\n// @Description Provides interfaces for worker startup and handling message business calls\n// @Author Aceld - Thu Mar 11 10:32:29 CST 2019\npackage ziface\n\n// IMsgHandle Abstract layer of message management(消息管理抽象层)\ntype IMsgHandle interface {\n\t// Add specific handling logic for messages, msgID supports int and string types\n\t// (为消息添加具体的处理逻辑, msgID，支持整型，字符串)\n\tAddRouter(msgID uint32, router IRouter)\n\tAddRouterSlices(msgId uint32, handler ...RouterHandler) IRouterSlices\n\tGroup(start, end uint32, Handlers ...RouterHandler) IGroupRouterSlices\n\tUse(Handlers ...RouterHandler) IRouterSlices\n\n\tStartWorkerPool()                    //  Start the worker pool\n\tSendMsgToTaskQueue(request IRequest) // Pass the message to the TaskQueue for processing by the worker(将消息交给TaskQueue,由worker进行处理)\n\n\tExecute(request IRequest) // Execute interceptor methods on the responsibility chain(执行责任链上的拦截器方法)\n\n\t// Register the entry point of the responsibility chain. After each interceptor is processed,\n\t// the data is passed to the next interceptor, so that the message can be handled and passed layer by layer,\n\t// the order depends on the registration order\n\t// (注册责任链任务入口，每个拦截器处理完后，数据都会传递至下一个拦截器，使得消息可以层层处理层层传递，顺序取决于注册顺序)\n\tAddInterceptor(interceptor IInterceptor)\n\n\t// SetHeadInterceptor sets the head interceptor of the responsibility chain, which is the first interceptor to be executed\n\t// (SetHeadInterceptor 设置责任链的头拦截器，也就是第一个要执行的拦截器)\n\t// 默认情况下，责任链的头拦截器是msg decoder\n\t// this function will replace the head interceptor\n\tSetHeadInterceptor(interceptor IInterceptor)\n}\n"
  },
  {
    "path": "ziface/inotify.go",
    "content": "package ziface\n\ntype Inotify interface {\n\t// Whether there is a connection with this id\n\t// (是否有这个id)\n\tHasIdConn(id uint64) bool\n\n\t// Get the number of connections stored\n\t// (存储的map长度)\n\tConnNums() int\n\n\t// Add a connection\n\t// (添加连接)\n\tSetNotifyID(Id uint64, conn IConnection)\n\n\t// Get a connection by id\n\t// (得到某个连接)\n\tGetNotifyByID(Id uint64) (IConnection, error)\n\n\t// Delete a connection by id\n\t// (删除某个连接)\n\tDelNotifyByID(Id uint64)\n\n\t// Notify a connection with the given id\n\t// (通知某个id的方法)\n\tNotifyToConnByID(Id uint64, MsgId uint32, data []byte) error\n\n\t// Notify all connections\n\t// (通知所有人)\n\tNotifyAll(MsgId uint32, data []byte) error\n\n\t// Notify a connection with the given id using a buffer queue\n\t// (通过缓冲队列通知某个id的方法)\n\tNotifyBuffToConnByID(Id uint64, MsgId uint32, data []byte) error\n\n\t// Notify all connections using a buffer queue\n\t// (缓冲队列通知所有人)\n\tNotifyBuffAll(MsgId uint32, data []byte) error\n}\n"
  },
  {
    "path": "ziface/irequest.go",
    "content": "// @Title irequest.go\n// @Description Provides all interface declarations for connection requests\n// @Author Aceld - Thu Mar 11 10:32:29 CST 2019\npackage ziface\n\ntype HandleStep int\n\n// IFuncRequest function message interface (函数消息接口)\ntype IFuncRequest interface {\n\tCallFunc()\n}\n\n// IRequest interface:\n// It actually packages the connection information and request data of the client request into Request\n// (实际上是把客户端请求的连接信息 和 请求的数据 包装到了 Request里)\ntype IRequest interface {\n\tGetConnection() IConnection // Get the connection information of the request(获取请求连接信息)\n\n\tGetData() []byte  // Get the data of the request message(获取请求消息的数据)\n\tGetMsgID() uint32 // Get the message ID of the request(获取请求的消息ID)\n\n\tGetMessage() IMessage // Get the raw data of the request message (获取请求消息的原始数据 add by uuxia 2023-03-10)\n\n\tGetResponse() IcResp // Get the serialized data after parsing(获取解析完后序列化数据)\n\tSetResponse(IcResp)  // Set the serialized data after parsing(设置解析完后序列化数据)\n\n\tBindRouter(router IRouter) // Bind which router handles this request(绑定这次请求由哪个路由处理)\n\t// Move on to the next handler to start execution, but the function that calls this method will execute in reverse order of their order\n\t// (转进到下一个处理器开始执行 但是调用此方法的函数会根据先后顺序逆序执行)\n\tCall()\n\n\t//erminate the execution of the processing function, but the function that calls this method will be executed until completion\n\t// 终止处理函数的运行 但调用此方法的函数会执行完毕\n\tAbort()\n\n\t//Specify which Handler function to execute next in the Handle\n\t// (指定接下来的Handle去执行哪个Handler函数)\n\t// Be careful, it will cause loop calling\n\t// (慎用，会导致循环调用)\n\tGoto(HandleStep)\n\n\t// New router operation\n\t// (新路由操作)\n\tBindRouterSlices([]RouterHandler)\n\n\t// Execute the next function\n\t// (执行下一个函数)\n\tRouterSlicesNext()\n\n\t// 重置一个 Request\n\t//Reset(conn IConnection, msg IMessage)\n\t// 复制一份 Request 对象\n\tCopy() IRequest\n\t//Set 在 Request 中存放一个上下文\n\tSet(key string, value interface{})\n\t//Get 从 Request 中获取一个上下文信息\n\tGet(key string) (value interface{}, exists bool)\n}\n\ntype BaseRequest struct{}\n\nfunc (br *BaseRequest) GetConnection() IConnection       { return nil }\nfunc (br *BaseRequest) GetData() []byte                  { return nil }\nfunc (br *BaseRequest) GetMsgID() uint32                 { return 0 }\nfunc (br *BaseRequest) GetMessage() IMessage             { return nil }\nfunc (br *BaseRequest) GetResponse() IcResp              { return nil }\nfunc (br *BaseRequest) SetResponse(resp IcResp)          {}\nfunc (br *BaseRequest) BindRouter(router IRouter)        {}\nfunc (br *BaseRequest) Call()                            {}\nfunc (br *BaseRequest) Abort()                           {}\nfunc (br *BaseRequest) Goto(HandleStep)                  {}\nfunc (br *BaseRequest) BindRouterSlices([]RouterHandler) {}\nfunc (br *BaseRequest) RouterSlicesNext()                {}\nfunc (br *BaseRequest) Copy() IRequest                   { return nil }\n\nfunc (br *BaseRequest) Set(key string, value interface{}) {}\n\nfunc (br *BaseRequest) Get(key string) (value interface{}, exists bool) { return nil, false }\n"
  },
  {
    "path": "ziface/irouter.go",
    "content": "// @Title irouter.go\n// @Description Provides all interface declarations for message routing\n// @Author Aceld - Thu Mar 11 10:32:29 CST 2019\npackage ziface\n\n// IRouter is the interface for message routing. The route is the processing\n// business method set by the framework user for this connection. The IRequest\n// in the route includes the connection information and the request data\n// information for this connection.\n// (路由接口， 这里面路由是 使用框架者给该连接自定的 处理业务方法\n// 路由里的IRequest 则包含用该连接的连接信息和该连接的请求数据信息)\ntype IRouter interface {\n\tPreHandle(request IRequest)  //Hook method before processing conn business(在处理conn业务之前的钩子方法)\n\tHandle(request IRequest)     //Method for processing conn business(处理conn业务的方法)\n\tPostHandle(request IRequest) //Hook method after processing conn business(处理conn业务之后的钩子方法)\n}\n\n// RouterHandler is a method slice collection style router. Unlike the old version,\n// the new version only saves the router method collection, and the specific execution\n// is handed over to the IRequest of each request.\n// (方法切片集合式路路由\n// 不同于旧版 新版本仅保存路由方法集合，具体执行交给每个请求的 IRequest)\ntype RouterHandler func(request IRequest)\ntype IRouterSlices interface {\n\t// Add global components (添加全局组件)\n\tUse(Handlers ...RouterHandler)\n\n\t// Add a route (添加业务处理器集合)\n\tAddHandler(msgId uint32, handlers ...RouterHandler)\n\n\t// Router group management （路由分组管理，并且会返回一个组管理器）\n\tGroup(start, end uint32, Handlers ...RouterHandler) IGroupRouterSlices\n\n\t// Get the method set collection for processing （获得当前的所有注册在MsgId的处理器集合）\n\tGetHandlers(MsgId uint32) ([]RouterHandler, bool)\n}\n\ntype IGroupRouterSlices interface {\n\t// Add global components (添加全局组件)\n\tUse(Handlers ...RouterHandler)\n\n\t// Add group routing components (添加业务处理器集合)\n\tAddHandler(MsgId uint32, Handlers ...RouterHandler)\n}\n"
  },
  {
    "path": "ziface/iserver.go",
    "content": "// @Title iserver.go\n// @Description Provides all interface declarations for the Server abstraction layer\n// @Author Aceld - Thu Mar 11 10:32:29 CST 2019\npackage ziface\n\nimport (\n\t\"net/http\"\n\t\"time\"\n)\n\n// Defines the server interface\ntype IServer interface {\n\tStart() // Start the server method(启动服务器方法)\n\tStop()  // Stop the server method (停止服务器方法)\n\tServe() // Start the business service method(开启业务服务方法)\n\n\t// Routing feature: register a routing business method for the current service for client link processing use\n\t//(路由功能：给当前服务注册一个路由业务方法，供客户端连接处理使用)\n\tAddRouter(msgID uint32, router IRouter)\n\n\t// New version of routing (新版路由方式)\n\tAddRouterSlices(msgID uint32, router ...RouterHandler) IRouterSlices\n\n\t// Route group management (路由组管理)\n\tGroup(start, end uint32, Handlers ...RouterHandler) IGroupRouterSlices\n\n\t// Common component management (公共组件管理)\n\tUse(Handlers ...RouterHandler) IRouterSlices\n\n\t// Get connection management (得到连接管理)\n\tGetConnMgr() IConnManager\n\n\t// Set Hook function when the connection is created for the Server (设置该Server的连接创建时Hook函数)\n\tSetOnConnStart(func(IConnection))\n\n\t// Set Hook function when the connection is disconnected for the Server\n\t// (设置该Server的连接断开时的Hook函数)\n\tSetOnConnStop(func(IConnection))\n\n\t// Get Hook function when the connection is created for the Server\n\t// (得到该Server的连接创建时Hook函数)\n\tGetOnConnStart() func(IConnection)\n\n\t// Get Hook function when the connection is disconnected for the Server\n\t// (得到该Server的连接断开时的Hook函数)\n\tGetOnConnStop() func(IConnection)\n\n\t// Get the data protocol packet binding method for the Server\n\t// (获取Server绑定的数据协议封包方式)\n\tGetPacket() IDataPack\n\n\t// Get the message processing module binding method for the Server\n\t// (获取Server绑定的消息处理模块)\n\tGetMsgHandler() IMsgHandle\n\n\t// Set the data protocol packet binding method for the Server\n\t// (设置Server绑定的数据协议封包方式)\n\tSetPacket(IDataPack)\n\n\t// Start the heartbeat check\n\t// (启动心跳检测)\n\tStartHeartBeat(time.Duration)\n\n\t// Start the heartbeat check (custom callback)\n\t// 启动心跳检测(自定义回调)\n\tStartHeartBeatWithOption(time.Duration, *HeartBeatOption)\n\n\t// Get the heartbeat checker\n\t// (获取心跳检测器)\n\tGetHeartBeat() IHeartbeatChecker\n\n\tGetLengthField() *LengthField\n\tSetDecoder(IDecoder)\n\tAddInterceptor(IInterceptor)\n\n\t// Add WebSocket authentication method\n\t// (添加websocket认证方法)\n\tSetWebsocketAuth(func(r *http.Request) error)\n\n\t// Get the server name (获取服务器名称)\n\tServerName() string\n}\n"
  },
  {
    "path": "ziface/options.go",
    "content": "package ziface\n\nimport \"time\"\n\ntype MsgSendOptionObj struct {\n\tTimeout time.Duration\n}\n\ntype MsgSendOption func(opt *MsgSendOptionObj)\n\nfunc WithSendMsgTimeout(timeout time.Duration) MsgSendOption {\n\treturn func(opt *MsgSendOptionObj) {\n\t\topt.Timeout = timeout\n\t}\n}\n"
  },
  {
    "path": "zinterceptor/chain.go",
    "content": "/**\n * @author uuxia\n * @date 15:56 2023/3/10\n * @description 责任链模式\n **/\npackage zinterceptor\n\nimport \"github.com/aceld/zinx/ziface\"\n\ntype Chain struct {\n\treq          ziface.IcReq\n\tposition     int\n\tinterceptors []ziface.IInterceptor\n}\n\nfunc NewChain(list []ziface.IInterceptor, pos int, req ziface.IcReq) ziface.IChain {\n\treturn &Chain{\n\t\treq:          req,\n\t\tposition:     pos,\n\t\tinterceptors: list,\n\t}\n}\n\nfunc (c *Chain) Request() ziface.IcReq {\n\treturn c.req\n}\n\nfunc (c *Chain) Proceed(request ziface.IcReq) ziface.IcResp {\n\tif c.position < len(c.interceptors) {\n\t\tchain := NewChain(c.interceptors, c.position+1, request)\n\t\tinterceptor := c.interceptors[c.position]\n\t\tresponse := interceptor.Intercept(chain)\n\t\treturn response\n\t}\n\treturn request\n}\n\n// GetIMessage  Get IMessage from the Chain (从Chain中获取IMessage)\nfunc (c *Chain) GetIMessage() ziface.IMessage {\n\n\treq := c.Request()\n\tif req == nil {\n\t\treturn nil\n\t}\n\n\tiRequest := c.ShouldIRequest(req)\n\tif iRequest == nil {\n\t\treturn nil\n\t}\n\n\treturn iRequest.GetMessage()\n}\n\n// Next Enter the next chain task with IMessage and decoded data,iMessage is the decoded IMessage response is the decoded data\n// (Next 通过IMessage和解码后数据进入下一个责任链任务, iMessage 为解码后的IMessage, response 为解码后的数据)\nfunc (c *Chain) ProceedWithIMessage(iMessage ziface.IMessage, response ziface.IcReq) ziface.IcResp {\n\tif iMessage == nil || response == nil {\n\t\treturn c.Proceed(c.Request())\n\t}\n\n\treq := c.Request()\n\tif req == nil {\n\t\treturn c.Proceed(c.Request())\n\t}\n\n\tiRequest := c.ShouldIRequest(req)\n\tif iRequest == nil {\n\t\treturn c.Proceed(c.Request())\n\t}\n\n\t// Set the request of chain for the next request\n\t// (设置chain的request下一次请求)\n\tiRequest.SetResponse(response)\n\n\treturn c.Proceed(iRequest)\n}\n\n// ShouldIRequest Determine if it is IRequest(判断是否是IRequest)\nfunc (c *Chain) ShouldIRequest(icReq ziface.IcReq) ziface.IRequest {\n\tif icReq == nil {\n\t\treturn nil\n\t}\n\n\tswitch icReq.(type) {\n\tcase ziface.IRequest:\n\t\treturn icReq.(ziface.IRequest)\n\tdefault:\n\t\treturn nil\n\t}\n}\n"
  },
  {
    "path": "zinterceptor/framedecoder.go",
    "content": "/**\n * @author uuxia\n * @date 15:57 2023/3/10\n * @description 通用解码器\n **/\n\npackage zinterceptor\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"math\"\n)\n\n// FrameDecoder\n// A decoder that splits the received {@link ByteBuf}s dynamically by the\n// value of the length field in the message.  It is particularly useful when you\n// decode a binary message which has an integer header field that represents the\n// length of the message body or the whole message.\n//\n// ziface.LengthField has many configuration parameters so\n// that it can decode any message with a length field, which is often seen in\n// proprietary client-server protocols. Here are some example that will give\n// you the basic idea on which option does what.\n//\n// <I. 2 bytes length field at offset 0, do not strip header>\n//\n// The value of the length field in this example is <tt>12 (0x0C)</tt> which\n// represents the length of \"HELLO, WORLD\".  By default, the decoder assumes\n// that the length field represents the number of the bytes that follows the\n// length field.  Therefore, it can be decoded with the simplistic parameter\n// combination.\n//\n// LengthFieldOffset   = 0\n// LengthFieldLength   = 2\n// LengthAdjustment    = 0\n// InitialBytesToStrip = 0 (= do not strip header)\n//\n// BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)\n// +--------+----------------+      +--------+----------------+\n// | Length | Actual Content |----->| Length | Actual Content |\n// | 0x000C | \"HELLO, WORLD\" |      | 0x000C | \"HELLO, WORLD\" |\n// +--------+----------------+      +--------+----------------+\n//\n//\n// <II. 2 bytes length field at offset 0, strip header>\n//\n// Because we can get the length of the content by calling\n// {@link ByteBuf#readableBytes()}, you might want to strip the length\n// field by specifying `InitialBytesToStrip`.  In this example, we\n// specified `2`, that is same with the length of the length field, to\n// strip the first two bytes.\n//\n// LengthFieldOffset   = 0\n// LengthFieldLength   = 2\n// LengthAdjustment    = 0\n// InitialBytesToStrip = 2 (= the length of the Length field)\n//\n// BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)\n// +--------+----------------+      +----------------+\n// | Length | Actual Content |----->| Actual Content |\n// | 0x000C | \"HELLO, WORLD\" |      | \"HELLO, WORLD\" |\n// +--------+----------------+      +----------------+\n//\n//  <III. 2 bytes length field at offset 0, do not strip header, the length field>\n//\n//\trepresents the length of the whole message</h3>\n//\n// In most cases, the length field represents the length of the message body\n// only, as shown in the previous examples.  However, in some protocols, the\n// length field represents the length of the whole message, including the\n// message header.  In such a case, we specify a non-zero\n// `LengthAdjustment`.  Because the length value in this example message\n//\n// is always greater than the body length by `2`, we specify `-2`\n// as `LengthAdjustment` for compensation.\n//\n// LengthFieldOffset   =  0\n// LengthFieldLength   =  2\n// LengthAdjustment    = -2 (= the length of the Length field)\n// InitialBytesToStrip =  0\n//\n// BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)\n// +--------+----------------+      +--------+----------------+\n// | Length | Actual Content |----->| Length | Actual Content |\n// | 0x000E | \"HELLO, WORLD\" |      | 0x000E | \"HELLO, WORLD\" |\n// +--------+----------------+      +--------+----------------+\n//\n// <IV. 3 bytes length field at the end of 5 bytes header, do not strip header>\n//\n// The following message is a simple variation of the first example.  An extra\n// header value is prepended to the message.  <tt>LengthAdjustment</tt> is zero\n// again because the decoder always takes the length of the prepended data into\n// account during frame length calculation.\n//\n// LengthFieldOffset   = 2 (= the length of Header 1)\n// LengthFieldLength   = 3\n// LengthAdjustment    = 0\n// InitialBytesToStrip = 0\n//\n// BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)\n// +----------+----------+----------------+      +----------+----------+----------------+\n// | Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |\n// |  0xCAFE  | 0x00000C | \"HELLO, WORLD\" |      |  0xCAFE  | 0x00000C | \"HELLO, WORLD\" |\n// +----------+----------+----------------+      +----------+----------+----------------+\n//\n//\n// <V. 3 bytes length field at the beginning of 5 bytes header, do not strip header>\n//\n// This is an advanced example that shows the case where there is an extra\n// header between the length field and the message body.  You have to specify a\n// positive `LengthAdjustment` so that the decoder counts the extra\n// header into the frame length calculation.\n//\n// LengthFieldOffset   = 0\n// LengthFieldLength   = 3\n// LengthAdjustment    = 2 (= the length of Header 1)\n// InitialBytesToStrip = 0\n//\n// BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)\n// +----------+----------+----------------+      +----------+----------+----------------+\n// |  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |\n// | 0x00000C |  0xCAFE  | \"HELLO, WORLD\" |      | 0x00000C |  0xCAFE  | \"HELLO, WORLD\" |\n// +----------+----------+----------------+      +----------+----------+----------------+\n//\n//\n//  <VI. 2 bytes length field at offset 1 in the middle of 4 bytes header,\n//\tstrip the first header field and the length field>\n//\n// This is a combination of all the examples above.  There are the prepended\n// header before the length field and the extra header after the length field.\n// The prepended header affects the\t`LengthFieldOffset` and the extra\n// header affects the `LengthAdjustment`.  We also specified a non-zero\n// `InitialBytesToStrip` to strip the length field and the prepended\n// header from the frame.  If you don't want to strip the prepended header, you\n// could specify `0` for `initialBytesToSkip`.\n//\n// LengthFieldOffset   = 1 (= the length of HDR1)\n// LengthFieldLength   = 2\n// LengthAdjustment    = 1 (= the length of HDR2)\n// InitialBytesToStrip = 3 (= the length of HDR1 + LEN)\n//\n// BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)\n// +------+--------+------+----------------+      +------+----------------+\n// | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |\n// | 0xCA | 0x000C | 0xFE | \"HELLO, WORLD\" |      | 0xFE | \"HELLO, WORLD\" |\n// +------+--------+------+----------------+      +------+----------------+\n//\n//\n//  <VII. 2 bytes length field at offset 1 in the middle of 4 bytes header,\n//\tstrip the first header field and the length field, the length field\n//\trepresents the length of the whole message>\n//\n// Let's give another twist to the previous example.  The only difference from\n// the previous example is that the length field represents the length of the\n// whole message instead of the message body, just like the third example.\n// We have to count the length of HDR1 and Length into `LengthAdjustment`.\n// Please note that we don't need to take the length of HDR2 into account\n// because the length field already includes the whole header length.\n//\n// LengthFieldOffset   =  1\n// LengthFieldLength   =  2\n// LengthAdjustment    = -3 (= the length of HDR1 + LEN, negative)\n// InitialBytesToStrip =  3\n//\n// BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)\n// +------+--------+------+----------------+      +------+----------------+\n// | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |\n// | 0xCA | 0x0010 | 0xFE | \"HELLO, WORLD\" |      | 0xFE | \"HELLO, WORLD\" |\n// +------+--------+------+----------------+      +------+----------------+\n\n// << 中文含义 By Aceld >>\n//\n// FrameDecoder\n// 一个解码器，根据消息中长度字段的值动态地拆分接收到的二进制数据帧,\n// 当您解码具有表示消息正文或整个消息长度的整数头字段的二进制消息时，它特别有用。\n//\n// ziface.LengthField 有许多配置参数，因此它可以解码任何具有长度字段的消息，\n// 这在专有的客户端-服务器协议中经常见到。\n//\n// 以下是一些示例，它们将为您提供基本的想法，了解每个选项的作用。\n//\n// 案例一. 在偏移量为0的位置使用2字节长度字段，不去掉消息头\n//\n// 在这个例子中，长度字段的值是 `12 (0x0C)`，表示\"HELLO, WORLD\"的长度。\n// 默认情况下，解码器会假设长度字段代表跟在长度字段后面的字节数。因此，\n// 可以使用简单的参数组合进行解码。\n//\n// LengthFieldOffset = 0\n// LengthFieldLength = 2\n// LengthAdjustment = 0\n// InitialBytesToStrip = 0 （= 不去掉消息头）\n//\n// 解码前（14个字节）                  解码后（14个字节）\n// +--------+----------------+      +--------+----------------+\n// | Length | Actual Content |----->| Length | Actual Content |\n// | 0x000C | \"HELLO, WORLD\" |      | 0x000C | \"HELLO, WORLD\" |\n// +--------+----------------+      +--------+----------------+\n//\n// 案例二. 位于偏移量0的2字节长度字段，去掉消息头\n//\n// 由于我们可以通过调用{@link ByteBuf#readableBytes()}来获取内容的长度，\n// 因此您可能希望通过指定\"InitialBytesToStrip\"来去掉长度字段。在此示例中，我们指定了\"2\"，\n// 与长度字段的长度相同，以去掉前两个字节。\n//\n// LengthFieldOffset = 0\n// LengthFieldLength = 2\n// LengthAdjustment = 0\n// InitialBytesToStrip = 2 （等于Length字段的长度）\n//\n// 解码前 (14 bytes)         \t\t解码后 (12 bytes)\n// +--------+----------------+      +----------------+\n// | Length | Actual Content |----->| Actual Content |\n// | 0x000C | \"HELLO, WORLD\" |      | \"HELLO, WORLD\" |\n// +--------+----------------+      +----------------+\n//\n// 案例三. 位于偏移量0处的2字节长度字段，不剥离头部，该长度字段表示整个消息的长度\n//\n// 在大多数情况下，长度字段仅表示消息体的长度，就像前面的例子所示。然而，在一些协议中，\n// 长度字段表示整个消息的长度，包括消息头部。在这种情况下，我们需要指定一个非零的LengthAdjustment。\n// 因为这个例子消息中长度值总是比消息体长度大2，所以我们将LengthAdjustment设置为-2进行补偿。\n//\n// LengthFieldOffset = 0\n// LengthFieldLength = 2\n// LengthAdjustment = -2 (长度字段的长度)\n// InitialBytesToStrip = 0\n//\n// 解码前 (14 bytes)         \t\t解码后 (14 bytes)\n// +--------+----------------+      +--------+----------------+\n// | Length | Actual Content |----->| Length | Actual Content |\n// | 0x000E | \"HELLO, WORLD\" |      | 0x000E | \"HELLO, WORLD\" |\n// +--------+----------------+      +--------+----------------+\n//\n// 案例四. 5个字节的头部中包含3个字节的长度字段，不去除头部\n//\n// 下面的消息是第一个示例的简单变体。在消息前面添加了额外的头部值。\n// LengthAdjustment 再次为零，因为解码器始终考虑预置数据的长度进行帧长度计算。\n//\n// LengthFieldOffset = 2（等于 Header 1 的长度）\n// LengthFieldLength = 3\n// LengthAdjustment = 0\n// InitialBytesToStrip = 0\n//\n// 解码前 (17 bytes)                      \t\t 解码后(17 bytes)\n// +----------+----------+----------------+      +----------+----------+----------------+\n// | Header 1 | Length   | Actual Content |----->| Header 1 | Length   | Actual Content |\n// | 0xCAFE   | 0x00000C | \"HELLO, WORLD\" |      | 0xCAFE   | 0x00000C | \"HELLO, WORLD\" |\n// +----------+----------+----------------+      +----------+----------+----------------+\n//\n// 案例五. 在 5 个字节的头部中有 3 个字节的长度字段，不剥离头部\n//\n// 这是一个高级的例子，展示了在长度字段和消息体之间有额外头部的情况。\n// 您需要指定一个正的 LengthAdjustment，以便解码器在帧长度计算中计算额外的头部。\n//\n// LengthFieldOffset = 0\n// LengthFieldLength = 3\n// LengthAdjustment = 2 （即 Header 1 的长度）\n// InitialBytesToStrip = 0\n//\n// 解码前 (17 bytes)                      \t\t 解码后 (17 bytes)\n// +----------+----------+----------------+      +----------+----------+----------------+\n// | Length   | Header 1 | Actual Content |----->| Length   | Header 1 | Actual Content |\n// | 0x00000C | 0xCAFE   | \"HELLO, WORLD\" |      | 0x00000C | 0xCAFE   | \"HELLO, WORLD\" |\n// +----------+----------+----------------+      +----------+----------+----------------+\n//\n//\n// 案例六. 4字节头部，其中2字节长度字段位于偏移量1的位置，剥离第一个头部字段和长度字段\n//\n// 这是以上所有示例的组合。在长度字段之前有预置的头部，\n// 而在长度字段之后有额外的头部。预置头部影响LengthFieldOffset，\n// 额外的头部影响LengthAdjustment。我们还指定了一个非零的\n// InitialBytesToStrip，以从帧中剥离长度字段和预置头部。\n// 如果您不想剥离预置头部，则可以将initialBytesToSkip指定为0。\n//\n// LengthFieldOffset = 1（HDR1的长度）\n// LengthFieldLength = 2\n// LengthAdjustment = 1（HDR2的长度）\n// InitialBytesToStrip = 3（HDR1 + LEN的长度）\n//\n// BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)\n// +------+--------+------+----------------+      +------+----------------+\n// | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |\n// | 0xCA | 0x000C | 0xFE | \"HELLO, WORLD\" |      | 0xFE | \"HELLO, WORLD\" |\n// +------+--------+------+----------------+      +------+----------------+\n//\n// 案例七. 2字节长度字段在4字节头部的偏移量为1的位置，去除第一个头字段和长度字段，长度字段表示整个消息的长度\n//\n// 让我们对前面的示例进行一些变化。\n// 与先前的示例唯一的区别在于，长度字段表示整个消息的长度，而不是消息正文，就像第三个示例一样。\n// 我们必须将HDR1和Length的长度计入LengthAdjustment。\n// 请注意，我们不需要考虑HDR2的长度，因为长度字段已经包含了整个头部的长度。\n//\n// LengthFieldOffset = 1\n// LengthFieldLength = 2\n// LengthAdjustment = -3 （HDR1 + LEN的长度，为负数）\n// InitialBytesToStrip = 3\n//\n// 在解码之前（16个字节）                              解码之后（13个字节）\n// +------+--------+------+----------------+       +------+----------------+\n// | HDR1 | Length | HDR2 | Actual Content |-----> | HDR2 | Actual Content |\n// | 0xCA | 0x0010 | 0xFE | \"HELLO, WORLD\" |       | 0xFE | \"HELLO, WORLD\" |\n// +------+--------+------+----------------+       +------+----------------+\n\n// FrameDecoder is a decoder based on the LengthField pattern.\ntype FrameDecoder struct {\n\tziface.LengthField // Basic properties inherited from ILengthField\n\n\tLengthFieldEndOffset   int   // Offset of the end position of the length field (LengthFieldOffset+LengthFieldLength) (长度字段结束位置的偏移量)\n\tfailFast               bool  // Fast failure (快速失败)\n\tdiscardingTooLongFrame bool  // true indicates discard mode is enabled, false indicates normal working mode (true 表示开启丢弃模式，false 正常工作模式)\n\ttooLongFrameLength     int64 // When the length of a packet exceeds maxLength, discard mode is enabled, and this field records the length of the data to be discarded (当某个数据包的长度超过maxLength，则开启丢弃模式，此字段记录需要丢弃的数据长度)\n\tbytesToDiscard         int64 // Records how many bytes still need to be discarded (记录还剩余多少字节需要丢弃)\n\tin                     []byte\n}\n\nfunc NewFrameDecoder(lf ziface.LengthField) ziface.IFrameDecoder {\n\n\tframeDecoder := new(FrameDecoder)\n\n\tif lf.Order == nil {\n\t\tframeDecoder.Order = binary.BigEndian\n\t} else {\n\t\tframeDecoder.Order = lf.Order\n\t}\n\tframeDecoder.MaxFrameLength = lf.MaxFrameLength\n\tframeDecoder.LengthFieldOffset = lf.LengthFieldOffset\n\tframeDecoder.LengthFieldLength = lf.LengthFieldLength\n\tframeDecoder.LengthAdjustment = lf.LengthAdjustment\n\tframeDecoder.InitialBytesToStrip = lf.InitialBytesToStrip\n\n\t//self\n\tframeDecoder.LengthFieldEndOffset = lf.LengthFieldOffset + lf.LengthFieldLength\n\tframeDecoder.in = make([]byte, 0)\n\n\treturn frameDecoder\n}\n\nfunc NewFrameDecoderByParams(maxFrameLength uint64, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip int) ziface.IFrameDecoder {\n\treturn NewFrameDecoder(ziface.LengthField{\n\t\tMaxFrameLength:      maxFrameLength,\n\t\tLengthFieldOffset:   lengthFieldOffset,\n\t\tLengthFieldLength:   lengthFieldLength,\n\t\tLengthAdjustment:    lengthAdjustment,\n\t\tInitialBytesToStrip: initialBytesToStrip,\n\t\tOrder:               binary.BigEndian,\n\t})\n}\n\nfunc (d *FrameDecoder) fail(frameLength int64) {\n\t//丢弃完成或未完成都抛异常\n\t//if frameLength > 0 {\n\t//\tmsg := fmt.Sprintf(\"Adjusted frame length exceeds %d : %d - discarded\", this.MaxFrameLength, frameLength)\n\t//\tpanic(msg)\n\t//} else {\n\t//\tmsg := fmt.Sprintf(\"Adjusted frame length exceeds %d - discarded\", this.MaxFrameLength)\n\t//\tpanic(msg)\n\t//}\n}\n\nfunc (d *FrameDecoder) discardingTooLongFrameFunc(buffer *bytes.Buffer) {\n\t// Save the number of bytes still to be discarded\n\t// (保存还需丢弃多少字节)\n\tbytesToDiscard := d.bytesToDiscard\n\n\t// Get the number of bytes that can be discarded now, there may be a half package situation\n\t// (获取当前可以丢弃的字节数，有可能出现半包)\n\tlocalBytesToDiscard := math.Min(float64(bytesToDiscard), float64(buffer.Len()))\n\n\t// Discard (丢弃)\n\tbuffer.Next(int(localBytesToDiscard))\n\n\t// Update the number of bytes still to be discarded (更新还需丢弃的字节数)\n\tbytesToDiscard -= int64(localBytesToDiscard)\n\n\td.bytesToDiscard = bytesToDiscard\n\n\t// Determine if fast failure is needed, go back to the logic above (是否需要快速失败，回到上面的逻辑)\n\td.failIfNecessary(false)\n}\n\nfunc (d *FrameDecoder) getUnadjustedFrameLength(buf *bytes.Buffer, offset int, length int, order binary.ByteOrder) int64 {\n\t// Value of the length field (长度字段的值)\n\tvar frameLength int64\n\n\tarr := buf.Bytes()\n\tarr = arr[offset : offset+length]\n\n\tbuffer := bytes.NewBuffer(arr)\n\n\tswitch length {\n\tcase 1:\n\t\t//byte\n\t\tvar value uint8\n\t\tbinary.Read(buffer, order, &value)\n\t\tframeLength = int64(value)\n\tcase 2:\n\t\t//short\n\t\tvar value uint16\n\t\tbinary.Read(buffer, order, &value)\n\t\tframeLength = int64(value)\n\tcase 3:\n\t\t// int occupies 32 bits, here take out the last 24 bits and return as int type\n\t\t// (int占32位，这里取出后24位，返回int类型)\n\t\tif order == binary.LittleEndian {\n\t\t\tn := uint(arr[0]) | uint(arr[1])<<8 | uint(arr[2])<<16\n\t\t\tframeLength = int64(n)\n\t\t} else {\n\t\t\tn := uint(arr[2]) | uint(arr[1])<<8 | uint(arr[0])<<16\n\t\t\tframeLength = int64(n)\n\t\t}\n\tcase 4:\n\t\t//int\n\t\tvar value uint32\n\t\tbinary.Read(buffer, order, &value)\n\t\tframeLength = int64(value)\n\tcase 8:\n\t\t//long\n\t\tbinary.Read(buffer, order, &frameLength)\n\tdefault:\n\t\tpanic(fmt.Sprintf(\"unsupported LengthFieldLength: %d (expected: 1, 2, 3, 4, or 8)\", d.LengthFieldLength))\n\t}\n\treturn frameLength\n}\n\nfunc (d *FrameDecoder) failOnNegativeLengthField(in *bytes.Buffer, frameLength int64, lengthFieldEndOffset int) {\n\tin.Next(lengthFieldEndOffset)\n\tpanic(fmt.Sprintf(\"negative pre-adjustment length field: %d\", frameLength))\n}\n\nfunc (d *FrameDecoder) failIfNecessary(firstDetectionOfTooLongFrame bool) {\n\tif d.bytesToDiscard == 0 {\n\t\t// Indicates that the data to be discarded has been discarded (说明需要丢弃的数据已经丢弃完成)\n\t\t// Save the length of the discarded data packet (保存一下被丢弃的数据包长度)\n\t\ttooLongFrameLength := d.tooLongFrameLength\n\t\td.tooLongFrameLength = 0\n\n\t\t// Turn off discard mode (关闭丢弃模式)\n\t\td.discardingTooLongFrame = false\n\n\t\t// failFast: Default is true (failFast：默认true)\n\t\t// firstDetectionOfTooLongFrame: Passed in as true (firstDetectionOfTooLongFrame：传入true)\n\t\tif !d.failFast || firstDetectionOfTooLongFrame {\n\t\t\t// Fast failure (快速失败)\n\t\t\td.fail(tooLongFrameLength)\n\t\t}\n\t} else {\n\t\t// Indicates that the discard has not been completed yet (说明还未丢弃完成)\n\t\tif d.failFast && firstDetectionOfTooLongFrame {\n\t\t\t// Fast failure (快速失败)\n\t\t\td.fail(d.tooLongFrameLength)\n\t\t}\n\t}\n}\n\n// exceededFrameLength\n// frameLength: Length of the data packet (frameLength：数据包的长度)\nfunc (d *FrameDecoder) exceededFrameLength(in *bytes.Buffer, frameLength int64) {\n\t// Packet length - readable bytes (两种情况)\n\t// 1. Total length of the data packet is 100, readable bytes is 50, indicating that there are still 50 bytes to be discarded but have not been received yet\n\t// (数据包总长度为100，可读的字节数为50，说明还剩余50个字节需要丢弃但还未接收到)\n\t// 2. Total length of the data packet is 100, readable bytes is 150, indicating that the buffer already contains the entire data packet\n\t// (数据包总长度为100，可读的字节数为150，说明缓冲区已经包含了整个数据包)\n\tdiscard := frameLength - int64(in.Len())\n\n\t// Record the maximum length of the data packet (记录一下最大的数据包的长度)\n\td.tooLongFrameLength = frameLength\n\n\tif discard < 0 {\n\t\t// Indicates the second case, directly discard the current data packet (说明是第2种情况，直接丢弃当前数据包)\n\t\tin.Next(int(frameLength))\n\t} else {\n\t\t// Indicates the first case, some data is still pending reception (说明是第1种情况，还有部分数据未接收到)\n\t\t// Enable discard mode (开启丢弃模式)\n\t\td.discardingTooLongFrame = true\n\n\t\t// Record how many bytes need to be discarded next time (记录下次还需丢弃多少字节)\n\t\td.bytesToDiscard = discard\n\n\t\t// Discard all data in the buffer (丢弃缓冲区所有数据)\n\t\tin.Next(in.Len())\n\t}\n\n\t// Update the status and determine if there is an error. (更新状态，判断是否有误)\n\td.failIfNecessary(true)\n}\n\nfunc (d *FrameDecoder) failOnFrameLengthLessThanInitialBytesToStrip(in *bytes.Buffer, frameLength int64, initialBytesToStrip int) {\n\tin.Next(int(frameLength))\n\tpanic(fmt.Sprintf(\"Adjusted frame length (%d) is less  than InitialBytesToStrip: %d\", frameLength, initialBytesToStrip))\n}\n\nfunc (d *FrameDecoder) decode(buf []byte) []byte {\n\tin := bytes.NewBuffer(buf)\n\n\t// Determine if it is in discard mode (判断是否为丢弃模式)\n\tif d.discardingTooLongFrame {\n\t\td.discardingTooLongFrameFunc(in)\n\t}\n\n\t// Determine if the number of readable bytes in the buffer is less than the offset of the length field\n\t// (判断缓冲区中可读的字节数是否小于长度字段的偏移量)\n\tif in.Len() < d.LengthFieldEndOffset {\n\t\t// Indicates that the length field packets are incomplete, half package\n\t\t// (说明长度字段的包都还不完整，半包)\n\t\treturn nil\n\t}\n\n\t// --> If execution reaches here, it means that the value of the length field can be parsed <--\n\t// (执行到这，说明可以解析出长度字段的值了)\n\n\t// Calculate the offset of the length field\n\t// (计算出长度字段的开始偏移量)\n\tactualLengthFieldOffset := d.LengthFieldOffset\n\n\t// Get the value of the length field, excluding the adjustment value of lengthAdjustment\n\t// (获取长度字段的值，不包括lengthAdjustment的调整值)\n\tframeLength := d.getUnadjustedFrameLength(in, actualLengthFieldOffset, d.LengthFieldLength, d.Order)\n\n\t// If the data frame length is less than 0, it means it is an error data packet\n\t// (如果数据帧长度小于0，说明是个错误的数据包)\n\tif frameLength < 0 {\n\t\t// It will skip the number of bytes of this data packet and throw an exception\n\t\t// (内部会跳过这个数据包的字节数，并抛异常)\n\t\td.failOnNegativeLengthField(in, frameLength, d.LengthFieldEndOffset)\n\t}\n\n\t// Apply the formula: Number of bytes after the length field = value of the length field + lengthAdjustment (应用公式:长度字段后的字节数=长度字段的值+长度调整值)\n\t// frameLength is the value of the length field, plus lengthAdjustment equals the number of bytes after the length field (lengthFieldEndOffset is lengthFieldOffset+lengthFieldLength)\n\t// (frameLength 是长度字段的值,加上长度调整值等于长度字段后的字节数,lengthFieldEndOffset 是长度字段的偏移量加上长度字段本身)\n\t// So the frameLength calculated in the end is the length of the entire data packet (那说明最后计算出的frameLength就是整个数据包的长度)\n\tframeLength += int64(d.LengthAdjustment) + int64(d.LengthFieldEndOffset)\n\n\t// Discard mode is turned on here (丢弃模式就是在这开启的)\n\t// If the data packet length is greater than the maximum length (如果数据包长度大于最大长度)\n\tif uint64(frameLength) > d.MaxFrameLength {\n\t\t// It has exceeded the maximum length of a single data frame, and the exceeded part is processed\n\t\t// (已经超过单次数据帧最大长度，对超过的部分进行处理)\n\t\td.exceededFrameLength(in, frameLength)\n\t\treturn nil\n\t}\n\n\t// --> If execution reaches here, it means normal mode <--\n\t// (执行到这, 说明是正常模式)\n\n\t// Size of the data packet (数据包的大小)\n\tframeLengthInt := int(frameLength)\n\t// Determine if the number of readable bytes in the buffer is less than the size of the data packet (判断缓冲区可读字节数是否小于数据包的字节数)\n\tif in.Len() < frameLengthInt {\n\t\t// Half package, will parse again later (半包，等会再来解析)\n\t\treturn nil\n\t}\n\n\t// --> If execution reaches here, it means that the buffer already contains the entire data packet <--\n\t// (执行到这, 说明缓冲区的数据已经包含了数据包)\n\n\t// Whether the number of bytes to be skipped is greater than the length of the data packet (跳过的字节数是否大于数据包长度)\n\tif d.InitialBytesToStrip > frameLengthInt {\n\t\t// Will throw an exception if the length of the data packet is less than the number of bytes to be skipped (如果数据包长度小于跳过的字节数，将抛出异常)\n\t\td.failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, d.InitialBytesToStrip)\n\t}\n\n\t// Skip the initialBytesToStrip bytes (跳过initialBytesToStrip个字节)\n\tin.Next(d.InitialBytesToStrip)\n\n\t// Decode (解码)\n\t// Get the real data length after skipping (获取跳过后的真实数据长度)\n\tactualFrameLength := frameLengthInt - d.InitialBytesToStrip\n\n\t// Extract the real data (提取真实的数据)\n\tbuff := make([]byte, actualFrameLength)\n\t_, _ = in.Read(buff)\n\n\treturn buff\n}\n\nfunc (d *FrameDecoder) Decode(buff []byte) [][]byte {\n\n\td.in = append(d.in, buff...)\n\tresp := make([][]byte, 0)\n\n\tfor {\n\t\tarr := d.decode(d.in)\n\n\t\tif arr != nil {\n\t\t\t// Indicates that a complete packet has been parsed\n\t\t\t// (证明已经解析出一个完整包)\n\t\t\tresp = append(resp, arr)\n\t\t\t_size := len(arr) + d.InitialBytesToStrip\n\t\t\tif _size > 0 {\n\t\t\t\td.in = d.in[_size:]\n\t\t\t}\n\t\t} else {\n\t\t\treturn resp\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "zinterceptor/interceptor.go",
    "content": "/**\n * @author uuxia\n * @date 15:58 2023/3/10\n * @description 通过拦截，处理数据，任务向下传递\n **/\n\npackage zinterceptor\n\n// 暂时不用\n\n/*\n// Interceptor 基于LengthField规则的拦截器\ntype Interceptor struct {\n\tframeDecoder ziface.IFrameDecoder\n}\n\nfunc NewInterceptor(maxFrameLength uint64,\n\tlengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip int) ziface.IInterceptor {\n\treturn &Interceptor{\n\t\tframeDecoder: NewFrameDecoderByParams(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip),\n\t}\n}\n\nfunc (l *Interceptor) Intercept(chain ziface.IChain) ziface.IcResp {\n\treq := chain.Request()\n\n\tif req == nil || l.frameDecoder == nil {\n\t\tgoto END\n\t}\n\n\tswitch req.(type) {\n\tcase ziface.IRequest:\n\t\tiRequest := req.(ziface.IRequest)\n\t\tiMessage := iRequest.GetMessage()\n\n\t\tif iMessage == nil {\n\t\t\tbreak\n\t\t}\n\n\t\tdata := iMessage.GetData()\n\n\t\tbytebuffers := l.frameDecoder.Decode(data)\n\t\tsize := len(bytebuffers)\n\t\tif size == 0 { //半包，或者其他情况，任务就不要往下再传递了\n\t\t\treturn nil\n\t\t}\n\n\t\tfor i := 0; i < size; i++ {\n\t\t\tbuffer := bytebuffers[i]\n\t\t\tif buffer == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tbufferSize := len(buffer)\n\t\t\tiMessage.SetData(buffer)\n\t\t\tiMessage.SetDataLen(uint32(bufferSize))\n\n\t\t\tif i < size-1 {\n\t\t\t\tchain.Proceed(chain.Request())\n\t\t\t}\n\t\t}\n\t}\n\nEND:\n\treturn chain.Proceed(chain.Request())\n}\n*/\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/README-CN.md",
    "content": "# Zinx应用-MMO游戏案例\n\n[English](README.md) | 简体中文\n\n\n## 〇. MMO Game Client 案例源代码(仅供学习使用)\n\nhttps://github.com/aceld/mmo_game_client\n\n\n## 一、应用案例介绍\n\n​\t“ 好了，以上Zinx的框架的一些核心功能我们已经完成了，那么接下来我们就要基于Zinx完成一个服务端的应用程序了，整理用一个游戏应用服务器作为Zinx的一个应用案例。”\n\n​\t游戏场景是一款MMO大型多人在线游戏，带unity3d 客户端的服务器端demo，该demo实现了mmo游戏的基础模块aoi(基于兴趣范围的广播), 世界聊天等。\n\n![13-Zinx游戏-示例图.png](https://upload-images.jianshu.io/upload_images/11093205-593bb6246327e900.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)\n\n## 二、服务器应用基础协议\n\n| MsgID | Client      | Server      | 描述                                                         |\n| ----- | ----------- | ----------- | ------------------------------------------------------------ |\n| 1     | -           | SyncPid     | 同步玩家本次登录的ID(用来标识玩家)                           |\n| 2     | Talk        | -           | 世界聊天                                                     |\n| 3     | MovePackege | -           | 移动                                                         |\n| 200   | -           | BroadCast   | 广播消息(Tp 1 世界聊天 2 坐标(出生点同步) 3 动作 4 移动之后坐标信息更新) |\n| 201   | -           | SyncPid     | 广播消息 掉线/aoi消失在视野                                  |\n| 202   | -           | SyncPlayers | 同步周围的人位置信息(包括自己)                               |\n\n\n## 三、Zinx 开发文档\n\n[ < Zinx Wiki : English > ](https://github.com/aceld/zinx/wiki)\n\n[ < Zinx 文档 : 简体中文> ](https://www.yuque.com/aceld/tsgooa/sbvzgczh3hqz8q3l)\n\n## 四、Zinx 在线开发教程\n\n### 文字教程\n\n| platform | Entry | \n| ---- | ---- | \n| <img src=\"https://user-images.githubusercontent.com/7778936/236784004-b6d99e26-b1ab-4bc3-988e-7a46108b85fe.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [Zinx Framework tutorial-Lightweight server based on Golang](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)| \n|<img src=\"https://user-images.githubusercontent.com/7778936/236784168-6528a9b8-d37b-4b02-a37c-b9988d7508d8.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />|[《Golang轻量级并发服务器框架zinx》](https://www.yuque.com/aceld)|\n\n\n### 视频教程\n\n| platform | online video | \n| ---- | ---- | \n| <img src=\"https://s1.ax1x.com/2022/09/22/xFePUK.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.bilibili.com/video/av71067087)| \n| <img src=\"https://s1.ax1x.com/2022/09/22/xFesxJ.png\" width = \"100\" height = \"80\" alt=\"\" align=center />  | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.douyin.com/video/6983301202939333891) |\n| <img src=\"https://s1.ax1x.com/2022/09/23/xkQcng.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [![zinx-youtube](https://s2.ax1x.com/2019/10/14/KSurCR.jpg)](https://www.youtube.com/watch?v=U95iF-HMWsU&list=PL_GrAPKmuajzeNI8HBTi-k5NQO1g0rM-A)| \n\n\n\n---\n### 关于作者：\n\n作者：`Aceld(刘丹冰)`\n简书号：`IT无崖子`\n\n`mail`:\n[danbing.at@gmail.com](mailto:danbing.at@gmail.com)\n`github`:\n[https://github.com/aceld](https://github.com/aceld)\n`原创书籍gitbook`:\n[http://legacy.gitbook.com/@aceld](http://legacy.gitbook.com/@aceld)\n\n### Zinx技术讨论社区\n\n| platform | Entry | \n| ---- | ---- | \n| <img src=\"https://user-images.githubusercontent.com/7778936/236775008-6bd488e3-249a-4d43-8885-7e3889e11e2d.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| https://discord.gg/xQ8Xxfyfcz| \n| <img src=\"https://user-images.githubusercontent.com/7778936/236775137-5381f8a6-f534-49c4-8628-e52bf245c3bc.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />  | 加微信: `ace_ld`  或扫二维码，备注`zinx`即可。</br><img src=\"https://user-images.githubusercontent.com/7778936/236781258-2f0371bd-5797-49e8-a74c-680e9f15843d.png\" width = \"150\" height = \"150\" alt=\"\" align=center /> |\n|<img src=\"https://user-images.githubusercontent.com/7778936/236778547-9cdadfb6-0f62-48ac-851a-b940389038d0.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />|<img src=\"https://s1.ax1x.com/2020/07/07/UFyUdx.th.jpg\" height = \"150\"  alt=\"\" align=center /> **WeChat Public Account** |\n|<img src=\"https://user-images.githubusercontent.com/7778936/236779000-70f16c8f-0eec-4b5f-9faa-e1d5229a43e0.png\" width = \"100\" height = \"100\" alt=\"\" align=center />|<img src=\"https://s1.ax1x.com/2020/07/07/UF6Y9S.th.png\" width = \"150\" height = \"150\" alt=\"\" align=center /> **QQ Group** |\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/README.md",
    "content": "# Zinx Application - MMO Game Case Study\n\nEnglish | [简体中文](README-CN.md)\n\n## 0. MMO Game Client Source Code\n\nhttps://github.com/aceld/mmo_game_client\n\n## 1.Introduction to the Application Case\n\n \"In the previous chapters, we have completed some of the core functionalities of the Zinx framework. Now, we are going to build a server-side application based on Zinx. To illustrate this, we will use a game application server as an example of Zinx's practical application.\"\n\n  The game scenario is an MMO (Massively Multiplayer Online) game with a Unity3D client. The server-side demo implements essential modules of an MMO game, including AOI (Area Of Interest) based broadcasting and global chat.\n\n![mmo-game01](https://github.com/aceld/zinx/assets/7778936/b9576a95-d33f-4ffd-bf89-94a551af1977)\n\n## 2.Server Application Basic Protocols\n\n| MsgID | Client      | Server      | 描述                                                       |\n| ----- | ----------- | ----------- | ---------------------------------------------------------- |\n| 1     | -           | SyncPid     | Synchronize the player's login ID (used for player ID).|\n| 2     | Talk        | -           | World chat.                                                   |\n| 3     | MovePackege | -           | Movement.                                                       |\n| 200   | -           | BroadCast   | Broadcast messages (Tp 1: world chat, 2: coordinates synchronization (spawn point sync), 3: action, 4: updated coordinates after movement). |\n| 201   | -           | SyncPid     | Broadcast messages for disconnect/aoi disappearance.|\n| 202   | -           | SyncPlayers | Synchronize the position information of nearby players (including oneself).                               |\n\n\n## 3. Zinx Documentation\n\n[ < Zinx Wiki : English > ](https://github.com/aceld/zinx/wiki)\n\n[ < Zinx 文档 : 简体中文> ](https://www.yuque.com/aceld/tsgooa/sbvzgczh3hqz8q3l)\n\n## 4.Tutorial \n### 4.1 Online Tutorial\n\n| platform | Entry | \n| ---- | ---- | \n| <img src=\"https://user-images.githubusercontent.com/7778936/236784004-b6d99e26-b1ab-4bc3-988e-7a46108b85fe.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [Zinx Framework tutorial-Lightweight server based on Golang](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)| \n|<img src=\"https://user-images.githubusercontent.com/7778936/236784168-6528a9b8-d37b-4b02-a37c-b9988d7508d8.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />|[《Golang轻量级并发服务器框架zinx》](https://www.yuque.com/aceld)|\n\n\n### 4.2 Online Tutorial Video\n\n| platform | online video | \n| ---- | ---- | \n| <img src=\"https://s1.ax1x.com/2022/09/22/xFePUK.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.bilibili.com/video/av71067087)| \n| <img src=\"https://s1.ax1x.com/2022/09/22/xFeRVx.png\" width = \"100\" height = \"100\" alt=\"\" align=center />  | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.douyin.com/video/6983301202939333891) |\n| <img src=\"https://s1.ax1x.com/2022/09/23/xkQcng.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| [![zinx-youtube](https://s2.ax1x.com/2019/10/14/KSurCR.jpg)](https://www.youtube.com/watch?v=U95iF-HMWsU&list=PL_GrAPKmuajzeNI8HBTi-k5NQO1g0rM-A)| \n\n\n---\n### 关于作者：\n\n作者：`Aceld(刘丹冰)`\n简书号：`IT无崖子`\n\n`mail`:\n[danbing.at@gmail.com](mailto:danbing.at@gmail.com)\n`github`:\n[https://github.com/aceld](https://github.com/aceld)\n`原创书籍gitbook`:\n[http://legacy.gitbook.com/@aceld](http://legacy.gitbook.com/@aceld)\n\n### Zinx技术讨论社区\n\n| platform | Entry | \n| ---- | ---- | \n| <img src=\"https://user-images.githubusercontent.com/7778936/236775008-6bd488e3-249a-4d43-8885-7e3889e11e2d.png\" width = \"100\" height = \"100\" alt=\"\" align=center />| https://discord.gg/xQ8Xxfyfcz| \n| <img src=\"https://user-images.githubusercontent.com/7778936/236775137-5381f8a6-f534-49c4-8628-e52bf245c3bc.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />  | 加微信: `ace_ld`  或扫二维码，备注`zinx`即可。</br><img src=\"https://user-images.githubusercontent.com/7778936/236781258-2f0371bd-5797-49e8-a74c-680e9f15843d.png\" width = \"150\" height = \"150\" alt=\"\" align=center /> |\n|<img src=\"https://user-images.githubusercontent.com/7778936/236778547-9cdadfb6-0f62-48ac-851a-b940389038d0.jpeg\" width = \"100\" height = \"100\" alt=\"\" align=center />|<img src=\"https://s1.ax1x.com/2020/07/07/UFyUdx.th.jpg\" height = \"150\"  alt=\"\" align=center /> **WeChat Public Account** |\n|<img src=\"https://user-images.githubusercontent.com/7778936/236779000-70f16c8f-0eec-4b5f-9faa-e1d5229a43e0.png\" width = \"100\" height = \"100\" alt=\"\" align=center />|<img src=\"https://s1.ax1x.com/2020/07/07/UF6Y9S.th.png\" width = \"150\" height = \"150\" alt=\"\" align=center /> **QQ Group** |\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/api/move.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/core\"\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/pb\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\n// MoveApi Player movement\n// MoveApi 玩家移动\ntype MoveApi struct {\n\tznet.BaseRouter\n}\n\nfunc (*MoveApi) Handle(request ziface.IRequest) {\n\t//1. MoveApi Player movement\n\t// (1. 将客户端传来的proto协议解码)\n\tmsg := &pb.Position{}\n\terr := proto.Unmarshal(request.GetData(), msg)\n\tif err != nil {\n\t\tfmt.Println(\"Move: Position Unmarshal error \", err)\n\t\treturn\n\t}\n\n\t// 2. Identify which player sent the current message, retrieve from the connection property pID\n\t// (2. 得知当前的消息是从哪个玩家传递来的,从连接属性pID中获取)\n\tpID, err := request.GetConnection().GetProperty(\"pID\")\n\tif err != nil {\n\t\tfmt.Println(\"GetProperty pID error\", err)\n\t\trequest.GetConnection().Stop()\n\t\treturn\n\t}\n\n\t//fmt.Printf(\"user pID = %d , move(%f,%f,%f,%f)\\n\", pID, msg.X, msg.Y, msg.Z, msg.V)\n\n\t// 3. Get the player object based on pID\n\t// (3. 根据pID得到player对象)\n\tplayer := core.WorldMgrObj.GetPlayerByPID(pID.(int32))\n\n\t// 4. Have the player object initiate the broadcast of movement position information\n\t// (4. 让player对象发起移动位置信息广播)\n\tplayer.UpdatePos(msg.X, msg.Y, msg.Z, msg.V)\n}\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/api/world_chat.go",
    "content": "package api\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/core\"\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/pb\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\n// WorldChatApi World chat route business\n// (WorldChatApi 世界聊天 路由业务)\ntype WorldChatApi struct {\n\tznet.BaseRouter\n}\n\nfunc (*WorldChatApi) Handle(request ziface.IRequest) {\n\t// 1. Decode the incoming proto protocol from the client\n\t// (1. 将客户端传来的proto协议解码)\n\tmsg := &pb.Talk{}\n\terr := proto.Unmarshal(request.GetData(), msg)\n\tif err != nil {\n\t\tfmt.Println(\"Talk Unmarshal error \", err)\n\t\treturn\n\t}\n\n\t// 2. Identify which player sent the current message, retrieve from the connection property pID\n\t// (2. 得知当前的消息是从哪个玩家传递来的,从连接属性pID中获取)\n\tpID, err := request.GetConnection().GetProperty(\"pID\")\n\tif err != nil {\n\t\tfmt.Println(\"GetProperty pID error\", err)\n\t\trequest.GetConnection().Stop()\n\t\treturn\n\t}\n\n\t// 3. Get the player object based on pID\n\t// (3. 根据pID得到player对象)\n\tplayer := core.WorldMgrObj.GetPlayerByPID(pID.(int32))\n\n\t// 4. Have the player object initiate the chat broadcast request\n\t// (4. 让player对象发起聊天广播请求)\n\tplayer.Talk(msg.Content)\n}\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/client_AI_robot.go",
    "content": "//go:build robot\n// +build robot\n\npackage main\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"fmt\"\n\t\"io\"\n\t\"math/rand\"\n\t\"net\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/pb\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\ntype Message struct {\n\tLen   uint32\n\tMsgID uint32\n\tData  []byte\n}\n\ntype TcpClient struct {\n\tconn     net.Conn\n\tX        float32\n\tY        float32\n\tZ        float32\n\tV        float32\n\tPID      int32\n\tisOnline chan bool\n}\n\nfunc (this *TcpClient) Unpack(headdata []byte) (head *Message, err error) {\n\theadBuf := bytes.NewReader(headdata)\n\n\thead = &Message{}\n\n\t// 读取Len\n\tif err = binary.Read(headBuf, binary.LittleEndian, &head.Len); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 读取MsgID\n\tif err = binary.Read(headBuf, binary.LittleEndian, &head.MsgID); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// 封包太大\n\t//if head.Len > MaxPacketSize {\n\t//\treturn nil, packageTooBig\n\t//}\n\n\treturn head, nil\n}\n\nfunc (this *TcpClient) Pack(msgID uint32, dataBytes []byte) (out []byte, err error) {\n\toutbuff := bytes.NewBuffer([]byte{})\n\t// 写Len\n\tif err = binary.Write(outbuff, binary.LittleEndian, uint32(len(dataBytes))); err != nil {\n\t\treturn\n\t}\n\t// 写MsgID\n\tif err = binary.Write(outbuff, binary.LittleEndian, msgID); err != nil {\n\t\treturn\n\t}\n\n\t//all pkg data\n\tif err = binary.Write(outbuff, binary.LittleEndian, dataBytes); err != nil {\n\t\treturn\n\t}\n\n\tout = outbuff.Bytes()\n\n\treturn\n}\n\nfunc (this *TcpClient) SendMsg(msgID uint32, data proto.Message) {\n\n\t// 进行编码\n\tbinaryData, err := proto.Marshal(data)\n\tif err != nil {\n\t\tfmt.Println(fmt.Sprintf(\"marshaling error:  %s\", err))\n\t\treturn\n\t}\n\n\tsendData, err := this.Pack(msgID, binaryData)\n\tif err == nil {\n\t\t_, _ = this.conn.Write(sendData)\n\t} else {\n\t\tfmt.Println(err)\n\t}\n\n\treturn\n}\n\nfunc (this *TcpClient) AIRobotAction() {\n\t//聊天或者移动\n\n\t//随机获得动作\n\ttp := rand.Intn(2)\n\tif tp == 0 {\n\t\tcontent := fmt.Sprintf(\"hello 我是player %d, 你是谁?\", this.PID)\n\t\tmsg := &pb.Talk{\n\t\t\tContent: content,\n\t\t}\n\t\tthis.SendMsg(2, msg)\n\t} else {\n\t\t//移动\n\t\tx := this.X\n\t\tz := this.Z\n\n\t\trandPos := rand.Intn(2)\n\t\tif randPos == 0 {\n\t\t\tx -= float32(rand.Intn(10))\n\t\t\tz -= float32(rand.Intn(10))\n\t\t} else {\n\t\t\tx += float32(rand.Intn(10))\n\t\t\tz += float32(rand.Intn(10))\n\t\t}\n\n\t\t//纠正坐标位置\n\t\tif x > 410 {\n\t\t\tx = 410\n\t\t} else if x < 85 {\n\t\t\tx = 85\n\t\t}\n\n\t\tif z > 400 {\n\t\t\tz = 400\n\t\t} else if z < 75 {\n\t\t\tz = 75\n\t\t}\n\n\t\t//移动方向角度\n\t\trandV := rand.Intn(2)\n\t\tv := this.V\n\t\tif randV == 0 {\n\t\t\tv = 25\n\t\t} else {\n\t\t\tv = 335\n\t\t}\n\t\t//封装Position消息\n\t\tmsg := &pb.Position{\n\t\t\tX: x,\n\t\t\tY: this.Y,\n\t\t\tZ: z,\n\t\t\tV: v,\n\t\t}\n\n\t\tfmt.Println(fmt.Sprintf(\"player ID: %d. Walking...\", this.PID))\n\t\t//发送移动MsgID:3的指令\n\t\tthis.SendMsg(3, msg)\n\t}\n}\n\n/*\n处理一个回执业务\n*/\nfunc (this *TcpClient) DoMsg(msg *Message) {\n\t//处理消息\n\tfmt.Println(fmt.Sprintf(\"msg ID :%d, data len: %d\", msg.MsgID, msg.Len))\n\tif msg.MsgID == 1 {\n\t\t//服务器回执给客户端 分配ID\n\n\t\t//解析proto\n\t\tsyncpID := &pb.SyncPID{}\n\t\t_ = proto.Unmarshal(msg.Data, syncpID)\n\n\t\t//给当前客户端ID进行赋值\n\t\tthis.PID = syncpID.PID\n\t} else if msg.MsgID == 200 {\n\t\t//服务器回执客户端广播数据\n\n\t\t//解析proto\n\t\tbdata := &pb.BroadCast{}\n\t\t_ = proto.Unmarshal(msg.Data, bdata)\n\n\t\t//初次玩家上线 广播位置消息\n\t\tif bdata.Tp == 2 && bdata.PID == this.PID {\n\t\t\t//本人\n\t\t\t//更新客户端坐标\n\t\t\tthis.X = bdata.GetP().X\n\t\t\tthis.Y = bdata.GetP().Y\n\t\t\tthis.Z = bdata.GetP().Z\n\t\t\tthis.V = bdata.GetP().V\n\t\t\tfmt.Println(fmt.Sprintf(\"player ID: %d online.. at(%f,%f,%f,%f)\", bdata.PID, this.X, this.Y, this.Z, this.V))\n\n\t\t\t//玩家已经成功上线\n\t\t\tthis.isOnline <- true\n\n\t\t} else if bdata.Tp == 1 {\n\t\t\tfmt.Println(fmt.Sprintf(\"世界聊天,玩家%d说的话是: %s\", bdata.PID, bdata.GetContent()))\n\t\t}\n\t}\n}\n\nfunc (this *TcpClient) Start() {\n\tgo func() {\n\t\tfor {\n\t\t\t//读取服务端发来的数据 ==》 SyncPID\n\t\t\t//1.读取8字节\n\t\t\t//第一次读取，读取数据头\n\t\t\theadData := make([]byte, 8)\n\n\t\t\tif _, err := io.ReadFull(this.conn, headData); err != nil {\n\t\t\t\tfmt.Println(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tpkgHead, err := this.Unpack(headData)\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t//data\n\t\t\tif pkgHead.Len > 0 {\n\t\t\t\tpkgHead.Data = make([]byte, pkgHead.Len)\n\t\t\t\tif _, err := io.ReadFull(this.conn, pkgHead.Data); err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//处理服务器回执业务\n\t\t\tthis.DoMsg(pkgHead)\n\t\t}\n\t}()\n\n\t// 10s后，断开连接\n\tfor {\n\t\tselect {\n\t\tcase <-this.isOnline:\n\t\t\tgo func() {\n\t\t\t\tfor {\n\t\t\t\t\tthis.AIRobotAction()\n\t\t\t\t\ttime.Sleep(time.Second)\n\t\t\t\t}\n\t\t\t}()\n\t\tcase <-time.After(time.Second * 10):\n\t\t\t_ = this.conn.Close()\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc NewTcpClient(ip string, port int) *TcpClient {\n\taddrStr := fmt.Sprintf(\"%s:%d\", ip, port)\n\tconn, err := net.Dial(\"tcp\", addrStr)\n\tif err != nil {\n\t\tfmt.Println(\"net.Dial err: \", err)\n\t\tpanic(err)\n\t}\n\n\tclient := &TcpClient{\n\t\tconn:     conn,\n\t\tPID:      0,\n\t\tX:        0,\n\t\tY:        0,\n\t\tZ:        0,\n\t\tV:        0,\n\t\tisOnline: make(chan bool),\n\t}\n\n\tfmt.Println(fmt.Sprintf(\"conn: %+v. Connected to server...\", conn))\n\n\treturn client\n}\n\nfunc main() {\n\t// 开启一个waitgroup，同时运行3个goroutine\n\n\truntime.GOMAXPROCS(runtime.NumCPU())\n\tvar wg sync.WaitGroup\n\twg.Add(3)\n\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor i := 0; i < 10; i++ {\n\t\t\tclient := NewTcpClient(\"127.0.0.1\", 8999)\n\t\t\tclient.Start()\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor i := 0; i < 10; i++ {\n\t\t\tclient := NewTcpClient(\"127.0.0.1\", 8999)\n\t\t\tclient.Start()\n\t\t}\n\t}()\n\n\tgo func() {\n\t\tdefer wg.Done()\n\t\tfor i := 0; i < 10; i++ {\n\t\t\tclient := NewTcpClient(\"127.0.0.1\", 8999)\n\t\t\tclient.Start()\n\t\t}\n\t}()\n\n\tfmt.Println(\"AI robot start\")\n\twg.Wait()\n\n\tfmt.Println(\"AI robot exit\")\n\tselect {}\n}\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/conf/zinx.json",
    "content": "{\r\n  \"Name\":\"Zinx Game\",\r\n  \"Host\":\"0.0.0.0\",\r\n  \"TcpPort\":8999,\r\n  \"MaxConn\":3000,\r\n  \"WorkerPoolSize\":10,\r\n  \"LogDir\": \"./mmo_game_log\",\r\n  \"LogFile\":\"mmo_game.log\"\r\n}\r\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/core/aoi.go",
    "content": "package core\n\nimport \"fmt\"\n\nconst (\n\tAOI_MIN_X  int = 85\n\tAOI_MAX_X  int = 410\n\tAOI_CNTS_X int = 10\n\tAOI_MIN_Y  int = 75\n\tAOI_MAX_Y  int = 400\n\tAOI_CNTS_Y int = 20\n)\n\n// AOIManager AOI management module(AOI管理模块)\ntype AOIManager struct {\n\tMinX  int           // Left boundary coordinate of the area(区域左边界坐标)\n\tMaxX  int           // Right boundary coordinate of the area(区域右边界坐标)\n\tCntsX int           // Number of grids in the x direction(x方向格子的数量)\n\tMinY  int           // Upper boundary coordinate of the area(区域上边界坐标)\n\tMaxY  int           // Lower boundary coordinate of the area(区域下边界坐标)\n\tCntsY int           // Number of grids in the y direction(y方向的格子数量)\n\tgrIDs map[int]*GrID // Which grids are present in the current area, key = grid ID, value = grid object(当前区域中都有哪些格子，key=格子ID， value=格子对象)\n}\n\n// NewAOIManager Initialize an AOI area(初始化一个AOI区域)\nfunc NewAOIManager(minX, maxX, cntsX, minY, maxY, cntsY int) *AOIManager {\n\taoiMgr := &AOIManager{\n\t\tMinX:  minX,\n\t\tMaxX:  maxX,\n\t\tCntsX: cntsX,\n\t\tMinY:  minY,\n\t\tMaxY:  maxY,\n\t\tCntsY: cntsY,\n\t\tgrIDs: make(map[int]*GrID),\n\t}\n\n\t// Initialize all grids in the AOI region (给AOI初始化区域中所有的格子)\n\n\tfor y := 0; y < cntsY; y++ {\n\t\tfor x := 0; x < cntsX; x++ {\n\t\t\t// Calculate the grid ID\n\t\t\t// Grid number: ID = IDy *nx + IDx (obtain grid number using grid coordinates)\n\t\t\t// 计算格子ID\n\t\t\t// 格子编号：ID = IDy *nx + IDx  (利用格子坐标得到格子编号)\n\t\t\tgID := y*cntsX + x\n\n\t\t\t// Initialize a grid in the AOI map, where the key is the current grid's ID\n\t\t\t// 初始化一个格子放在AOI中的map里，key是当前格子的ID\n\t\t\taoiMgr.grIDs[gID] = NewGrID(gID,\n\t\t\t\taoiMgr.MinX+x*aoiMgr.grIDWIDth(),\n\t\t\t\taoiMgr.MinX+(x+1)*aoiMgr.grIDWIDth(),\n\t\t\t\taoiMgr.MinY+y*aoiMgr.grIDLength(),\n\t\t\t\taoiMgr.MinY+(y+1)*aoiMgr.grIDLength())\n\t\t}\n\t}\n\n\treturn aoiMgr\n}\n\n// grIDWIDth Get the width of each grid in the x-axis direction\n// (得到每个格子在x轴方向的宽度)\nfunc (m *AOIManager) grIDWIDth() int {\n\treturn (m.MaxX - m.MinX) / m.CntsX\n}\n\n// grIDLength Get the length of each grid in the x-axis direction\n// (得到每个格子在x轴方向的长度)\nfunc (m *AOIManager) grIDLength() int {\n\treturn (m.MaxY - m.MinY) / m.CntsY\n}\n\n// String Print information method\n// (打印信息方法)\nfunc (m *AOIManager) String() string {\n\ts := fmt.Sprintf(\"AOIManagr:\\nminX:%d, maxX:%d, cntsX:%d, minY:%d, maxY:%d, cntsY:%d\\n GrIDs in AOI Manager:\\n\",\n\t\tm.MinX, m.MaxX, m.CntsX, m.MinY, m.MaxY, m.CntsY)\n\tfor _, grID := range m.grIDs {\n\t\ts += fmt.Sprintln(grID)\n\t}\n\n\treturn s\n}\n\n// GetSurroundGrIDsByGID Get the surrounding nine grids information based on the grid's gID\n// 根据格子的gID得到当前周边的九宫格信息\nfunc (m *AOIManager) GetSurroundGrIDsByGID(gID int) (grIDs []*GrID) {\n\t// Check if gID exists\n\t// 判断gID是否存在\n\tif _, ok := m.grIDs[gID]; !ok {\n\t\treturn\n\t}\n\n\t// Add the current gID to the nine grids\n\t// 将当前gID添加到九宫格中\n\tgrIDs = append(grIDs, m.grIDs[gID])\n\n\t// Get the coordinates of the grid based on gID\n\t// 根据gID, 得到格子所在的坐标\n\tx, y := gID%m.CntsX, gID/m.CntsX\n\n\t// Create a temporary array to store the coordinates of the surrounding grids\n\t// 新建一个临时存储周围格子的数组\n\tsurroundGID := make([]int, 0)\n\n\t// Create eight direction vectors: Upper left: (-1, -1), Left middle: (-1, 0), Upper right: (-1,1),\n\t// Middle upper: (0,-1), Middle lower: (0,1), Right upper: (1, -1), Right middle: (1, 0), Right lower: (1, 1),\n\t// respectively insert these eight direction vectors into the x and y component arrays in order\n\t// 新建8个方向向量: 左上: (-1, -1), 左中: (-1, 0), 左下: (-1,1), 中上: (0,-1), 中下: (0,1), 右上:(1, -1)\n\t// 右中: (1, 0), 右下: (1, 1), 分别将这8个方向的方向向量按顺序写入x, y的分量数组\n\tdx := []int{-1, -1, -1, 0, 0, 1, 1, 1}\n\tdy := []int{-1, 0, 1, -1, 1, -1, 0, 1}\n\n\t// Get the relative coordinates of the surrounding points based on the eight direction vectors,\n\t// select the coordinates that do not go out of bounds, and convert the coordinates to gID\n\t// 根据8个方向向量, 得到周围点的相对坐标, 挑选出没有越界的坐标, 将坐标转换为gID\n\tfor i := 0; i < 8; i++ {\n\t\tnewX := x + dx[i]\n\t\tnewY := y + dy[i]\n\n\t\tif newX >= 0 && newX < m.CntsX && newY >= 0 && newY < m.CntsY {\n\t\t\tsurroundGID = append(surroundGID, newY*m.CntsX+newX)\n\t\t}\n\t}\n\n\t// Get grid information based on valid gID\n\t// 根据没有越界的gID, 得到格子信息\n\tfor _, gID := range surroundGID {\n\t\tgrIDs = append(grIDs, m.grIDs[gID])\n\t}\n\n\treturn\n}\n\n// GetGIDByPos Get the corresponding grid ID by horizontal and vertical coordinates\n// 通过横纵坐标获取对应的格子ID\nfunc (m *AOIManager) GetGIDByPos(x, y float32) int {\n\tgx := (int(x) - m.MinX) / m.grIDWIDth()\n\tgy := (int(y) - m.MinY) / m.grIDLength()\n\n\treturn gy*m.CntsX + gx\n}\n\n// GetPIDsByPos Get all PlayerIDs within the surrounding nine grids by horizontal and vertical coordinates\n// 通过横纵坐标得到周边九宫格内的全部PlayerIDs\nfunc (m *AOIManager) GetPIDsByPos(x, y float32) (playerIDs []int) {\n\t// Get which grid ID the current coordinates belong to\n\t// 根据横纵坐标得到当前坐标属于哪个格子ID\n\tgID := m.GetGIDByPos(x, y)\n\n\t// Get information about the surrounding nine grids based on the grid ID\n\t// 根据格子ID得到周边九宫格的信息\n\tgrIDs := m.GetSurroundGrIDsByGID(gID)\n\tfor _, v := range grIDs {\n\t\tplayerIDs = append(playerIDs, v.GetPlyerIDs()...)\n\t\t//fmt.Printf(\"===> grID ID : %d, pIDs : %v  ====\", v.GID, v.GetPlyerIDs())\n\t}\n\n\treturn\n}\n\n// GetPIDsByGID Get all PlayerIDs within the surrounding nine grids by horizontal and vertical coordinates\n// 通过GID获取当前格子的全部playerID\nfunc (m *AOIManager) GetPIDsByGID(gID int) (playerIDs []int) {\n\tplayerIDs = m.grIDs[gID].GetPlyerIDs()\n\treturn\n}\n\n// RemovePIDFromGrID Remove a PlayerID from a grid\n// 移除一个格子中的PlayerID\nfunc (m *AOIManager) RemovePIDFromGrID(pID, gID int) {\n\tm.grIDs[gID].Remove(pID)\n}\n\n// AddPIDToGrID Add a PlayerID to a grid\n// 添加一个PlayerID到一个格子中\nfunc (m *AOIManager) AddPIDToGrID(pID, gID int) {\n\tm.grIDs[gID].Add(pID)\n}\n\n// AddToGrIDByPos Add a Player to a grid based on horizontal and vertical coordinates\n// 通过横纵坐标添加一个Player到一个格子中\nfunc (m *AOIManager) AddToGrIDByPos(pID int, x, y float32) {\n\tgID := m.GetGIDByPos(x, y)\n\tgrID := m.grIDs[gID]\n\tgrID.Add(pID)\n}\n\n// RemoveFromGrIDByPos Remove a Player from the corresponding grid based on horizontal and vertical coordinates\n// 通过横纵坐标把一个Player从对应的格子中删除\nfunc (m *AOIManager) RemoveFromGrIDByPos(pID int, x, y float32) {\n\tgID := m.GetGIDByPos(x, y)\n\tgrID := m.grIDs[gID]\n\tgrID.Remove(pID)\n}\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/core/aoi_test.go",
    "content": "package core\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestNewAOIManager(t *testing.T) {\n\taoiMgr := NewAOIManager(100, 300, 4, 200, 450, 5)\n\tfmt.Println(aoiMgr)\n}\n\nfunc TestAOIManagerSuroundGrIDsByGID(t *testing.T) {\n\taoiMgr := NewAOIManager(0, 250, 5, 0, 250, 5)\n\n\tfor k := range aoiMgr.grIDs {\n\t\t// Get the surrounding nine grids of the current grid\n\t\t// (得到当前格子周边的九宫格)\n\t\tgrIDs := aoiMgr.GetSurroundGrIDsByGID(k)\n\n\t\t// Get all IDs of the surrounding nine grids\n\t\t// (得到九宫格所有的IDs)\n\t\tfmt.Println(\"gID : \", k, \" grIDs len = \", len(grIDs))\n\t\tgIDs := make([]int, 0, len(grIDs))\n\t\tfor _, grID := range grIDs {\n\t\t\tgIDs = append(gIDs, grID.GID)\n\t\t}\n\t\tfmt.Printf(\"grID ID: %d, surrounding grID IDs are %v\\n\", k, gIDs)\n\t}\n}\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/core/grid.go",
    "content": "package core\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n)\n\n// GrID A grid class in a map 一个地图中的格子类\ntype GrID struct {\n\tGID       int          // Grid ID\n\tMinX      int          // Left boundary coordinate of the grid\n\tMaxX      int          // Right boundary coordinate of the grid\n\tMinY      int          // Upper boundary coordinate of the grid\n\tMaxY      int          // Lower boundary coordinate of the grid\n\tplayerIDs map[int]bool // IDs of players or objects in the current grid\n\tpIDLock   sync.RWMutex // Lock for protecting the playerIDs map\n}\n\n// NewGrID Initialize a grid\nfunc NewGrID(gID, minX, maxX, minY, maxY int) *GrID {\n\treturn &GrID{\n\t\tGID:       gID,\n\t\tMinX:      minX,\n\t\tMaxX:      maxX,\n\t\tMinY:      minY,\n\t\tMaxY:      maxY,\n\t\tplayerIDs: make(map[int]bool),\n\t}\n}\n\n// Add a player to the current grid\nfunc (g *GrID) Add(playerID int) {\n\tg.pIDLock.Lock()\n\tdefer g.pIDLock.Unlock()\n\n\tg.playerIDs[playerID] = true\n}\n\n// Remove a player from the grid\nfunc (g *GrID) Remove(playerID int) {\n\tg.pIDLock.Lock()\n\tdefer g.pIDLock.Unlock()\n\n\tdelete(g.playerIDs, playerID)\n}\n\n// GetPlyerIDs Get all players in the current grid\nfunc (g *GrID) GetPlyerIDs() (playerIDs []int) {\n\tg.pIDLock.RLock()\n\tdefer g.pIDLock.RUnlock()\n\n\tfor k := range g.playerIDs {\n\t\tplayerIDs = append(playerIDs, k)\n\t}\n\n\treturn\n}\n\n// String Print information method\nfunc (g *GrID) String() string {\n\treturn fmt.Sprintf(\"GrID ID: %d, minX:%d, maxX:%d, minY:%d, maxY:%d, playerIDs:%v\",\n\t\tg.GID, g.MinX, g.MaxX, g.MinY, g.MaxY, g.playerIDs)\n}\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/core/player.go",
    "content": "package core\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/pb\"\n\t\"github.com/golang/protobuf/proto\"\n)\n\n// Player object\ntype Player struct {\n\tPID  int32              // Player ID\n\tConn ziface.IConnection // Current player's connection\n\tX    float32            // Planar x coordinate(平面x坐标)\n\tY    float32            // Height(高度)\n\tZ    float32            // Planar y coordinate (Note: not Y)- 平面y坐标 (注意不是Y)\n\tV    float32            //  Rotation 0-360 degrees(旋转0-360度)\n}\n\n// Player ID Generator\nvar PIDGen int32 = 1  // Counter for generating player IDs(用来生成玩家ID的计数器)\nvar IDLock sync.Mutex // Mutex for protecting PIDGen(保护PIDGen的互斥机制)\n\n// NewPlayer Create a player object\nfunc NewPlayer(conn ziface.IConnection) *Player {\n\tIDLock.Lock()\n\tID := PIDGen\n\tPIDGen++\n\tIDLock.Unlock()\n\n\tp := &Player{\n\t\tPID:  ID,\n\t\tConn: conn,\n\t\tX:    float32(160 + rand.Intn(50)), // Randomly offset on the X-axis based on the point 160(随机在160坐标点 基于X轴偏移若干坐标)\n\t\tY:    0,                            // Height is 0\n\t\tZ:    float32(134 + rand.Intn(50)), // Randomly offset on the Y-axis based on the point 134(随机在134坐标点 基于Y轴偏移若干坐标)\n\t\tV:    0,                            // Angle is 0, not yet implemented(角度为0，尚未实现)\n\t}\n\n\treturn p\n}\n\n// SyncPID Inform the client about pID and synchronize the generated player ID to the client\n// (告知客户端pID,同步已经生成的玩家ID给客户端)\nfunc (p *Player) SyncPID() {\n\t// Assemble MsgID0 proto data\n\t// (组建MsgID0 proto数据)\n\tdata := &pb.SyncPID{\n\t\tPID: p.PID,\n\t}\n\n\t// Send data to the client\n\t// (发送数据给客户端)\n\tp.SendMsg(1, data)\n}\n\n// BroadCastStartPosition Broadcast the player's starting position\n// (广播玩家自己的出生地点)\nfunc (p *Player) BroadCastStartPosition() {\n\n\t// Assemble MsgID200 proto data\n\t// (组建MsgID200 proto数据)\n\tmsg := &pb.BroadCast{\n\t\tPID: p.PID,\n\t\tTp:  2, //TP:2  represents broadcasting coordinates (广播坐标)\n\t\tData: &pb.BroadCast_P{\n\t\t\tP: &pb.Position{\n\t\t\t\tX: p.X,\n\t\t\t\tY: p.Y,\n\t\t\t\tZ: p.Z,\n\t\t\t\tV: p.V,\n\t\t\t},\n\t\t},\n\t}\n\n\t// Send data to the client\n\t// 发送数据给客户端\n\tp.SendMsg(200, msg)\n}\n\n// SyncSurrounding Broadcast the player's position to the surrounding players in the same grid\n// 给当前玩家周边的(九宫格内)玩家广播自己的位置，让他们显示自己\nfunc (p *Player) SyncSurrounding() {\n\t//1 Get pIDs of players in the surrounding nine grids based on the player's position\n\t// 根据自己的位置，获取周围九宫格内的玩家pID\n\tpIDs := WorldMgrObj.AoiMgr.GetPIDsByPos(p.X, p.Z)\n\n\t// 2 Get all player objects based on the pIDs\n\t// 根据pID得到所有玩家对象\n\tplayers := make([]*Player, 0, len(pIDs))\n\n\t// 3 Send MsgID:200 message to these players to display themselves in each other's views\n\t// 给这些玩家发送MsgID:200消息，让自己出现在对方视野中\n\tfor _, pID := range pIDs {\n\t\tplayers = append(players, WorldMgrObj.GetPlayerByPID(int32(pID)))\n\t}\n\n\t// 3.1 Assemble MsgID200 proto data\n\t// 组建MsgID200 proto数据\n\tmsg := &pb.BroadCast{\n\t\tPID: p.PID,\n\t\tTp:  2, //TP:2 represents broadcasting coordinates (广播坐标)\n\t\tData: &pb.BroadCast_P{\n\t\t\tP: &pb.Position{\n\t\t\t\tX: p.X,\n\t\t\t\tY: p.Y,\n\t\t\t\tZ: p.Z,\n\t\t\t\tV: p.V,\n\t\t\t},\n\t\t},\n\t}\n\n\t// 3.2 Send the 200 message to each player's client to display characters\n\t// 每个玩家分别给对应的客户端发送200消息，显示人物\n\tfor _, player := range players {\n\t\tplayer.SendMsg(200, msg)\n\t}\n\t// 4 Make surrounding players in the nine grids appear in the player's view\n\t// 让周围九宫格内的玩家出现在自己的视野中\n\n\t// 4.1 Create Message SyncPlayers data\n\t// 制作Message SyncPlayers 数据\n\tplayersData := make([]*pb.Player, 0, len(players))\n\tfor _, player := range players {\n\t\tp := &pb.Player{\n\t\t\tPID: player.PID,\n\t\t\tP: &pb.Position{\n\t\t\t\tX: player.X,\n\t\t\t\tY: player.Y,\n\t\t\t\tZ: player.Z,\n\t\t\t\tV: player.V,\n\t\t\t},\n\t\t}\n\t\tplayersData = append(playersData, p)\n\t}\n\n\t// 4.2 Encapsulate SyncPlayers protobuf data\n\t// 封装SyncPlayer protobuf数据\n\tSyncPlayersMsg := &pb.SyncPlayers{\n\t\tPs: playersData[:],\n\t}\n\n\t// 4.3 Send all player data to the current player to display surrounding players\n\t// 给当前玩家发送需要显示周围的全部玩家数据\n\tp.SendMsg(202, SyncPlayersMsg)\n}\n\n// Talk Broadcast player chat\n// 广播玩家聊天\nfunc (p *Player) Talk(content string) {\n\t// 1. Assemble MsgID200 proto data\n\tmsg := &pb.BroadCast{\n\t\tPID: p.PID,\n\t\tTp:  1, // TP: 1 represents chat broadcast (代表聊天广播)\n\t\tData: &pb.BroadCast_Content{\n\t\t\tContent: content,\n\t\t},\n\t}\n\n\t// 2. Get all online players in the current world\n\t// 得到当前世界所有的在线玩家\n\tplayers := WorldMgrObj.GetAllPlayers()\n\n\t// 3. Send MsgID:200 message to all players\n\t// 向所有的玩家发送MsgID:200消息\n\tfor _, player := range players {\n\t\tplayer.SendMsg(200, msg)\n\t}\n}\n\n// UpdatePos Broadcast player position update\n// (广播玩家位置移动)\nfunc (p *Player) UpdatePos(x float32, y float32, z float32, v float32) {\n\n\t// Trigger visibility change and addition business\n\t// Calculate the old grid gID\n\t// 触发消失视野和添加视野业务\n\t// 计算旧格子gID\n\toldGID := WorldMgrObj.AoiMgr.GetGIDByPos(p.X, p.Z)\n\t// Calculate the new grid gID\n\t// 计算新格子gID\n\tnewGID := WorldMgrObj.AoiMgr.GetGIDByPos(x, z)\n\n\t// Update the player's position information\n\t// 更新玩家的位置信息\n\tp.X = x\n\tp.Y = y\n\tp.Z = z\n\tp.V = v\n\n\tif oldGID != newGID {\n\t\t// Trigger grid switch\n\t\t// Remove pID from the old aoi grid\n\t\t// 触发gird切换\n\t\t// 把pID从就的aoi格子中删除\n\t\tWorldMgrObj.AoiMgr.RemovePIDFromGrID(int(p.PID), oldGID)\n\n\t\t// 把pID添加到新的aoi格子中去\n\t\t// Add pID to the new aoi grid\n\t\tWorldMgrObj.AoiMgr.AddPIDToGrID(int(p.PID), newGID)\n\n\t\t_ = p.OnExchangeAoiGrID(oldGID, newGID)\n\t}\n\n\t// Assemble protobuf data, send position to surrounding players\n\t// 组装protobuf协议，发送位置给周围玩家\n\tmsg := &pb.BroadCast{\n\t\tPID: p.PID,\n\t\tTp:  4, //Tp:4  Coordinates information after movement(移动之后的坐标信息)\n\t\tData: &pb.BroadCast_P{\n\t\t\tP: &pb.Position{\n\t\t\t\tX: p.X,\n\t\t\t\tY: p.Y,\n\t\t\t\tZ: p.Z,\n\t\t\t\tV: p.V,\n\t\t\t},\n\t\t},\n\t}\n\n\t// Get all players around the current player\n\t// (获取当前玩家周边全部玩家)\n\tplayers := p.GetSurroundingPlayers()\n\n\t// Send MsgID:200 message to each player's client, updating position after movement\n\t// (向周边的每个玩家发送MsgID:200消息，移动位置更新消息)\n\tfor _, player := range players {\n\t\tplayer.SendMsg(200, msg)\n\t}\n}\n\nfunc (p *Player) OnExchangeAoiGrID(oldGID, newGID int) error {\n\t// Get members in the old nine-grid from the old grid\n\t// (获取就的九宫格成员)\n\toldGrIDs := WorldMgrObj.AoiMgr.GetSurroundGrIDsByGID(oldGID)\n\n\t// Create a hash table for the old nine-grid members to quickly search\n\t// 为旧的九宫格成员建立哈希表,用来快速查找\n\toldGrIDsMap := make(map[int]bool, len(oldGrIDs))\n\tfor _, grID := range oldGrIDs {\n\t\toldGrIDsMap[grID.GID] = true\n\t}\n\n\t// Get members in the new nine-grid from the new grid\n\t// 获取新的九宫格成员\n\tnewGrIDs := WorldMgrObj.AoiMgr.GetSurroundGrIDsByGID(newGID)\n\n\t// Create a hash table for the new nine-grid members to quickly search\n\t// 为新的九宫格成员建立哈希表,用来快速查找\n\tnewGrIDsMap := make(map[int]bool, len(newGrIDs))\n\tfor _, grID := range newGrIDs {\n\t\tnewGrIDsMap[grID.GID] = true\n\t}\n\n\t//------ > Handle visibility disappearance (处理视野消失) <-------\n\tofflineMsg := &pb.SyncPID{\n\t\tPID: p.PID,\n\t}\n\n\t// Find the grid IDs that appear in the old nine-grid but not in the new nine-grid\n\t// (找到在旧的九宫格中出现,但是在新的九宫格中没有出现的格子)\n\tleavingGrIDs := make([]*GrID, 0)\n\tfor _, grID := range oldGrIDs {\n\t\tif _, ok := newGrIDsMap[grID.GID]; !ok {\n\t\t\tleavingGrIDs = append(leavingGrIDs, grID)\n\t\t}\n\t}\n\n\t// Get all players in the disappearing grids\n\t// (获取需要消失的格子中的全部玩家)\n\tfor _, grID := range leavingGrIDs {\n\t\tplayers := WorldMgrObj.GetPlayersByGID(grID.GID)\n\t\tfor _, player := range players {\n\n\t\t\t// Make oneself disappear in the views of other players\n\t\t\t// 让自己在其他玩家的客户端中消失\n\t\t\tplayer.SendMsg(201, offlineMsg)\n\n\t\t\t// Make other players' information disappear in one's own client\n\t\t\t// 将其他玩家信息 在自己的客户端中消失\n\t\t\tanotherOfflineMsg := &pb.SyncPID{\n\t\t\t\tPID: player.PID,\n\t\t\t}\n\t\t\tp.SendMsg(201, anotherOfflineMsg)\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t}\n\t}\n\n\t// ------ > Handle visibility appearance(处理视野出现) <-------\n\n\t// Find the grid IDs that appear in the new nine-grid but not in the old nine-grid\n\t// 找到在新的九宫格内出现,但是没有在就的九宫格内出现的格子\n\tenteringGrIDs := make([]*GrID, 0)\n\tfor _, grID := range newGrIDs {\n\t\tif _, ok := oldGrIDsMap[grID.GID]; !ok {\n\t\t\tenteringGrIDs = append(enteringGrIDs, grID)\n\t\t}\n\t}\n\n\tonlineMsg := &pb.BroadCast{\n\t\tPID: p.PID,\n\t\tTp:  2,\n\t\tData: &pb.BroadCast_P{\n\t\t\tP: &pb.Position{\n\t\t\t\tX: p.X,\n\t\t\t\tY: p.Y,\n\t\t\t\tZ: p.Z,\n\t\t\t\tV: p.V,\n\t\t\t},\n\t\t},\n\t}\n\n\t// Get all players in the appearing grids\n\t// 获取需要显示格子的全部玩家\n\tfor _, grID := range enteringGrIDs {\n\t\tplayers := WorldMgrObj.GetPlayersByGID(grID.GID)\n\n\t\tfor _, player := range players {\n\t\t\t// Make oneself appear in the views of other players\n\t\t\t// 让自己出现在其他人视野中\n\t\t\tplayer.SendMsg(200, onlineMsg)\n\n\t\t\t// Make other players appear in one's own client\n\t\t\t// 让其他人出现在自己的视野中\n\t\t\tanotherOnlineMsg := &pb.BroadCast{\n\t\t\t\tPID: player.PID,\n\t\t\t\tTp:  2,\n\t\t\t\tData: &pb.BroadCast_P{\n\t\t\t\t\tP: &pb.Position{\n\t\t\t\t\t\tX: player.X,\n\t\t\t\t\t\tY: player.Y,\n\t\t\t\t\t\tZ: player.Z,\n\t\t\t\t\t\tV: player.V,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t}\n\n\t\t\ttime.Sleep(200 * time.Millisecond)\n\t\t\tp.SendMsg(200, anotherOnlineMsg)\n\t\t}\n\t}\n\n\treturn nil\n}\n\n// Get information of surrounding players in the current player's AOI\n// 获得当前玩家的AOI周边玩家信息\nfunc (p *Player) GetSurroundingPlayers() []*Player {\n\t// Get all pIDs in the current AOI area\n\t// 得到当前AOI区域的所有pID\n\tpIDs := WorldMgrObj.AoiMgr.GetPIDsByPos(p.X, p.Z)\n\n\t// Put all players corresponding to pIDs into the Player slice\n\t// 将所有pID对应的Player放到Player切片中\n\tplayers := make([]*Player, 0, len(pIDs))\n\tfor _, pID := range pIDs {\n\t\tplayers = append(players, WorldMgrObj.GetPlayerByPID(int32(pID)))\n\t}\n\n\treturn players\n}\n\n// Player logs off\n// 玩家下线\nfunc (p *Player) LostConnection() {\n\t// 1 Get players in the surrounding AOI nine-grid\n\t// 获取周围AOI九宫格内的玩家\n\tplayers := p.GetSurroundingPlayers()\n\n\t// 2 Assemble MsgID:201 message\n\t// 封装MsgID:201消息\n\tmsg := &pb.SyncPID{\n\t\tPID: p.PID,\n\t}\n\n\t// 3 Send messages to surrounding players\n\t// 向周围玩家发送消息\n\tfor _, player := range players {\n\t\tplayer.SendMsg(201, msg)\n\t}\n\n\t// 4 Remove the current player from AOI in the world manager\n\t// 世界管理器将当前玩家从AOI中摘除\n\tWorldMgrObj.AoiMgr.RemoveFromGrIDByPos(int(p.PID), p.X, p.Z)\n\tWorldMgrObj.RemovePlayerByPID(p.PID)\n}\n\n// SendMsg Send messages to the client, mainly serializing and sending the protobuf data of the pb Message\n//\n//\t(发送消息给客户端，主要是将pb的protobuf数据序列化之后发送)\nfunc (p *Player) SendMsg(msgID uint32, data proto.Message) {\n\tif p.Conn == nil {\n\t\tfmt.Println(\"connection in player is nil\")\n\t\treturn\n\t}\n\n\t// fmt.Printf(\"before Marshal data = %+v\\n\", data)\n\n\t// Serialize the proto Message structure\n\t// 将proto Message结构体序列化\n\tmsg, err := proto.Marshal(data)\n\tif err != nil {\n\t\tfmt.Println(\"marshal msg err: \", err)\n\t\treturn\n\t}\n\n\t// fmt.Printf(\"after Marshal data = %+v\\n\", msg)\n\n\t// Call the Zinx framework's SendMsg to send the packet\n\t// 调用Zinx框架的SendMsg发包\n\tif err := p.Conn.SendMsg(msgID, msg); err != nil {\n\t\tfmt.Println(\"Player SendMsg error !\")\n\t\treturn\n\t}\n\n\treturn\n}\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/core/world_manager.go",
    "content": "package core\n\nimport (\n\t\"sync\"\n)\n\n// WorldManager The overall management module of the current game world 当前游戏世界的总管理模块\ntype WorldManager struct {\n\tAoiMgr  *AOIManager       // AOI planning manager for the current world map(当前世界地图的AOI规划管理器)\n\tPlayers map[int32]*Player // Collection of currently online players(当前在线的玩家集合)\n\tpLock   sync.RWMutex      // Mutual exclusion mechanism to protect Players(保护Players的互斥读写机制)\n}\n\n// Provide an external handle to the world management module\n// 提供一个对外的世界管理模块句柄\nvar WorldMgrObj *WorldManager\n\n// Provide an initialization method for WorldManager\n// 提供WorldManager 初始化方法\nfunc init() {\n\tWorldMgrObj = &WorldManager{\n\t\tPlayers: make(map[int32]*Player),\n\t\tAoiMgr:  NewAOIManager(AOI_MIN_X, AOI_MAX_X, AOI_CNTS_X, AOI_MIN_Y, AOI_MAX_Y, AOI_CNTS_Y),\n\t}\n}\n\n// AddPlayer Provide the ability to add a player, adding the player to the player information table Players\n// (提供添加一个玩家的的功能，将玩家添加进玩家信息表Players)\nfunc (wm *WorldManager) AddPlayer(player *Player) {\n\t// Add the player to the world manager\n\t// 将player添加到 世界管理器中\n\twm.pLock.Lock()\n\twm.Players[player.PID] = player\n\twm.pLock.Unlock()\n\n\t// Add the player to the AOI network planning\n\t// 将player 添加到AOI网络规划中\n\twm.AoiMgr.AddToGrIDByPos(int(player.PID), player.X, player.Z)\n}\n\n// RemovePlayerByPID Remove a player from the player information table by player ID\n// 从玩家信息表中移除一个玩家\nfunc (wm *WorldManager) RemovePlayerByPID(pID int32) {\n\twm.pLock.Lock()\n\tdelete(wm.Players, pID)\n\twm.pLock.Unlock()\n}\n\n// GetPlayerByPID Get corresponding player information by player ID\n// 通过玩家ID 获取对应玩家信息\nfunc (wm *WorldManager) GetPlayerByPID(pID int32) *Player {\n\twm.pLock.RLock()\n\tdefer wm.pLock.RUnlock()\n\n\treturn wm.Players[pID]\n}\n\n// GetAllPlayers Get information of all players\n// 获取所有玩家的信息\nfunc (wm *WorldManager) GetAllPlayers() []*Player {\n\twm.pLock.RLock()\n\tdefer wm.pLock.RUnlock()\n\n\t// Create a slice to return player collection\n\t// 创建返回的player集合切片\n\tplayers := make([]*Player, 0)\n\n\t// Add to the slice\n\t// 添加切片\n\tfor _, v := range wm.Players {\n\t\tplayers = append(players, v)\n\t}\n\n\treturn players\n}\n\n// GetPlayersByGID Get information of all players in a specific gID\n// 获取指定gID中的所有player信息\nfunc (wm *WorldManager) GetPlayersByGID(gID int) []*Player {\n\t// Get all pIDs corresponding to the gID\n\t// 通过gID获取 对应 格子中的所有pID\n\tpIDs := wm.AoiMgr.grIDs[gID].GetPlyerIDs()\n\n\t// Get player objects corresponding to pIDs\n\t// 通过pID找到对应的player对象\n\tplayers := make([]*Player, 0, len(pIDs))\n\twm.pLock.RLock()\n\tfor _, pID := range pIDs {\n\t\tplayers = append(players, wm.Players[int32(pID)])\n\t}\n\twm.pLock.RUnlock()\n\n\treturn players\n}\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/pb/build.sh",
    "content": "#!/bin/bash\nprotoc --go_out=. *.proto\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/pb/msg.pb.go",
    "content": "// Code generated by protoc-gen-go. DO NOT EDIT.\n// source: msg.proto\n\npackage pb\n\nimport (\n\tfmt \"fmt\"\n\tproto \"github.com/golang/protobuf/proto\"\n\tmath \"math\"\n)\n\n// Reference imports to suppress errors if they are not otherwise used.\nvar _ = proto.Marshal\nvar _ = fmt.Errorf\nvar _ = math.Inf\n\n// This is a compile-time assertion to ensure that this generated file\n// is compatible with the proto package it is being compiled against.\n// A compilation error at this line likely means your copy of the\n// proto package needs to be updated.\nconst _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package\n\n// Synchronize client player ID\n// 同步客户端玩家ID\ntype SyncPID struct {\n\tPID                  int32    `protobuf:\"varint,1,opt,name=PID,proto3\" json:\"PID,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *SyncPID) Reset()         { *m = SyncPID{} }\nfunc (m *SyncPID) String() string { return proto.CompactTextString(m) }\nfunc (*SyncPID) ProtoMessage()    {}\nfunc (*SyncPID) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_c06e4cca6c2cc899, []int{0}\n}\n\nfunc (m *SyncPID) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_SyncPID.Unmarshal(m, b)\n}\nfunc (m *SyncPID) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_SyncPID.Marshal(b, m, deterministic)\n}\nfunc (m *SyncPID) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_SyncPID.Merge(m, src)\n}\nfunc (m *SyncPID) XXX_Size() int {\n\treturn xxx_messageInfo_SyncPID.Size(m)\n}\nfunc (m *SyncPID) XXX_DiscardUnknown() {\n\txxx_messageInfo_SyncPID.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_SyncPID proto.InternalMessageInfo\n\nfunc (m *SyncPID) GetPID() int32 {\n\tif m != nil {\n\t\treturn m.PID\n\t}\n\treturn 0\n}\n\n// Player position\ntype Position struct {\n\tX                    float32  `protobuf:\"fixed32,1,opt,name=X,proto3\" json:\"X,omitempty\"`\n\tY                    float32  `protobuf:\"fixed32,2,opt,name=Y,proto3\" json:\"Y,omitempty\"`\n\tZ                    float32  `protobuf:\"fixed32,3,opt,name=Z,proto3\" json:\"Z,omitempty\"`\n\tV                    float32  `protobuf:\"fixed32,4,opt,name=V,proto3\" json:\"V,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Position) Reset()         { *m = Position{} }\nfunc (m *Position) String() string { return proto.CompactTextString(m) }\nfunc (*Position) ProtoMessage()    {}\nfunc (*Position) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_c06e4cca6c2cc899, []int{1}\n}\n\nfunc (m *Position) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_Position.Unmarshal(m, b)\n}\nfunc (m *Position) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_Position.Marshal(b, m, deterministic)\n}\nfunc (m *Position) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Position.Merge(m, src)\n}\nfunc (m *Position) XXX_Size() int {\n\treturn xxx_messageInfo_Position.Size(m)\n}\nfunc (m *Position) XXX_DiscardUnknown() {\n\txxx_messageInfo_Position.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Position proto.InternalMessageInfo\n\nfunc (m *Position) GetX() float32 {\n\tif m != nil {\n\t\treturn m.X\n\t}\n\treturn 0\n}\n\nfunc (m *Position) GetY() float32 {\n\tif m != nil {\n\t\treturn m.Y\n\t}\n\treturn 0\n}\n\nfunc (m *Position) GetZ() float32 {\n\tif m != nil {\n\t\treturn m.Z\n\t}\n\treturn 0\n}\n\nfunc (m *Position) GetV() float32 {\n\tif m != nil {\n\t\treturn m.V\n\t}\n\treturn 0\n}\n\n// Player broadcast data\n// 玩家广播数据\ntype BroadCast struct {\n\tPID int32 `protobuf:\"varint,1,opt,name=PID,proto3\" json:\"PID,omitempty\"`\n\t// 1 - World chat, 2 - Player position, 3 - Action, 4 - Update of coordinates after movement\n\t// 1-世界聊天  2-玩家位置 3 动作 4 移动之后坐标信息更新\n\tTp int32 `protobuf:\"varint,2,opt,name=Tp,proto3\" json:\"Tp,omitempty\"`\n\t// Types that are valid to be assigned to Data:\n\t//\t*BroadCast_Content\n\t//\t*BroadCast_P\n\t//\t*BroadCast_ActionData\n\tData                 isBroadCast_Data `protobuf_oneof:\"Data\"`\n\tXXX_NoUnkeyedLiteral struct{}         `json:\"-\"`\n\tXXX_unrecognized     []byte           `json:\"-\"`\n\tXXX_sizecache        int32            `json:\"-\"`\n}\n\nfunc (m *BroadCast) Reset()         { *m = BroadCast{} }\nfunc (m *BroadCast) String() string { return proto.CompactTextString(m) }\nfunc (*BroadCast) ProtoMessage()    {}\nfunc (*BroadCast) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_c06e4cca6c2cc899, []int{2}\n}\n\nfunc (m *BroadCast) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_BroadCast.Unmarshal(m, b)\n}\nfunc (m *BroadCast) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_BroadCast.Marshal(b, m, deterministic)\n}\nfunc (m *BroadCast) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_BroadCast.Merge(m, src)\n}\nfunc (m *BroadCast) XXX_Size() int {\n\treturn xxx_messageInfo_BroadCast.Size(m)\n}\nfunc (m *BroadCast) XXX_DiscardUnknown() {\n\txxx_messageInfo_BroadCast.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_BroadCast proto.InternalMessageInfo\n\nfunc (m *BroadCast) GetPID() int32 {\n\tif m != nil {\n\t\treturn m.PID\n\t}\n\treturn 0\n}\n\nfunc (m *BroadCast) GetTp() int32 {\n\tif m != nil {\n\t\treturn m.Tp\n\t}\n\treturn 0\n}\n\ntype isBroadCast_Data interface {\n\tisBroadCast_Data()\n}\n\ntype BroadCast_Content struct {\n\tContent string `protobuf:\"bytes,3,opt,name=Content,proto3,oneof\"`\n}\n\ntype BroadCast_P struct {\n\tP *Position `protobuf:\"bytes,4,opt,name=P,proto3,oneof\"`\n}\n\ntype BroadCast_ActionData struct {\n\tActionData int32 `protobuf:\"varint,5,opt,name=ActionData,proto3,oneof\"`\n}\n\nfunc (*BroadCast_Content) isBroadCast_Data() {}\n\nfunc (*BroadCast_P) isBroadCast_Data() {}\n\nfunc (*BroadCast_ActionData) isBroadCast_Data() {}\n\nfunc (m *BroadCast) GetData() isBroadCast_Data {\n\tif m != nil {\n\t\treturn m.Data\n\t}\n\treturn nil\n}\n\nfunc (m *BroadCast) GetContent() string {\n\tif x, ok := m.GetData().(*BroadCast_Content); ok {\n\t\treturn x.Content\n\t}\n\treturn \"\"\n}\n\nfunc (m *BroadCast) GetP() *Position {\n\tif x, ok := m.GetData().(*BroadCast_P); ok {\n\t\treturn x.P\n\t}\n\treturn nil\n}\n\nfunc (m *BroadCast) GetActionData() int32 {\n\tif x, ok := m.GetData().(*BroadCast_ActionData); ok {\n\t\treturn x.ActionData\n\t}\n\treturn 0\n}\n\n// XXX_OneofWrappers is for the internal use of the proto package.\nfunc (*BroadCast) XXX_OneofWrappers() []interface{} {\n\treturn []interface{}{\n\t\t(*BroadCast_Content)(nil),\n\t\t(*BroadCast_P)(nil),\n\t\t(*BroadCast_ActionData)(nil),\n\t}\n}\n\n// Player chat data\n// 玩家聊天数据\ntype Talk struct {\n\tContent              string   `protobuf:\"bytes,1,opt,name=Content,proto3\" json:\"Content,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{} `json:\"-\"`\n\tXXX_unrecognized     []byte   `json:\"-\"`\n\tXXX_sizecache        int32    `json:\"-\"`\n}\n\nfunc (m *Talk) Reset()         { *m = Talk{} }\nfunc (m *Talk) String() string { return proto.CompactTextString(m) }\nfunc (*Talk) ProtoMessage()    {}\nfunc (*Talk) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_c06e4cca6c2cc899, []int{3}\n}\n\nfunc (m *Talk) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_Talk.Unmarshal(m, b)\n}\nfunc (m *Talk) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_Talk.Marshal(b, m, deterministic)\n}\nfunc (m *Talk) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Talk.Merge(m, src)\n}\nfunc (m *Talk) XXX_Size() int {\n\treturn xxx_messageInfo_Talk.Size(m)\n}\nfunc (m *Talk) XXX_DiscardUnknown() {\n\txxx_messageInfo_Talk.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Talk proto.InternalMessageInfo\n\nfunc (m *Talk) GetContent() string {\n\tif m != nil {\n\t\treturn m.Content\n\t}\n\treturn \"\"\n}\n\n// Player information\ntype Player struct {\n\tPID                  int32     `protobuf:\"varint,1,opt,name=PID,proto3\" json:\"PID,omitempty\"`\n\tP                    *Position `protobuf:\"bytes,2,opt,name=P,proto3\" json:\"P,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *Player) Reset()         { *m = Player{} }\nfunc (m *Player) String() string { return proto.CompactTextString(m) }\nfunc (*Player) ProtoMessage()    {}\nfunc (*Player) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_c06e4cca6c2cc899, []int{4}\n}\n\nfunc (m *Player) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_Player.Unmarshal(m, b)\n}\nfunc (m *Player) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_Player.Marshal(b, m, deterministic)\n}\nfunc (m *Player) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_Player.Merge(m, src)\n}\nfunc (m *Player) XXX_Size() int {\n\treturn xxx_messageInfo_Player.Size(m)\n}\nfunc (m *Player) XXX_DiscardUnknown() {\n\txxx_messageInfo_Player.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_Player proto.InternalMessageInfo\n\nfunc (m *Player) GetPID() int32 {\n\tif m != nil {\n\t\treturn m.PID\n\t}\n\treturn 0\n}\n\nfunc (m *Player) GetP() *Position {\n\tif m != nil {\n\t\treturn m.P\n\t}\n\treturn nil\n}\n\n// Synchronize player display data\n// 同步玩家显示数据\ntype SyncPlayers struct {\n\tPs                   []*Player `protobuf:\"bytes,1,rep,name=ps,proto3\" json:\"ps,omitempty\"`\n\tXXX_NoUnkeyedLiteral struct{}  `json:\"-\"`\n\tXXX_unrecognized     []byte    `json:\"-\"`\n\tXXX_sizecache        int32     `json:\"-\"`\n}\n\nfunc (m *SyncPlayers) Reset()         { *m = SyncPlayers{} }\nfunc (m *SyncPlayers) String() string { return proto.CompactTextString(m) }\nfunc (*SyncPlayers) ProtoMessage()    {}\nfunc (*SyncPlayers) Descriptor() ([]byte, []int) {\n\treturn fileDescriptor_c06e4cca6c2cc899, []int{5}\n}\n\nfunc (m *SyncPlayers) XXX_Unmarshal(b []byte) error {\n\treturn xxx_messageInfo_SyncPlayers.Unmarshal(m, b)\n}\nfunc (m *SyncPlayers) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {\n\treturn xxx_messageInfo_SyncPlayers.Marshal(b, m, deterministic)\n}\nfunc (m *SyncPlayers) XXX_Merge(src proto.Message) {\n\txxx_messageInfo_SyncPlayers.Merge(m, src)\n}\nfunc (m *SyncPlayers) XXX_Size() int {\n\treturn xxx_messageInfo_SyncPlayers.Size(m)\n}\nfunc (m *SyncPlayers) XXX_DiscardUnknown() {\n\txxx_messageInfo_SyncPlayers.DiscardUnknown(m)\n}\n\nvar xxx_messageInfo_SyncPlayers proto.InternalMessageInfo\n\nfunc (m *SyncPlayers) GetPs() []*Player {\n\tif m != nil {\n\t\treturn m.Ps\n\t}\n\treturn nil\n}\n\nfunc init() {\n\tproto.RegisterType((*SyncPID)(nil), \"pb.SyncPID\")\n\tproto.RegisterType((*Position)(nil), \"pb.Position\")\n\tproto.RegisterType((*BroadCast)(nil), \"pb.BroadCast\")\n\tproto.RegisterType((*Talk)(nil), \"pb.Talk\")\n\tproto.RegisterType((*Player)(nil), \"pb.Player\")\n\tproto.RegisterType((*SyncPlayers)(nil), \"pb.SyncPlayers\")\n}\n\nfunc init() { proto.RegisterFile(\"msg.proto\", fileDescriptor_c06e4cca6c2cc899) }\n\nvar fileDescriptor_c06e4cca6c2cc899 = []byte{\n\t// 278 bytes of a gzipped FileDescriptorProto\n\t0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xbf, 0x4e, 0xc3, 0x30,\n\t0x10, 0x87, 0x63, 0xe7, 0x4f, 0xc9, 0xa5, 0x42, 0xc8, 0x93, 0x15, 0x18, 0x22, 0x4f, 0x65, 0xc9,\n\t0x50, 0x24, 0x76, 0xd2, 0x0c, 0xe9, 0x66, 0x99, 0xa8, 0x6a, 0xbb, 0x39, 0xa5, 0x42, 0x15, 0x25,\n\t0xb6, 0x62, 0x2f, 0x7d, 0x0c, 0x5e, 0x83, 0xa7, 0x44, 0x76, 0x54, 0x40, 0x6a, 0x27, 0xfb, 0xbb,\n\t0xb3, 0x7f, 0xf7, 0xc9, 0x86, 0xf4, 0xd3, 0xbc, 0x97, 0x7a, 0x50, 0x56, 0x11, 0xac, 0x3b, 0x76,\n\t0x0f, 0x93, 0xd7, 0x53, 0xbf, 0xe3, 0xcb, 0x9a, 0xdc, 0x41, 0xc8, 0x97, 0x35, 0x45, 0x05, 0x9a,\n\t0xc5, 0xc2, 0x6d, 0x59, 0x05, 0x37, 0x5c, 0x99, 0x83, 0x3d, 0xa8, 0x9e, 0x4c, 0x01, 0xad, 0x7d,\n\t0x0f, 0x0b, 0xb4, 0x76, 0xb4, 0xa1, 0x78, 0xa4, 0x8d, 0xa3, 0x2d, 0x0d, 0x47, 0xda, 0x3a, 0x5a,\n\t0xd1, 0x68, 0xa4, 0x15, 0xfb, 0x42, 0x90, 0x56, 0x83, 0x92, 0x6f, 0x0b, 0x69, 0xec, 0xe5, 0x0c,\n\t0x72, 0x0b, 0xb8, 0xd5, 0x3e, 0x2a, 0x16, 0xb8, 0xd5, 0x24, 0x87, 0xc9, 0x42, 0xf5, 0x76, 0xdf,\n\t0x5b, 0x9f, 0x98, 0x36, 0x81, 0x38, 0x17, 0xc8, 0x03, 0x20, 0xee, 0x93, 0xb3, 0xf9, 0xb4, 0xd4,\n\t0x5d, 0x79, 0x96, 0x6b, 0x02, 0x81, 0x38, 0x29, 0x00, 0x5e, 0x76, 0x0e, 0x6b, 0x69, 0x25, 0x8d,\n\t0x5d, 0x62, 0x13, 0x88, 0x7f, 0xb5, 0x2a, 0x81, 0xc8, 0xad, 0xac, 0x80, 0xa8, 0x95, 0xc7, 0x0f,\n\t0x42, 0xff, 0x66, 0x39, 0xa3, 0xf4, 0x77, 0x12, 0x7b, 0x86, 0x84, 0x1f, 0xe5, 0x69, 0x3f, 0x5c,\n\t0x31, 0xce, 0x9d, 0x05, 0xbe, 0xb4, 0x10, 0x88, 0xb3, 0x47, 0xc8, 0xfc, 0x73, 0xfa, 0xbb, 0x86,\n\t0xe4, 0x80, 0xb5, 0xa1, 0xa8, 0x08, 0x67, 0xd9, 0x1c, 0xfc, 0x59, 0xdf, 0x10, 0x58, 0x9b, 0x2a,\n\t0xfe, 0xc6, 0x98, 0x77, 0x5d, 0xe2, 0xff, 0xe2, 0xe9, 0x27, 0x00, 0x00, 0xff, 0xff, 0x31, 0xa4,\n\t0x44, 0x79, 0x98, 0x01, 0x00, 0x00,\n}\n"
  },
  {
    "path": "zinx_app_demo/mmo_game/pb/msg.proto",
    "content": "syntax=\"proto3\";                // Version of protobuf\r\npackage pb;                     // Current package name\r\noption csharp_namespace=\"Pb\";   // Option for C# (给C#提供的选项)\r\n\r\n// Synchronize client player ID\r\n// 同步客户端玩家ID\r\nmessage SyncPID{\r\n\tint32 PID=1;\r\n}\r\n\r\n// Player position\r\nmessage Position{\r\n\tfloat X=1;\r\n\tfloat Y=2;\r\n\tfloat Z=3;\r\n\tfloat V=4;\r\n}\r\n\r\n// Player broadcast data\r\n// 玩家广播数据\r\nmessage BroadCast{\r\n\tint32 PID=1;\r\n\t// 1 - World chat, 2 - Player position, 3 - Action, 4 - Update of coordinates after movement\r\n\t// 1-世界聊天  2-玩家位置 3 动作 4 移动之后坐标信息更新\r\n\tint32 Tp=2;\r\n\toneof Data {\r\n\t\tstring Content=3;    // Chat message(聊天的信息)\r\n\t\tPosition P=4;        // Player's position for broadcasting(广播用户的位置)\r\n\t\tint32 ActionData=5;\r\n\t}\r\n}\r\n\r\n// Player chat data\r\n// 玩家聊天数据\r\nmessage Talk{\r\n\tstring Content=1;    //聊天内容\r\n}\r\n\r\n// Player information\r\nmessage Player{\r\n\tint32 PID=1;\r\n\tPosition P=2;\r\n}\r\n\r\n// Synchronize player display data\r\n// 同步玩家显示数据\r\nmessage SyncPlayers{\r\n\trepeated Player ps=1;\r\n}"
  },
  {
    "path": "zinx_app_demo/mmo_game/server.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/api\"\n\t\"github.com/aceld/zinx/zinx_app_demo/mmo_game/core\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"github.com/aceld/zinx/zpack\"\n)\n\n// OnConnectionAdd is a hook function called when a client establishes a connection\n// 当客户端建立连接的时候的hook函数\nfunc OnConnectionAdd(conn ziface.IConnection) {\n\tfmt.Println(\"=====> OnConnectionAdd is Called ...\")\n\t// Create a new player\n\t// 创建一个玩家\n\tplayer := core.NewPlayer(conn)\n\n\t// Synchronize the current player's ID to the client using MsgID:1 message\n\t// 同步当前的PlayerID给客户端， 走MsgID:1 消息\n\tplayer.SyncPID()\n\n\t// Synchronize the initial coordinate information of the current player to the client using MsgID:200 message\n\t// 同步当前玩家的初始化坐标信息给客户端，走MsgID:200消息\n\tplayer.BroadCastStartPosition()\n\n\t// Add the newly online player to the WorldManager\n\t// 将当前新上线玩家添加到worldManager中\n\tcore.WorldMgrObj.AddPlayer(player)\n\n\t// Bind the property \"pID\" to the connection\n\t// 将该连接绑定属性PID\n\tconn.SetProperty(\"pID\", player.PID)\n\n\t// Synchronize online player information and display surrounding player information\n\t// 同步周边玩家上线信息，与现实周边玩家信息\n\tplayer.SyncSurrounding()\n\n\tfmt.Println(\"=====> Player pIDID = \", player.PID, \" arrived ====\")\n}\n\n// OnConnectionLost Hook function called when a client disconnects\n// 当客户端断开连接的时候的hook函数\nfunc OnConnectionLost(conn ziface.IConnection) {\n\t// Get the \"pID\" property of the current connection\n\t// 获取当前连接的PID属性\n\tpID, _ := conn.GetProperty(\"pID\")\n\tvar playerID int32\n\tif pID != nil {\n\t\tplayerID = pID.(int32)\n\t}\n\n\t// Get the corresponding player object based on the player ID\n\t// 根据pID获取对应的玩家对象\n\tplayer := core.WorldMgrObj.GetPlayerByPID(playerID)\n\n\t// Trigger the player's disconnection business logic\n\t// 触发玩家下线业务\n\tif player != nil {\n\t\tplayer.LostConnection()\n\t}\n\n\tfmt.Println(\"====> Player \", playerID, \" left =====\")\n\n}\n\nfunc main() {\n\t// Create a server instance\n\t// 创建服务器句柄\n\ts := znet.NewServer()\n\n\t// Register functions for client connection establishment and loss\n\t// 注册客户端连接建立和丢失函数\n\ts.SetOnConnStart(OnConnectionAdd)\n\ts.SetOnConnStop(OnConnectionLost)\n\n\t// Register routers\n\ts.AddRouter(2, &api.WorldChatApi{})\n\ts.AddRouter(3, &api.MoveApi{})\n\n\t// Add LTV data format Decoder\n\ts.SetDecoder(zdecoder.NewLTV_Little_Decoder())\n\t// Add LTV data format Pack packet Encoder\n\ts.SetPacket(zpack.NewDataPackLtv())\n\n\t// Start the server\n\ts.Serve()\n}\n"
  },
  {
    "path": "zlog/default.go",
    "content": "package zlog\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\n\t\"github.com/aceld/zinx/ziface\"\n)\n\nvar zLogInstance ziface.ILogger = new(zinxDefaultLog)\n\ntype zinxDefaultLog struct{}\n\nfunc (log *zinxDefaultLog) InfoF(format string, v ...interface{}) {\n\tStdZinxLog.Infof(format, v...)\n}\n\nfunc (log *zinxDefaultLog) ErrorF(format string, v ...interface{}) {\n\tStdZinxLog.Errorf(format, v...)\n}\n\nfunc (log *zinxDefaultLog) DebugF(format string, v ...interface{}) {\n\tStdZinxLog.Debugf(format, v...)\n}\n\nfunc (log *zinxDefaultLog) InfoFX(ctx context.Context, format string, v ...interface{}) {\n\tfmt.Println(ctx)\n\tStdZinxLog.Infof(format, v...)\n}\n\nfunc (log *zinxDefaultLog) ErrorFX(ctx context.Context, format string, v ...interface{}) {\n\tfmt.Println(ctx)\n\tStdZinxLog.Errorf(format, v...)\n}\n\nfunc (log *zinxDefaultLog) DebugFX(ctx context.Context, format string, v ...interface{}) {\n\tfmt.Println(ctx)\n\tStdZinxLog.Debugf(format, v...)\n}\n\nfunc SetLogger(newlog ziface.ILogger) {\n\tzLogInstance = newlog\n}\n\nfunc Ins() ziface.ILogger {\n\treturn zLogInstance\n}\n"
  },
  {
    "path": "zlog/logger_core.go",
    "content": "// Package zlog provides logging interfaces for zinx.\n// This includes:\n//\n// - stdzlog module, which provides global logging methods\n// - zlogger module, which defines logging protocols as object methods\n//\n// Current file description:\n// @Title zlogger.go\n// @Description Basic logging interface, including Debug, Fatal, etc.\n// @Author Aceld - Thu Mar 11 10:32:29 CST 2019\npackage zlog\n\n/*\n\tAll methods and APIs of the log class.\n\tAdd By Aceld(刘丹冰) 2019-4-23\n*/\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zutils\"\n)\n\nconst (\n\tLOG_MAX_BUF = 1024 * 1024\n)\n\n// Log header information flag, using bitmap mode, users can choose which flag bits to print in the header\n// (日志头部信息标记位，采用bitmap方式，用户可以选择头部需要哪些标记位被打印)\nconst (\n\tBitDate         = 1 << iota                            // Date flag bit 2019/01/23 (日期标记位)\n\tBitTime                                                // Time flag bit 01:23:12 (时间标记位)\n\tBitMicroSeconds                                        // Microsecond flag bit 01:23:12.111222 (微秒级标记位)\n\tBitLongFile                                            // Complete file name /home/go/src/zinx/server.go (完整文件名称)\n\tBitShortFile                                           // Last file name server.go (最后文件名)\n\tBitLevel                                               // Current log level: 0(Debug), 1(Info), 2(Warn), 3(Error), 4(Panic), 5(Fatal) (当前日志级别)\n\tBitStdFlag      = BitDate | BitTime                    // Standard log header format (标准头部日志格式)\n\tBitDefault      = BitLevel | BitShortFile | BitStdFlag // Default log header format (默认日志头部格式)\n)\n\n// Log Level\nconst (\n\tLogDebug = iota\n\tLogInfo\n\tLogWarn\n\tLogError\n\tLogPanic\n\tLogFatal\n)\n\n// Log Level String\nvar levels = []string{\n\t\"[DEBUG]\",\n\t\"[INFO]\",\n\t\"[WARN]\",\n\t\"[ERROR]\",\n\t\"[PANIC]\",\n\t\"[FATAL]\",\n}\n\ntype ZinxLoggerCore struct {\n\t// to ensure thread-safe when multiple goroutines read and write files to prevent mixed-up content, achieving concurrency safety\n\t// (确保多协程读写文件，防止文件内容混乱，做到协程安全)\n\tmu sync.Mutex\n\n\t// the prefix string for each line of the log, which has the log tag\n\t// (每行log日志的前缀字符串,拥有日志标记)\n\tprefix string\n\n\t// log tag bit (日志标记位)\n\tflag int\n\n\t// the output buffer (输出的缓冲区)\n\tbuf bytes.Buffer\n\n\t// log isolation level\n\t// (日志隔离级别)\n\tisolationLevel int\n\n\t// call stack depth of the function that gets the log file name and code using runtime.Call\n\t// (获取日志文件名和代码上述的runtime.Call 的函数调用层数)\n\tcalldDepth int\n\n\tfw *zutils.Writer\n\n\tonLogHook func([]byte)\n}\n\n/*\nNewZinxLog Create a new log\n\nout: The file io for standard output\nprefix: The prefix of the log\nflag: The flag of the log header information\n*/\nfunc NewZinxLog(prefix string, flag int) *ZinxLoggerCore {\n\n\t// By default, debug is turned on, the depth is 2, and the ZinxLogger object calling the log print method can call up to two levels to reach the output function\n\t// (默认 debug打开， calledDepth深度为2,ZinxLogger对象调用日志打印方法最多调用两层到达output函数)\n\tzlog := &ZinxLoggerCore{prefix: prefix, flag: flag, isolationLevel: 0, calldDepth: 2}\n\n\t// Set the log object's resource cleanup destructor method (this is not necessary, as go's Gc will automatically collect, but for the sake of neatness)\n\t// (设置log对象 回收资源 析构方法(不设置也可以，go的Gc会自动回收，强迫症没办法))\n\truntime.SetFinalizer(zlog, CleanZinxLog)\n\treturn zlog\n}\n\n// CleanZinxLog Recycle log resources\nfunc CleanZinxLog(log *ZinxLoggerCore) {\n\tlog.closeFile()\n}\n\nfunc (log *ZinxLoggerCore) SetLogHook(f func([]byte)) {\n\tlog.onLogHook = f\n}\n\n/*\nformatHeader generates the header information for a log entry.\n\nt: The current time.\nfile: The file name of the source code invoking the log function.\nline: The line number of the source code invoking the log function.\nlevel: The log level of the current log entry.\n*/\nfunc (log *ZinxLoggerCore) formatHeader(t time.Time, file string, line int, level int) {\n\tvar buf *bytes.Buffer = &log.buf\n\t// If the current prefix string is not empty, write the prefix first.\n\tif log.prefix != \"\" {\n\t\tbuf.WriteByte('<')\n\t\tbuf.WriteString(log.prefix)\n\t\tbuf.WriteByte('>')\n\t}\n\n\t// If the time-related flags are set, add the time information to the log header.\n\tif log.flag&(BitDate|BitTime|BitMicroSeconds) != 0 {\n\t\t// Date flag is set\n\t\tif log.flag&BitDate != 0 {\n\t\t\tyear, month, day := t.Date()\n\t\t\titoa(buf, year, 4)\n\t\t\tbuf.WriteByte('/') // \"2019/\"\n\t\t\titoa(buf, int(month), 2)\n\t\t\tbuf.WriteByte('/') // \"2019/04/\"\n\t\t\titoa(buf, day, 2)\n\t\t\tbuf.WriteByte(' ') // \"2019/04/11 \"\n\t\t}\n\n\t\t// Time flag is set\n\t\tif log.flag&(BitTime|BitMicroSeconds) != 0 {\n\t\t\thour, min, sec := t.Clock()\n\t\t\titoa(buf, hour, 2)\n\t\t\tbuf.WriteByte(':') // \"11:\"\n\t\t\titoa(buf, min, 2)\n\t\t\tbuf.WriteByte(':') // \"11:15:\"\n\t\t\titoa(buf, sec, 2)  // \"11:15:33\"\n\t\t\t// Microsecond flag is set\n\t\t\tif log.flag&BitMicroSeconds != 0 {\n\t\t\t\tbuf.WriteByte('.')\n\t\t\t\titoa(buf, t.Nanosecond()/1e3, 6) // \"11:15:33.123123\n\t\t\t}\n\t\t\tbuf.WriteByte(' ')\n\t\t}\n\n\t\t// Log level flag is set\n\t\tif log.flag&BitLevel != 0 {\n\t\t\tbuf.WriteString(levels[level])\n\t\t}\n\n\t\t// Short file name flag or long file name flag is set\n\t\tif log.flag&(BitShortFile|BitLongFile) != 0 {\n\t\t\t// Short file name flag is set\n\t\t\tif log.flag&BitShortFile != 0 {\n\t\t\t\tshort := file\n\t\t\t\tfor i := len(file) - 1; i > 0; i-- {\n\t\t\t\t\tif file[i] == '/' {\n\t\t\t\t\t\t// Get the file name after the last '/' character, e.g. \"zinx.go\" from \"/home/go/src/zinx.go\"\n\t\t\t\t\t\tshort = file[i+1:]\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tfile = short\n\t\t\t}\n\t\t\tbuf.WriteString(file)\n\t\t\tbuf.WriteByte(':')\n\t\t\titoa(buf, line, -1) // line number\n\t\t\tbuf.WriteString(\": \")\n\t\t}\n\t}\n}\n\n// OutPut outputs log file, the original method\nfunc (log *ZinxLoggerCore) OutPut(level int, s string) error {\n\tnow := time.Now() // get current time\n\tvar file string   // file name of the current caller of the log interface\n\tvar line int      // line number of the executed code\n\tlog.mu.Lock()\n\tdefer log.mu.Unlock()\n\n\tif log.flag&(BitShortFile|BitLongFile) != 0 {\n\t\tlog.mu.Unlock()\n\t\tvar ok bool\n\t\t// get the file name and line number of the current caller\n\t\t_, file, line, ok = runtime.Caller(log.calldDepth)\n\t\tif !ok {\n\t\t\tfile = \"unknown-file\"\n\t\t\tline = 0\n\t\t}\n\t\tlog.mu.Lock()\n\t}\n\n\t// reset buffer\n\tlog.buf.Reset()\n\t// write log header\n\tlog.formatHeader(now, file, line, level)\n\t// write log content\n\tlog.buf.WriteString(s)\n\t// add line break\n\tif len(s) > 0 && s[len(s)-1] != '\\n' {\n\t\tlog.buf.WriteByte('\\n')\n\t}\n\n\tvar err error\n\tif log.fw == nil {\n\t\t// if log file is not set, output to console\n\t\t_, _ = os.Stderr.Write(log.buf.Bytes())\n\t} else {\n\t\t// write the filled buffer to IO output\n\t\t_, err = log.fw.Write(log.buf.Bytes())\n\t}\n\n\tif log.onLogHook != nil {\n\t\tlog.onLogHook(log.buf.Bytes())\n\t}\n\treturn err\n}\n\nfunc (log *ZinxLoggerCore) verifyLogIsolation(logLevel int) bool {\n\treturn log.isolationLevel > logLevel\n}\n\nfunc (log *ZinxLoggerCore) Debugf(format string, v ...interface{}) {\n\tif log.verifyLogIsolation(LogDebug) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogDebug, fmt.Sprintf(format, v...))\n}\n\nfunc (log *ZinxLoggerCore) Debug(v ...interface{}) {\n\tif log.verifyLogIsolation(LogDebug) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogDebug, fmt.Sprintln(v...))\n}\n\nfunc (log *ZinxLoggerCore) Infof(format string, v ...interface{}) {\n\tif log.verifyLogIsolation(LogInfo) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogInfo, fmt.Sprintf(format, v...))\n}\n\nfunc (log *ZinxLoggerCore) Info(v ...interface{}) {\n\tif log.verifyLogIsolation(LogInfo) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogInfo, fmt.Sprintln(v...))\n}\n\nfunc (log *ZinxLoggerCore) Warnf(format string, v ...interface{}) {\n\tif log.verifyLogIsolation(LogWarn) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogWarn, fmt.Sprintf(format, v...))\n}\n\nfunc (log *ZinxLoggerCore) Warn(v ...interface{}) {\n\tif log.verifyLogIsolation(LogWarn) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogWarn, fmt.Sprintln(v...))\n}\n\nfunc (log *ZinxLoggerCore) Errorf(format string, v ...interface{}) {\n\tif log.verifyLogIsolation(LogError) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogError, fmt.Sprintf(format, v...))\n}\n\nfunc (log *ZinxLoggerCore) Error(v ...interface{}) {\n\tif log.verifyLogIsolation(LogError) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogError, fmt.Sprintln(v...))\n}\n\nfunc (log *ZinxLoggerCore) Fatalf(format string, v ...interface{}) {\n\tif log.verifyLogIsolation(LogFatal) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogFatal, fmt.Sprintf(format, v...))\n\tos.Exit(1)\n}\n\nfunc (log *ZinxLoggerCore) Fatal(v ...interface{}) {\n\tif log.verifyLogIsolation(LogFatal) {\n\t\treturn\n\t}\n\t_ = log.OutPut(LogFatal, fmt.Sprintln(v...))\n\tos.Exit(1)\n}\n\nfunc (log *ZinxLoggerCore) Panicf(format string, v ...interface{}) {\n\tif log.verifyLogIsolation(LogPanic) {\n\t\treturn\n\t}\n\ts := fmt.Sprintf(format, v...)\n\t_ = log.OutPut(LogPanic, s)\n\tpanic(s)\n}\n\nfunc (log *ZinxLoggerCore) Panic(v ...interface{}) {\n\tif log.verifyLogIsolation(LogPanic) {\n\t\treturn\n\t}\n\ts := fmt.Sprintln(v...)\n\t_ = log.OutPut(LogPanic, s)\n\tpanic(s)\n}\n\nfunc (log *ZinxLoggerCore) Stack(v ...interface{}) {\n\ts := fmt.Sprint(v...)\n\ts += \"\\n\"\n\tbuf := make([]byte, LOG_MAX_BUF)\n\tn := runtime.Stack(buf, true) //得到当前堆栈信息\n\ts += string(buf[:n])\n\ts += \"\\n\"\n\t_ = log.OutPut(LogError, s)\n}\n\n// Flags gets the current log bitmap flags\n// (获取当前日志bitmap标记)\nfunc (log *ZinxLoggerCore) Flags() int {\n\tlog.mu.Lock()\n\tdefer log.mu.Unlock()\n\treturn log.flag\n}\n\n// ResetFlags resets the log Flags bitmap flags\n// (重新设置日志Flags bitMap 标记位)\nfunc (log *ZinxLoggerCore) ResetFlags(flag int) {\n\tlog.mu.Lock()\n\tdefer log.mu.Unlock()\n\tlog.flag = flag\n}\n\n// AddFlag adds a flag to the bitmap flags\n// (添加flag标记)\nfunc (log *ZinxLoggerCore) AddFlag(flag int) {\n\tlog.mu.Lock()\n\tdefer log.mu.Unlock()\n\tlog.flag |= flag\n}\n\n// SetPrefix sets a custom prefix for the log\n// (设置日志的 用户自定义前缀字符串)\nfunc (log *ZinxLoggerCore) SetPrefix(prefix string) {\n\tlog.mu.Lock()\n\tdefer log.mu.Unlock()\n\tlog.prefix = prefix\n}\n\n// SetLogFile sets the log file output\n// (设置日志文件输出)\nfunc (log *ZinxLoggerCore) SetLogFile(fileDir string, fileName string) {\n\tif log.fw != nil {\n\t\tlog.fw.Close()\n\t}\n\tlog.fw = zutils.New(filepath.Join(fileDir, fileName))\n}\n\n// SetMaxAge 最大保留天数\nfunc (log *ZinxLoggerCore) SetMaxAge(ma int) {\n\tif log.fw == nil {\n\t\treturn\n\t}\n\tlog.mu.Lock()\n\tdefer log.mu.Unlock()\n\tlog.fw.SetMaxAge(ma)\n}\n\n// SetMaxSize 单个日志最大容量 单位：字节\nfunc (log *ZinxLoggerCore) SetMaxSize(ms int64) {\n\tif log.fw == nil {\n\t\treturn\n\t}\n\tlog.mu.Lock()\n\tdefer log.mu.Unlock()\n\tlog.fw.SetMaxSize(ms)\n}\n\n// SetCons 同时输出控制台\nfunc (log *ZinxLoggerCore) SetCons(b bool) {\n\tif log.fw == nil {\n\t\treturn\n\t}\n\tlog.mu.Lock()\n\tdefer log.mu.Unlock()\n\tlog.fw.SetCons(b)\n}\n\n// Close the file associated with the log\n// (关闭日志绑定的文件)\nfunc (log *ZinxLoggerCore) closeFile() {\n\tif log.fw != nil {\n\t\tlog.fw.Close()\n\t}\n}\n\nfunc (log *ZinxLoggerCore) SetLogLevel(logLevel int) {\n\tlog.isolationLevel = logLevel\n}\n\n// Convert an integer to a fixed-length string, where the width of the string should be greater than 0\n// Ensure that the buffer has sufficient capacity\n// (将一个整形转换成一个固定长度的字符串，字符串宽度应该是大于0的\n// 要确保buffer是有容量空间的)\nfunc itoa(buf *bytes.Buffer, i int, wID int) {\n\tvar u uint = uint(i)\n\tif u == 0 && wID <= 1 {\n\t\tbuf.WriteByte('0')\n\t\treturn\n\t}\n\n\t// Assemble decimal in reverse order.\n\tvar b [32]byte\n\tbp := len(b)\n\tfor ; u > 0 || wID > 0; u /= 10 {\n\t\tbp--\n\t\twID--\n\t\tb[bp] = byte(u%10) + '0'\n\t}\n\n\t// avoID slicing b to avoID an allocation.\n\tfor bp < len(b) {\n\t\tbuf.WriteByte(b[bp])\n\t\tbp++\n\t}\n}\n"
  },
  {
    "path": "zlog/stdzlog.go",
    "content": "// @Title stdzlog.go\n// @Description Wraps zlogger log methods to provide global methods\n// @Author Aceld - Thu Mar 11 10:32:29 CST 2019\npackage zlog\n\n/*\n\tA global Log handle is provided by default for external use, which can be called directly through the API series.\n\tThe global log object is StdZinxLog.\n\tNote: The methods in this file do not support customization and cannot replace the log recording mode.\n\n\tIf you need a custom logger, please use the following methods:\n\tzlog.SetLogger(yourLogger)\n\tzlog.Ins().InfoF() and other methods.\n\n   全局默认提供一个Log对外句柄，可以直接使用API系列调用\n   全局日志对象 StdZinxLog\n   注意：本文件方法不支持自定义，无法替换日志记录模式，如果需要自定义Logger:\n\n   请使用如下方法:\n   zlog.SetLogger(yourLogger)\n   zlog.Ins().InfoF()等方法\n*/\n\n// StdZinxLog creates a global log\nvar StdZinxLog = NewZinxLog(\"\", BitDefault)\n\n// Flags gets the flags of StdZinxLog\nfunc Flags() int {\n\treturn StdZinxLog.Flags()\n}\n\n// ResetFlags sets the flags of StdZinxLog\nfunc ResetFlags(flag int) {\n\tStdZinxLog.ResetFlags(flag)\n}\n\n// AddFlag adds a flag to StdZinxLog\nfunc AddFlag(flag int) {\n\tStdZinxLog.AddFlag(flag)\n}\n\n// SetPrefix sets the log prefix of StdZinxLog\nfunc SetPrefix(prefix string) {\n\tStdZinxLog.SetPrefix(prefix)\n}\n\n// SetLogFile sets the log file of StdZinxLog\nfunc SetLogFile(fileDir string, fileName string) {\n\tStdZinxLog.SetLogFile(fileDir, fileName)\n}\n\n// SetMaxAge 最大保留天数\nfunc SetMaxAge(ma int) {\n\tStdZinxLog.SetMaxAge(ma)\n}\n\n// SetMaxSize 单个日志最大容量 单位：字节\nfunc SetMaxSize(ms int64) {\n\tStdZinxLog.SetMaxSize(ms)\n}\n\n// SetCons 同时输出控制台\nfunc SetCons(b bool) {\n\tStdZinxLog.SetCons(b)\n}\n\n// SetLogLevel sets the log level of StdZinxLog\nfunc SetLogLevel(logLevel int) {\n\tStdZinxLog.SetLogLevel(logLevel)\n}\n\nfunc Debugf(format string, v ...interface{}) {\n\tStdZinxLog.Debugf(format, v...)\n}\n\nfunc Debug(v ...interface{}) {\n\tStdZinxLog.Debug(v...)\n}\n\nfunc Infof(format string, v ...interface{}) {\n\tStdZinxLog.Infof(format, v...)\n}\n\nfunc Info(v ...interface{}) {\n\tStdZinxLog.Info(v...)\n}\n\nfunc Warnf(format string, v ...interface{}) {\n\tStdZinxLog.Warnf(format, v...)\n}\n\nfunc Warn(v ...interface{}) {\n\tStdZinxLog.Warn(v...)\n}\n\nfunc Errorf(format string, v ...interface{}) {\n\tStdZinxLog.Errorf(format, v...)\n}\n\nfunc Error(v ...interface{}) {\n\tStdZinxLog.Error(v...)\n}\n\nfunc Fatalf(format string, v ...interface{}) {\n\tStdZinxLog.Fatalf(format, v...)\n}\n\nfunc Fatal(v ...interface{}) {\n\tStdZinxLog.Fatal(v...)\n}\n\nfunc Panicf(format string, v ...interface{}) {\n\tStdZinxLog.Panicf(format, v...)\n}\n\nfunc Panic(v ...interface{}) {\n\tStdZinxLog.Panic(v...)\n}\n\nfunc Stack(v ...interface{}) {\n\tStdZinxLog.Stack(v...)\n}\n\nfunc init() {\n\t// Since the StdZinxLog object wraps all output methods with an extra layer, the call depth is one more than a normal logger object\n\t// The call depth of a regular zinxLogger object is 2, and the call depth of StdZinxLog is 3\n\t// (因为StdZinxLog对象 对所有输出方法做了一层包裹，所以在打印调用函数的时候，比正常的logger对象多一层调用\n\t// 一般的zinxLogger对象 calldDepth=2, StdZinxLog的calldDepth=3)\n\tStdZinxLog.calldDepth = 3\n}\n"
  },
  {
    "path": "zlog/zlog_test.go",
    "content": "package zlog_test\n\nimport (\n\t\"testing\"\n\n\t\"github.com/aceld/zinx/zlog\"\n)\n\nfunc TestStdZLog(t *testing.T) {\n\n\t//测试 默认debug输出\n\tzlog.Debug(\"zinx debug content1\")\n\tzlog.Debug(\"zinx debug content2\")\n\n\tzlog.Debugf(\" zinx debug a = %d\\n\", 10)\n\n\t//设置log标记位，加上长文件名称 和 微秒 标记\n\tzlog.ResetFlags(zlog.BitDate | zlog.BitLongFile | zlog.BitLevel)\n\tzlog.Info(\"zinx info content\")\n\n\t//设置日志前缀，主要标记当前日志模块\n\tzlog.SetPrefix(\"MODULE\")\n\tzlog.Error(\"zinx error content\")\n\n\t//添加标记位\n\tzlog.AddFlag(zlog.BitShortFile | zlog.BitTime)\n\tzlog.Stack(\" Zinx Stack! \")\n\n\t//设置日志写入文件\n\tzlog.SetLogFile(\"./log\", \"testfile.log\")\n\tzlog.Debug(\"===> zinx debug content ~~666\")\n\tzlog.Debug(\"===> zinx debug content ~~888\")\n\tzlog.Error(\"===> zinx Error!!!! ~~~555~~~\")\n\n\t//调试隔离级别\n\tzlog.Debug(\"=================================>\")\n\t//1.debug\n\tzlog.SetLogLevel(zlog.LogInfo)\n\tzlog.Debug(\"===> 调试Debug：debug不应该出现\")\n\tzlog.Info(\"===> 调试Debug：info应该出现\")\n\tzlog.Warn(\"===> 调试Debug：warn应该出现\")\n\tzlog.Error(\"===> 调试Debug：error应该出现\")\n\t//2.info\n\tzlog.SetLogLevel(zlog.LogWarn)\n\tzlog.Debug(\"===> 调试Info：debug不应该出现\")\n\tzlog.Info(\"===> 调试Info：info不应该出现\")\n\tzlog.Warn(\"===> 调试Info：warn应该出现\")\n\tzlog.Error(\"===> 调试Info：error应该出现\")\n\t//3.warn\n\tzlog.SetLogLevel(zlog.LogError)\n\tzlog.Debug(\"===> 调试Warn：debug不应该出现\")\n\tzlog.Info(\"===> 调试Warn：info不应该出现\")\n\tzlog.Warn(\"===> 调试Warn：warn不应该出现\")\n\tzlog.Error(\"===> 调试Warn：error应该出现\")\n\t//4.error\n\tzlog.SetLogLevel(zlog.LogPanic)\n\tzlog.Debug(\"===> 调试Error：debug不应该出现\")\n\tzlog.Info(\"===> 调试Error：info不应该出现\")\n\tzlog.Warn(\"===> 调试Error：warn不应该出现\")\n\tzlog.Error(\"===> 调试Error：error不应该出现\")\n}\n\nfunc TestZLogger(t *testing.T) {\n}\n"
  },
  {
    "path": "znet/acceptdelay.go",
    "content": "package znet\n\nimport (\n\t\"time\"\n)\n\nconst (\n\tmaxDelay = 1 * time.Second\n)\n\nvar AcceptDelay *acceptDelay\n\nfunc init() {\n\tAcceptDelay = &acceptDelay{duration: 0}\n}\n\ntype acceptDelay struct {\n\tduration time.Duration\n}\n\nfunc (d *acceptDelay) Delay() {\n\td.Up()\n\td.do()\n}\n\nfunc (d *acceptDelay) Reset() {\n\td.duration = 0\n}\n\nfunc (d *acceptDelay) Up() {\n\tif d.duration == 0 {\n\t\td.duration = 5 * time.Millisecond\n\t\treturn\n\t}\n\td.duration = 2 * d.duration\n\tif d.duration > maxDelay {\n\t\td.duration = maxDelay\n\t}\n}\n\nfunc (d *acceptDelay) do() {\n\tif d.duration > 0 {\n\t\ttime.Sleep(d.duration)\n\t}\n}\n"
  },
  {
    "path": "znet/acceptdelay_test.go",
    "content": "package znet\n\nimport (\n\t\"fmt\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc setup() {\n\tfmt.Println(\"Test Begin\")\n}\n\nfunc teardown() {\n\tAcceptDelay.Reset()\n\tfmt.Println(\"Test End\")\n}\n\nfunc TestDelay(t *testing.T) {\n\tassert.Equal(t, time.Duration(0), AcceptDelay.duration)\n\tAcceptDelay.Up()\n\tassert.Equal(t, 5*time.Millisecond, AcceptDelay.duration)\n\tAcceptDelay.Reset()\n\tassert.Equal(t, time.Duration(0), AcceptDelay.duration)\n\n\tfor i := 0; i < 600; i++ {\n\t\tAcceptDelay.Up()\n\t}\n\tassert.Equal(t, 1*time.Second, AcceptDelay.duration)\n}\n\nfunc TestMain(m *testing.M) {\n\tsetup()\n\tcode := m.Run()\n\tteardown()\n\tos.Exit(code)\n}\n"
  },
  {
    "path": "znet/callbacks.go",
    "content": "package znet\n\n// callbackNode represents a node in the callback linked list\n// Each node contains handler identifier, key, callback function and pointer to next node\ntype callbackNode struct {\n\thandler any           // Handler identifier, used to identify the source or type of callback\n\tkey     any           // Unique identifier key for callback, used in combination with handler\n\tcall    func()        // Actual callback function to be executed\n\tnext    *callbackNode // Pointer to next node, forming linked list structure\n}\n\n// callbacks is a singly linked list structure for managing multiple callback functions\n// Supports dynamic addition, removal and execution of callbacks\ntype callbacks struct {\n\tfirst *callbackNode // Pointer to the first node of the linked list\n\tlast  *callbackNode // Pointer to the last node of the linked list, used for quick addition of new nodes\n}\n\n// Add adds a new callback function to the callback linked list\n// Parameters:\n//   - handler: Handler identifier, can be any type\n//   - key: Unique identifier key for callback, used in combination with handler\n//   - callback: Callback function to be executed, ignored if nil\n//\n// Note: If a callback with the same handler and key already exists, it will be replaced\nfunc (t *callbacks) Add(handler, key any, callback func()) {\n\t// Prevent adding empty callback function\n\tif callback == nil {\n\t\treturn\n\t}\n\n\t// Check if a callback with the same handler and key already exists\n\tfor cb := t.first; cb != nil; cb = cb.next {\n\t\tif cb.handler == handler && cb.key == key {\n\t\t\t// Replace existing callback\n\t\t\tcb.call = callback\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Create new callback node\n\tnewItem := &callbackNode{handler, key, callback, nil}\n\n\tif t.first == nil {\n\t\t// If linked list is empty, new node becomes the first node\n\t\tt.first = newItem\n\t} else {\n\t\t// Otherwise add new node to the end of linked list\n\t\tt.last.next = newItem\n\t}\n\t// Update pointer to last node\n\tt.last = newItem\n}\n\n// Remove removes the specified callback function from the callback linked list\n// Parameters:\n//   - handler: Handler identifier of the callback to be removed\n//   - key: Unique identifier key of the callback to be removed\n//\n// Note: If no matching callback is found, this method has no effect\nfunc (t *callbacks) Remove(handler, key any) {\n\tvar prev *callbackNode\n\n\t// Traverse linked list to find the node to be removed\n\tfor callback := t.first; callback != nil; prev, callback = callback, callback.next {\n\t\t// Found matching node\n\t\tif callback.handler == handler && callback.key == key {\n\t\t\tif t.first == callback {\n\t\t\t\t// If it's the first node, update first pointer\n\t\t\t\tt.first = callback.next\n\t\t\t} else if prev != nil {\n\t\t\t\t// If it's a middle node, update the next pointer of the previous node\n\t\t\t\tprev.next = callback.next\n\t\t\t}\n\n\t\t\tif t.last == callback {\n\t\t\t\t// If it's the last node, update last pointer\n\t\t\t\tt.last = prev\n\t\t\t}\n\n\t\t\t// Return immediately after finding and removing\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// Invoke executes all registered callback functions in the linked list\n// Executes each callback in the order they were added\n// Note: If a callback function is nil, it will be skipped\n// If a callback panics, it will be handled by the outer caller's panic recovery\nfunc (t *callbacks) Invoke() {\n\t// Traverse the entire linked list starting from the head node\n\tfor callback := t.first; callback != nil; callback = callback.next {\n\t\tcallback.call()\n\t}\n}\n\n// Len returns the number of callback functions in the linked list\n// Return value: Total number of currently registered callback functions\nfunc (t *callbacks) Len() int {\n\tvar count int\n\n\t// Traverse linked list to count\n\tfor callback := t.first; callback != nil; callback = callback.next {\n\t\tcount++\n\t}\n\n\treturn count\n}\n"
  },
  {
    "path": "znet/callbacks_test.go",
    "content": "package znet\n\nimport (\n\t\"testing\"\n)\n\nfunc TestCallback(t *testing.T) {\n\t// Test empty list\n\tcb := &callbacks{}\n\tif cb.Len() != 0 {\n\t\tt.Errorf(\"Expected count for empty list is 0, but got %d\", cb.Len())\n\t}\n\n\t// Ensure invoking on an empty registry is a no-op (no panic).\n\tcb.Invoke()\n\n\t// Test adding callback functions\n\tvar count, expected, remove, totalCount int\n\ttotalCount = 10\n\tremove = 5\n\n\t// Add multiple callback functions\n\tfor i := 1; i < totalCount; i++ {\n\t\texpected = expected + i\n\t\tfunc(ii int) {\n\t\t\tcb.Add(ii, ii, func() { count = count + ii })\n\t\t}(i)\n\t}\n\n\t// Verify count after adding\n\texpectedCallbacks := totalCount - 1\n\tif cb.Len() != expectedCallbacks {\n\t\tt.Errorf(\"Expected callback count is %d, but got %d\", expectedCallbacks, cb.Len())\n\t}\n\n\t// Test adding nil callback\n\tcb.Add(remove, remove, nil)\n\tif cb.Len() != expectedCallbacks {\n\t\tt.Errorf(\"Expected count after adding nil callback is %d, but got %d\", expectedCallbacks, cb.Len())\n\t}\n\n\t// Replace an existing callback with a non-nil one; count should remain unchanged.\n\tcb.Add(remove, remove, func() { count += remove })\n\tif cb.Len() != expectedCallbacks {\n\t\tt.Errorf(\"Expected count after replacing existing callback is %d, but got %d\", expectedCallbacks, cb.Len())\n\t}\n\n\t// Remove specified callback\n\tcb.Remove(remove, remove)\n\n\t// Try to remove non-existent callback\n\tcb.Remove(remove+1, remove+2)\n\n\t// Execute all callbacks\n\tcb.Invoke()\n\n\t// Verify execution result\n\texpectedSum := expected - remove\n\tif count != expectedSum {\n\t\tt.Errorf(\"Expected execution result is %d, but got %d\", expectedSum, count)\n\t}\n\n\t// Test string type handler and key\n\tcb2 := &callbacks{}\n\n\t// Add callbacks\n\tcb2.Add(\"handler1\", \"key1\", func() {})\n\tcb2.Add(\"handler2\", \"key2\", func() {})\n\tcb2.Add(\"handler3\", \"key3\", func() {})\n\n\tif cb2.Len() != 3 {\n\t\tt.Errorf(\"Expected callback count is 3, but got %d\", cb2.Len())\n\t}\n\n\t// Remove middle callback\n\tcb2.Remove(\"handler2\", \"key2\")\n\tif cb2.Len() != 2 {\n\t\tt.Errorf(\"Expected count after removing middle callback is 2, but got %d\", cb2.Len())\n\t}\n\n\t// Remove first callback\n\tcb2.Remove(\"handler1\", \"key1\")\n\tif cb2.Len() != 1 {\n\t\tt.Errorf(\"Expected count after removing first callback is 1, but got %d\", cb2.Len())\n\t}\n\n\t// Remove last callback\n\tcb2.Remove(\"handler3\", \"key3\")\n\tif cb2.Len() != 0 {\n\t\tt.Errorf(\"Expected count after removing last callback is 0, but got %d\", cb2.Len())\n\t}\n\n\t// Test removing non-existent callback\n\tcb2.Add(\"handler1\", \"key1\", func() {})\n\tcb2.Remove(\"handler2\", \"key2\") // Try to remove non-existent callback\n\n\t// Should still have 1 callback\n\tif cb2.Len() != 1 {\n\t\tt.Errorf(\"Expected callback count is 1, but got %d\", cb2.Len())\n\t}\n}\n\nfunc TestCallbackInvokePanicPropagation(t *testing.T) {\n\tcb := &callbacks{}\n\tcb.Add(\"h\", \"k1\", func() { panic(\"boom\") })\n\n\t// Test that panic is propagated (not swallowed by Invoke)\n\tdefer func() {\n\t\tif r := recover(); r != nil {\n\t\t\tif r != \"boom\" {\n\t\t\t\tt.Errorf(\"Expected panic 'boom', got %v\", r)\n\t\t\t}\n\t\t} else {\n\t\t\tt.Errorf(\"Expected panic to be propagated, but it was swallowed\")\n\t\t}\n\t}()\n\n\t// This should panic and be caught by the defer above\n\tcb.Invoke()\n}\n"
  },
  {
    "path": "znet/chainbuilder.go",
    "content": "/**\n * @author uuxia\n * @date 15:57 2023/3/10\n * @description 拦截器管理\n **/\n\npackage znet\n\nimport (\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zinterceptor\"\n)\n\n// chainBuilder is a builder for creating a chain of interceptors.\n// (责任链构造器)\ntype chainBuilder struct {\n\tbody       []ziface.IInterceptor\n\thead, tail ziface.IInterceptor\n}\n\n// newChainBuilder creates a new instance of chainBuilder.\nfunc newChainBuilder() *chainBuilder {\n\treturn &chainBuilder{\n\t\tbody: make([]ziface.IInterceptor, 0),\n\t}\n}\n\n// Head adds an interceptor to the head of the chain.\nfunc (ic *chainBuilder) Head(interceptor ziface.IInterceptor) {\n\tic.head = interceptor\n}\n\n// Tail adds an interceptor to the tail of the chain.\nfunc (ic *chainBuilder) Tail(interceptor ziface.IInterceptor) {\n\tic.tail = interceptor\n}\n\n// AddInterceptor adds an interceptor to the body of the chain.\nfunc (ic *chainBuilder) AddInterceptor(interceptor ziface.IInterceptor) {\n\tic.body = append(ic.body, interceptor)\n}\n\n// Execute executes all the interceptors in the current chain in order.\nfunc (ic *chainBuilder) Execute(req ziface.IcReq) ziface.IcResp {\n\n\t// Put all the interceptors into the builder\n\tvar interceptors []ziface.IInterceptor\n\tif ic.head != nil {\n\t\tinterceptors = append(interceptors, ic.head)\n\t}\n\tif len(ic.body) > 0 {\n\t\tinterceptors = append(interceptors, ic.body...)\n\t}\n\tif ic.tail != nil {\n\t\tinterceptors = append(interceptors, ic.tail)\n\t}\n\n\t// Create a new interceptor chain and execute each interceptor\n\tchain := zinterceptor.NewChain(interceptors, 0, req)\n\n\t// Execute the chain\n\treturn chain.Proceed(req)\n}\n"
  },
  {
    "path": "znet/client.go",
    "content": "package znet\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"github.com/gorilla/websocket\"\n)\n\ntype Client struct {\n\tsync.WaitGroup\n\tsync.Mutex\n\tstarted bool\n\tctx     context.Context\n\tcancel  context.CancelFunc\n\t// Client Name 客户端的名称\n\tName string\n\t// IP of the target server to connect 目标连接服务器的IP\n\tIp string\n\t// Port of the target server to connect 目标连接服务器的端口\n\tPort int\n\tUrl  *url.URL // 扩展，连接时带上其他参数\n\t// Custom headers for WebSocket connection WebSocket连接的自定义头信息\n\tWsHeader http.Header\n\t// Client version tcp,websocket,客户端版本 tcp,websocket\n\tversion string\n\t// Connection instance 连接实例\n\tconn ziface.IConnection\n\t// Connection instance 连接实例的锁，保证可见性\n\tconnMux sync.Mutex\n\t// Hook function called on connection start 该client的连接创建时Hook函数\n\tonConnStart func(conn ziface.IConnection)\n\t// Hook function called on connection stop 该client的连接断开时的Hook函数\n\tonConnStop func(conn ziface.IConnection)\n\t// Data packet packer 数据报文封包方式\n\tpacket ziface.IDataPack\n\t// Asynchronous channel for capturing connection close status 异步捕获连接关闭状态\n\t// exitChan chan struct{}\n\t// Message management module 消息管理模块\n\tmsgHandler ziface.IMsgHandle\n\t// Disassembly and assembly decoder for resolving sticky and broken packages\n\t//断粘包解码器\n\tdecoder ziface.IDecoder\n\t// Heartbeat checker 心跳检测器\n\thc ziface.IHeartbeatChecker\n\t// Use TLS 使用TLS\n\tuseTLS bool\n\t// For websocket connections\n\tdialer *websocket.Dialer\n\t// Error channel\n\terrChan chan error\n}\n\nfunc NewClient(ip string, port int, opts ...ClientOption) ziface.IClient {\n\n\tc := &Client{\n\t\t// Default name, can be modified using the WithNameClient Option\n\t\t// (默认名称，可以使用WithNameClient的Option修改)\n\t\tName: \"ZinxClientTcp\",\n\t\tIp:   ip,\n\t\tPort: port,\n\n\t\tmsgHandler: newCliMsgHandle(),\n\t\tpacket:     zpack.Factory().NewPack(ziface.ZinxDataPack), // Default to using Zinx's TLV packet format(默认使用zinx的TLV封包方式)\n\t\tdecoder:    zdecoder.NewTLVDecoder(),                     // Default to using Zinx's TLV decoder(默认使用zinx的TLV解码器)\n\t\tversion:    \"tcp\",\n\t\terrChan:    make(chan error, 1),\n\t}\n\n\t// Apply Option settings (应用Option设置)\n\tfor _, opt := range opts {\n\t\topt(c)\n\t}\n\n\treturn c\n}\n\nfunc NewWsClient(ip string, port int, opts ...ClientOption) ziface.IClient {\n\n\tc := &Client{\n\t\t// Default name, can be modified using the WithNameClient Option\n\t\t// (默认名称，可以使用WithNameClient的Option修改)\n\t\tName: \"ZinxClientWs\",\n\t\tIp:   ip,\n\t\tPort: port,\n\n\t\tmsgHandler: newCliMsgHandle(),\n\t\tpacket:     zpack.Factory().NewPack(ziface.ZinxDataPack), // Default to using Zinx's TLV packet format(默认使用zinx的TLV封包方式)\n\t\tdecoder:    zdecoder.NewTLVDecoder(),                     // Default to using Zinx's TLV decoder(默认使用zinx的TLV解码器)\n\t\tversion:    \"websocket\",\n\t\tdialer:     &websocket.Dialer{},\n\t\terrChan:    make(chan error, 1),\n\t}\n\n\t// Apply Option settings (应用Option设置)\n\tfor _, opt := range opts {\n\t\topt(c)\n\t}\n\n\treturn c\n}\n\nfunc NewTLSClient(ip string, port int, opts ...ClientOption) ziface.IClient {\n\n\tc, _ := NewClient(ip, port, opts...).(*Client)\n\n\tc.useTLS = true\n\n\treturn c\n}\n\n// notify error unblock\nfunc (c *Client) notifyErr(err error) {\n\tselect {\n\tcase c.errChan <- err:\n\tdefault:\n\t}\n}\n\n// Start starts the client, sends requests and establishes a connection.\n// (重新启动客户端，发送请求且建立连接)\nfunc (c *Client) Restart() {\n\t//try to stop and wait until client stoped\n\tc.Stop()\n\n\t//set started flag\n\tc.Lock()\n\tif c.started {\n\t\t// already started, just return\n\t\tc.Unlock()\n\t\treturn\n\t}\n\tc.started = true\n\tc.ctx, c.cancel = context.WithCancel(context.Background())\n\tc.Add(1)\n\tc.Unlock()\n\n\tzlog.Ins().InfoF(\"[START] Zinx Client dial RemoteAddr: %s:%d\\n\", c.Ip, c.Port)\n\tgo func() {\n\t\tdefer c.Done()\n\n\t\t// Create a raw socket and get net.Conn (创建原始Socket，得到net.Conn)\n\t\tvar connect ziface.IConnection\n\t\tswitch c.version {\n\t\tcase \"websocket\":\n\t\t\twsAddr := fmt.Sprintf(\"ws://%s:%d\", c.Ip, c.Port)\n\t\t\tif c.Url != nil {\n\t\t\t\twsAddr = c.Url.String()\n\t\t\t}\n\n\t\t\t// Create a raw socket and get net.Conn (创建原始Socket，得到net.Conn)\n\t\t\twsConn, _, err := c.dialer.DialContext(c.ctx, wsAddr, c.WsHeader)\n\t\t\tif err != nil {\n\t\t\t\t// connection failed\n\t\t\t\tzlog.Ins().ErrorF(\"WsClient connect to server failed, err:%v\", err)\n\t\t\t\tc.notifyErr(err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\t// Create Connection object\n\t\t\tconnect = newWsClientConn(c, wsConn)\n\n\t\tdefault:\n\t\t\tvar conn net.Conn\n\t\t\tvar err error\n\t\t\tif c.useTLS {\n\t\t\t\t// TLS encryption\n\t\t\t\tconfig := &tls.Config{\n\t\t\t\t\t// Skip certificate verification here because the CA certificate of the certificate issuer is not authenticated\n\t\t\t\t\t// (这里是跳过证书验证，因为证书签发机构的CA证书是不被认证的)\n\t\t\t\t\tInsecureSkipVerify: true,\n\t\t\t\t}\n\t\t\t\td := &tls.Dialer{\n\t\t\t\t\tConfig: config,\n\t\t\t\t}\n\t\t\t\t//conn, err = tls.Dial(\"tcp\", fmt.Sprintf(\"%v:%v\", net.ParseIP(c.Ip), c.Port), config)\n\t\t\t\tconn, err = d.DialContext(c.ctx, \"tcp\", fmt.Sprintf(\"%v:%v\", net.ParseIP(c.Ip), c.Port))\n\t\t\t\tif err != nil {\n\t\t\t\t\tzlog.Ins().ErrorF(\"tls client connect to server failed, err:%v\", err)\n\t\t\t\t\tc.notifyErr(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t//conn, err = net.DialTCP(\"tcp\", nil, addr)\n\t\t\t\td := &net.Dialer{}\n\t\t\t\tconn, err = d.DialContext(c.ctx, \"tcp\", fmt.Sprintf(\"%v:%v\", net.ParseIP(c.Ip), c.Port))\n\t\t\t\tif err != nil {\n\t\t\t\t\t// connection failed\n\t\t\t\t\tzlog.Ins().ErrorF(\"client connect to server failed, err:%v\", err)\n\t\t\t\t\tc.notifyErr(err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Create Connection object\n\t\t\tconnect = newClientConn(c, conn)\n\t\t}\n\n\t\t// Set connection to the client\n\t\tc.setConn(connect)\n\n\t\tzlog.Ins().InfoF(\"[START] Zinx Client LocalAddr: %s, RemoteAddr: %s\\n\", connect.LocalAddr(), connect.RemoteAddr())\n\t\t// HeartBeat detection\n\t\tif c.hc != nil {\n\t\t\t// Bind connection and heartbeat detector after connection is successfully established\n\t\t\t// (创建连接成功，绑定连接与心跳检测器)\n\t\t\tc.hc.BindConn(connect)\n\t\t}\n\n\t\t// Start connection\n\t\tgo connect.Start()\n\n\t\t<-c.ctx.Done()\n\t\tzlog.Ins().InfoF(\"client exit.\")\n\t}()\n}\n\n// Start starts the client, sends requests and establishes a connection.\n// (启动客户端，发送请求且建立连接)\nfunc (c *Client) Start() {\n\n\t// Add the decoder to the interceptor list (将解码器添加到拦截器)\n\tif c.decoder != nil {\n\t\tc.msgHandler.AddInterceptor(c.decoder)\n\t}\n\n\tc.Restart()\n}\n\n// StartHeartBeat starts heartbeat detection with a fixed time interval.\n// interval: the time interval between each heartbeat message.\n// (启动心跳检测, interval: 每次发送心跳的时间间隔)\nfunc (c *Client) StartHeartBeat(interval time.Duration) {\n\tchecker := NewHeartbeatChecker(interval)\n\n\t// Add the heartbeat checker's route to the client's message handler.\n\t// (添加心跳检测的路由)\n\tc.AddRouter(checker.MsgID(), checker.Router())\n\n\t// Bind the heartbeat checker to the client's connection.\n\t// (client绑定心跳检测器)\n\tc.hc = checker\n}\n\n// StartHeartBeatWithOption starts heartbeat detection with a custom callback function.\n// interval: the time interval between each heartbeat message.\n// option: a HeartBeatOption struct that contains the custom callback function and message\n// 启动心跳检测(自定义回调)\nfunc (c *Client) StartHeartBeatWithOption(interval time.Duration, option *ziface.HeartBeatOption) {\n\t// Create a new heartbeat checker with the given interval.\n\tchecker := NewHeartbeatChecker(interval)\n\n\t// Set the heartbeat checker's callback function and message ID based on the HeartBeatOption struct.\n\tif option != nil {\n\t\tchecker.SetHeartbeatMsgFunc(option.MakeMsg)\n\t\tchecker.SetOnRemoteNotAlive(option.OnRemoteNotAlive)\n\t\tchecker.BindRouter(option.HeartBeatMsgID, option.Router)\n\t}\n\n\t// Add the heartbeat checker's route to the client's message handler.\n\tc.AddRouter(checker.MsgID(), checker.Router())\n\n\t// Bind the heartbeat checker to the client's connection.\n\tc.hc = checker\n}\n\n// 保证重复调用Stop不会导致panic\nfunc (c *Client) Stop() {\n\tc.Lock()\n\tdefer c.Unlock()\n\tif !c.started {\n\t\treturn\n\t}\n\tc.started = false\n\n\tcon := c.Conn()\n\tif con != nil {\n\t\tzlog.Ins().InfoF(\"[STOP] Zinx Client LocalAddr: %s, RemoteAddr: %s\\n\", con.LocalAddr(), con.RemoteAddr())\n\t\tcon.Stop()\n\t}\n\n\t// c.exitChan <- struct{}{}\n\t// close(c.exitChan)\n\t// close(c.ErrChan)\n\tif c.cancel != nil {\n\t\tc.cancel()\n\t}\n\tc.Wait()\n}\n\nfunc (c *Client) AddRouter(msgID uint32, router ziface.IRouter) {\n\tc.msgHandler.AddRouter(msgID, router)\n}\n\nfunc (c *Client) Conn() ziface.IConnection {\n\tc.connMux.Lock()\n\tdefer c.connMux.Unlock()\n\treturn c.conn\n}\n\nfunc (c *Client) setConn(con ziface.IConnection) {\n\tc.connMux.Lock()\n\tdefer c.connMux.Unlock()\n\tc.conn = con\n}\n\nfunc (c *Client) SetOnConnStart(hookFunc func(ziface.IConnection)) {\n\tc.onConnStart = hookFunc\n}\n\nfunc (c *Client) SetOnConnStop(hookFunc func(ziface.IConnection)) {\n\tc.onConnStop = hookFunc\n}\n\nfunc (c *Client) GetOnConnStart() func(ziface.IConnection) {\n\treturn c.onConnStart\n}\n\nfunc (c *Client) GetOnConnStop() func(ziface.IConnection) {\n\treturn c.onConnStop\n}\n\nfunc (c *Client) GetPacket() ziface.IDataPack {\n\treturn c.packet\n}\n\nfunc (c *Client) SetPacket(packet ziface.IDataPack) {\n\tc.packet = packet\n}\n\nfunc (c *Client) GetMsgHandler() ziface.IMsgHandle {\n\treturn c.msgHandler\n}\n\nfunc (c *Client) AddInterceptor(interceptor ziface.IInterceptor) {\n\tc.msgHandler.AddInterceptor(interceptor)\n}\n\nfunc (c *Client) SetDecoder(decoder ziface.IDecoder) {\n\tc.decoder = decoder\n}\nfunc (c *Client) GetLengthField() *ziface.LengthField {\n\tif c.decoder != nil {\n\t\treturn c.decoder.GetLengthField()\n\t}\n\treturn nil\n}\n\nfunc (c *Client) GetErrChan() <-chan error {\n\treturn c.errChan\n}\n\nfunc (c *Client) SetName(name string) {\n\tc.Name = name\n}\n\nfunc (c *Client) GetName() string {\n\treturn c.Name\n}\n\nfunc (c *Client) SetUrl(url *url.URL) {\n\tc.Url = url\n}\n\nfunc (c *Client) GetUrl() *url.URL {\n\treturn c.Url\n}\n\nfunc (c *Client) SetWsHeader(header http.Header) {\n\tc.WsHeader = header\n}\n\nfunc (c *Client) GetWsHeader() http.Header {\n\treturn c.WsHeader\n}\n"
  },
  {
    "path": "znet/connection.go",
    "content": "package znet\n\nimport (\n\t\"bufio\"\n\t\"context\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"net\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zinterceptor\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/zpack\"\n\n\t\"github.com/gorilla/websocket\"\n)\n\n// CallBackFunc defines the callback function type\n// (定义回调函数类型)\ntype CallBackFunc func()\n\n// Connection TCP connection module\n// Used to handle the read and write business of TCP connections, one Connection corresponds to one connection\n// (用于处理Tcp连接的读写业务 一个连接对应一个Connection)\ntype Connection struct {\n\t// // The socket TCP socket of the current connection(当前连接的socket TCP套接字)\n\tconn net.Conn\n\n\t// The buffer writer of the current connection(当前连接的写缓冲)\n\tbufWriter *bufio.Writer\n\n\t// The ID of the current connection, also known as SessionID, globally unique, used by server Connection\n\t// uint64 range: 0~18,446,744,073,709,551,615\n\t// This is the maximum number of connID theoretically supported by the process\n\t// (当前连接的ID 也可以称作为SessionID，ID全局唯一 ，服务端Connection使用\n\t// uint64 取值范围：0 ~ 18,446,744,073,709,551,615\n\t// 这个是理论支持的进程connID的最大数量)\n\tconnID uint64\n\n\t// connection id for string\n\t// (字符串的连接id)\n\tconnIdStr string\n\n\t// The workerid responsible for handling the link\n\t// 负责处理该连接的workerid\n\tworkerID uint32\n\n\t// The message management module that manages MsgID and the corresponding processing method\n\t// (消息管理MsgID和对应处理方法的消息管理模块)\n\tmsgHandler ziface.IMsgHandle\n\n\t// Channel to notify that the connection has exited/stopped\n\t// (告知该连接已经退出/停止的channel)\n\tctx    context.Context\n\tcancel context.CancelFunc\n\n\t// Buffered channel used for message communication between the read and write goroutines\n\t// (有缓冲管道，用于读、写两个goroutine之间的消息通信)\n\tmsgBuffChan chan []byte\n\n\t// Go StartWriter Flag\n\t// (开始初始化写协程标志)\n\tstartWriterFlag int32\n\n\t// Connection properties\n\t// (连接属性)\n\tproperty map[string]interface{}\n\n\t// Lock to protect the current property\n\t// (保护当前property的锁)\n\tpropertyLock sync.Mutex\n\n\t// Which Connection Manager the current connection belongs to\n\t// (当前连接是属于哪个Connection Manager的)\n\tconnManager ziface.IConnManager\n\n\t// Hook function when the current connection is created\n\t// (当前连接创建时Hook函数)\n\tonConnStart func(conn ziface.IConnection)\n\n\t// Hook function when the current connection is disconnected\n\t// (当前连接断开时的Hook函数)\n\tonConnStop func(conn ziface.IConnection)\n\n\t// Data packet packaging method\n\t// (数据报文封包方式)\n\tpacket ziface.IDataPack\n\n\t// Last activity time\n\t// (最后一次活动时间)\n\tlastActivityTime time.Time\n\n\t// Framedecoder for solving fragmentation and packet sticking problems\n\t// (断粘包解码器)\n\tframeDecoder ziface.IFrameDecoder\n\n\t// Heartbeat checker\n\t// (心跳检测器)\n\thc ziface.IHeartbeatChecker\n\n\t// Connection name, default to be the same as the name of the Server/Client that created the connection\n\t// (连接名称，默认与创建连接的Server/Client的Name一致)\n\tname string\n\n\t// Local address of the current connection\n\t// (当前连接的本地地址)\n\tlocalAddr string\n\n\t// Remote address of the current connection\n\t// (当前连接的远程地址)\n\tremoteAddr string\n\n\t// Close callback\n\tcloseCallback callbacks\n\n\t// Close callback mutex\n\tcloseCallbackMutex sync.RWMutex\n}\n\n// newServerConn :for Server, method to create a Server-side connection with Server-specific properties\n// (创建一个Server服务端特性的连接的方法)\nfunc newServerConn(server ziface.IServer, conn net.Conn, connID uint64) ziface.IConnection {\n\n\t// Initialize Conn properties\n\tc := &Connection{\n\t\tconn:            conn,\n\t\tbufWriter:       bufio.NewWriterSize(conn, 16*1024),\n\t\tconnID:          connID,\n\t\tconnIdStr:       strconv.FormatUint(connID, 10),\n\t\tstartWriterFlag: 0,\n\t\tmsgBuffChan:     nil,\n\t\tproperty:        nil,\n\t\tname:            server.ServerName(),\n\t\tlocalAddr:       conn.LocalAddr().String(),\n\t\tremoteAddr:      conn.RemoteAddr().String(),\n\t}\n\n\tlengthField := server.GetLengthField()\n\tif lengthField != nil {\n\t\tc.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField)\n\t}\n\n\t// Inherited properties from server (从server继承过来的属性)\n\tc.packet = server.GetPacket()\n\tc.onConnStart = server.GetOnConnStart()\n\tc.onConnStop = server.GetOnConnStop()\n\tc.msgHandler = server.GetMsgHandler()\n\n\t// Bind the current Connection with the Server's ConnManager\n\t// (将当前的Connection与Server的ConnManager绑定)\n\tc.connManager = server.GetConnMgr()\n\n\t// Add the newly created Conn to the connection manager\n\t// (将新创建的Conn添加到连接管理中)\n\tserver.GetConnMgr().Add(c)\n\n\treturn c\n}\n\n// newServerConn :for Server, method to create a Client-side connection with Client-specific properties\n// (创建一个Client服务端特性的连接的方法)\nfunc newClientConn(client ziface.IClient, conn net.Conn) ziface.IConnection {\n\tc := &Connection{\n\t\tconn:            conn,\n\t\tbufWriter:       bufio.NewWriterSize(conn, 16*1024),\n\t\tconnID:          0,  // client ignore\n\t\tconnIdStr:       \"\", // client ignore\n\t\tstartWriterFlag: 0,\n\t\tmsgBuffChan:     nil,\n\t\tproperty:        nil,\n\t\tname:            client.GetName(),\n\t\tlocalAddr:       conn.LocalAddr().String(),\n\t\tremoteAddr:      conn.RemoteAddr().String(),\n\t}\n\n\tlengthField := client.GetLengthField()\n\tif lengthField != nil {\n\t\tc.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField)\n\t}\n\n\t// Inherited properties from server (从client继承过来的属性)\n\tc.packet = client.GetPacket()\n\tc.onConnStart = client.GetOnConnStart()\n\tc.onConnStop = client.GetOnConnStop()\n\tc.msgHandler = client.GetMsgHandler()\n\n\treturn c\n}\n\n// StartWriter is the goroutine that writes messages to the client\n// (写消息Goroutine， 用户将数据发送给客户端)\nfunc (c *Connection) StartWriter() {\n\tzlog.Ins().InfoF(\"Writer Goroutine is running\")\n\tticker := time.NewTicker(10 * time.Millisecond)\n\tdefer func() {\n\t\tzlog.Ins().InfoF(\"%s [conn Writer exit!]\", c.RemoteAddr().String())\n\t\tticker.Stop()\n\t\tc.Flush()\n\t}()\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\terr := c.Flush()\n\t\t\tif err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\"Flush Buff Data error: %v Conn Writer exit\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\tcase data, ok := <-c.msgBuffChan:\n\t\t\tif ok {\n\t\t\t\tif err := c.SendBuf(data); err != nil {\n\t\t\t\t\tzlog.Ins().ErrorF(\"Send Buff Data error:, %s Conn Writer exit\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tzlog.Ins().ErrorF(\"msgBuffChan is Closed\")\n\t\t\t\treturn\n\t\t\t}\n\t\tcase <-c.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// StartReader is a goroutine that reads data from the client\n// (读消息Goroutine，用于从客户端中读取数据)\nfunc (c *Connection) StartReader() {\n\tzlog.Ins().InfoF(\"[Reader Goroutine is running]\")\n\tdefer zlog.Ins().InfoF(\"%s [conn Reader exit!]\", c.RemoteAddr().String())\n\tdefer c.Stop()\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tzlog.Ins().ErrorF(\"connID=%d, panic err=%v\", c.GetConnID(), err)\n\t\t}\n\t}()\n\n\t//Reduce buffer allocation times to improve efficiency\n\t// add by ray 2023-02-03\n\tbuffer := make([]byte, zconf.GlobalObject.IOReadBuffSize)\n\n\tfor {\n\t\tselect {\n\t\tcase <-c.ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\n\t\t\t// read data from the connection's IO into the memory buffer\n\t\t\t// (从conn的IO中读取数据到内存缓冲buffer中)\n\t\t\tn, err := c.conn.Read(buffer)\n\t\t\tif err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\"read msg head [read datalen=%d], error = %s\", n, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tzlog.Ins().DebugF(\"read buffer %s \\n\", hex.EncodeToString(buffer[0:n]))\n\n\t\t\t// If normal data is read from the peer, update the heartbeat detection Active state\n\t\t\t// (正常读取到对端数据，更新心跳检测Active状态)\n\t\t\tif n > 0 && c.hc != nil {\n\t\t\t\tc.updateActivity()\n\t\t\t}\n\n\t\t\t// Deal with the custom protocol fragmentation problem, added by uuxia 2023-03-21\n\t\t\t// (处理自定义协议断粘包问题)\n\t\t\tif c.frameDecoder != nil {\n\t\t\t\t// Decode the 0-n bytes of data read\n\t\t\t\t// (为读取到的0-n个字节的数据进行解码)\n\t\t\t\tbufArrays := c.frameDecoder.Decode(buffer[0:n])\n\t\t\t\tif bufArrays == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor _, bytes := range bufArrays {\n\t\t\t\t\t// zlog.Ins().DebugF(\"read buffer %s \\n\", hex.EncodeToString(bytes))\n\t\t\t\t\tmsg := zpack.NewMessage(uint32(len(bytes)), bytes)\n\t\t\t\t\t// Get the current client's Request data\n\t\t\t\t\t// (得到当前客户端请求的Request数据)\n\t\t\t\t\treq := GetRequest(c, msg)\n\t\t\t\t\tc.msgHandler.Execute(req)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmsg := zpack.NewMessage(uint32(n), buffer[0:n])\n\t\t\t\t// Get the current client's Request data\n\t\t\t\t// (得到当前客户端请求的Request数据)\n\t\t\t\treq := GetRequest(c, msg)\n\t\t\t\tc.msgHandler.Execute(req)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Start starts the connection and makes the current connection work.\n// (启动连接，让当前连接开始工作)\nfunc (c *Connection) Start() {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tzlog.Ins().ErrorF(\"Connection Start() error: %v\", err)\n\t\t}\n\t}()\n\tc.ctx, c.cancel = context.WithCancel(context.Background())\n\n\t// Execute the hook method for processing business logic when creating a connection\n\t// (按照用户传递进来的创建连接时需要处理的业务，执行钩子方法)\n\tc.callOnConnStart()\n\n\t// Start heartbeating detection\n\tif c.hc != nil {\n\t\tc.hc.Start()\n\t\tc.updateActivity()\n\t}\n\n\t// 占用workerid\n\tc.workerID = useWorker(c)\n\n\t// Start the Goroutine for reading data from the client\n\t// (开启用户从客户端读取数据流程的Goroutine)\n\tgo c.StartReader()\n\n\tselect {\n\tcase <-c.ctx.Done():\n\t\tc.finalizer()\n\n\t\t// 归还workerid\n\t\tfreeWorker(c)\n\t\treturn\n\t}\n}\n\n// Stop stops the connection and ends the current connection state.\n// (停止连接，结束当前连接状态)\nfunc (c *Connection) Stop() {\n\tc.cancel()\n}\n\nfunc (c *Connection) GetConnection() net.Conn {\n\treturn c.conn\n}\n\nfunc (c *Connection) GetWsConn() *websocket.Conn {\n\treturn nil\n}\n\n// Deprecated: use GetConnection instead\nfunc (c *Connection) GetTCPConnection() net.Conn {\n\treturn c.conn\n}\n\nfunc (c *Connection) GetConnID() uint64 {\n\treturn c.connID\n}\n\nfunc (c *Connection) GetConnIdStr() string {\n\treturn c.connIdStr\n}\n\nfunc (c *Connection) GetWorkerID() uint32 {\n\treturn c.workerID\n}\n\nfunc (c *Connection) RemoteAddr() net.Addr {\n\treturn c.conn.RemoteAddr()\n}\n\nfunc (c *Connection) LocalAddr() net.Addr {\n\treturn c.conn.LocalAddr()\n}\n\nfunc (c *Connection) Flush() error {\n\tif c.isClosed() == true {\n\t\treturn errors.New(\"connection closed when flush data\")\n\t}\n\treturn c.bufWriter.Flush()\n}\n\nfunc (c *Connection) Send(data []byte) error {\n\tif c.isClosed() == true {\n\t\treturn errors.New(\"connection closed when send msg\")\n\t}\n\t_, err := c.conn.Write(data)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"SendMsg err data = %+v, err = %+v\", data, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *Connection) SendBuf(data []byte) error {\n\tif c.isClosed() == true {\n\t\treturn errors.New(\"connection closed when send msg\")\n\t}\n\t_, err := c.bufWriter.Write(data)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"SendMsg err data = %+v, err = %+v\", data, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (c *Connection) SendToQueue(data []byte, opts ...ziface.MsgSendOption) error {\n\n\tif c.msgBuffChan == nil && c.setStartWriterFlag() {\n\t\tc.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen)\n\t\t// Start a Goroutine to write data back to the client\n\t\t// This method only reads data from the MsgBuffChan without allocating memory or starting a Goroutine\n\t\t// (开启用于写回客户端数据流程的Goroutine\n\t\t// 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程)\n\t\tgo c.StartWriter()\n\t}\n\n\topt := ziface.MsgSendOptionObj{\n\t\tTimeout: 5 * time.Millisecond,\n\t}\n\n\tfor _, o := range opts {\n\t\to(&opt)\n\t}\n\n\tidleTimeout := time.NewTimer(opt.Timeout)\n\tdefer idleTimeout.Stop()\n\n\tif c.isClosed() == true {\n\t\treturn errors.New(\"Connection closed when send buff msg\")\n\t}\n\n\tif data == nil {\n\t\tzlog.Ins().ErrorF(\"Pack data is nil\")\n\t\treturn errors.New(\"Pack data is nil\")\n\t}\n\n\t// Send timeout\n\tselect {\n\tcase <-c.ctx.Done():\n\t\t// Close all channels associated with the connection\n\t\tclose(c.msgBuffChan)\n\t\treturn errors.New(\"connection closed when send buff msg\")\n\tcase <-idleTimeout.C:\n\t\treturn errors.New(\"send buff msg timeout\")\n\tcase c.msgBuffChan <- data:\n\t\treturn nil\n\t}\n}\n\n// SendMsg directly sends Message data to the remote TCP client.\n// (直接将Message数据发送数据给远程的TCP客户端)\nfunc (c *Connection) SendMsg(msgID uint32, data []byte) error {\n\n\tif c.isClosed() == true {\n\t\treturn errors.New(\"connection closed when send msg\")\n\t}\n\t// Pack data and send it\n\tmsg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data))\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"Pack error msg ID = %d\", msgID)\n\t\treturn errors.New(\"Pack error msg \")\n\t}\n\n\terr = c.Send(msg)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"SendMsg err msg ID = %d, data = %+v, err = %+v\", msgID, string(msg), err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *Connection) SendBuffMsg(msgID uint32, data []byte, opts ...ziface.MsgSendOption) error {\n\tmsg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data))\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"Pack error msg ID = %d\", msgID)\n\t\treturn errors.New(\"Pack error msg \")\n\t}\n\treturn c.SendToQueue(msg, opts...)\n\n}\n\nfunc (c *Connection) SetProperty(key string, value interface{}) {\n\tc.propertyLock.Lock()\n\tdefer c.propertyLock.Unlock()\n\tif c.property == nil {\n\t\tc.property = make(map[string]interface{})\n\t}\n\n\tc.property[key] = value\n}\n\nfunc (c *Connection) GetProperty(key string) (interface{}, error) {\n\tc.propertyLock.Lock()\n\tdefer c.propertyLock.Unlock()\n\n\tif value, ok := c.property[key]; ok {\n\t\treturn value, nil\n\t}\n\n\treturn nil, errors.New(\"no property found\")\n}\n\nfunc (c *Connection) RemoveProperty(key string) {\n\tc.propertyLock.Lock()\n\tdefer c.propertyLock.Unlock()\n\n\tdelete(c.property, key)\n}\n\nfunc (c *Connection) Context() context.Context {\n\treturn c.ctx\n}\n\nfunc (c *Connection) finalizer() {\n\t// Call the callback function registered by the user when closing the connection if it exists\n\t// (如果用户注册了该连接的\t关闭回调业务，那么在此刻应该显示调用)\n\tc.callOnConnStop()\n\n\t// Stop the heartbeat detector associated with the connection\n\tif c.hc != nil {\n\t\tc.hc.Stop()\n\t}\n\n\t// Close the socket connection\n\t_ = c.conn.Close()\n\n\t// Remove the connection from the connection manager\n\tif c.connManager != nil {\n\t\tc.connManager.Remove(c)\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif err := recover(); err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\"Conn finalizer panic: %v\", err)\n\t\t\t}\n\t\t}()\n\n\t\tc.InvokeCloseCallbacks()\n\t}()\n\n\tzlog.Ins().InfoF(\"Conn Stop()...ConnID = %d\", c.connID)\n}\n\nfunc (c *Connection) callOnConnStart() {\n\tif c.onConnStart != nil {\n\t\tzlog.Ins().InfoF(\"ZINX CallOnConnStart....\")\n\t\tc.onConnStart(c)\n\t}\n}\n\nfunc (c *Connection) callOnConnStop() {\n\tif c.onConnStop != nil {\n\t\tzlog.Ins().InfoF(\"ZINX CallOnConnStop....\")\n\t\tc.onConnStop(c)\n\t}\n}\n\nfunc (c *Connection) IsAlive() bool {\n\tif c.isClosed() {\n\t\treturn false\n\t}\n\t// Check the last activity time of the connection. If it's beyond the heartbeat interval,\n\t// then the connection is considered dead.\n\t// (检查连接最后一次活动时间，如果超过心跳间隔，则认为连接已经死亡)\n\treturn time.Now().Sub(c.lastActivityTime) < zconf.GlobalObject.HeartbeatMaxDuration()\n}\n\nfunc (c *Connection) updateActivity() {\n\tc.lastActivityTime = time.Now()\n}\n\nfunc (c *Connection) SetHeartBeat(checker ziface.IHeartbeatChecker) {\n\tc.hc = checker\n}\n\nfunc (c *Connection) LocalAddrString() string {\n\treturn c.localAddr\n}\n\nfunc (c *Connection) RemoteAddrString() string {\n\treturn c.remoteAddr\n}\n\nfunc (c *Connection) GetName() string {\n\treturn c.name\n}\n\nfunc (c *Connection) GetMsgHandler() ziface.IMsgHandle {\n\treturn c.msgHandler\n}\n\nfunc (c *Connection) isClosed() bool {\n\treturn c.ctx == nil || c.ctx.Err() != nil\n}\n\nfunc (c *Connection) setStartWriterFlag() bool {\n\treturn atomic.CompareAndSwapInt32(&c.startWriterFlag, 0, 1)\n}\n\nfunc (s *Connection) AddCloseCallback(handler, key interface{}, f func()) {\n\tif s.isClosed() {\n\t\treturn\n\t}\n\ts.closeCallbackMutex.Lock()\n\tdefer s.closeCallbackMutex.Unlock()\n\ts.closeCallback.Add(handler, key, f)\n}\n\nfunc (s *Connection) RemoveCloseCallback(handler, key interface{}) {\n\tif s.isClosed() {\n\t\treturn\n\t}\n\ts.closeCallbackMutex.Lock()\n\tdefer s.closeCallbackMutex.Unlock()\n\ts.closeCallback.Remove(handler, key)\n}\n\nfunc (s *Connection) InvokeCloseCallbacks() {\n\ts.closeCallbackMutex.RLock()\n\tdefer s.closeCallbackMutex.RUnlock()\n\ts.closeCallback.Invoke()\n}\n"
  },
  {
    "path": "znet/connmanager.go",
    "content": "package znet\n\nimport (\n\t\"errors\"\n\t\"strconv\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/zutils\"\n)\n\ntype ConnManager struct {\n\tconnections zutils.ShardLockMaps\n}\n\nfunc newConnManager() *ConnManager {\n\treturn &ConnManager{\n\t\tconnections: zutils.NewShardLockMaps(),\n\t}\n}\n\nfunc (connMgr *ConnManager) Add(conn ziface.IConnection) {\n\n\tconnMgr.connections.Set(conn.GetConnIdStr(), conn) // 将conn连接添加到ConnManager中\n\n\tzlog.Ins().DebugF(\"connection add to ConnManager successfully: conn num = %d\", connMgr.Len())\n}\n\nfunc (connMgr *ConnManager) Remove(conn ziface.IConnection) {\n\n\tconnMgr.connections.Remove(conn.GetConnIdStr()) // 删除连接信息\n\n\tzlog.Ins().DebugF(\"connection Remove ConnID=%d successfully: conn num = %d\", conn.GetConnID(), connMgr.Len())\n}\n\nfunc (connMgr *ConnManager) Get(connID uint64) (ziface.IConnection, error) {\n\n\tstrConnId := strconv.FormatUint(connID, 10)\n\tif conn, ok := connMgr.connections.Get(strConnId); ok {\n\t\treturn conn.(ziface.IConnection), nil\n\t}\n\n\treturn nil, errors.New(\"connection not found\")\n}\n\n// Get2 It is recommended to use this method to obtain connection instances\nfunc (connMgr *ConnManager) Get2(strConnId string) (ziface.IConnection, error) {\n\n\tif conn, ok := connMgr.connections.Get(strConnId); ok {\n\t\treturn conn.(ziface.IConnection), nil\n\t}\n\n\treturn nil, errors.New(\"connection not found\")\n}\n\nfunc (connMgr *ConnManager) Len() int {\n\n\tlength := connMgr.connections.Count()\n\n\treturn length\n}\n\nfunc (connMgr *ConnManager) ClearConn() {\n\n\t// Stop and delete all connection information\n\tfor item := range connMgr.connections.IterBuffered() {\n\t\tval := item.Val\n\t\tif conn, ok := val.(ziface.IConnection); ok {\n\t\t\t// stop will eventually trigger the deletion of the connection,\n\t\t\t// no additional deletion is required\n\t\t\tconn.Stop()\n\t\t}\n\t}\n\n\tzlog.Ins().InfoF(\"Clear All Connections successfully: conn num = %d\", connMgr.Len())\n}\n\nfunc (connMgr *ConnManager) GetAllConnID() []uint64 {\n\n\tstrConnIdList := connMgr.connections.Keys()\n\tids := make([]uint64, 0, len(strConnIdList))\n\n\tfor _, strId := range strConnIdList {\n\t\tconnId, err := strconv.ParseUint(strId, 10, 64)\n\t\tif err == nil {\n\t\t\tids = append(ids, connId)\n\t\t} else {\n\t\t\tzlog.Ins().InfoF(\"GetAllConnID Id: %d, error: %v\", connId, err)\n\t\t}\n\t}\n\n\treturn ids\n}\n\nfunc (connMgr *ConnManager) GetAllConnIdStr() []string {\n\treturn connMgr.connections.Keys()\n}\n\nfunc (connMgr *ConnManager) Range(cb func(uint64, ziface.IConnection, interface{}) error, args interface{}) (err error) {\n\n\tconnMgr.connections.IterCb(func(key string, v interface{}) {\n\t\tconn, _ := v.(ziface.IConnection)\n\t\tconnId, _ := strconv.ParseUint(key, 10, 64)\n\t\terr = cb(connId, conn, args)\n\t\tif err != nil {\n\t\t\tzlog.Ins().InfoF(\"Range key: %v, v: %v, error: %v\", key, v, err)\n\t\t}\n\t})\n\n\treturn err\n}\n\n// Range2 It is recommended to use this method to 'Range'\nfunc (connMgr *ConnManager) Range2(cb func(string, ziface.IConnection, interface{}) error, args interface{}) (err error) {\n\n\tconnMgr.connections.IterCb(func(key string, v interface{}) {\n\t\tconn, _ := v.(ziface.IConnection)\n\t\terr = cb(conn.GetConnIdStr(), conn, args)\n\t\tif err != nil {\n\t\t\tzlog.Ins().InfoF(\"Range2 key: %v, v: %v, error: %v\", key, v, err)\n\t\t}\n\t})\n\n\treturn err\n}\n"
  },
  {
    "path": "znet/defaultrouterfunc.go",
    "content": "package znet\n\nimport (\n\t\"bytes\"\n\t\"fmt\"\n\t\"path\"\n\t\"runtime\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n)\n\nconst (\n\t// The number of stack frames to start tracing from\n\t// (开始追踪堆栈信息的层数)\n\tStackBegin = 3\n\t// The number of stack frames to trace until the end\n\t// (追踪到最后的层数)\n\tStackEnd = 5\n)\n\n// This is used to store some default middlewares that can be used in RouterSlicesMode\n// (用来存放一些RouterSlicesMode下的路由可用的默认中间件)\n\n// RouterRecovery If you use the server obtained from initializing with NewDefaultRouterSlicesServer method,\n// this function will be included. It is used to catch any panics that occur during request handling\n// and attempt to record the context information.\n// (如果使用NewDefaultRouterSlicesServer方法初始化的获得的server将自带这个函数\n// 作用是接收业务执行上产生的panic并且尝试记录现场信息)\nfunc RouterRecovery(request ziface.IRequest) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tpanicInfo := getInfo(StackBegin)\n\t\t\t// Record the error\n\t\t\tzlog.Ins().ErrorF(\"MsgId:%d Handler panic: info:%s err:%v\", request.GetMsgID(), panicInfo, err)\n\n\t\t\t//fmt.Printf(\"MsgId:%d Handler panic: info:%s err:%v\", request.GetMsgID(), panicInfo, err)\n\n\t\t\t// Should return an error (应该回传一个错误的)\n\t\t\t//request.GetConnection().SendMsg()\n\t\t}\n\n\t}()\n\trequest.RouterSlicesNext()\n}\n\n// RouterTime Simply accumulates the time taken by all the routing groups, but not enabled\n// (简单累计所有路由组的耗时，不启用)\nfunc RouterTime(request ziface.IRequest) {\n\tnow := time.Now()\n\trequest.RouterSlicesNext()\n\tduration := time.Since(now)\n\tfmt.Println(duration.String())\n}\n\nfunc getInfo(ship int) (infoStr string) {\n\n\tpanicInfo := new(bytes.Buffer)\n\t// It is also possible to not specify the end point layer as i := ship;; i++ and end the loop with if !ok,\n\t// but it will keep going until the bottom layer error information is reached.\n\t// (也可以不指定终点层数即i := ship;; i++ 通过if！ok 结束循环，但是会一直追到最底层报错信息)\n\tfor i := ship; i <= StackEnd; i++ {\n\t\tpc, file, lineNo, ok := runtime.Caller(i)\n\t\tif !ok {\n\t\t\tbreak\n\t\t}\n\t\tfuncname := runtime.FuncForPC(pc).Name()\n\t\tfilename := path.Base(file)\n\t\tfuncname = strings.Split(funcname, \".\")[1]\n\t\tfmt.Fprintf(panicInfo, \"funcname:%s filename:%s LineNo:%d\\n\", funcname, filename, lineNo)\n\t}\n\treturn panicInfo.String()\n\n}\n"
  },
  {
    "path": "znet/heartbeat.go",
    "content": "package znet\n\nimport (\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n)\n\ntype HeartbeatChecker struct {\n\tinterval time.Duration //  Heartbeat detection interval(心跳检测时间间隔)\n\tquitChan chan bool     // Quit signal(退出信号)\n\n\tmakeMsg ziface.HeartBeatMsgFunc //User-defined heartbeat message processing method(用户自定义的心跳检测消息处理方法)\n\n\tonRemoteNotAlive ziface.OnRemoteNotAlive //  User-defined method for handling remote connections that are not alive (用户自定义的远程连接不存活时的处理方法)\n\n\tmsgID        uint32                 // Heartbeat message ID(心跳的消息ID)\n\trouter       ziface.IRouter         // User-defined heartbeat message business processing router(用户自定义的心跳检测消息业务处理路由)\n\trouterSlices []ziface.RouterHandler //(用户自定义的心跳检测消息业务处理新路由)\n\tconn         ziface.IConnection     // Bound connection(绑定的连接)\n\n\tbeatFunc ziface.HeartBeatFunc // // User-defined heartbeat sending function(用户自定义心跳发送函数)\n}\n\n/*\nDefault callback routing business for receiving remote heartbeat messages\n(收到remote心跳消息的默认回调路由业务)\n*/\ntype HeatBeatDefaultRouter struct {\n\tBaseRouter\n}\n\nfunc (r *HeatBeatDefaultRouter) Handle(req ziface.IRequest) {\n\tzlog.Ins().DebugF(\"Recv Heartbeat from %s, MsgID = %+v, Data = %s\",\n\t\treq.GetConnection().RemoteAddr(), req.GetMsgID(), string(req.GetData()))\n}\n\nfunc HeatBeatDefaultHandle(req ziface.IRequest) {\n\tzlog.Ins().DebugF(\"Recv Heartbeat from %s, MsgID = %+v, Data = %s\",\n\t\treq.GetConnection().RemoteAddr(), req.GetMsgID(), string(req.GetData()))\n}\n\nfunc makeDefaultMsg(conn ziface.IConnection) []byte {\n\tmsg := fmt.Sprintf(\"heartbeat [%s->%s]\", conn.LocalAddr(), conn.RemoteAddr())\n\treturn []byte(msg)\n}\n\nfunc notAliveDefaultFunc(conn ziface.IConnection) {\n\tzlog.Ins().InfoF(\"Remote connection %s is not alive, stop it\", conn.RemoteAddr())\n\tconn.Stop()\n}\n\nfunc NewHeartbeatChecker(interval time.Duration) ziface.IHeartbeatChecker {\n\theartbeat := &HeartbeatChecker{\n\t\tinterval: interval,\n\t\tquitChan: make(chan bool),\n\n\t\t// Use default heartbeat message generation function and remote connection not alive handling method\n\t\t// (均使用默认的心跳消息生成函数和远程连接不存活时的处理方法)\n\t\tmakeMsg:          makeDefaultMsg,\n\t\tonRemoteNotAlive: notAliveDefaultFunc,\n\t\tmsgID:            ziface.HeartBeatDefaultMsgID,\n\t\trouter:           &HeatBeatDefaultRouter{},\n\t\trouterSlices:     []ziface.RouterHandler{HeatBeatDefaultHandle},\n\t\tbeatFunc:         nil,\n\t}\n\n\treturn heartbeat\n}\n\nfunc (h *HeartbeatChecker) SetOnRemoteNotAlive(f ziface.OnRemoteNotAlive) {\n\tif f != nil {\n\t\th.onRemoteNotAlive = f\n\t}\n}\n\nfunc (h *HeartbeatChecker) SetHeartbeatMsgFunc(f ziface.HeartBeatMsgFunc) {\n\tif f != nil {\n\t\th.makeMsg = f\n\t}\n}\n\nfunc (h *HeartbeatChecker) SetHeartbeatFunc(beatFunc ziface.HeartBeatFunc) {\n\tif beatFunc != nil {\n\t\th.beatFunc = beatFunc\n\t}\n}\n\nfunc (h *HeartbeatChecker) BindRouter(msgID uint32, router ziface.IRouter) {\n\tif router != nil && msgID != ziface.HeartBeatDefaultMsgID {\n\t\th.msgID = msgID\n\t\th.router = router\n\t}\n}\n\nfunc (h *HeartbeatChecker) BindRouterSlices(msgID uint32, handlers ...ziface.RouterHandler) {\n\tif len(handlers) > 0 && msgID != ziface.HeartBeatDefaultMsgID {\n\t\th.msgID = msgID\n\t\th.routerSlices = append(h.routerSlices, handlers...)\n\t}\n}\n\nfunc (h *HeartbeatChecker) start() {\n\tticker := time.NewTicker(h.interval)\n\tfor {\n\t\tselect {\n\t\tcase <-ticker.C:\n\t\t\th.check()\n\t\tcase <-h.quitChan:\n\t\t\tticker.Stop()\n\t\t\treturn\n\t\t}\n\t}\n}\n\nfunc (h *HeartbeatChecker) Start() {\n\tgo h.start()\n}\n\nfunc (h *HeartbeatChecker) Stop() {\n\tzlog.Ins().InfoF(\"heartbeat checker stop, connID=%+v\", h.conn.GetConnID())\n\th.quitChan <- true\n}\n\nfunc (h *HeartbeatChecker) SendHeartBeatMsg() error {\n\n\tmsg := h.makeMsg(h.conn)\n\n\terr := h.conn.SendMsg(h.msgID, msg)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"send heartbeat msg error: %v, msgId=%+v msg=%+v\", err, h.msgID, msg)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (h *HeartbeatChecker) check() (err error) {\n\n\tif h.conn == nil {\n\t\treturn nil\n\t}\n\n\tif !h.conn.IsAlive() {\n\t\th.onRemoteNotAlive(h.conn)\n\t} else {\n\t\tif h.beatFunc != nil {\n\t\t\terr = h.beatFunc(h.conn)\n\t\t} else {\n\t\t\terr = h.SendHeartBeatMsg()\n\t\t}\n\t}\n\n\treturn err\n}\n\nfunc (h *HeartbeatChecker) BindConn(conn ziface.IConnection) {\n\th.conn = conn\n\tconn.SetHeartBeat(h)\n}\n\n// Clone clones to a specified connection\n// (克隆到一个指定的连接上)\nfunc (h *HeartbeatChecker) Clone() ziface.IHeartbeatChecker {\n\n\theartbeat := &HeartbeatChecker{\n\t\tinterval:         h.interval,\n\t\tquitChan:         make(chan bool),\n\t\tbeatFunc:         h.beatFunc,\n\t\tmakeMsg:          h.makeMsg,\n\t\tonRemoteNotAlive: h.onRemoteNotAlive,\n\t\tmsgID:            h.msgID,\n\t\trouter:           h.router,\n\t\tconn:             nil, // The bound connection needs to be reassigned\n\t}\n\n\t// deep copy routerSlices\n\theartbeat.routerSlices = append(heartbeat.routerSlices, h.routerSlices...)\n\n\treturn heartbeat\n}\n\nfunc (h *HeartbeatChecker) MsgID() uint32 {\n\treturn h.msgID\n}\n\nfunc (h *HeartbeatChecker) Router() ziface.IRouter {\n\treturn h.router\n}\n\nfunc (h *HeartbeatChecker) RouterSlices() []ziface.RouterHandler {\n\treturn h.routerSlices\n}\n"
  },
  {
    "path": "znet/kcp_connection.go",
    "content": "package znet\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"net\"\n\t\"strconv\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/zinterceptor\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"github.com/gorilla/websocket\"\n\t\"github.com/xtaci/kcp-go\"\n)\n\n// Connection KCP connection module\n// Used to handle the read and write business of KCP connections, one Connection corresponds to one connection\n// (用于处理KCP连接的读写业务 一个连接对应一个Connection)\ntype KcpConnection struct {\n\t// The socket KCP socket of the current connection(当前连接的socket KCP套接字)\n\tconn *kcp.UDPSession\n\n\t// The ID of the current connection, also known as SessionID, globally unique, used by server Connection\n\t// uint64 range: 0~18,446,744,073,709,551,615\n\t// This is the maximum number of connID theoretically supported by the process\n\t// (当前连接的ID 也可以称作为SessionID，ID全局唯一 ，服务端Connection使用\n\t// uint64 取值范围：0 ~ 18,446,744,073,709,551,615\n\t// 这个是理论支持的进程connID的最大数量)\n\tconnID uint64\n\n\t// connection id for string\n\t// (字符串的连接id)\n\tconnIdStr string\n\n\t// The workerid responsible for handling the link\n\t// 负责处理该连接的workerid\n\tworkerID uint32\n\n\t// The message management module that manages MsgID and the corresponding processing method\n\t// (消息管理MsgID和对应处理方法的消息管理模块)\n\tmsgHandler ziface.IMsgHandle\n\n\t// Channel to notify that the connection has exited/stopped\n\t// (告知该连接已经退出/停止的channel)\n\tctx    context.Context\n\tcancel context.CancelFunc\n\n\t// Buffered channel used for message communication between the read and write goroutines\n\t// (有缓冲管道，用于读、写两个goroutine之间的消息通信)\n\tmsgBuffChan chan []byte\n\n\t// Lock for user message reception and transmission\n\t// (用户收发消息的Lock)\n\tmsgLock sync.RWMutex\n\n\t// Connection properties\n\t// (连接属性)\n\tproperty map[string]interface{}\n\n\t// Lock to protect the current property\n\t// (保护当前property的锁)\n\tpropertyLock sync.Mutex\n\n\t// The current connection's close state\n\t// (当前连接的关闭状态)\n\tclosed int32\n\n\t// Which Connection Manager the current connection belongs to\n\t// (当前连接是属于哪个Connection Manager的)\n\tconnManager ziface.IConnManager\n\n\t// Hook function when the current connection is created\n\t// (当前连接创建时Hook函数)\n\tonConnStart func(conn ziface.IConnection)\n\n\t// Hook function when the current connection is disconnected\n\t// (当前连接断开时的Hook函数)\n\tonConnStop func(conn ziface.IConnection)\n\n\t// Data packet packaging method\n\t// (数据报文封包方式)\n\tpacket ziface.IDataPack\n\n\t// Last activity time\n\t// (最后一次活动时间)\n\tlastActivityTime time.Time\n\n\t// Framedecoder for solving fragmentation and packet sticking problems\n\t// (断粘包解码器)\n\tframeDecoder ziface.IFrameDecoder\n\n\t// Heartbeat checker\n\t// (心跳检测器)\n\thc ziface.IHeartbeatChecker\n\n\t// Connection name, default to be the same as the name of the Server/Client that created the connection\n\t// (连接名称，默认与创建连接的Server/Client的Name一致)\n\tname string\n\n\t// Local address of the current connection\n\t// (当前连接的本地地址)\n\tlocalAddr string\n\n\t// Remote address of the current connection\n\t// (当前连接的远程地址)\n\tremoteAddr string\n\n\t// Close callback\n\tcloseCallback callbacks\n\n\t// Close callback mutex\n\tcloseCallbackMutex sync.RWMutex\n}\n\n// newKcpServerConn :for Server, method to create a Server-side connection with Server-specific properties\n// (创建一个Server服务端特性的连接的方法)\nfunc newKcpServerConn(server ziface.IServer, conn *kcp.UDPSession, connID uint64) ziface.IConnection {\n\t// Initialize Conn properties\n\tc := &KcpConnection{\n\t\tconn:        conn,\n\t\tconnID:      connID,\n\t\tconnIdStr:   strconv.FormatUint(connID, 10),\n\t\tmsgBuffChan: nil,\n\t\tproperty:    nil,\n\t\tname:        server.ServerName(),\n\t\tlocalAddr:   conn.LocalAddr().String(),\n\t\tremoteAddr:  conn.RemoteAddr().String(),\n\t}\n\n\tlengthField := server.GetLengthField()\n\tif lengthField != nil {\n\t\tc.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField)\n\t}\n\n\t// Inherited properties from server (从server继承过来的属性)\n\tc.packet = server.GetPacket()\n\tc.onConnStart = server.GetOnConnStart()\n\tc.onConnStop = server.GetOnConnStop()\n\tc.msgHandler = server.GetMsgHandler()\n\n\t// Bind the current Connection with the Server's ConnManager\n\t// (将当前的Connection与Server的ConnManager绑定)\n\tc.connManager = server.GetConnMgr()\n\n\t// Add the newly created Conn to the connection manager\n\t// (将新创建的Conn添加到连接管理中)\n\tserver.GetConnMgr().Add(c)\n\n\treturn c\n}\n\n// newKcpServerConn :for Server, method to create a Client-side connection with Client-specific properties\n// (创建一个Client服务端特性的连接的方法)\nfunc newKcpClientConn(client ziface.IClient, conn *kcp.UDPSession) ziface.IConnection {\n\tc := &KcpConnection{\n\t\tconn:        conn,\n\t\tconnID:      0,  // client ignore\n\t\tconnIdStr:   \"\", // client ignore\n\t\tmsgBuffChan: nil,\n\t\tproperty:    nil,\n\t\tname:        client.GetName(),\n\t\tlocalAddr:   conn.LocalAddr().String(),\n\t\tremoteAddr:  conn.RemoteAddr().String(),\n\t}\n\n\tlengthField := client.GetLengthField()\n\tif lengthField != nil {\n\t\tc.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField)\n\t}\n\n\t// Inherited properties from server (从client继承过来的属性)\n\tc.packet = client.GetPacket()\n\tc.onConnStart = client.GetOnConnStart()\n\tc.onConnStop = client.GetOnConnStop()\n\tc.msgHandler = client.GetMsgHandler()\n\n\treturn c\n}\n\n// StartWriter is the goroutine that writes messages to the client\n// (写消息Goroutine， 用户将数据发送给客户端)\nfunc (c *KcpConnection) StartWriter() {\n\tzlog.Ins().DebugF(\"Writer Goroutine is running\")\n\tdefer zlog.Ins().DebugF(\"%s [conn Writer exit!]\", c.RemoteAddr().String())\n\n\tfor {\n\t\tselect {\n\t\tcase data, ok := <-c.msgBuffChan:\n\t\t\tif ok {\n\t\t\t\tif err := c.Send(data); err != nil {\n\t\t\t\t\tzlog.Ins().ErrorF(\"Send Buff Data error:, %s Conn Writer exit\", err)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t} else {\n\t\t\t\tzlog.Ins().ErrorF(\"msgBuffChan is Closed\")\n\t\t\t\tbreak\n\t\t\t}\n\t\tcase <-c.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// StartReader is a goroutine that reads data from the client\n// (读消息Goroutine，用于从客户端中读取数据)\nfunc (c *KcpConnection) StartReader() {\n\tzlog.Ins().DebugF(\"[Reader Goroutine is running]\")\n\tdefer zlog.Ins().DebugF(\"%s [conn Reader exit!]\", c.RemoteAddr().String())\n\tdefer c.Stop()\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tzlog.Ins().ErrorF(\"connID=%d, panic err=%v\", c.GetConnID(), err)\n\t\t}\n\t}()\n\n\tfor {\n\t\tselect {\n\t\tcase <-c.ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\t// add by uuxia 2023-02-03\n\t\t\tbuffer := make([]byte, zconf.GlobalObject.IOReadBuffSize)\n\n\t\t\t// read data from the connection's IO into the memory buffer\n\t\t\t// (从conn的IO中读取数据到内存缓冲buffer中)\n\t\t\tn, err := c.conn.Read(buffer)\n\t\t\tif err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\"read msg head [read datalen=%d], error = %s\", n, err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tzlog.Ins().DebugF(\"read buffer %s \\n\", hex.EncodeToString(buffer[0:n]))\n\n\t\t\t// If normal data is read from the peer, update the heartbeat detection Active state\n\t\t\t// (正常读取到对端数据，更新心跳检测Active状态)\n\t\t\tif n > 0 && c.hc != nil {\n\t\t\t\tc.updateActivity()\n\t\t\t}\n\n\t\t\t// Deal with the custom protocol fragmentation problem, added by uuxia 2023-03-21\n\t\t\t// (处理自定义协议断粘包问题)\n\t\t\tif c.frameDecoder != nil {\n\t\t\t\t// Decode the 0-n bytes of data read\n\t\t\t\t// (为读取到的0-n个字节的数据进行解码)\n\t\t\t\tbufArrays := c.frameDecoder.Decode(buffer[0:n])\n\t\t\t\tif bufArrays == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor _, bytes := range bufArrays {\n\t\t\t\t\t// zlog.Ins().DebugF(\"read buffer %s \\n\", hex.EncodeToString(bytes))\n\t\t\t\t\tmsg := zpack.NewMessage(uint32(len(bytes)), bytes)\n\t\t\t\t\t// Get the current client's Request data\n\t\t\t\t\t// (得到当前客户端请求的Request数据)\n\t\t\t\t\treq := GetRequest(c, msg)\n\t\t\t\t\tc.msgHandler.Execute(req)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmsg := zpack.NewMessage(uint32(n), buffer[0:n])\n\t\t\t\t// Get the current client's Request data\n\t\t\t\t// (得到当前客户端请求的Request数据)\n\t\t\t\treq := GetRequest(c, msg)\n\t\t\t\tc.msgHandler.Execute(req)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Start starts the connection and makes the current connection work.\n// (启动连接，让当前连接开始工作)\nfunc (c *KcpConnection) Start() {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tzlog.Ins().ErrorF(\"Connection Start() error: %v\", err)\n\t\t}\n\t}()\n\tc.ctx, c.cancel = context.WithCancel(context.Background())\n\n\t// Execute the hook method for processing business logic when creating a connection\n\t// (按照用户传递进来的创建连接时需要处理的业务，执行钩子方法)\n\tc.callOnConnStart()\n\n\t// Start heartbeating detection\n\tif c.hc != nil {\n\t\tc.hc.Start()\n\t\tc.updateActivity()\n\t}\n\n\t// 占用workerid\n\tc.workerID = useWorker(c)\n\n\t// Start the Goroutine for reading data from the client\n\t// (开启用户从客户端读取数据流程的Goroutine)\n\tgo c.StartReader()\n\n\tselect {\n\tcase <-c.ctx.Done():\n\t\tc.finalizer()\n\n\t\t// 归还workerid\n\t\tfreeWorker(c)\n\t\treturn\n\t}\n}\n\n// Stop stops the connection and ends the current connection state.\n// (停止连接，结束当前连接状态)\nfunc (c *KcpConnection) Stop() {\n\tc.cancel()\n}\n\nfunc (c *KcpConnection) GetConnection() net.Conn {\n\treturn c.conn\n}\n\nfunc (c *KcpConnection) GetWsConn() *websocket.Conn {\n\treturn nil\n}\n\n// Deprecated: use GetConnection instead\nfunc (c *KcpConnection) GetTCPConnection() net.Conn {\n\treturn c.conn\n}\n\nfunc (c *KcpConnection) GetConnID() uint64 {\n\treturn c.connID\n}\n\nfunc (c *KcpConnection) GetConnIdStr() string {\n\treturn c.connIdStr\n}\n\nfunc (c *KcpConnection) GetWorkerID() uint32 {\n\treturn c.workerID\n}\n\nfunc (c *KcpConnection) RemoteAddr() net.Addr {\n\treturn c.conn.RemoteAddr()\n}\n\nfunc (c *KcpConnection) LocalAddr() net.Addr {\n\treturn c.conn.LocalAddr()\n}\n\nfunc (c *KcpConnection) Send(data []byte) error {\n\tc.msgLock.RLock()\n\tdefer c.msgLock.RUnlock()\n\tif c.isClosed() {\n\t\treturn errors.New(\"connection closed when send msg\")\n\t}\n\n\t_, err := c.conn.Write(data)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"SendMsg err data = %+v, err = %+v\", data, err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *KcpConnection) SendToQueue(data []byte, opts ...ziface.MsgSendOption) error {\n\tc.msgLock.RLock()\n\tdefer c.msgLock.RUnlock()\n\n\tif c.msgBuffChan == nil {\n\t\tc.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen)\n\t\t// Start a Goroutine to write data back to the client\n\t\t// This method only reads data from the MsgBuffChan without allocating memory or starting a Goroutine\n\t\t// (开启用于写回客户端数据流程的Goroutine\n\t\t// 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程)\n\t\tgo c.StartWriter()\n\t}\n\n\topt := ziface.MsgSendOptionObj{\n\t\tTimeout: 5 * time.Millisecond,\n\t}\n\n\tfor _, o := range opts {\n\t\to(&opt)\n\t}\n\n\tidleTimeout := time.NewTimer(opt.Timeout)\n\tdefer idleTimeout.Stop()\n\n\tif c.isClosed() {\n\t\treturn errors.New(\"Connection closed when send buff msg\")\n\t}\n\n\tif data == nil {\n\t\tzlog.Ins().ErrorF(\"Pack data is nil\")\n\t\treturn errors.New(\"Pack data is nil\")\n\t}\n\n\t// Send timeout\n\tselect {\n\tcase <-idleTimeout.C:\n\t\treturn errors.New(\"send buff msg timeout\")\n\tcase c.msgBuffChan <- data:\n\t\treturn nil\n\t}\n}\n\n// SendMsg directly sends Message data to the remote KCP client.\n// (直接将Message数据发送数据给远程的KCP客户端)\nfunc (c *KcpConnection) SendMsg(msgID uint32, data []byte) error {\n\tif c.isClosed() {\n\t\treturn errors.New(\"connection closed when send msg\")\n\t}\n\t// Pack data and send it\n\tmsg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data))\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"Pack error msg ID = %d\", msgID)\n\t\treturn errors.New(\"Pack error msg \")\n\t}\n\n\terr = c.Send(msg)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"SendMsg err msg ID = %d, data = %+v, err = %+v\", msgID, string(msg), err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *KcpConnection) SendBuffMsg(msgID uint32, data []byte, opts ...ziface.MsgSendOption) error {\n\tif c.isClosed() {\n\t\treturn errors.New(\"connection closed when send buff msg\")\n\t}\n\tif c.msgBuffChan == nil {\n\t\tc.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen)\n\t\t// Start a Goroutine to write data back to the client\n\t\t// This method only reads data from the MsgBuffChan without allocating memory or starting a Goroutine\n\t\t// (开启用于写回客户端数据流程的Goroutine\n\t\t// 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程)\n\t\tgo c.StartWriter()\n\t}\n\n\topt := ziface.MsgSendOptionObj{\n\t\tTimeout: 5 * time.Millisecond,\n\t}\n\n\tfor _, o := range opts {\n\t\to(&opt)\n\t}\n\n\tidleTimeout := time.NewTimer(opt.Timeout)\n\tdefer idleTimeout.Stop()\n\n\tmsg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data))\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"Pack error msg ID = %d\", msgID)\n\t\treturn errors.New(\"Pack error msg \")\n\t}\n\n\t// send timeout\n\tselect {\n\tcase <-idleTimeout.C:\n\t\treturn errors.New(\"send buff msg timeout\")\n\tcase c.msgBuffChan <- msg:\n\t\treturn nil\n\t}\n}\n\nfunc (c *KcpConnection) SetProperty(key string, value interface{}) {\n\tc.propertyLock.Lock()\n\tdefer c.propertyLock.Unlock()\n\tif c.property == nil {\n\t\tc.property = make(map[string]interface{})\n\t}\n\n\tc.property[key] = value\n}\n\nfunc (c *KcpConnection) GetProperty(key string) (interface{}, error) {\n\tc.propertyLock.Lock()\n\tdefer c.propertyLock.Unlock()\n\n\tif value, ok := c.property[key]; ok {\n\t\treturn value, nil\n\t}\n\n\treturn nil, errors.New(\"no property found\")\n}\n\nfunc (c *KcpConnection) RemoveProperty(key string) {\n\tc.propertyLock.Lock()\n\tdefer c.propertyLock.Unlock()\n\n\tdelete(c.property, key)\n}\n\nfunc (c *KcpConnection) Context() context.Context {\n\treturn c.ctx\n}\n\nfunc (c *KcpConnection) finalizer() {\n\t// If the connection has already been closed\n\tif c.isClosed() == true {\n\t\treturn\n\t}\n\n\t//set closed\n\tif !c.setClose() {\n\t\treturn\n\t}\n\n\t// Call the callback function registered by the user when closing the connection if it exists\n\t//(如果用户注册了该连接的\t关闭回调业务，那么在此刻应该显示调用)\n\tc.callOnConnStop()\n\n\tc.msgLock.Lock()\n\tdefer c.msgLock.Unlock()\n\n\t// Stop the heartbeat detector associated with the connection\n\tif c.hc != nil {\n\t\tc.hc.Stop()\n\t}\n\n\t// Close the socket connection\n\t_ = c.conn.Close()\n\n\t// Remove the connection from the connection manager\n\tif c.connManager != nil {\n\t\tc.connManager.Remove(c)\n\t}\n\n\t// Close all channels associated with the connection\n\tif c.msgBuffChan != nil {\n\t\tclose(c.msgBuffChan)\n\t}\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif err := recover(); err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\"Conn finalizer panic: %v\", err)\n\t\t\t}\n\t\t}()\n\n\t\tc.InvokeCloseCallbacks()\n\t}()\n\n\tzlog.Ins().DebugF(\"Conn Stop()...ConnID = %d\", c.connID)\n}\n\nfunc (c *KcpConnection) callOnConnStart() {\n\tif c.onConnStart != nil {\n\t\tzlog.Ins().DebugF(\"ZINX CallOnConnStart....\")\n\t\tc.onConnStart(c)\n\t}\n}\n\nfunc (c *KcpConnection) callOnConnStop() {\n\tif c.onConnStop != nil {\n\t\tzlog.Ins().DebugF(\"ZINX CallOnConnStop....\")\n\t\tc.onConnStop(c)\n\t}\n}\n\nfunc (c *KcpConnection) IsAlive() bool {\n\tif c.isClosed() {\n\t\treturn false\n\t}\n\t// Check the last activity time of the connection. If it's beyond the heartbeat interval,\n\t// then the connection is considered dead.\n\t// (检查连接最后一次活动时间，如果超过心跳间隔，则认为连接已经死亡)\n\treturn time.Now().Sub(c.lastActivityTime) < zconf.GlobalObject.HeartbeatMaxDuration()\n}\n\nfunc (c *KcpConnection) updateActivity() {\n\tc.lastActivityTime = time.Now()\n}\n\nfunc (c *KcpConnection) SetHeartBeat(checker ziface.IHeartbeatChecker) {\n\tc.hc = checker\n}\n\nfunc (c *KcpConnection) LocalAddrString() string {\n\treturn c.localAddr\n}\n\nfunc (c *KcpConnection) RemoteAddrString() string {\n\treturn c.remoteAddr\n}\n\nfunc (c *KcpConnection) GetName() string {\n\treturn c.name\n}\n\nfunc (c *KcpConnection) GetMsgHandler() ziface.IMsgHandle {\n\treturn c.msgHandler\n}\n\nfunc (c *KcpConnection) isClosed() bool {\n\treturn atomic.LoadInt32(&c.closed) != 0\n}\n\nfunc (c *KcpConnection) setClose() bool {\n\treturn atomic.CompareAndSwapInt32(&c.closed, 0, 1)\n}\n\nfunc (s *KcpConnection) AddCloseCallback(handler, key interface{}, f func()) {\n\tif s.isClosed() {\n\t\treturn\n\t}\n\ts.closeCallbackMutex.Lock()\n\tdefer s.closeCallbackMutex.Unlock()\n\ts.closeCallback.Add(handler, key, f)\n}\n\nfunc (s *KcpConnection) RemoveCloseCallback(handler, key interface{}) {\n\tif s.isClosed() {\n\t\treturn\n\t}\n\ts.closeCallbackMutex.Lock()\n\tdefer s.closeCallbackMutex.Unlock()\n\ts.closeCallback.Remove(handler, key)\n}\n\n// invokeCloseCallbacks 触发 close callback, 在独立协程完成\nfunc (s *KcpConnection) InvokeCloseCallbacks() {\n\ts.closeCallbackMutex.RLock()\n\tdefer s.closeCallbackMutex.RUnlock()\n\ts.closeCallback.Invoke()\n}\n\n// Implement other KCP specific methods here...\n// ...\n// ...\n\n// For example, to create a new KCP connection:\n// func newKCPConn(client ziface.IClient, conn net.Conn) ziface.IConnection {\n//   c := &Connection{\n//     conn:        conn,\n//     connID:      0, // client ignore\n//     isClosed:    false,\n//     msgBuffChan: nil,\n//     property:    nil,\n//     name:        client.GetName(),\n//     localAddr:   conn.LocalAddr().String(),\n//     remoteAddr:  conn.RemoteAddr().String(),\n//     // ... Initialize other KCP specific properties ...\n//   }\n//   // Inherited properties from client\n//   c.packet = client.GetPacket()\n//   c.onConnStart = client.GetOnConnStart()\n//   c.onConnStop = client.GetOnConnStop()\n//   c.msgHandler = client.GetMsgHandler()\n\n//   return c\n// }\n\n// And similarly for the Server-side KCP connection:\n// func newServerKCPConn(server ziface.IServer, conn net.Conn, connID uint64) ziface.IConnection {\n//   c := &Connection{\n//     conn:        conn,\n//     connID:      connID,\n//     isClosed:    false,\n//     msgBuffChan: nil,\n//     property:    nil,\n//     name:        server.ServerName(),\n//     localAddr:   conn.LocalAddr().String(),\n//     remoteAddr:  conn.RemoteAddr().String(),\n//     // ... Initialize other KCP specific properties ...\n//   }\n//   // Inherited properties from server\n//   c.packet = server.GetPacket()\n//   c.onConnStart = server.GetOnConnStart()\n//   c.onConnStop = server.GetOnConnStop()\n//   c.msgHandler = server.GetMsgHandler()\n\n//   // ... Additional initialization specific to KCP ...\n\n//   return c\n// }\n"
  },
  {
    "path": "znet/msghandler.go",
    "content": "package znet\n\nimport (\n\t\"encoding/hex\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n)\n\nconst (\n\t// If the Worker goroutine pool is not started, a virtual WorkerID is assigned to the MsgHandler, which is 0, for metric counting\n\t// After starting the Worker goroutine pool, the ID of each worker is 0,1,2,3...\n\t// (如果不启动Worker协程池，则会给MsgHandler分配一个虚拟的WorkerID，这个workerID为0, 便于指标统计\n\t// 启动了Worker协程池后，每个worker的ID为0,1,2,3...)\n\tWorkerIDWithoutWorkerPool int = 0\n)\n\n// MsgHandle is the module for handling message processing callbacks\n// (对消息的处理回调模块)\ntype MsgHandle struct {\n\t// A map property that stores the processing methods for each MsgID\n\t// (存放每个MsgID 所对应的处理方法的map属性)\n\tApis map[uint32]ziface.IRouter\n\n\t// The number of worker goroutines in the business work Worker pool\n\t// (业务工作Worker池的数量)\n\tWorkerPoolSize uint32\n\n\t// A collection of idle workers, used for zconf.WorkerModeBind\n\t// 空闲worker集合，用于zconf.WorkerModeBind\n\tfreeWorkers  map[uint32]struct{}\n\tfreeWorkerMu sync.Mutex\n\n\t// A message queue for workers to take tasks\n\t// (Worker负责取任务的消息队列)\n\tTaskQueue []chan ziface.IRequest\n\n\t// A collection of extra workers, used for zconf.WorkerModeDynamicBind\n\t// (池里的工作线程不够用的时候, 可临时额外分配workerID集合, 用于zconf.WorkerModeDynamicBind)\n\textraFreeWorkers  map[uint32]struct{}\n\textraFreeWorkerMu sync.Mutex\n\n\t// Chain builder for the responsibility chain\n\t// (责任链构造器)\n\tbuilder      *chainBuilder\n\tRouterSlices *RouterSlices\n}\n\n// newMsgHandle creates MsgHandle\n// zinxRole: IServer\nfunc newMsgHandle() *MsgHandle {\n\tvar freeWorkers map[uint32]struct{}\n\tvar extraFreeWorkers map[uint32]struct{}\n\n\tif zconf.GlobalObject.WorkerMode == zconf.WorkerModeBind {\n\t\t// Assign a workder to each link, avoid interactions when multiple links are processed by the same worker\n\t\t// MaxWorkerTaskLen can also be reduced, for example, 50\n\t\t// 为每个连接分配一个workder，避免同一worker处理多个连接时的互相影响\n\t\t// 同时可以减小MaxWorkerTaskLen，比如50，因为每个worker的负担减轻了\n\t\tzconf.GlobalObject.WorkerPoolSize = uint32(zconf.GlobalObject.MaxConn)\n\t\tfreeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.WorkerPoolSize)\n\t\tfor i := uint32(0); i < zconf.GlobalObject.WorkerPoolSize; i++ {\n\t\t\tfreeWorkers[i] = struct{}{}\n\t\t}\n\t}\n\n\tTaskQueueLen := zconf.GlobalObject.WorkerPoolSize\n\n\tif zconf.GlobalObject.WorkerMode == zconf.WorkerModeDynamicBind {\n\t\tzlog.Ins().DebugF(\"WorkerMode = %s\", zconf.WorkerModeDynamicBind)\n\t\tfreeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.WorkerPoolSize)\n\t\tfor i := uint32(0); i < zconf.GlobalObject.WorkerPoolSize; i++ {\n\t\t\tfreeWorkers[i] = struct{}{}\n\t\t}\n\n\t\textraFreeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.MaxConn-int(zconf.GlobalObject.WorkerPoolSize))\n\t\tfor i := zconf.GlobalObject.WorkerPoolSize; i < uint32(zconf.GlobalObject.MaxConn); i++ {\n\t\t\textraFreeWorkers[i] = struct{}{}\n\t\t}\n\t\tTaskQueueLen = uint32(zconf.GlobalObject.MaxConn)\n\t}\n\n\thandle := &MsgHandle{\n\t\tApis:         make(map[uint32]ziface.IRouter),\n\t\tRouterSlices: NewRouterSlices(),\n\t\tfreeWorkers:  freeWorkers,\n\t\tbuilder:      newChainBuilder(),\n\t\t// 可额外临时分配的workerID集合\n\t\textraFreeWorkers: extraFreeWorkers,\n\t}\n\n\t// server\n\thandle.WorkerPoolSize = zconf.GlobalObject.WorkerPoolSize\n\t// One worker corresponds to one queue (一个worker对应一个queue)\n\thandle.TaskQueue = make([]chan ziface.IRequest, TaskQueueLen)\n\n\t// It is necessary to add the MsgHandle to the responsibility chain here, and it is the last link in the responsibility chain. After decoding in the MsgHandle, data distribution is done by router\n\t// (此处必须把 msghandler 添加到责任链中，并且是责任链最后一环，在msghandler中进行解码后由router做数据分发)\n\thandle.builder.Tail(handle)\n\treturn handle\n}\n\n// newCliMsgHandle creates MsgHandle\n// zinxRole: IClient\nfunc newCliMsgHandle() *MsgHandle {\n\tvar freeWorkers map[uint32]struct{}\n\tvar extraFreeWorkers map[uint32]struct{}\n\n\tif zconf.GlobalObject.WorkerMode == zconf.WorkerModeBind {\n\t\t// Assign a workder to each link, avoid interactions when multiple links are processed by the same worker\n\t\t// MaxWorkerTaskLen can also be reduced, for example, 50\n\t\t// 为每个连接分配一个workder，避免同一worker处理多个连接时的互相影响\n\t\t// 同时可以减小MaxWorkerTaskLen，比如50，因为每个worker的负担减轻了\n\t\tzconf.GlobalObject.WorkerPoolSize = uint32(zconf.GlobalObject.MaxConn)\n\t\tfreeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.WorkerPoolSize)\n\t\tfor i := uint32(0); i < zconf.GlobalObject.WorkerPoolSize; i++ {\n\t\t\tfreeWorkers[i] = struct{}{}\n\t\t}\n\t}\n\n\tTaskQueueLen := zconf.GlobalObject.WorkerPoolSize\n\n\tif zconf.GlobalObject.WorkerMode == zconf.WorkerModeDynamicBind {\n\t\tzlog.Ins().DebugF(\"WorkerMode = %s\", zconf.WorkerModeDynamicBind)\n\t\tfreeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.WorkerPoolSize)\n\t\tfor i := uint32(0); i < zconf.GlobalObject.WorkerPoolSize; i++ {\n\t\t\tfreeWorkers[i] = struct{}{}\n\t\t}\n\n\t\textraFreeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.MaxConn-int(zconf.GlobalObject.WorkerPoolSize))\n\t\tfor i := zconf.GlobalObject.WorkerPoolSize; i < uint32(zconf.GlobalObject.MaxConn); i++ {\n\t\t\textraFreeWorkers[i] = struct{}{}\n\t\t}\n\t\tTaskQueueLen = uint32(zconf.GlobalObject.MaxConn)\n\t}\n\n\thandle := &MsgHandle{\n\t\tApis:         make(map[uint32]ziface.IRouter),\n\t\tRouterSlices: NewRouterSlices(),\n\t\tfreeWorkers:  freeWorkers,\n\t\tbuilder:      newChainBuilder(),\n\t\t// 可额外临时分配的workerID集合\n\t\textraFreeWorkers: extraFreeWorkers,\n\t}\n\n\t// client: Set worker pool size to 0 to turn off the worker pool in the client (客户端将协程池关闭)\n\thandle.WorkerPoolSize = 0\n\t// One worker corresponds to one queue (一个worker对应一个queue)\n\thandle.TaskQueue = make([]chan ziface.IRequest, TaskQueueLen)\n\n\t// It is necessary to add the MsgHandle to the responsibility chain here, and it is the last link in the responsibility chain. After decoding in the MsgHandle, data distribution is done by router\n\t// (此处必须把 msghandler 添加到责任链中，并且是责任链最后一环，在msghandler中进行解码后由router做数据分发)\n\thandle.builder.Tail(handle)\n\treturn handle\n}\n\n// Use worker ID\n// 占用workerID\nfunc useWorker(conn ziface.IConnection) uint32 {\n\tvar workerId uint32\n\n\tmh, _ := conn.GetMsgHandler().(*MsgHandle)\n\tif mh == nil {\n\t\tzlog.Ins().ErrorF(\"useWorker failed, mh is nil\")\n\t\treturn 0\n\t}\n\n\tif zconf.GlobalObject.WorkerMode == zconf.WorkerModeBind {\n\t\tmh.freeWorkerMu.Lock()\n\t\tdefer mh.freeWorkerMu.Unlock()\n\n\t\tfor k := range mh.freeWorkers {\n\t\t\tdelete(mh.freeWorkers, k)\n\t\t\treturn k\n\t\t}\n\t}\n\n\tif zconf.GlobalObject.WorkerMode == zconf.WorkerModeDynamicBind {\n\t\tmh.freeWorkerMu.Lock()\n\t\t// try to get workerID from workerPool first\n\t\t// 首先尝试从工作线程池里获取一个空闲的workerID\n\t\tfor workerID := range mh.freeWorkers {\n\t\t\tdelete(mh.freeWorkers, workerID)\n\t\t\tmh.freeWorkerMu.Unlock()\n\t\t\treturn workerID\n\t\t}\n\t\tmh.freeWorkerMu.Unlock()\n\n\t\t// 工作池的worker用完了，临时从extraFreeWorkers取一个额外的workerID, 并相应启动一个临时的worker\n\t\tmh.extraFreeWorkerMu.Lock()\n\t\tdefer mh.extraFreeWorkerMu.Unlock()\n\t\tfor workerID := range mh.extraFreeWorkers {\n\t\t\tzlog.Ins().DebugF(\"start extra worker, workerID=%d\", workerID)\n\t\t\tmh.TaskQueue[workerID] = make(chan ziface.IRequest, zconf.GlobalObject.MaxWorkerTaskLen)\n\t\t\tgo mh.StartOneWorker(int(workerID), mh.TaskQueue[workerID])\n\t\t\treturn workerID\n\t\t}\n\t}\n\n\t//Compatible with the situation where the client has no worker, and solve the situation divide 0\n\t//(兼容client没有worker情况，解决除0的情况)\n\tif mh.WorkerPoolSize == 0 {\n\t\tworkerId = 0\n\t} else {\n\t\t// Assign the worker responsible for processing the current connection based on the ConnID\n\t\t// Using a round-robin average allocation rule to get the workerID that needs to process this connection\n\t\t// (根据ConnID来分配当前的连接应该由哪个worker负责处理\n\t\t// 轮询的平均分配法则\n\t\t// 得到需要处理此条连接的workerID)\n\t\tworkerId = uint32(conn.GetConnID() % uint64(mh.WorkerPoolSize))\n\t}\n\n\treturn workerId\n}\n\n// Free worker ID\n// 释放workerid\nfunc freeWorker(conn ziface.IConnection) {\n\tmh, _ := conn.GetMsgHandler().(*MsgHandle)\n\tif mh == nil {\n\t\tzlog.Ins().ErrorF(\"useWorker failed, mh is nil\")\n\t\treturn\n\t}\n\n\tif zconf.GlobalObject.WorkerMode == zconf.WorkerModeBind {\n\t\tmh.freeWorkerMu.Lock()\n\t\tdefer mh.freeWorkerMu.Unlock()\n\n\t\tmh.freeWorkers[conn.GetWorkerID()] = struct{}{}\n\t}\n\n\tif zconf.GlobalObject.WorkerMode == zconf.WorkerModeDynamicBind {\n\t\tworkerID := conn.GetWorkerID()\n\t\tif workerID < mh.WorkerPoolSize {\n\t\t\t// 说明这个是工作线程池里的workerID，回收这个workerID, workerID对应的worker不需要销毁\n\t\t\tmh.freeWorkerMu.Lock()\n\t\t\tmh.freeWorkers[workerID] = struct{}{}\n\t\t\tmh.freeWorkerMu.Unlock()\n\t\t} else {\n\t\t\t// 说明这个worker是一个临时的worker，需要销毁这个worker\n\t\t\tmh.StopOneWorker(int(workerID))\n\t\t\t// 回收workerID, 放回额外workerID池里\n\t\t\tmh.extraFreeWorkerMu.Lock()\n\t\t\tmh.extraFreeWorkers[workerID] = struct{}{}\n\t\t\tmh.extraFreeWorkerMu.Unlock()\n\t\t}\n\t}\n}\n\n// Data processing interceptor that is necessary by default in Zinx\n// (Zinx默认必经的数据处理拦截器)\nfunc (mh *MsgHandle) Intercept(chain ziface.IChain) ziface.IcResp {\n\trequest := chain.Request()\n\tif request != nil {\n\t\tswitch request.(type) {\n\t\tcase ziface.IRequest:\n\t\t\tiRequest := request.(ziface.IRequest)\n\t\t\tif mh.WorkerPoolSize > 0 {\n\t\t\t\t// If the worker pool mechanism has been started, hand over the message to the worker for processing\n\t\t\t\t// (已经启动工作池机制，将消息交给Worker处理)\n\t\t\t\tmh.SendMsgToTaskQueue(iRequest)\n\t\t\t} else {\n\n\t\t\t\t// Execute the corresponding Handle method from the bound message and its corresponding processing method\n\t\t\t\t// (从绑定好的消息和对应的处理方法中执行对应的Handle方法)\n\t\t\t\tif !zconf.GlobalObject.RouterSlicesMode {\n\t\t\t\t\tgo mh.doMsgHandler(iRequest, WorkerIDWithoutWorkerPool)\n\t\t\t\t} else if zconf.GlobalObject.RouterSlicesMode {\n\t\t\t\t\tgo mh.doMsgHandlerSlices(iRequest, WorkerIDWithoutWorkerPool)\n\t\t\t\t}\n\n\t\t\t}\n\t\t}\n\t}\n\n\treturn chain.Proceed(chain.Request())\n}\n\n// SetHeadInterceptor sets the head interceptor of the responsibility chain, which is the first interceptor to be executed\n// (SetHeadInterceptor 设置责任链的头拦截器，也就是第一个要执行的拦截器)\n// will replace the default head interceptor\nfunc (mh *MsgHandle) SetHeadInterceptor(interceptor ziface.IInterceptor) {\n\tif mh.builder != nil {\n\t\tmh.builder.Head(interceptor)\n\t}\n}\n\nfunc (mh *MsgHandle) AddInterceptor(interceptor ziface.IInterceptor) {\n\tif mh.builder != nil {\n\t\tmh.builder.AddInterceptor(interceptor)\n\t}\n}\n\n// SendMsgToTaskQueue sends the message to the TaskQueue for processing by the worker\n// (将消息交给TaskQueue,由worker进行处理)\nfunc (mh *MsgHandle) SendMsgToTaskQueue(request ziface.IRequest) {\n\tworkerID := request.GetConnection().GetWorkerID()\n\t// zlog.Ins().DebugF(\"Add ConnID=%d request msgID=%d to workerID=%d\", request.GetConnection().GetConnID(), request.GetMsgID(), workerID)\n\t// Send the request message to the task queue\n\tmh.TaskQueue[workerID] <- request\n\tzlog.Ins().DebugF(\"SendMsgToTaskQueue-->%s\", hex.EncodeToString(request.GetData()))\n}\n\n// doFuncHandler handles functional requests (执行函数式请求)\nfunc (mh *MsgHandle) doFuncHandler(request ziface.IFuncRequest, workerID int) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tzlog.Ins().ErrorF(\"workerID: %d doFuncRequest panic: %v\", workerID, err)\n\t\t}\n\t}()\n\t// Execute the functional request (执行函数式请求)\n\trequest.CallFunc()\n}\n\n// doMsgHandler immediately handles messages in a non-blocking manner\n// (立即以非阻塞方式处理消息)\nfunc (mh *MsgHandle) doMsgHandler(request ziface.IRequest, workerID int) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tzlog.Ins().ErrorF(\"workerID: %d doMsgHandler panic: %v\", workerID, err)\n\t\t}\n\t}()\n\n\tmsgId := request.GetMsgID()\n\thandler, ok := mh.Apis[msgId]\n\n\tif !ok {\n\t\tzlog.Ins().ErrorF(\"api msgID = %d is not FOUND!\", request.GetMsgID())\n\t\treturn\n\t}\n\n\t// Bind the Request request to the corresponding Router relationship\n\t// (Request请求绑定Router对应关系)\n\trequest.BindRouter(handler)\n\n\t// Execute the corresponding processing method\n\trequest.Call()\n\n\t// 执行完成后回收 Request 对象回对象池\n\tPutRequest(request)\n}\n\nfunc (mh *MsgHandle) Execute(request ziface.IRequest) {\n\t// Pass the message to the responsibility chain to handle it through interceptors layer by layer and pass it on layer by layer.\n\t// (将消息丢到责任链，通过责任链里拦截器层层处理层层传递)\n\tmh.builder.Execute(request)\n}\n\n// AddRouter adds specific processing logic for messages\n// (为消息添加具体的处理逻辑)\nfunc (mh *MsgHandle) AddRouter(msgID uint32, router ziface.IRouter) {\n\t// 1. Check whether the current API processing method bound to the msgID already exists\n\t// (判断当前msg绑定的API处理方法是否已经存在)\n\tif _, ok := mh.Apis[msgID]; ok {\n\t\tmsgErr := fmt.Sprintf(\"repeated api , msgID = %+v\\n\", msgID)\n\t\tpanic(msgErr)\n\t}\n\t// 2. Add the binding relationship between msg and API\n\t// (添加msg与api的绑定关系)\n\tmh.Apis[msgID] = router\n\tzlog.Ins().InfoF(\"Add Router msgID = %d\", msgID)\n}\n\n// AddRouterSlices adds router handlers using slices\n// (切片路由添加)\nfunc (mh *MsgHandle) AddRouterSlices(msgId uint32, handler ...ziface.RouterHandler) ziface.IRouterSlices {\n\tmh.RouterSlices.AddHandler(msgId, handler...)\n\treturn mh.RouterSlices\n}\n\n// Group routes into a group (路由分组)\nfunc (mh *MsgHandle) Group(start, end uint32, Handlers ...ziface.RouterHandler) ziface.IGroupRouterSlices {\n\treturn NewGroup(start, end, mh.RouterSlices, Handlers...)\n}\nfunc (mh *MsgHandle) Use(Handlers ...ziface.RouterHandler) ziface.IRouterSlices {\n\tmh.RouterSlices.Use(Handlers...)\n\treturn mh.RouterSlices\n}\n\nfunc (mh *MsgHandle) doMsgHandlerSlices(request ziface.IRequest, workerID int) {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tzlog.Ins().ErrorF(\"workerID: %d doMsgHandler panic: %v\", workerID, err)\n\t\t}\n\t}()\n\n\tmsgId := request.GetMsgID()\n\thandlers, ok := mh.RouterSlices.GetHandlers(msgId)\n\tif !ok {\n\t\tzlog.Ins().ErrorF(\"api msgID = %d is not FOUND!\", request.GetMsgID())\n\t\treturn\n\t}\n\n\trequest.BindRouterSlices(handlers)\n\trequest.RouterSlicesNext()\n\t// 执行完成后回收 Request 对象回对象池\n\tPutRequest(request)\n}\n\nfunc (mh *MsgHandle) StopOneWorker(workerID int) {\n\tzlog.Ins().DebugF(\"stop Worker ID = %d \", workerID)\n\t// Stop the worker by closing the corresponding taskQueue\n\t// (停止一个Worker，通过关闭对应的taskQueue)\n\tclose(mh.TaskQueue[workerID])\n}\n\n// StartOneWorker starts a worker workflow\n// (启动一个Worker工作流程)\nfunc (mh *MsgHandle) StartOneWorker(workerID int, taskQueue chan ziface.IRequest) {\n\tzlog.Ins().DebugF(\"Worker ID = %d is started.\", workerID)\n\t// Continuously wait for messages in the queue\n\t// (不断地等待队列中的消息)\n\tfor {\n\t\tselect {\n\t\t// If there is a message, take out the Request from the queue and execute the bound business method\n\t\t// (有消息则取出队列的Request，并执行绑定的业务方法)\n\t\tcase request, ok := <-taskQueue:\n\t\t\tif !ok {\n\t\t\t\t// DynamicBind Mode, destroy current worker by close the taskQueue\n\t\t\t\t// (DynamicBind模式下，临时创建的worker, 是通过关闭taskQueue 来销毁当前worker)\n\t\t\t\tzlog.Ins().ErrorF(\" taskQueue is closed, Worker ID = %d quit\", workerID)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tswitch req := request.(type) {\n\n\t\t\tcase ziface.IFuncRequest:\n\t\t\t\t// Internal function call request (内部函数调用request)\n\n\t\t\t\tmh.doFuncHandler(req, workerID)\n\n\t\t\tcase ziface.IRequest: // Client message request\n\n\t\t\t\tif !zconf.GlobalObject.RouterSlicesMode {\n\t\t\t\t\tmh.doMsgHandler(req, workerID)\n\t\t\t\t} else if zconf.GlobalObject.RouterSlicesMode {\n\t\t\t\t\tmh.doMsgHandlerSlices(req, workerID)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\n// StartWorkerPool starts the worker pool\nfunc (mh *MsgHandle) StartWorkerPool() {\n\t// Iterate through the required number of workers and start them one by one\n\t// (遍历需要启动worker的数量，依此启动)\n\tfor i := 0; i < int(mh.WorkerPoolSize); i++ {\n\t\t// A worker is started\n\t\t// Allocate space for the corresponding task queue for the current worker\n\t\t// (给当前worker对应的任务队列开辟空间)\n\t\tmh.TaskQueue[i] = make(chan ziface.IRequest, zconf.GlobalObject.MaxWorkerTaskLen)\n\n\t\t// Start the current worker, blocking and waiting for messages to be passed in the corresponding task queue\n\t\t// (启动当前Worker，阻塞的等待对应的任务队列是否有消息传递进来)\n\t\tgo mh.StartOneWorker(i, mh.TaskQueue[i])\n\t}\n}\n"
  },
  {
    "path": "znet/options.go",
    "content": "package znet\n\nimport (\n\t\"net/http\"\n\t\"net/url\"\n\n\t\"github.com/aceld/zinx/ziface\"\n)\n\n// Options for Server\n// (Server的服务Option)\ntype Option func(s *Server)\n\n// Implement custom data packet format by implementing the Packet interface,\n// otherwise use the default data packet format\n// (只要实现Packet 接口可自由实现数据包解析格式，如果没有则使用默认解析格式)\nfunc WithPacket(pack ziface.IDataPack) Option {\n\treturn func(s *Server) {\n\t\ts.SetPacket(pack)\n\t}\n}\n\n// Options for Client\ntype ClientOption func(c ziface.IClient)\n\n// Implement custom data packet format by implementing the Packet interface for client,\n// otherwise use the default data packet format\nfunc WithPacketClient(pack ziface.IDataPack) ClientOption {\n\treturn func(c ziface.IClient) {\n\t\tc.SetPacket(pack)\n\t}\n}\n\n// Set client name\nfunc WithNameClient(name string) ClientOption {\n\treturn func(c ziface.IClient) {\n\t\tc.SetName(name)\n\t}\n}\n\nfunc WithUrl(url *url.URL) ClientOption {\n\treturn func(c ziface.IClient) {\n\t\tc.SetUrl(url)\n\t}\n}\n\n// Set custom headers for WebSocket connection\nfunc WithWsHeader(header http.Header) ClientOption {\n\treturn func(c ziface.IClient) {\n\t\tc.SetWsHeader(header)\n\t}\n}\n"
  },
  {
    "path": "znet/request.go",
    "content": "package znet\n\nimport (\n\t\"math\"\n\t\"sync\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zpack\"\n)\n\nconst (\n\tPRE_HANDLE  ziface.HandleStep = iota // PreHandle for pre-processing\n\tHANDLE                               // Handle for processing\n\tPOST_HANDLE                          // PostHandle for post-processing\n\n\tHANDLE_OVER\n)\n\nvar RequestPool = new(sync.Pool)\n\nfunc init() {\n\tRequestPool.New = func() interface{} {\n\t\treturn allocateRequest()\n\t}\n}\n\n// Request 请求\ntype Request struct {\n\tziface.BaseRequest\n\tconn     ziface.IConnection     // the connection which has been established with the client(已经和客户端建立好的连接)\n\tmsg      ziface.IMessage        // the request data sent by the client(客户端请求的数据)\n\trouter   ziface.IRouter         // the router that handles this request(请求处理的函数)\n\tsteps    ziface.HandleStep      // used to control the execution of router functions(用来控制路由函数执行)\n\tstepLock sync.RWMutex           // concurrency lock(并发互斥)\n\tneedNext bool                   // whether to execute the next router function(是否需要执行下一个路由函数)\n\ticResp   ziface.IcResp          // response data returned by the interceptors (拦截器返回数据)\n\thandlers []ziface.RouterHandler // router function slice(路由函数切片)\n\tindex    int8                   // router function slice index(路由函数切片索引)\n\tkeys     map[string]interface{} // keys 路由处理时可能会存取的上下文信息\n}\n\nfunc (r *Request) GetResponse() ziface.IcResp {\n\treturn r.icResp\n}\n\nfunc (r *Request) SetResponse(response ziface.IcResp) {\n\tr.icResp = response\n}\n\nfunc NewRequest(conn ziface.IConnection, msg ziface.IMessage) ziface.IRequest {\n\treq := new(Request)\n\treq.steps = PRE_HANDLE\n\treq.conn = conn\n\treq.msg = msg\n\treq.stepLock = sync.RWMutex{}\n\treq.needNext = true\n\treq.index = -1\n\treturn req\n}\n\nfunc GetRequest(conn ziface.IConnection, msg ziface.IMessage) ziface.IRequest {\n\n\t// 根据当前模式判断是否使用对象池\n\tif zconf.GlobalObject.RequestPoolMode {\n\t\t// 从对象池中取得一个 Request 对象,如果池子中没有可用的 Request 对象则会调用 allocateRequest 函数构造一个新的对象分配\n\t\tr := RequestPool.Get().(*Request)\n\t\t// 因为取出的 Request 对象可能是已存在也可能是新构造的,无论是哪种情况都应该初始化再返回使用\n\t\tr.Reset(conn, msg)\n\t\treturn r\n\t}\n\treturn NewRequest(conn, msg)\n}\n\nfunc PutRequest(request ziface.IRequest) {\n\t// 判断是否开启了对象池模式\n\tif zconf.GlobalObject.RequestPoolMode {\n\t\tRequestPool.Put(request)\n\t}\n}\n\nfunc allocateRequest() ziface.IRequest {\n\treq := new(Request)\n\treq.steps = PRE_HANDLE\n\treq.needNext = true\n\treq.index = -1\n\treturn req\n}\n\nfunc (r *Request) Reset(conn ziface.IConnection, msg ziface.IMessage) {\n\tr.steps = PRE_HANDLE\n\tr.conn = conn\n\tr.msg = msg\n\tr.needNext = true\n\tr.index = -1\n\tr.keys = nil\n\n}\n\n// Copy 在执行路由函数的时候可能会出现需要再起一个协程的需求,但是 Request 对象由对象池管理后无法保证新协程中的 Request 参数一致\n// 通过 Copy 方法复制一份 Request 对象保持创建协程时候的参数一致。但新开的协程不应该在对原始的执行过程有影响，所以不包含连接和路由对象。\n// 但如果一定对连接信息有所需要可以在 Copy 后手动 set 一份参数在 Request 对象中\nfunc (r *Request) Copy() ziface.IRequest {\n\t// 构造一个新的 Request 对象，复制部分原始对象的参数,但是复制的 Request 不应该再对原始连接操作,所以不含有连接参数\n\t// 同理也不应该再执行路由方法,路由函数也不包含\n\tnewRequest := &Request{\n\t\tconn:     nil,\n\t\trouter:   nil,\n\t\tsteps:    r.steps,\n\t\tneedNext: false,\n\t\ticResp:   nil,\n\t\thandlers: nil,\n\t\tindex:    math.MaxInt8,\n\t}\n\n\t// 复制原本的上下文信息\n\tnewRequest.keys = make(map[string]interface{})\n\tfor k, v := range r.keys {\n\t\tnewRequest.keys[k] = v\n\t}\n\n\t// 复制一份原本的 icResp\n\tcopyResp := []ziface.IcResp{r.icResp}\n\tnewIcResp := make([]ziface.IcResp, 0, 1)\n\tcopy(newIcResp, copyResp)\n\tfor _, v := range newIcResp {\n\t\tnewRequest.icResp = v\n\t}\n\t// 复制一份原本的 msg 信息\n\tnewRequest.msg = zpack.NewMessageByMsgId(r.msg.GetMsgID(), r.msg.GetDataLen(), r.msg.GetRawData())\n\n\treturn newRequest\n}\n\n// Set 在 Request 中存放一个上下文，如果 keys 为空会实例化一个\nfunc (r *Request) Set(key string, value interface{}) {\n\tr.stepLock.Lock()\n\tif r.keys == nil {\n\t\tr.keys = make(map[string]interface{})\n\t}\n\n\tr.keys[key] = value\n\tr.stepLock.Unlock()\n}\n\n// Get 在 Request 中取出一个上下文信息\nfunc (r *Request) Get(key string) (value interface{}, exists bool) {\n\tr.stepLock.RLock()\n\tvalue, exists = r.keys[key]\n\tr.stepLock.RUnlock()\n\treturn\n}\n\nfunc (r *Request) GetMessage() ziface.IMessage {\n\treturn r.msg\n}\n\nfunc (r *Request) GetConnection() ziface.IConnection {\n\treturn r.conn\n}\n\nfunc (r *Request) GetData() []byte {\n\treturn r.msg.GetData()\n}\n\nfunc (r *Request) GetMsgID() uint32 {\n\treturn r.msg.GetMsgID()\n}\n\nfunc (r *Request) BindRouter(router ziface.IRouter) {\n\tr.router = router\n}\n\nfunc (r *Request) next() {\n\tif r.needNext == false {\n\t\tr.needNext = true\n\t\treturn\n\t}\n\n\tr.stepLock.Lock()\n\tr.steps++\n\tr.stepLock.Unlock()\n}\n\nfunc (r *Request) Goto(step ziface.HandleStep) {\n\tr.stepLock.Lock()\n\tr.steps = step\n\tr.needNext = false\n\tr.stepLock.Unlock()\n}\n\nfunc (r *Request) Call() {\n\n\tif r.router == nil {\n\t\treturn\n\t}\n\n\tfor r.steps < HANDLE_OVER {\n\t\tswitch r.steps {\n\t\tcase PRE_HANDLE:\n\t\t\tr.router.PreHandle(r)\n\t\tcase HANDLE:\n\t\t\tr.router.Handle(r)\n\t\tcase POST_HANDLE:\n\t\t\tr.router.PostHandle(r)\n\t\t}\n\n\t\tr.next()\n\t}\n\n\tr.steps = PRE_HANDLE\n}\n\nfunc (r *Request) Abort() {\n\tif zconf.GlobalObject.RouterSlicesMode {\n\t\tr.index = int8(len(r.handlers))\n\t} else {\n\t\tr.stepLock.Lock()\n\t\tr.steps = HANDLE_OVER\n\t\tr.stepLock.Unlock()\n\t}\n}\n\n// BindRouterSlices New version\nfunc (r *Request) BindRouterSlices(handlers []ziface.RouterHandler) {\n\tr.handlers = handlers\n}\n\nfunc (r *Request) RouterSlicesNext() {\n\tr.index++\n\tfor r.index < int8(len(r.handlers)) {\n\t\tr.handlers[r.index](r)\n\t\tr.index++\n\t}\n}\n"
  },
  {
    "path": "znet/request_func.go",
    "content": "package znet\n\nimport \"github.com/aceld/zinx/ziface\"\n\ntype RequestFunc struct {\n\tziface.BaseRequest\n\tconn     ziface.IConnection\n\tcallFunc func()\n}\n\nfunc (rf *RequestFunc) GetConnection() ziface.IConnection {\n\treturn rf.conn\n}\n\nfunc (rf *RequestFunc) CallFunc() {\n\tif rf.callFunc != nil {\n\t\trf.callFunc()\n\t}\n}\n\nfunc NewFuncRequest(conn ziface.IConnection, callFunc func()) ziface.IRequest {\n\treq := new(RequestFunc)\n\treq.conn = conn\n\treq.callFunc = callFunc\n\treturn req\n}\n"
  },
  {
    "path": "znet/router.go",
    "content": "package znet\n\nimport (\n\t\"strconv\"\n\t\"sync\"\n\n\t\"github.com/aceld/zinx/ziface\"\n)\n\n// BaseRouter is used as the base class when implementing a router.\n// Depending on the needs, the methods of this base class can be overridden.\n// (实现router时，先嵌入这个基类，然后根据需要对这个基类的方法进行重写)\ntype BaseRouter struct{}\n\n// Here, all of BaseRouter's methods are empty, because some routers may not want to have PreHandle or PostHandle.\n// Therefore, inheriting all routers from BaseRouter has the advantage that PreHandle and PostHandle do not need to be\n// implemented to instantiate a router.\n// (这里之所以BaseRouter的方法都为空，\n// 是因为有的Router不希望有PreHandle或PostHandle\n// 所以Router全部继承BaseRouter的好处是，不需要实现PreHandle和PostHandle也可以实例化)\n\n// PreHandle -\nfunc (br *BaseRouter) PreHandle(req ziface.IRequest) {}\n\n// Handle -\nfunc (br *BaseRouter) Handle(req ziface.IRequest) {}\n\n// PostHandle -\nfunc (br *BaseRouter) PostHandle(req ziface.IRequest) {}\n\n// New slice-based router\n// The new version of the router has basic logic that allows users to pass in varying numbers of router handlers.\n// The router will save all of these router handler functions and find them when a request comes in, then execute them using IRequest.\n// The router can set globally shared components using the Use method.\n// The router can be grouped using Group, and groups also have their own Use method for setting group-shared components.\n// (新切片集合式路由\n// 新版本路由基本逻辑,用户可以传入不等数量的路由路由处理器\n// 路由本体会讲这些路由处理器函数全部保存,在请求来的时候找到，并交由IRequest去执行\n// 路由可以设置全局的共用组件通过Use方法\n// 路由可以分组,通过Group,分组也有自己对应Use方法设置组共有组件)\n\ntype RouterSlices struct {\n\tApis     map[uint32][]ziface.RouterHandler\n\tHandlers []ziface.RouterHandler\n\tsync.RWMutex\n}\n\nfunc NewRouterSlices() *RouterSlices {\n\treturn &RouterSlices{\n\t\tApis:     make(map[uint32][]ziface.RouterHandler, 10),\n\t\tHandlers: make([]ziface.RouterHandler, 0, 6),\n\t}\n}\n\nfunc (r *RouterSlices) Use(handles ...ziface.RouterHandler) {\n\tr.Handlers = append(r.Handlers, handles...)\n}\n\nfunc (r *RouterSlices) AddHandler(msgId uint32, Handlers ...ziface.RouterHandler) {\n\t// 1. Check if the API handler method bound to the current msg already exists\n\tif _, ok := r.Apis[msgId]; ok {\n\t\tpanic(\"repeated api , msgId = \" + strconv.Itoa(int(msgId)))\n\t}\n\n\tfinalSize := len(r.Handlers) + len(Handlers)\n\tmergedHandlers := make([]ziface.RouterHandler, finalSize)\n\tcopy(mergedHandlers, r.Handlers)\n\tcopy(mergedHandlers[len(r.Handlers):], Handlers)\n\tr.Apis[msgId] = append(r.Apis[msgId], mergedHandlers...)\n}\n\nfunc (r *RouterSlices) GetHandlers(MsgId uint32) ([]ziface.RouterHandler, bool) {\n\tr.RLock()\n\tdefer r.RUnlock()\n\thandlers, ok := r.Apis[MsgId]\n\treturn handlers, ok\n}\n\nfunc (r *RouterSlices) Group(start, end uint32, Handlers ...ziface.RouterHandler) ziface.IGroupRouterSlices {\n\treturn NewGroup(start, end, r, Handlers...)\n}\n\ntype GroupRouter struct {\n\tstart    uint32\n\tend      uint32\n\tHandlers []ziface.RouterHandler\n\trouter   ziface.IRouterSlices\n}\n\nfunc NewGroup(start, end uint32, router *RouterSlices, Handlers ...ziface.RouterHandler) *GroupRouter {\n\tg := &GroupRouter{\n\t\tstart:    start,\n\t\tend:      end,\n\t\tHandlers: make([]ziface.RouterHandler, 0, len(Handlers)),\n\t\trouter:   router,\n\t}\n\tg.Handlers = append(g.Handlers, Handlers...)\n\treturn g\n}\n\nfunc (g *GroupRouter) Use(Handlers ...ziface.RouterHandler) {\n\tg.Handlers = append(g.Handlers, Handlers...)\n}\n\nfunc (g *GroupRouter) AddHandler(MsgId uint32, Handlers ...ziface.RouterHandler) {\n\tif MsgId < g.start || MsgId > g.end {\n\t\tpanic(\"add router to group err in msgId:\" + strconv.Itoa(int(MsgId)))\n\t}\n\n\tfinalSize := len(g.Handlers) + len(Handlers)\n\tmergedHandlers := make([]ziface.RouterHandler, finalSize)\n\tcopy(mergedHandlers, g.Handlers)\n\tcopy(mergedHandlers[len(g.Handlers):], Handlers)\n\n\tg.router.AddHandler(MsgId, mergedHandlers...)\n}\n"
  },
  {
    "path": "znet/routerSilces_test.go",
    "content": "package znet\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n)\n\nfunc A1(request ziface.IRequest) {\n\tfmt.Println(\"我要写入一些上下文到 Request 中了\")\n\trequest.Set(\"Hey\", \"zinx!\")\n\trequest.Set(\"Age\", 2)\n}\nfunc A2(request ziface.IRequest) {\n\tname, _ := request.Get(\"Hey\")\n\tage, _ := request.Get(\"Age\")\n\n\tfmt.Printf(\"我是练习时长%v年半的%v \\n\", age, name)\n\n\t//如果需要开新协程操作应该 copy\n\tcp := request.Copy()\n\tgo A4(cp)\n\trequest.Abort()\n}\n\nfunc A3(request ziface.IRequest) {\n\tfmt.Println(\"No! 不带我玩\")\n}\n\nfunc A4(request ziface.IRequest) {\n\t// 需要新线程同时也需要上下文的情况\n\tfmt.Println(request)\n}\n\nfunc TestRouterAdd(t *testing.T) {\n\n\tserver := NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: \"127.0.0.1\"})\n\tserver.AddRouterSlices(1, A1, A2, A3)\n\tserver.Serve()\n\n}\n"
  },
  {
    "path": "znet/server.go",
    "content": "package znet\n\nimport (\n\t\"crypto/rand\"\n\t\"crypto/tls\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/gorilla/websocket\"\n\n\t\"github.com/aceld/zinx/logo\"\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/zdecoder\"\n\t\"github.com/aceld/zinx/zlog\"\n\n\t\"github.com/xtaci/kcp-go\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zpack\"\n)\n\n// Server interface implementation, defines a Server service class\n// (接口实现，定义一个Server服务类)\ntype Server struct {\n\t// Name of the server (服务器的名称)\n\tName string\n\t//tcp4 or other\n\tIPVersion string\n\t// IP version (e.g. \"tcp4\") - 服务绑定的IP地址\n\tIP string\n\t// IP address the server is bound to (服务绑定的端口)\n\tPort int\n\t// 服务绑定的websocket 端口 (Websocket port the server is bound to)\n\tWsPort int\n\t// 服务绑定的websocket 路径 (Websocket path the server is bound to)\n\tWsPath string\n\t// 服务绑定的kcp 端口 (kcp port the server is bound to)\n\tKcpPort int\n\n\t// Current server's message handler module, used to bind MsgID to corresponding processing methods\n\t// (当前Server的消息管理模块，用来绑定MsgID和对应的处理方法)\n\tmsgHandler ziface.IMsgHandle\n\n\t// Routing mode (路由模式)\n\tRouterSlicesMode bool\n\t// Request 对象池模式\n\tRequestPoolMode bool\n\t// Current server's connection manager (当前Server的连接管理器)\n\tConnMgr ziface.IConnManager\n\n\t// Hook function called when a new connection is established\n\t// (该Server的连接创建时Hook函数)\n\tonConnStart func(conn ziface.IConnection)\n\n\t// Hook function called when a connection is terminated\n\t// (该Server的连接断开时的Hook函数)\n\tonConnStop func(conn ziface.IConnection)\n\n\t// Data packet encapsulation method\n\t// (数据报文封包方式)\n\tpacket ziface.IDataPack\n\n\t// Asynchronous capture of connection closing status\n\t// (异步捕获连接关闭状态)\n\texitChan chan struct{}\n\n\t// Decoder for dealing with message fragmentation and reassembly\n\t// (断粘包解码器)\n\tdecoder ziface.IDecoder\n\n\t// Heartbeat checker\n\t// (心跳检测器)\n\thc ziface.IHeartbeatChecker\n\n\t// websocket\n\tupgrader *websocket.Upgrader\n\n\t// websocket connection authentication\n\twebsocketAuth func(r *http.Request) error\n\n\tkcpConfig *KcpConfig\n\n\t// connection id\n\tcID uint64\n}\n\ntype KcpConfig struct {\n\t// changes ack flush option, set true to flush ack immediately,\n\t// (改变ack刷新选项，设置为true立即刷新ack)\n\tKcpACKNoDelay bool\n\t// toggles the stream mode on/off\n\t// (切换流模式开/关)\n\tKcpStreamMode bool\n\t// Whether nodelay mode is enabled, 0 is not enabled; 1 enabled.\n\t// (是否启用nodelay模式，0不启用；1启用)\n\tKcpNoDelay int\n\t// Protocol internal work interval, in milliseconds, such as 10 ms or 20 ms.\n\t// (协议内部工作的间隔，单位毫秒，比如10ms或者20ms)\n\tKcpInterval int\n\t// Fast retransmission mode, 0 represents off by default, 2 can be set (2 ACK spans will result in direct retransmission)\n\t// (快速重传模式，默认为0关闭，可以设置2（2次ACK跨越将会直接重传）\n\tKcpResend int\n\t// Whether to turn off flow control, 0 represents “Do not turn off” by default, 1 represents “Turn off”.\n\t// (是否关闭流控，默认是0代表不关闭，1代表关闭)\n\tKcpNc int\n\t// SND_BUF, this unit is the packet, default 32.\n\t// (SND_BUF发送缓冲区大小，单位是包，默认是32)\n\tKcpSendWindow int\n\t// RCV_BUF, this unit is the packet, default 32.\n\t// (RCV_BUF接收缓冲区大小，单位是包，默认是32)\n\tKcpRecvWindow int\n\t// FEC data shards, default 0.\n\t// (FEC数据分片,用于前向纠错比例配制) 默认是0\n\tKcpFecDataShards int\n\t// FEC parity shards, default 0.\n\t// (FEC校验分片,用于前向纠错比例配制) 默认是0\n\tKcpFecParityShards int\n}\n\n// newServerWithConfig creates a server handle based on config\n// (根据config创建一个服务器句柄)\nfunc newServerWithConfig(config *zconf.Config, ipVersion string, opts ...Option) ziface.IServer {\n\tlogo.PrintLogo()\n\n\ts := &Server{\n\t\tName:             config.Name,\n\t\tIPVersion:        ipVersion,\n\t\tIP:               config.Host,\n\t\tPort:             config.TCPPort,\n\t\tWsPort:           config.WsPort,\n\t\tWsPath:           config.WsPath,\n\t\tKcpPort:          config.KcpPort,\n\t\tmsgHandler:       newMsgHandle(),\n\t\tRouterSlicesMode: config.RouterSlicesMode,\n\t\tRequestPoolMode:  config.RequestPoolMode,\n\t\tConnMgr:          newConnManager(),\n\t\texitChan:         nil,\n\t\t// Default to using Zinx's TLV data pack format\n\t\t// (默认使用zinx的TLV封包方式)\n\t\tpacket:  zpack.Factory().NewPack(ziface.ZinxDataPack),\n\t\tdecoder: zdecoder.NewTLVDecoder(), // Default to using TLV decode (默认使用TLV的解码方式)\n\t\tupgrader: &websocket.Upgrader{\n\t\t\tReadBufferSize: int(config.IOReadBuffSize),\n\t\t\tCheckOrigin: func(r *http.Request) bool {\n\t\t\t\treturn true\n\t\t\t},\n\t\t},\n\t\tkcpConfig: &KcpConfig{\n\t\t\tKcpACKNoDelay:      config.KcpACKNoDelay,\n\t\t\tKcpStreamMode:      config.KcpStreamMode,\n\t\t\tKcpNoDelay:         config.KcpNoDelay,\n\t\t\tKcpInterval:        config.KcpInterval,\n\t\t\tKcpResend:          config.KcpResend,\n\t\t\tKcpNc:              config.KcpNc,\n\t\t\tKcpSendWindow:      config.KcpSendWindow,\n\t\t\tKcpRecvWindow:      config.KcpRecvWindow,\n\t\t\tKcpFecDataShards:   config.KcpFecDataShards,\n\t\t\tKcpFecParityShards: config.KcpFecParityShards,\n\t\t},\n\t}\n\n\tfor _, opt := range opts {\n\t\topt(s)\n\t}\n\n\t// Display current configuration information\n\t// (提示当前配置信息)\n\tconfig.Show()\n\n\treturn s\n}\n\n// NewServer creates a server handle\n// (创建一个服务器句柄)\nfunc NewServer(opts ...Option) ziface.IServer {\n\treturn newServerWithConfig(zconf.GlobalObject, \"tcp\", opts...)\n}\n\n// NewUserConfServer creates a server handle using user-defined configuration\n// (创建一个服务器句柄)\nfunc NewUserConfServer(config *zconf.Config, opts ...Option) ziface.IServer {\n\n\t// Refresh user configuration to global configuration variable\n\t// (刷新用户配置到全局配置变量)\n\tzconf.UserConfToGlobal(config)\n\n\ts := newServerWithConfig(zconf.GlobalObject, \"tcp4\", opts...)\n\treturn s\n}\n\n// NewDefaultRouterSlicesServer creates a server handle with a default RouterRecovery processor.\n// (创建一个默认自带一个Recover处理器的服务器句柄)\nfunc NewDefaultRouterSlicesServer(opts ...Option) ziface.IServer {\n\tzconf.GlobalObject.RouterSlicesMode = true\n\ts := newServerWithConfig(zconf.GlobalObject, \"tcp\", opts...)\n\ts.Use(RouterRecovery)\n\treturn s\n}\n\n// NewUserConfDefaultRouterSlicesServer creates a server handle with user-configured options and a default Recover handler.\n// If the user does not wish to use the Use method, they should use NewUserConfServer instead.\n// (创建一个用户配置的自带一个Recover处理器的服务器句柄，如果用户不希望Use这个方法，那么应该使用NewUserConfServer)\nfunc NewUserConfDefaultRouterSlicesServer(config *zconf.Config, opts ...Option) ziface.IServer {\n\n\tif !config.RouterSlicesMode {\n\t\tpanic(\"RouterSlicesMode is false\")\n\t}\n\n\t// Refresh user configuration to global configuration variable (刷新用户配置到全局配置变量)\n\tzconf.UserConfToGlobal(config)\n\n\ts := newServerWithConfig(zconf.GlobalObject, \"tcp4\", opts...)\n\ts.Use(RouterRecovery)\n\treturn s\n}\n\nfunc (s *Server) StartConn(conn ziface.IConnection) {\n\t// HeartBeat check\n\tif s.hc != nil {\n\t\t// Clone a heart-beat checker from the server side\n\t\theartBeatChecker := s.hc.Clone()\n\n\t\t// Bind current connection\n\t\theartBeatChecker.BindConn(conn)\n\t}\n\n\t// Start processing business for the current connection\n\tconn.Start()\n}\n\nfunc (s *Server) ListenTcpConn() {\n\tzlog.Ins().InfoF(\"[START] TCP Server name: %s,listener at IP: %s, Port %d is starting\", s.Name, s.IP, s.Port)\n\t// 1. Get a TCP address\n\taddr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf(\"%s:%d\", s.IP, s.Port))\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"[START] resolve tcp addr err: %v\\n\", err)\n\t\treturn\n\t}\n\n\t// 2. Listen to the server address\n\tvar listener net.Listener\n\tif zconf.GlobalObject.CertFile != \"\" && zconf.GlobalObject.PrivateKeyFile != \"\" {\n\t\t// Read certificate and private key\n\t\tcrt, err := tls.LoadX509KeyPair(zconf.GlobalObject.CertFile, zconf.GlobalObject.PrivateKeyFile)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\n\t\t// TLS connection\n\t\ttlsConfig := &tls.Config{}\n\t\ttlsConfig.Certificates = []tls.Certificate{crt}\n\t\ttlsConfig.Time = time.Now\n\t\ttlsConfig.Rand = rand.Reader\n\t\tlistener, err = tls.Listen(s.IPVersion, fmt.Sprintf(\"%s:%d\", s.IP, s.Port), tlsConfig)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t} else {\n\t\tlistener, err = net.ListenTCP(s.IPVersion, addr)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n\t// 3. Start server network connection business\n\tgo func() {\n\t\tfor {\n\t\t\t// 3.1 Set the maximum connection control for the server. If it exceeds the maximum connection, wait.\n\t\t\t// (设置服务器最大连接控制,如果超过最大连接，则等待)\n\t\t\tif s.ConnMgr.Len() >= zconf.GlobalObject.MaxConn {\n\t\t\t\tzlog.Ins().InfoF(\"Exceeded the maxConnNum:%d, Wait:%d\", zconf.GlobalObject.MaxConn, AcceptDelay.duration)\n\t\t\t\tAcceptDelay.Delay()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// 3.2 Block and wait for a client to establish a connection request.\n\t\t\t// (阻塞等待客户端建立连接请求)\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\t//Go 1.17+\n\t\t\t\tif errors.Is(err, net.ErrClosed) {\n\t\t\t\t\tzlog.Ins().ErrorF(\"Listener closed\")\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tzlog.Ins().ErrorF(\"Accept err: %v\", err)\n\t\t\t\tAcceptDelay.Delay()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tAcceptDelay.Reset()\n\n\t\t\t// 3.4 Handle the business method for this new connection request. At this time, the handler and conn should be bound.\n\t\t\t// (处理该新连接请求的 业务 方法， 此时应该有 handler 和 conn是绑定的)\n\t\t\tnewCid := atomic.AddUint64(&s.cID, 1)\n\t\t\tdealConn := newServerConn(s, conn, newCid)\n\n\t\t\tgo s.StartConn(dealConn)\n\n\t\t}\n\t}()\n\tselect {\n\tcase <-s.exitChan:\n\t\terr := listener.Close()\n\t\tif err != nil {\n\t\t\tzlog.Ins().ErrorF(\"listener close err: %v\", err)\n\t\t}\n\t}\n}\n\nfunc (s *Server) ListenWebsocketConn() {\n\tzlog.Ins().InfoF(\"[START] WEBSOCKET Server name: %s,listener at IP: %s, Port %d, Path %s is starting\", s.Name, s.IP, s.WsPort, s.WsPath)\n\thttp.HandleFunc(s.WsPath, func(w http.ResponseWriter, r *http.Request) {\n\t\t// 1. Check if the server has reached the maximum allowed number of connections\n\t\t// (设置服务器最大连接控制,如果超过最大连接，则等待)\n\t\tif s.ConnMgr.Len() >= zconf.GlobalObject.MaxConn {\n\t\t\tzlog.Ins().InfoF(\"Exceeded the maxConnNum:%d, Wait:%d\", zconf.GlobalObject.MaxConn, AcceptDelay.duration)\n\t\t\tAcceptDelay.Delay()\n\t\t\treturn\n\t\t}\n\t\t// 2. If websocket authentication is required, set the authentication information\n\t\t// (如果需要 websocket 认证请设置认证信息)\n\t\tif s.websocketAuth != nil {\n\t\t\terr := s.websocketAuth(r)\n\t\t\tif err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\" websocket auth err:%v\", err)\n\t\t\t\tw.WriteHeader(401)\n\t\t\t\tAcceptDelay.Delay()\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\t\t// 3. Check if there is a subprotocol specified in the header\n\t\t// (判断 header 里面是有子协议)\n\t\tif len(r.Header.Get(\"Sec-Websocket-Protocol\")) > 0 {\n\t\t\ts.upgrader.Subprotocols = websocket.Subprotocols(r)\n\t\t}\n\t\t// 4. Upgrade the connection to a websocket connection\n\t\t// (升级成 websocket 连接)\n\t\tconn, err := s.upgrader.Upgrade(w, r, nil)\n\t\tif err != nil {\n\t\t\tzlog.Ins().ErrorF(\"new websocket err:%v\", err)\n\t\t\tw.WriteHeader(500)\n\t\t\tAcceptDelay.Delay()\n\t\t\treturn\n\t\t}\n\t\tAcceptDelay.Reset()\n\t\t// 5. Handle the business logic of the new connection, which should already be bound to a handler and conn\n\t\t// 5. 处理该新连接请求的 业务 方法， 此时应该有 handler 和 conn是绑定的\n\t\tnewCid := atomic.AddUint64(&s.cID, 1)\n\t\twsConn := newWebsocketConn(s, conn, newCid, r)\n\t\tgo s.StartConn(wsConn)\n\n\t})\n\n\tif zconf.GlobalObject.CertFile != \"\" && zconf.GlobalObject.PrivateKeyFile != \"\" {\n\t\terr := http.ListenAndServeTLS(fmt.Sprintf(\"%s:%d\", s.IP, s.WsPort), zconf.GlobalObject.CertFile, zconf.GlobalObject.PrivateKeyFile, nil)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t} else {\n\t\terr := http.ListenAndServe(fmt.Sprintf(\"%s:%d\", s.IP, s.WsPort), nil)\n\t\tif err != nil {\n\t\t\tpanic(err)\n\t\t}\n\t}\n\n}\n\nfunc (s *Server) ListenKcpConn() {\n\n\t// 1. Listen to the server address\n\tlistener, err := kcp.ListenWithOptions(fmt.Sprintf(\"%s:%d\", s.IP, s.KcpPort), nil, s.kcpConfig.KcpFecDataShards, s.kcpConfig.KcpFecParityShards)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"[START] resolve KCP addr err: %v\\n\", err)\n\t\treturn\n\t}\n\n\tzlog.Ins().InfoF(\"[START] KCP server listening at IP: %s, Port %d, Addr %s\", s.IP, s.KcpPort, listener.Addr().String())\n\t// 2. Start server network connection business\n\tgo func() {\n\t\tfor {\n\t\t\t// 2.1 Set the maximum connection control for the server. If it exceeds the maximum connection, wait.\n\t\t\t// (设置服务器最大连接控制,如果超过最大连接，则等待)\n\t\t\tif s.ConnMgr.Len() >= zconf.GlobalObject.MaxConn {\n\t\t\t\tzlog.Ins().InfoF(\"Exceeded the maxConnNum:%d, Wait:%d\", zconf.GlobalObject.MaxConn, AcceptDelay.duration)\n\t\t\t\tAcceptDelay.Delay()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\t// 2.2 Block and wait for a client to establish a connection request.\n\t\t\t// (阻塞等待客户端建立连接请求)\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\"Accept KCP err: %v\", err)\n\t\t\t\tAcceptDelay.Delay()\n\t\t\t\tcontinue\n\t\t\t}\n\n\t\t\tAcceptDelay.Reset()\n\n\t\t\t// 3.4 Handle the business method for this new connection request. At this time, the handler and conn should be bound.\n\t\t\t// (处理该新连接请求的 业务 方法， 此时应该有 handler 和 conn 是绑定的)\n\t\t\tnewCid := atomic.AddUint64(&s.cID, 1)\n\n\t\t\tkcpConn := conn.(*kcp.UDPSession)\n\t\t\tkcpConn.SetACKNoDelay(s.kcpConfig.KcpACKNoDelay)\n\t\t\tkcpConn.SetStreamMode(s.kcpConfig.KcpStreamMode)\n\t\t\tkcpConn.SetNoDelay(s.kcpConfig.KcpNoDelay, s.kcpConfig.KcpInterval, s.kcpConfig.KcpResend, s.kcpConfig.KcpNc)\n\t\t\tkcpConn.SetWindowSize(s.kcpConfig.KcpSendWindow, s.kcpConfig.KcpRecvWindow)\n\n\t\t\tdealConn := newKcpServerConn(s, kcpConn, newCid)\n\n\t\t\tgo s.StartConn(dealConn)\n\t\t}\n\t}()\n\tselect {\n\tcase <-s.exitChan:\n\t\terr := listener.Close()\n\t\tif err != nil {\n\t\t\tzlog.Ins().ErrorF(\"KCP listener close err: %v\", err)\n\t\t}\n\t}\n}\n\n// Start the network service\n// (开启网络服务)\nfunc (s *Server) Start() {\n\ts.exitChan = make(chan struct{})\n\n\t// Add decoder to interceptors head\n\t// (将解码器添加到拦截器最前面)\n\tif s.decoder != nil {\n\t\ts.msgHandler.SetHeadInterceptor(s.decoder)\n\t}\n\t// Start worker pool mechanism\n\t// (启动worker工作池机制)\n\ts.msgHandler.StartWorkerPool()\n\n\t// Start a goroutine to handle server listener business\n\t// (开启一个go去做服务端Listener业务)\n\tswitch zconf.GlobalObject.Mode {\n\tcase zconf.ServerModeTcp:\n\t\tgo s.ListenTcpConn()\n\tcase zconf.ServerModeWebsocket:\n\t\tgo s.ListenWebsocketConn()\n\tcase zconf.ServerModeKcp:\n\t\tgo s.ListenKcpConn()\n\tdefault:\n\t\tgo s.ListenTcpConn()\n\t\tgo s.ListenWebsocketConn()\n\t}\n\n}\n\n// Stop stops the server (停止服务)\nfunc (s *Server) Stop() {\n\tzlog.Ins().InfoF(\"[STOP] Zinx server , name %s\", s.Name)\n\n\t// Clear other connection information or other information that needs to be cleaned up\n\t// (将其他需要清理的连接信息或者其他信息 也要一并停止或者清理)\n\ts.ConnMgr.ClearConn()\n\ts.exitChan <- struct{}{}\n\tclose(s.exitChan)\n}\n\n// Serve runs the server (运行服务)\nfunc (s *Server) Serve() {\n\ts.Start()\n\t// Block, otherwise the listener's goroutine will exit when the main Go exits (阻塞,否则主Go退出， listenner的go将会退出)\n\tc := make(chan os.Signal, 1)\n\t// Listen for specified signals: ctrl+c or kill signal (监听指定信号 ctrl+c kill信号)\n\tsignal.Notify(c, syscall.SIGINT, syscall.SIGTERM)\n\tsig := <-c\n\tzlog.Ins().InfoF(\"[SERVE] Zinx server , name %s, Serve Interrupt, signal = %v\", s.Name, sig)\n}\n\nfunc (s *Server) AddRouter(msgID uint32, router ziface.IRouter) {\n\tif s.RouterSlicesMode {\n\t\tpanic(\"Server RouterSlicesMode is true \")\n\t}\n\ts.msgHandler.AddRouter(msgID, router)\n}\n\nfunc (s *Server) AddRouterSlices(msgID uint32, router ...ziface.RouterHandler) ziface.IRouterSlices {\n\tif !s.RouterSlicesMode {\n\t\tpanic(\"Server RouterSlicesMode is false \")\n\t}\n\treturn s.msgHandler.AddRouterSlices(msgID, router...)\n}\n\nfunc (s *Server) Group(start, end uint32, Handlers ...ziface.RouterHandler) ziface.IGroupRouterSlices {\n\tif !s.RouterSlicesMode {\n\t\tpanic(\"Server RouterSlicesMode is false\")\n\t}\n\treturn s.msgHandler.Group(start, end, Handlers...)\n}\n\nfunc (s *Server) Use(Handlers ...ziface.RouterHandler) ziface.IRouterSlices {\n\tif !s.RouterSlicesMode {\n\t\tpanic(\"Server RouterSlicesMode is false\")\n\t}\n\treturn s.msgHandler.Use(Handlers...)\n}\n\nfunc (s *Server) GetConnMgr() ziface.IConnManager {\n\treturn s.ConnMgr\n}\n\nfunc (s *Server) SetOnConnStart(hookFunc func(ziface.IConnection)) {\n\ts.onConnStart = hookFunc\n}\n\nfunc (s *Server) SetOnConnStop(hookFunc func(ziface.IConnection)) {\n\ts.onConnStop = hookFunc\n}\n\nfunc (s *Server) GetOnConnStart() func(ziface.IConnection) {\n\treturn s.onConnStart\n}\n\nfunc (s *Server) GetOnConnStop() func(ziface.IConnection) {\n\treturn s.onConnStop\n}\n\nfunc (s *Server) GetPacket() ziface.IDataPack {\n\treturn s.packet\n}\n\nfunc (s *Server) SetPacket(packet ziface.IDataPack) {\n\ts.packet = packet\n}\n\nfunc (s *Server) GetMsgHandler() ziface.IMsgHandle {\n\treturn s.msgHandler\n}\n\n// StartHeartBeat starts the heartbeat check.\n// interval is the time interval between each heartbeat.\n// (启动心跳检测\n// interval 每次发送心跳的时间间隔)\nfunc (s *Server) StartHeartBeat(interval time.Duration) {\n\tchecker := NewHeartbeatChecker(interval)\n\n\t// Add the heartbeat check router. (添加心跳检测的路由)\n\t//检测当前路由模式\n\tif s.RouterSlicesMode {\n\t\ts.AddRouterSlices(checker.MsgID(), checker.RouterSlices()...)\n\t} else {\n\t\ts.AddRouter(checker.MsgID(), checker.Router())\n\t}\n\n\t// Bind the heartbeat checker to the server. (server绑定心跳检测器)\n\ts.hc = checker\n}\n\n// StartHeartBeatWithOption starts the heartbeat detection with the given configuration.\n// interval is the time interval for sending heartbeat messages.\n// option is the configuration for heartbeat detection.\n// 启动心跳检测\n// (option 心跳检测的配置)\nfunc (s *Server) StartHeartBeatWithOption(interval time.Duration, option *ziface.HeartBeatOption) {\n\tchecker := NewHeartbeatChecker(interval)\n\n\t// Configure the heartbeat checker with the provided options\n\tif option != nil {\n\t\tchecker.SetHeartbeatMsgFunc(option.MakeMsg)\n\t\tchecker.SetOnRemoteNotAlive(option.OnRemoteNotAlive)\n\t\t//检测当前路由模式\n\t\tif s.RouterSlicesMode {\n\t\t\tchecker.BindRouterSlices(option.HeartBeatMsgID, option.RouterSlices...)\n\t\t} else {\n\t\t\tchecker.BindRouter(option.HeartBeatMsgID, option.Router)\n\t\t}\n\t}\n\n\t// Add the heartbeat checker's router to the server's router (添加心跳检测的路由)\n\t//检测当前路由模式\n\tif s.RouterSlicesMode {\n\t\ts.AddRouterSlices(checker.MsgID(), checker.RouterSlices()...)\n\t} else {\n\t\ts.AddRouter(checker.MsgID(), checker.Router())\n\t}\n\n\t// Bind the server with the heartbeat checker (server绑定心跳检测器)\n\ts.hc = checker\n}\n\nfunc (s *Server) GetHeartBeat() ziface.IHeartbeatChecker {\n\treturn s.hc\n}\n\nfunc (s *Server) SetDecoder(decoder ziface.IDecoder) {\n\ts.decoder = decoder\n}\n\nfunc (s *Server) GetLengthField() *ziface.LengthField {\n\tif s.decoder != nil {\n\t\treturn s.decoder.GetLengthField()\n\t}\n\treturn nil\n}\n\nfunc (s *Server) AddInterceptor(interceptor ziface.IInterceptor) {\n\ts.msgHandler.AddInterceptor(interceptor)\n}\n\nfunc (s *Server) SetWebsocketAuth(f func(r *http.Request) error) {\n\ts.websocketAuth = f\n}\n\nfunc (s *Server) ServerName() string {\n\treturn s.Name\n}\n\nfunc init() {}\n"
  },
  {
    "path": "znet/server_test.go",
    "content": "package znet\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zpack\"\n)\n\n// run in terminal:\n// go test -v ./znet -run=TestServer\n\n/*\nClientTest client\n*/\nfunc ClientTest(i uint32) {\n\n\tfmt.Println(\"Client Test ... start\")\n\n\t//3秒之后发起测试请求，给服务端开启服务的机会\n\ttime.Sleep(3 * time.Second)\n\n\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:8999\")\n\tif err != nil {\n\t\tfmt.Println(\"client start err, exit!\")\n\t\treturn\n\t}\n\n\tfor {\n\t\tdp := zpack.Factory().NewPack(ziface.ZinxDataPack)\n\t\tmsg, _ := dp.Pack(zpack.NewMsgPackage(i, []byte(\"client test message\")))\n\t\t_, err := conn.Write(msg)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client write err: \", err)\n\t\t\treturn\n\t\t}\n\n\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t_, err = io.ReadFull(conn, headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client read head err: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tmsgHead, err := dp.Unpack(headData)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client unpack head err: \", err)\n\t\t\treturn\n\t\t}\n\n\t\tif msgHead.GetDataLen() > 0 {\n\t\t\tmsg := msgHead.(*zpack.Message)\n\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"client unpack data err\")\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tfmt.Printf(\"==> Client receive Msg: ID = %d, len = %d , data = %s\\n\", msg.ID, msg.DataLen, msg.Data)\n\t\t}\n\n\t\ttime.Sleep(time.Second)\n\t}\n}\n\n/*\n\tserver\n*/\n\ntype PingRouter struct {\n\tBaseRouter\n}\n\n// Test PreHandle\nfunc (this *PingRouter) PreHandle(request ziface.IRequest) {\n\tfmt.Println(\"Call Router PreHandle\")\n\terr := request.GetConnection().SendMsg(1, []byte(\"before ping ....\\n\"))\n\tif err != nil {\n\t\tfmt.Println(\"preHandle SendMsg err: \", err)\n\t}\n}\n\n// Test Handle\nfunc (this *PingRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"Call PingRouter Handle\")\n\t//先读取客户端的数据，再回写ping...ping...ping\n\tfmt.Println(\"recv from client : msgID=\", request.GetMsgID(), \", data=\", string(request.GetData()))\n\n\terr := request.GetConnection().SendMsg(1, []byte(\"ping...ping...ping\\n\"))\n\tif err != nil {\n\t\tfmt.Println(\"Handle SendMsg err: \", err)\n\t}\n}\n\n// Test PostHandle\nfunc (this *PingRouter) PostHandle(request ziface.IRequest) {\n\tfmt.Println(\"Call Router PostHandle\")\n\terr := request.GetConnection().SendMsg(1, []byte(\"After ping .....\\n\"))\n\tif err != nil {\n\t\tfmt.Println(\"Post SendMsg err: \", err)\n\t}\n}\n\ntype HelloRouter struct {\n\tBaseRouter\n}\n\nfunc (this *HelloRouter) Handle(request ziface.IRequest) {\n\tfmt.Println(\"call helloRouter Handle\")\n\tfmt.Printf(\"receive from client msgID=%d, data=%s\\n\", request.GetMsgID(), string(request.GetData()))\n\n\terr := request.GetConnection().SendMsg(2, []byte(\"hello zix hello Router\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc DoConnectionBegin(conn ziface.IConnection) {\n\tfmt.Println(\"DoConnectionBegin is Called ... \")\n\terr := conn.SendMsg(2, []byte(\"DoConnection BEGIN...\"))\n\tif err != nil {\n\t\tfmt.Println(err)\n\t}\n}\n\nfunc DoConnectionLost(conn ziface.IConnection) {\n\tfmt.Println(\"DoConnectionLost is Called ... \")\n}\n\nfunc TestServer(t *testing.T) {\n\ts := NewServer()\n\n\ts.SetOnConnStart(DoConnectionBegin)\n\ts.SetOnConnStop(DoConnectionLost)\n\n\ts.AddRouter(1, &PingRouter{})\n\ts.AddRouter(2, &HelloRouter{})\n\n\tgo ClientTest(1)\n\tgo ClientTest(2)\n\n\tgo s.Serve()\n\n\tselect {\n\tcase <-time.After(time.Second * 10):\n\t\treturn\n\t}\n}\n\nfunc TestServerDeadLock(t *testing.T) {\n\ts := NewServer()\n\n\ts.Start()\n\ttime.Sleep(time.Second * 1)\n\n\tgo func() {\n\t\t_, _ = net.Dial(\"tcp\", \"127.0.0.1:8999\")\n\t}()\n\ttime.Sleep(time.Second * 1)\n\ts.Stop()\n}\n\ntype CloseConnectionBeforeSendMsgRouter struct {\n\tBaseRouter\n}\n\ntype DemoPacket struct {\n\tzpack.DataPack\n}\n\nfunc (d *DemoPacket) Pack(msg ziface.IMessage) ([]byte, error) {\n\ttime.Sleep(time.Second * 1)\n\treturn d.DataPack.Pack(msg)\n}\n\nfunc (br *CloseConnectionBeforeSendMsgRouter) Handle(req ziface.IRequest) {\n\tconnection := req.GetConnection()\n\tmsg := \"Zinx server response message for CloseConnectionBeforeSendMsgRouter\"\n\tconnection.Stop()\n\t_ = connection.SendMsg(1, []byte(msg))\n\tfmt.Println(\"send: \", msg)\n}\n\nfunc TestCloseConnectionBeforeSendMsg(t *testing.T) {\n\ts := NewServer()\n\ts.AddRouter(1, &CloseConnectionBeforeSendMsgRouter{})\n\n\ts.Start()\n\ttime.Sleep(time.Second * 1)\n\n\twg := sync.WaitGroup{}\n\twg.Add(1)\n\tgo func() {\n\t\tconn, _ := net.Dial(\"tcp\", \"127.0.0.1:8999\")\n\t\tdp := zpack.Factory().NewPack(ziface.ZinxDataPack)\n\t\tmsg := \"Zinx client request message for CloseConnectionBeforeSendMsgRouter\"\n\t\tpack, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(msg)))\n\t\t_, _ = conn.Write(pack)\n\t\tfmt.Println(\"send: \", msg)\n\t\tbuffer := make([]byte, 1024)\n\t\treadLen, _ := conn.Read(buffer)\n\t\tfmt.Println(\"received all data: \", string(buffer[dp.GetHeadLen():readLen]))\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\ts.Stop()\n}\n"
  },
  {
    "path": "znet/ws_connection.go",
    "content": "package znet\n\nimport (\n\t\"context\"\n\t\"encoding/hex\"\n\t\"errors\"\n\t\"net\"\n\t\"net/http\"\n\t\"strconv\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zinterceptor\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/zpack\"\n\t\"github.com/gorilla/websocket\"\n)\n\n// WsConnectionHttpReqCtxKey http请求上下文key\ntype WsConnectionHttpReqCtxKey struct{}\n\n// WsConnection is a module for handling the read and write operations of a WebSocket connection.\n// (Websocket连接模块, 用于处理 Websocket 连接的读写业务 一个连接对应一个Connection)\ntype WsConnection struct {\n\t// conn is the current connection's WebSocket socket TCP socket. (当前连接的socket TCP套接字)\n\tconn *websocket.Conn\n\n\t// connID is the current connection's ID, which can also be referred to as SessionID, and is globally unique.\n\t// uint64 range: 018,446,744,073,709,551,615\n\t// This is the maximum number of connIDs that the theory supports per process.\n\t// (当前连接的ID 也可以称作为SessionID，ID全局唯一 ，服务端Connection使用\n\t//  uint64 取值范围：0 ~ 18,446,744,073,709,551,615\n\t//  这个是理论支持的进程connID的最大数量)\n\tconnID uint64\n\n\t// connection id for string\n\t// (字符串的连接id)\n\tconnIdStr string\n\n\t// The workerid responsible for handling the link\n\t// 负责处理该连接的workerid\n\tworkerID uint32\n\n\t// msgHandler is the message management module for MsgID and the corresponding message handling method.\n\t// (消息管理MsgID和对应处理方法的消息管理模块)\n\tmsgHandler ziface.IMsgHandle\n\n\t// ctx and cancel are used to notify that the connection has exited/stopped.\n\t// (告知该连接已经退出/停止的channel)\n\tctx    context.Context\n\tcancel context.CancelFunc\n\n\t// msgBuffChan is a buffered channel used for message communication between the read and write goroutines.\n\t// (有缓冲管道，用于读、写两个goroutine之间的消息通信)\n\tmsgBuffChan chan []byte\n\n\t// msgLock is used for locking when users send and receive messages.\n\t// (用户收发消息的Lock)\n\tmsgLock sync.Mutex\n\n\t// property is the connection attribute. (连接属性)\n\tproperty map[string]interface{}\n\n\t// propertyLock protects the current property lock. (保护当前property的锁)\n\tpropertyLock sync.Mutex\n\n\t// isClosed is the current connection's closed state. (当前连接的关闭状态)\n\tisClosed bool\n\n\t// connManager is the Connection Manager to which the current connection belongs. (当前连接是属于哪个Connection Manager的)\n\tconnManager ziface.IConnManager\n\n\t// onConnStart is the Hook function when the current connection is created.\n\t// (当前连接创建时Hook函数)\n\tonConnStart func(conn ziface.IConnection)\n\n\t// onConnStop is the Hook function when the current connection is disconnected.\n\t// (当前连接断开时的Hook函数)\n\tonConnStop func(conn ziface.IConnection)\n\n\t// packet is the data packet format.\n\t// (数据报文封包方式)\n\tpacket ziface.IDataPack\n\n\t// lastActivityTime is the last time the connection was active.\n\t// (最后一次活动时间)\n\tlastActivityTime time.Time\n\n\t// frameDecoder is the decoder for splitting or splicing data packets.\n\t// (断粘包解码器)\n\tframeDecoder ziface.IFrameDecoder\n\n\t// hc is the Heartbeat Checker. (心跳检测器)\n\thc ziface.IHeartbeatChecker\n\n\t// name is the name of the connection and is the same as the Name of the Server/Client that created the connection.\n\t// (连接名称，默认与创建连接的Server/Client的Name一致)\n\tname string\n\n\t// localAddr is the local address of the current connection. (当前连接的本地地址)\n\tlocalAddr string\n\n\t// remoteAddr is the remote address of the current connection. (当前连接的远程地址)\n\tremoteAddr string\n\n\t// Close callback\n\tcloseCallback callbacks\n\n\t// Close callback mutex\n\tcloseCallbackMutex sync.RWMutex\n}\n\n// newServerConn: for Server, a method to create a connection with Server characteristics\n// Note: The name has been changed from NewConnection\n// (newServerConn :for Server, 创建一个Server服务端特性的连接的方法\n// Note: 名字由 NewConnection 更变)\nfunc newWebsocketConn(server ziface.IServer, conn *websocket.Conn, connID uint64, r *http.Request) ziface.IConnection {\n\t// Initialize Conn properties (初始化Conn属性)\n\tc := &WsConnection{\n\t\tctx:         context.WithValue(context.Background(), WsConnectionHttpReqCtxKey{}, r.Context()), // websocketAuth可以在上下文中传递特殊的参数或信息;比如鉴权后，设置一些用户信息或房间id\n\t\tconn:        conn,\n\t\tconnID:      connID,\n\t\tconnIdStr:   strconv.FormatUint(connID, 10),\n\t\tisClosed:    false,\n\t\tmsgBuffChan: nil,\n\t\tproperty:    nil,\n\t\tname:        server.ServerName(),\n\t\tlocalAddr:   conn.LocalAddr().String(),\n\t\tremoteAddr:  conn.RemoteAddr().String(),\n\t}\n\n\tlengthField := server.GetLengthField()\n\tif lengthField != nil {\n\t\tc.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField)\n\t}\n\n\t// Inherited attributes from server (从server继承过来的属性)\n\tc.packet = server.GetPacket()\n\tc.onConnStart = server.GetOnConnStart()\n\tc.onConnStop = server.GetOnConnStop()\n\tc.msgHandler = server.GetMsgHandler()\n\n\t// Bind the current Connection to the Server's ConnManager (将当前的Connection与Server的ConnManager绑定)\n\tc.connManager = server.GetConnMgr()\n\n\t// Add the newly created Conn to the connection management (将新创建的Conn添加到连接管理中)\n\tserver.GetConnMgr().Add(c)\n\n\treturn c\n}\n\n// newClientConn :for Client, creates a connection with Client-side features\n// (newClientConn :for Client, 创建一个Client服务端特性的连接的方法)\nfunc newWsClientConn(client ziface.IClient, conn *websocket.Conn) ziface.IConnection {\n\tc := &WsConnection{\n\t\tconn:        conn,\n\t\tconnID:      0,  // client ignore\n\t\tconnIdStr:   \"\", // client ignore\n\t\tisClosed:    false,\n\t\tmsgBuffChan: nil,\n\t\tproperty:    nil,\n\t\tname:        client.GetName(),\n\t\tlocalAddr:   conn.LocalAddr().String(),\n\t\tremoteAddr:  conn.RemoteAddr().String(),\n\t}\n\n\tlengthField := client.GetLengthField()\n\tif lengthField != nil {\n\t\tc.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField)\n\t}\n\n\t// Inherit properties from client (从client继承过来的属性)\n\tc.packet = client.GetPacket()\n\tc.onConnStart = client.GetOnConnStart()\n\tc.onConnStop = client.GetOnConnStop()\n\tc.msgHandler = client.GetMsgHandler()\n\n\treturn c\n}\n\n// StartWriter is a Goroutine that sends messages to the client\n// (StartWriter 写消息Goroutine， 用户将数据发送给客户端)\nfunc (c *WsConnection) StartWriter() {\n\tzlog.Ins().InfoF(\"Writer Goroutine is running\")\n\tdefer zlog.Ins().InfoF(\"%s [conn Writer exit!]\", c.RemoteAddr().String())\n\n\tfor {\n\t\tselect {\n\t\tcase data, ok := <-c.msgBuffChan:\n\t\t\tif ok {\n\t\t\t\tif err := c.Send(data); err != nil {\n\t\t\t\t\tzlog.Ins().ErrorF(\"Send Buff Data error:, %s Conn Writer exit\", err)\n\t\t\t\t\tbreak\n\t\t\t\t}\n\n\t\t\t} else {\n\t\t\t\tzlog.Ins().ErrorF(\"msgBuffChan is Closed\")\n\t\t\t\tbreak\n\t\t\t}\n\t\tcase <-c.ctx.Done():\n\t\t\treturn\n\t\t}\n\t}\n}\n\n// StartReader is a Goroutine that reads messages from the client.\n// (StartReader 读消息Goroutine，用于从客户端中读取数据)\nfunc (c *WsConnection) StartReader() {\n\tzlog.Ins().InfoF(\"[Reader Goroutine is running]\")\n\tdefer zlog.Ins().InfoF(\"%s [conn Reader exit!]\", c.RemoteAddr().String())\n\tdefer c.Stop()\n\n\t// Create a pack-unpack object. (创建拆包解包的对象)\n\tfor {\n\t\tselect {\n\t\tcase <-c.ctx.Done():\n\t\t\treturn\n\t\tdefault:\n\t\t\t// add by uuxia 2023-02-03\n\t\t\t// Read data from the conn's IO to the memory buffer.\n\t\t\t// (从conn的IO中读取数据到内存缓冲buffer中)\n\t\t\tmessageType, buffer, err := c.conn.ReadMessage()\n\t\t\tif err != nil {\n\t\t\t\tc.cancel()\n\t\t\t\treturn\n\t\t\t}\n\t\t\tif messageType == websocket.PingMessage {\n\t\t\t\tc.updateActivity()\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tn := len(buffer)\n\t\t\tif err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\"read msg head [read datalen=%d], error = %s\", n, err.Error())\n\t\t\t\treturn\n\t\t\t}\n\t\t\tzlog.Ins().DebugF(\"read buffer %s \\n\", hex.EncodeToString(buffer[0:n]))\n\n\t\t\t// Update the Active status of heartbeat detection normally after reading data from the peer.\n\t\t\t// (正常读取到对端数据，更新心跳检测Active状态)\n\t\t\tif n > 0 && c.hc != nil {\n\t\t\t\tc.updateActivity()\n\t\t\t}\n\n\t\t\t// Handle custom protocol fragmentation and packet sticking issues add by uuxia 2023-03-21\n\t\t\t// (处理自定义协议断粘包问题)\n\t\t\tif c.frameDecoder != nil {\n\t\t\t\t// Decode the 0-n bytes of data read.\n\t\t\t\t// (为读取到的0-n个字节的数据进行解码)\n\t\t\t\tbufArrays := c.frameDecoder.Decode(buffer)\n\t\t\t\tif bufArrays == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tfor _, bytes := range bufArrays {\n\t\t\t\t\tzlog.Ins().DebugF(\"read buffer %s \\n\", hex.EncodeToString(bytes))\n\t\t\t\t\tmsg := zpack.NewMessage(uint32(len(bytes)), bytes)\n\t\t\t\t\t// Get the Request data requested by the current client.\n\t\t\t\t\t// (得到当前客户端请求的Request数据)\n\t\t\t\t\treq := GetRequest(c, msg)\n\t\t\t\t\tc.msgHandler.Execute(req)\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tmsg := zpack.NewMessage(uint32(n), buffer[0:n])\n\t\t\t\t// Get the Request data requested by the current client.\n\t\t\t\t// (得到当前客户端请求的Request数据)\n\t\t\t\treq := GetRequest(c, msg)\n\t\t\t\tc.msgHandler.Execute(req)\n\t\t\t}\n\t\t}\n\t}\n}\n\n// Start starts the connection and makes it work.\n// (Start 启动连接，让当前连接开始工作)\nfunc (c *WsConnection) Start() {\n\tctx := c.ctx\n\tif ctx == nil {\n\t\tctx = context.Background()\n\t}\n\tc.ctx, c.cancel = context.WithCancel(ctx)\n\t// Execute the hook method according to the business needs of creating the connection passed in by the user.\n\t// (按照用户传递进来的创建连接时需要处理的业务，执行钩子方法)\n\tc.callOnConnStart()\n\n\t// Start the heartbeat check\n\t// (启动心跳检测)\n\tif c.hc != nil {\n\t\tc.hc.Start()\n\t\tc.updateActivity()\n\t}\n\n\t// 占用workerid\n\tc.workerID = useWorker(c)\n\n\t// Start the Goroutine for users to read data from the client.\n\t// (开启用户从客户端读取数据流程的Goroutine)\n\tgo c.StartReader()\n\n\tselect {\n\tcase <-c.ctx.Done():\n\t\tc.finalizer()\n\n\t\t// 归还workerid\n\t\tfreeWorker(c)\n\t\treturn\n\t}\n}\n\n// Stop stops the connection and ends its current state.\n// (停止连接，结束当前连接状态)\nfunc (c *WsConnection) Stop() {\n\tc.cancel()\n}\n\nfunc (c *WsConnection) GetConnection() net.Conn {\n\treturn nil\n}\n\nfunc (c *WsConnection) GetWsConn() *websocket.Conn {\n\treturn c.conn\n}\n\n// Deprecated: use GetConnection instead\nfunc (c *WsConnection) GetTCPConnection() net.Conn {\n\treturn nil\n}\n\nfunc (c *WsConnection) GetConnID() uint64 {\n\treturn c.connID\n}\n\nfunc (c *WsConnection) GetConnIdStr() string {\n\treturn c.connIdStr\n}\n\nfunc (c *WsConnection) GetWorkerID() uint32 {\n\treturn c.workerID\n}\n\nfunc (c *WsConnection) RemoteAddr() net.Addr {\n\treturn c.conn.RemoteAddr()\n}\n\nfunc (c *WsConnection) LocalAddr() net.Addr {\n\treturn c.conn.LocalAddr()\n}\n\nfunc (c *WsConnection) Send(data []byte) error {\n\tc.msgLock.Lock()\n\tdefer c.msgLock.Unlock()\n\tif c.isClosed == true {\n\t\treturn errors.New(\"WsConnection closed when send msg\")\n\t}\n\n\terr := c.conn.WriteMessage(websocket.BinaryMessage, data)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"SendMsg err data = %+v, err = %+v\", data, err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\nfunc (c *WsConnection) SendToQueue(data []byte, opts ...ziface.MsgSendOption) error {\n\tc.msgLock.Lock()\n\tdefer c.msgLock.Unlock()\n\n\tif c.msgBuffChan == nil {\n\t\tc.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen)\n\t\t// Start a goroutine for writing data back to the client,\n\t\t// which only reads data from MsgBuffChan and hasn't allocated memory or started the coroutine until SendBuffMsg is called\n\t\t// (开启用于写回客户端数据流程的Goroutine\n\t\t// 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程)\n\t\tgo c.StartWriter()\n\t}\n\n\topt := ziface.MsgSendOptionObj{\n\t\tTimeout: 5 * time.Millisecond,\n\t}\n\n\tfor _, o := range opts {\n\t\to(&opt)\n\t}\n\n\tidleTimeout := time.NewTimer(opt.Timeout)\n\tdefer idleTimeout.Stop()\n\n\tif c.isClosed == true {\n\t\treturn errors.New(\"WsConnection closed when send buff msg\")\n\t}\n\n\tif data == nil {\n\t\tzlog.Ins().ErrorF(\"Pack data is nil\")\n\t\treturn errors.New(\"Pack data is nil \")\n\t}\n\n\tselect {\n\tcase <-idleTimeout.C:\n\t\treturn errors.New(\"send buff msg timeout\")\n\tcase c.msgBuffChan <- data:\n\t\treturn nil\n\t}\n}\n\n// SendMsg directly sends the Message data to the remote TCP client.\n// (直接将Message数据发送数据给远程的TCP客户端)\nfunc (c *WsConnection) SendMsg(msgID uint32, data []byte) error {\n\tc.msgLock.Lock()\n\tdefer c.msgLock.Unlock()\n\tif c.isClosed == true {\n\t\treturn errors.New(\"WsConnection closed when send msg\")\n\t}\n\n\t// Package data and send\n\t// (将data封包，并且发送)\n\tmsg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data))\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"Pack error msg ID = %d\", msgID)\n\t\treturn errors.New(\"Pack error msg \")\n\t}\n\n\t// Write back to the client\n\terr = c.conn.WriteMessage(websocket.BinaryMessage, msg)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"SendMsg err msg ID = %d, data = %+v, err = %+v\", msgID, string(msg), err)\n\t\treturn err\n\t}\n\n\treturn nil\n}\n\n// SendBuffMsg sends BuffMsg\nfunc (c *WsConnection) SendBuffMsg(msgID uint32, data []byte, opts ...ziface.MsgSendOption) error {\n\tc.msgLock.Lock()\n\tdefer c.msgLock.Unlock()\n\n\tif c.msgBuffChan == nil {\n\t\tc.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen)\n\t\t// Start the Goroutine for writing back to the client data stream\n\t\t// This method only reads data from MsgBuffChan, allocating memory and starting Goroutine without calling SendBuffMsg\n\t\t// (开启用于写回客户端数据流程的Goroutine\n\t\t// 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程)\n\t\tgo c.StartWriter()\n\t}\n\n\topt := ziface.MsgSendOptionObj{\n\t\tTimeout: 5 * time.Millisecond,\n\t}\n\n\tfor _, o := range opts {\n\t\to(&opt)\n\t}\n\n\tidleTimeout := time.NewTimer(opt.Timeout)\n\tdefer idleTimeout.Stop()\n\n\tif c.isClosed == true {\n\t\treturn errors.New(\"WsConnection closed when send buff msg\")\n\t}\n\n\t// Package data and send\n\t// (将data封包，并且发送)\n\tmsg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data))\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"Pack error msg ID = %d\", msgID)\n\t\treturn errors.New(\"Pack error msg \")\n\t}\n\n\t// Send timeout\n\tselect {\n\tcase <-idleTimeout.C:\n\t\treturn errors.New(\"send buff msg timeout\")\n\tcase c.msgBuffChan <- msg:\n\t\treturn nil\n\t}\n}\n\nfunc (c *WsConnection) SetProperty(key string, value interface{}) {\n\tc.propertyLock.Lock()\n\tdefer c.propertyLock.Unlock()\n\tif c.property == nil {\n\t\tc.property = make(map[string]interface{})\n\t}\n\n\tc.property[key] = value\n}\n\nfunc (c *WsConnection) GetProperty(key string) (interface{}, error) {\n\tc.propertyLock.Lock()\n\tdefer c.propertyLock.Unlock()\n\n\tif value, ok := c.property[key]; ok {\n\t\treturn value, nil\n\t}\n\n\treturn nil, errors.New(\"no property found\")\n}\n\nfunc (c *WsConnection) RemoveProperty(key string) {\n\tc.propertyLock.Lock()\n\tdefer c.propertyLock.Unlock()\n\n\tdelete(c.property, key)\n}\n\n// Context returns the context for the connection, which can be used by user-defined goroutines to get the connection exit status.\n// (返回ctx，用于用户自定义的go程获取连接退出状态)\nfunc (c *WsConnection) Context() context.Context {\n\treturn c.ctx\n}\n\nfunc (c *WsConnection) finalizer() {\n\t// If the user has registered a close callback for the connection, it should be called explicitly at this moment.\n\t// (如果用户注册了该连接的\t关闭回调业务，那么在此刻应该显示调用)\n\tc.callOnConnStop()\n\n\tc.msgLock.Lock()\n\tdefer c.msgLock.Unlock()\n\n\t// If the current connection is already closed.\n\t// (如果当前连接已经关闭)\n\tif c.isClosed == true {\n\t\treturn\n\t}\n\n\t// Stop the heartbeat detector bound to the connection.\n\t// (关闭连接绑定的心跳检测器)\n\tif c.hc != nil {\n\t\tc.hc.Stop()\n\t}\n\n\t// Close the socket connection.\n\t// (关闭socket连接)\n\t_ = c.conn.Close()\n\n\t// Remove the connection from the connection manager.\n\t// (将连接从连接管理器中删除)\n\tif c.connManager != nil {\n\t\tc.connManager.Remove(c)\n\t}\n\n\t// Close all channels associated with this connection.\n\t// (关闭该连接全部管道)\n\tif c.msgBuffChan != nil {\n\t\tclose(c.msgBuffChan)\n\t}\n\n\t// Set the flag to indicate that the connection is closed. (设置标志位)\n\tc.isClosed = true\n\n\tgo func() {\n\t\tdefer func() {\n\t\t\tif err := recover(); err != nil {\n\t\t\t\tzlog.Ins().ErrorF(\"Conn finalizer panic: %v\", err)\n\t\t\t}\n\t\t}()\n\n\t\tc.InvokeCloseCallbacks()\n\t}()\n\n\tzlog.Ins().InfoF(\"Conn Stop()...ConnID = %d\", c.connID)\n}\n\nfunc (c *WsConnection) callOnConnStart() {\n\tif c.onConnStart != nil {\n\t\tzlog.Ins().InfoF(\"ZINX CallOnConnStart....\")\n\t\tc.onConnStart(c)\n\t}\n}\n\nfunc (c *WsConnection) callOnConnStop() {\n\tif c.onConnStop != nil {\n\t\tzlog.Ins().InfoF(\"ZINX CallOnConnStop....\")\n\t\tc.onConnStop(c)\n\t}\n}\n\nfunc (c *WsConnection) IsAlive() bool {\n\tif c.isClosed {\n\t\treturn false\n\t}\n\t// Check the time duration since the last activity of the connection, if it exceeds the maximum heartbeat interval,\n\t// then the connection is considered dead\n\t// (检查连接最后一次活动时间，如果超过心跳间隔，则认为连接已经死亡)\n\treturn time.Now().Sub(c.lastActivityTime) < zconf.GlobalObject.HeartbeatMaxDuration()\n}\n\nfunc (c *WsConnection) updateActivity() {\n\tc.lastActivityTime = time.Now()\n}\n\nfunc (c *WsConnection) SetHeartBeat(checker ziface.IHeartbeatChecker) {\n\tc.hc = checker\n}\n\nfunc (c *WsConnection) LocalAddrString() string {\n\treturn c.localAddr\n}\n\nfunc (c *WsConnection) RemoteAddrString() string {\n\treturn c.remoteAddr\n}\n\nfunc (c *WsConnection) GetName() string {\n\treturn c.name\n}\n\nfunc (c *WsConnection) GetMsgHandler() ziface.IMsgHandle {\n\treturn c.msgHandler\n}\n\nfunc (s *WsConnection) AddCloseCallback(handler, key interface{}, f func()) {\n\tif s.isClosed {\n\t\treturn\n\t}\n\ts.closeCallbackMutex.Lock()\n\tdefer s.closeCallbackMutex.Unlock()\n\ts.closeCallback.Add(handler, key, f)\n}\n\nfunc (s *WsConnection) RemoveCloseCallback(handler, key interface{}) {\n\tif s.isClosed {\n\t\treturn\n\t}\n\ts.closeCallbackMutex.Lock()\n\tdefer s.closeCallbackMutex.Unlock()\n\ts.closeCallback.Remove(handler, key)\n}\n\nfunc (s *WsConnection) InvokeCloseCallbacks() {\n\ts.closeCallbackMutex.RLock()\n\tdefer s.closeCallbackMutex.RUnlock()\n\ts.closeCallback.Invoke()\n}\n"
  },
  {
    "path": "znotify/notify.go",
    "content": "package znotify\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"strconv\"\n\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/zlog\"\n\t\"github.com/aceld/zinx/zutils\"\n)\n\n// ConnIDMap Establish a structure that maps user-defined IDs to connections\n// Map will have concurrent access issues, as well as looping through large amounts of data\n// Use the map structure of shard and lock storage to minimize lock granularity and lock holding time\n// (建立一个用户自定义ID和连接映射的结构\n// map会存在并发问题，大量数据循环读取问题\n// 使用分片加锁的map结构存储，尽量减少锁的粒度和锁的持有时间)\n\ntype notify struct {\n\tconnIdMap zutils.ShardLockMaps\n}\n\nfunc NewZNotify() ziface.Inotify {\n\treturn &notify{\n\t\tconnIdMap: zutils.NewShardLockMaps(),\n\t}\n}\n\nfunc (n *notify) genConnStrId(connID uint64) string {\n\tstrConnId := strconv.FormatUint(connID, 10)\n\treturn strConnId\n}\n\nfunc (n *notify) ConnNums() int {\n\treturn n.connIdMap.Count()\n}\n\nfunc (n *notify) HasIdConn(Id uint64) bool {\n\tstrId := n.genConnStrId(Id)\n\treturn n.connIdMap.Has(strId)\n}\n\nfunc (n *notify) SetNotifyID(Id uint64, conn ziface.IConnection) {\n\tstrId := n.genConnStrId(Id)\n\tn.connIdMap.Set(strId, conn)\n}\n\nfunc (n *notify) GetNotifyByID(Id uint64) (ziface.IConnection, error) {\n\n\tstrId := n.genConnStrId(Id)\n\tConn, ok := n.connIdMap.Get(strId)\n\tif !ok {\n\t\treturn nil, errors.New(\" Not Find UserId\")\n\t}\n\treturn Conn.(ziface.IConnection), nil\n}\n\nfunc (n *notify) DelNotifyByID(Id uint64) {\n\tstrId := n.genConnStrId(Id)\n\tn.connIdMap.Remove(strId)\n}\n\nfunc (n *notify) NotifyToConnByID(Id uint64, MsgId uint32, data []byte) error {\n\tConn, err := n.GetNotifyByID(Id)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = Conn.SendMsg(MsgId, data)\n\tif err != nil {\n\t\tfmt.Printf(\"Notify to %d err:%s \\n\", Id, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (n *notify) NotifyAll(MsgId uint32, data []byte) error {\n\n\tn.connIdMap.IterCb(func(key string, v interface{}) {\n\t\tconn, _ := v.(ziface.IConnection)\n\t\terr := conn.SendMsg(MsgId, data)\n\t\tif err != nil {\n\t\t\tzlog.Ins().ErrorF(\"Notify to %s err:%s \\n\", key, err)\n\t\t}\n\t})\n\n\treturn nil\n}\n\nfunc (n *notify) NotifyBuffToConnByID(Id uint64, MsgId uint32, data []byte) error {\n\tConn, err := n.GetNotifyByID(Id)\n\tif err != nil {\n\t\treturn err\n\t}\n\terr = Conn.SendBuffMsg(MsgId, data)\n\tif err != nil {\n\t\tzlog.Ins().ErrorF(\"Notify to %d err:%s \\n\", Id, err)\n\t\treturn err\n\t}\n\treturn nil\n}\n\nfunc (n *notify) NotifyBuffAll(MsgId uint32, data []byte) error {\n\n\tn.connIdMap.IterCb(func(key string, v interface{}) {\n\t\tconn, _ := v.(ziface.IConnection)\n\t\terr := conn.SendBuffMsg(MsgId, data)\n\t\tif err != nil {\n\t\t\tzlog.Ins().ErrorF(\"Notify to %s err:%s \\n\", key, err)\n\t\t}\n\t})\n\n\treturn nil\n}\n"
  },
  {
    "path": "znotify/notify_test.go",
    "content": "package znotify\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n\t\"github.com/aceld/zinx/znet\"\n\t\"github.com/aceld/zinx/zpack\"\n)\n\nvar nt = NewZNotify()\n\ntype router struct {\n\tznet.BaseRouter\n}\n\nfunc (r *router) Handle(req ziface.IRequest) {\n\tid, _ := strconv.Atoi(string(req.GetData()))\n\tnt.SetNotifyID(uint64(id), req.GetConnection())\n}\n\nfunc Server() {\n\ts := znet.NewUserConfServer(&zconf.Config{\n\t\tHost:             \"127.0.0.1\",\n\t\tTCPPort:          9991,\n\t\tName:             \"NtTest\",\n\t\tVersion:          \"1\",\n\t\tMaxConn:          10000,\n\t\tMaxPacketSize:    4096,\n\t\tWorkerPoolSize:   10,\n\t\tMaxWorkerTaskLen: 10,\n\t\tMaxMsgChanLen:    10,\n\t})\n\n\ts.AddRouter(1, &router{})\n\ts.Serve()\n}\n\nfunc Clinet() {\n\t//conf.ConfigInit()\n\n\tfor i := 0; i < 9000; i++ {\n\t\tgo func(i int) {\n\t\t\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:9991\")\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"net dial err:\", err)\n\t\t\t\treturn\n\t\t\t}\n\t\t\tdefer conn.Close()\n\t\t\t//连接调用write方法写入数据\n\t\t\tid := strconv.Itoa(i)\n\t\t\tdp := zpack.NewDataPack()\n\t\t\tmsg, err := dp.Pack(zpack.NewMsgPackage(1, []byte(id)))\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\t_, err = conn.Write(msg)\n\n\t\t\tif err != nil {\n\t\t\t\treturn\n\t\t\t}\n\t\t\tselect {}\n\t\t}(i)\n\t}\n}\n\nfunc ClientJoin() {\n\tt := time.NewTicker(50 * time.Millisecond)\n\ti := 10000\n\tfor {\n\t\tselect {\n\t\tcase <-t.C:\n\t\t\tgo func(i int) {\n\t\t\t\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:9991\")\n\t\t\t\tif err != nil {\n\t\t\t\t\tfmt.Println(\"net dial err:\", err)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tdefer conn.Close()\n\n\t\t\t\tid := strconv.Itoa(i)\n\t\t\t\tdp := zpack.NewDataPack()\n\t\t\t\tmsg, err := dp.Pack(zpack.NewMsgPackage(1, []byte(id)))\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\t_, err = conn.Write(msg)\n\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tselect {}\n\t\t\t}(i)\n\t\t\ti++\n\t\t}\n\t}\n\n}\n\nfunc TestAA(t *testing.T) {\n\ttime.AfterFunc(5*time.Second, func() {\n\t})\n\ttime.Sleep(6 * time.Second)\n\tnt.ConnNums()\n}\n\nfunc BenchmarkNotify(b *testing.B) {\n\tfmt.Println(\"Begin BenchmarkNotify\")\n\ttime.Sleep(60 * time.Second)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tnt.NotifyAll(1, []byte(\"雪下的是盐\"))\n\t}\n\tnt.ConnNums()\n}\n\nfunc init() {\n\tgo Server()\n\tgo Clinet()\n\tgo ClientJoin()\n}\n"
  },
  {
    "path": "zpack/datapack_ltv_littleendian.go",
    "content": "package zpack\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n)\n\n// DataPackLtv\n// LTV little-endian data packing and unpacking used by Zinx in its early days, compatible with previous applications\n// (Zinx早期使用的LTV 小端方式，兼容之前的应用)\ntype DataPackLtv struct{}\n\n// NewDataPackLtv initializes a packing and unpacking instance\n// (封包拆包实例初始化方法)\nfunc NewDataPackLtv() ziface.IDataPack {\n\treturn &DataPackLtv{}\n}\n\n// GetHeadLen returns the length of the message header\n// (获取包头长度方法)\nfunc (dp *DataPackLtv) GetHeadLen() uint32 {\n\t//ID uint32(4 bytes) +  DataLen uint32(4 bytes)\n\treturn defaultHeaderLen\n}\n\n// Pack packs the message (compresses the data)\n// (封包方法,压缩数据)\nfunc (dp *DataPackLtv) Pack(msg ziface.IMessage) ([]byte, error) {\n\t// Create a buffer to store the bytes\n\t// (创建一个存放bytes字节的缓冲)\n\tdataBuff := bytes.NewBuffer([]byte{})\n\n\t// Write the data length\n\tif err := binary.Write(dataBuff, binary.LittleEndian, msg.GetDataLen()); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Write the message ID\n\tif err := binary.Write(dataBuff, binary.LittleEndian, msg.GetMsgID()); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Write the data\n\tif err := binary.Write(dataBuff, binary.LittleEndian, msg.GetData()); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataBuff.Bytes(), nil\n}\n\n// Unpack unpacks the message (decompresses the data)\n// (拆包方法,解压数据)\nfunc (dp *DataPackLtv) Unpack(binaryData []byte) (ziface.IMessage, error) {\n\t// Create an ioReader for the input binary data\n\tdataBuff := bytes.NewReader(binaryData)\n\n\t// Only unpack the header information to obtain the data length and message ID\n\t// (只解压head的信息，得到dataLen和msgID)\n\tmsg := &Message{}\n\n\t// Read the data length\n\tif err := binary.Read(dataBuff, binary.LittleEndian, &msg.DataLen); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Read the message ID\n\tif err := binary.Read(dataBuff, binary.LittleEndian, &msg.ID); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Check whether the data length exceeds the maximum allowed packet size\n\t// (判断dataLen的长度是否超出我们允许的最大包长度)\n\tif zconf.GlobalObject.MaxPacketSize > 0 && msg.GetDataLen() > zconf.GlobalObject.MaxPacketSize {\n\t\treturn nil, errors.New(\"too large msg data received\")\n\t}\n\n\t// Only the header data needs to be unpacked, and then another data read is performed from the connection based on the header length\n\t// (这里只需要把head的数据拆包出来就可以了，然后再通过head的长度，再从conn读取一次数据)\n\treturn msg, nil\n}\n"
  },
  {
    "path": "zpack/datapack_tlv_bigendian.go",
    "content": "package zpack\n\nimport (\n\t\"bytes\"\n\t\"encoding/binary\"\n\t\"errors\"\n\n\t\"github.com/aceld/zinx/zconf\"\n\t\"github.com/aceld/zinx/ziface\"\n)\n\nvar defaultHeaderLen uint32 = 8\n\ntype DataPack struct{}\n\n// NewDataPack initializes a packing and unpacking instance\n// (封包拆包实例初始化方法)\nfunc NewDataPack() ziface.IDataPack {\n\treturn &DataPack{}\n}\n\n// GetHeadLen returns the length of the message header\n// (获取包头长度方法)\nfunc (dp *DataPack) GetHeadLen() uint32 {\n\t//ID uint32(4 bytes) +  DataLen uint32(4 bytes)\n\treturn defaultHeaderLen\n}\n\n// Pack packs the message (compresses the data)\n// (封包方法,压缩数据)\nfunc (dp *DataPack) Pack(msg ziface.IMessage) ([]byte, error) {\n\t// Create a buffer to store the bytes\n\t// (创建一个存放bytes字节的缓冲)\n\tdataBuff := bytes.NewBuffer([]byte{})\n\n\t// Write the message ID\n\tif err := binary.Write(dataBuff, binary.BigEndian, msg.GetMsgID()); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Write the data length\n\tif err := binary.Write(dataBuff, binary.BigEndian, msg.GetDataLen()); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Write the data\n\tif err := binary.Write(dataBuff, binary.BigEndian, msg.GetData()); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn dataBuff.Bytes(), nil\n}\n\n// Unpack unpacks the message (decompresses the data)\n// (拆包方法,解压数据)\nfunc (dp *DataPack) Unpack(binaryData []byte) (ziface.IMessage, error) {\n\t// Create an ioReader for the input binary data\n\tdataBuff := bytes.NewReader(binaryData)\n\n\t// Only unpack the header information to obtain the data length and message ID\n\t// (只解压head的信息，得到dataLen和msgID)\n\tmsg := &Message{}\n\n\t// Read the data length\n\tif err := binary.Read(dataBuff, binary.BigEndian, &msg.ID); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Read the message ID\n\tif err := binary.Read(dataBuff, binary.BigEndian, &msg.DataLen); err != nil {\n\t\treturn nil, err\n\t}\n\n\t// Check whether the data length exceeds the maximum allowed packet size\n\t// (判断dataLen的长度是否超出我们允许的最大包长度)\n\tif zconf.GlobalObject.MaxPacketSize > 0 && msg.GetDataLen() > zconf.GlobalObject.MaxPacketSize {\n\t\treturn nil, errors.New(\"too large msg data received\")\n\t}\n\n\t// Only the header data needs to be unpacked, and then another data read is performed from the connection based on the header length\n\t// (这里只需要把head的数据拆包出来就可以了，然后再通过head的长度，再从conn读取一次数据)\n\treturn msg, nil\n}\n"
  },
  {
    "path": "zpack/datapack_tlv_bigendian_test.go",
    "content": "package zpack\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"net\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/ziface\"\n)\n\n// run in terminal:\n// go test -v ./znet -run=TestDataPack\n\n// This function is responsible for testing the functionality of data packet splitting and packaging.\nfunc TestDataPack(t *testing.T) {\n\t// Create a TCP server socket.\n\tlistener, err := net.Listen(\"tcp\", \"127.0.0.1:7777\")\n\tif err != nil {\n\t\tfmt.Println(\"server listen err:\", err)\n\t\treturn\n\t}\n\n\t// Create a server goroutine, responsible for reading and parsing the data from the client goroutine that may contain sticky packets.\n\tgo func() {\n\t\tfor {\n\t\t\tconn, err := listener.Accept()\n\t\t\tif err != nil {\n\t\t\t\tfmt.Println(\"server accept err:\", err)\n\t\t\t}\n\n\t\t\t// Handle client requests\n\t\t\tgo func(conn net.Conn) {\n\t\t\t\t// Create a packet splitting and packaging object dp.\n\t\t\t\tdp := Factory().NewPack(ziface.ZinxDataPack)\n\t\t\t\tfor {\n\t\t\t\t\t// 1. Read the head part of the stream first.\n\t\t\t\t\theadData := make([]byte, dp.GetHeadLen())\n\t\t\t\t\t_, err := io.ReadFull(conn, headData) // ReadFull will fill msg until it's full\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"read head error\")\n\t\t\t\t\t}\n\t\t\t\t\t// Unpack the headData byte stream into msg.\n\t\t\t\t\tmsgHead, err := dp.Unpack(headData)\n\t\t\t\t\tif err != nil {\n\t\t\t\t\t\tfmt.Println(\"server unpack err:\", err)\n\t\t\t\t\t\treturn\n\t\t\t\t\t}\n\n\t\t\t\t\tif msgHead.GetDataLen() > 0 {\n\t\t\t\t\t\t// msg has data, read data again.\n\t\t\t\t\t\tmsg := msgHead.(*Message)\n\t\t\t\t\t\tmsg.Data = make([]byte, msg.GetDataLen())\n\n\t\t\t\t\t\t// Read the byte stream from io based on dataLen.\n\t\t\t\t\t\t_, err := io.ReadFull(conn, msg.Data)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\tfmt.Println(\"server unpack data err:\", err)\n\t\t\t\t\t\t\treturn\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tfmt.Println(\"==> Recv Msg: ID=\", msg.ID, \", len=\", msg.DataLen, \", data=\", string(msg.Data))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}(conn)\n\t\t}\n\t}()\n\n\t// Client goroutine, responsible for simulating data containing sticky packets and sending it to the server.\n\tgo func() {\n\t\tconn, err := net.Dial(\"tcp\", \"127.0.0.1:7777\")\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client dial err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\t// Create a packet splitting and packaging object dp.\n\t\tdp := Factory().NewPack(ziface.ZinxDataPack)\n\n\t\t// Package msg1.\n\t\tmsg1 := &Message{\n\t\t\tID:      0,\n\t\t\tDataLen: 5,\n\t\t\tData:    []byte{'h', 'e', 'l', 'l', 'o'},\n\t\t}\n\n\t\tsendData1, err := dp.Pack(msg1)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client pack msg1 err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\t// Package msg2.\n\t\tmsg2 := &Message{\n\t\t\tID:      1,\n\t\t\tDataLen: 7,\n\t\t\tData:    []byte{'w', 'o', 'r', 'l', 'd', '!', '!'},\n\t\t}\n\t\tsendData2, err := dp.Pack(msg2)\n\t\tif err != nil {\n\t\t\tfmt.Println(\"client temp msg2 err:\", err)\n\t\t\treturn\n\t\t}\n\n\t\t// Concatenate sendData1 and sendData2 to create a sticky packet.\n\t\tsendData1 = append(sendData1, sendData2...)\n\n\t\t// Write data to the server.\n\t\tconn.Write(sendData1)\n\t}()\n\n\t// Block the client.\n\tselect {\n\tcase <-time.After(time.Second):\n\t\treturn\n\t}\n}\n"
  },
  {
    "path": "zpack/message.go",
    "content": "package zpack\n\n// Message structure for messages\ntype Message struct {\n\tDataLen uint32 // Length of the message\n\tID      uint32 // ID of the message\n\tData    []byte // Content of the message\n\trawData []byte // Raw data of the message\n}\n\nfunc NewMsgPackage(ID uint32, data []byte) *Message {\n\treturn &Message{\n\t\tID:      ID,\n\t\tDataLen: uint32(len(data)),\n\t\tData:    data,\n\t\trawData: data,\n\t}\n}\n\nfunc NewMessage(len uint32, data []byte) *Message {\n\treturn &Message{\n\t\tDataLen: len,\n\t\tData:    data,\n\t\trawData: data,\n\t}\n}\n\nfunc NewMessageByMsgId(id uint32, len uint32, data []byte) *Message {\n\treturn &Message{\n\t\tID:      id,\n\t\tDataLen: len,\n\t\tData:    data,\n\t\trawData: data,\n\t}\n}\n\nfunc (msg *Message) Init(ID uint32, data []byte) {\n\tmsg.ID = ID\n\tmsg.Data = data\n\tmsg.rawData = data\n\tmsg.DataLen = uint32(len(data))\n}\n\nfunc (msg *Message) GetDataLen() uint32 {\n\treturn msg.DataLen\n}\n\nfunc (msg *Message) GetMsgID() uint32 {\n\treturn msg.ID\n}\n\nfunc (msg *Message) GetData() []byte {\n\treturn msg.Data\n}\n\nfunc (msg *Message) GetRawData() []byte {\n\treturn msg.rawData\n}\n\nfunc (msg *Message) SetDataLen(len uint32) {\n\tmsg.DataLen = len\n}\n\nfunc (msg *Message) SetMsgID(msgID uint32) {\n\tmsg.ID = msgID\n}\n\nfunc (msg *Message) SetData(data []byte) {\n\tmsg.Data = data\n}\n"
  },
  {
    "path": "zpack/packfactory.go",
    "content": "package zpack\n\nimport (\n\t\"sync\"\n\n\t\"github.com/aceld/zinx/ziface\"\n)\n\nvar pack_once sync.Once\n\ntype pack_factory struct{}\n\nvar factoryInstance *pack_factory\n\n/*\nFactory\tGenerates different packaging and unpackaging methods, singleton\n\n\t(生成不同封包解包的方式，单例)\n*/\nfunc Factory() *pack_factory {\n\tpack_once.Do(func() {\n\t\tfactoryInstance = new(pack_factory)\n\t})\n\n\treturn factoryInstance\n}\n\n// NewPack creates a concrete packaging and unpackaging object\n// (NewPack 创建一个具体的拆包解包对象)\nfunc (f *pack_factory) NewPack(kind string) ziface.IDataPack {\n\tvar dataPack ziface.IDataPack\n\n\tswitch kind {\n\t// Zinx standard default packaging and unpackaging method\n\t// (Zinx 标准默认封包拆包方式)\n\tcase ziface.ZinxDataPack:\n\t\tdataPack = NewDataPack()\n\tcase ziface.ZinxDataPackOld:\n\t\tdataPack = NewDataPackLtv()\n\t\t// case for custom packaging and unpackaging methods\n\t\t// (case 自定义封包拆包方式case)\n\tdefault:\n\t\tdataPack = NewDataPack()\n\t}\n\n\treturn dataPack\n}\n"
  },
  {
    "path": "ztimer/delayfunc.go",
    "content": "package ztimer\n\n/**\n* @Author: Aceld\n* @Date: 2019/4/30 11:57\n* @Mail: danbing.at@gmail.com\n */\nimport (\n\t\"fmt\"\n\t\"reflect\"\n\n\t\"github.com/aceld/zinx/zlog\"\n)\n\n/*\n   定义一个延迟调用函数\n\t延迟调用函数就是 时间定时器超时的时候，触发的事先注册好的\n\t回调函数\n*/\n\n// DelayFunc 延迟调用函数对象\ntype DelayFunc struct {\n\tf    func(...interface{}) //f : 延迟函数调用原型\n\targs []interface{}        //args: 延迟调用函数传递的形参\n}\n\n// NewDelayFunc 创建一个延迟调用函数\nfunc NewDelayFunc(f func(v ...interface{}), args []interface{}) *DelayFunc {\n\treturn &DelayFunc{\n\t\tf:    f,\n\t\targs: args,\n\t}\n}\n\n// String 打印当前延迟函数的信息，用于日志记录\nfunc (df *DelayFunc) String() string {\n\treturn fmt.Sprintf(\"{DelayFun:%s, args:%v}\", reflect.TypeOf(df.f).Name(), df.args)\n}\n\n// Call 执行延迟函数---如果执行失败，抛出异常\nfunc (df *DelayFunc) Call() {\n\tdefer func() {\n\t\tif err := recover(); err != nil {\n\t\t\tzlog.Ins().ErrorF(\"%s Call err: %v\", df.String(), err)\n\t\t}\n\t}()\n\n\t//调用定时器超时函数\n\tdf.f(df.args...)\n}\n"
  },
  {
    "path": "ztimer/delayfunc_test.go",
    "content": "/**\n* @Author: Aceld\n* @Date: 2019/4/30 15:17\n* @Mail: danbing.at@gmail.com\n*\n*  针对 delayFunc.go 做单元测试，主要测试延迟函数结构体是否正常使用\n */\npackage ztimer\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc SayHello(message ...interface{}) {\n\tfmt.Println(message[0].(string), \" \", message[1].(string))\n}\n\nfunc TestDelayfunc(t *testing.T) {\n\tdf := NewDelayFunc(SayHello, []interface{}{\"hello\", \"zinx!\"})\n\tfmt.Println(\"df.String() = \", df.String())\n\tdf.Call()\n}\n"
  },
  {
    "path": "ztimer/timer.go",
    "content": "package ztimer\n\n/**\n* @Author: Aceld\n* @Date: 2019/4/30 17:42\n* @Mail: danbing.at@gmail.com\n */\n\nimport (\n\t\"time\"\n)\n\nconst (\n\t//HourName 小时\n\tHourName = \"HOUR\"\n\t//HourInterval 小时间隔ms为精度\n\tHourInterval = 60 * 60 * 1e3\n\t//HourScales  12小时制\n\tHourScales = 12\n\n\t//MinuteName 分钟\n\tMinuteName = \"MINUTE\"\n\t//MinuteInterval 每分钟时间间隔\n\tMinuteInterval = 60 * 1e3\n\t//MinuteScales 60分钟\n\tMinuteScales = 60\n\n\t//SecondName  秒\n\tSecondName = \"SECOND\"\n\t//SecondInterval 秒的间隔\n\tSecondInterval = 1e3\n\t//SecondScales  60秒\n\tSecondScales = 60\n\t//TimersMaxCap //每个时间轮刻度挂载定时器的最大个数\n\tTimersMaxCap = 2048\n)\n\n/*\n   注意：\n    有关时间的几个换算\n   \ttime.Second(秒) = time.Millisecond * 1e3\n\ttime.Millisecond(毫秒) = time.Microsecond * 1e3\n\ttime.Microsecond(微秒) = time.Nanosecond * 1e3\n\n\ttime.Now().UnixNano() ==> time.Nanosecond (纳秒)\n*/\n\n// Timer 定时器实现\ntype Timer struct {\n\t//延迟调用函数\n\tdelayFunc *DelayFunc\n\t//调用时间(unix 时间， 单位ms)\n\tunixts int64\n}\n\n// UnixMilli 返回1970-1-1至今经历的毫秒数\nfunc UnixMilli() int64 {\n\treturn time.Now().UnixNano() / 1e6\n}\n\n// NewTimerAt   创建一个定时器,在指定的时间触发 定时器方法 df: DelayFunc类型的延迟调用函数类型；unixNano: unix计算机从1970-1-1至今经历的纳秒数\nfunc NewTimerAt(df *DelayFunc, unixNano int64) *Timer {\n\treturn &Timer{\n\t\tdelayFunc: df,\n\t\tunixts:    unixNano / 1e6, //将纳秒转换成对应的毫秒 ms ，定时器以ms为最小精度\n\t}\n}\n\n// NewTimerAfter 创建一个定时器，在当前时间延迟duration之后触发 定时器方法\nfunc NewTimerAfter(df *DelayFunc, duration time.Duration) *Timer {\n\treturn NewTimerAt(df, time.Now().UnixNano()+int64(duration))\n}\n\n// Run 启动定时器，用一个go承载\nfunc (t *Timer) Run() {\n\tgo func() {\n\t\tnow := UnixMilli()\n\t\t//设置的定时器是否在当前时间之后\n\t\tif t.unixts > now {\n\t\t\t//睡眠，直至时间超时,已微秒为单位进行睡眠\n\t\t\ttime.Sleep(time.Duration(t.unixts-now) * time.Millisecond)\n\t\t}\n\n\t\t//调用事先注册好的超时延迟方法\n\t\tt.delayFunc.Call()\n\t}()\n}\n"
  },
  {
    "path": "ztimer/timer_test.go",
    "content": "/**\n* @Author: Aceld\n* @Date: 2019/5/5 10:14\n* @Mail: danbing.at@gmail.com\n*\n* 针对timer.go做单元测试，主要测试定时器相关接口 依赖模块delayFunc.go\n */\npackage ztimer\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n)\n\n// 定义一个超时函数\nfunc myFunc(v ...interface{}) {\n\tfmt.Printf(\"No.%d function calld. delay %d second(s)\\n\", v[0].(int), v[1].(int))\n}\n\nfunc TestTimer(t *testing.T) {\n\n\tfor i := 0; i < 5; i++ {\n\t\tgo func(i int) {\n\t\t\tNewTimerAfter(NewDelayFunc(myFunc, []interface{}{i, 2 * i}), time.Duration(2*i)*time.Second).Run()\n\t\t}(i)\n\t}\n\n\t//主进程等待其他go，由于Run()方法是用一个新的go承载延迟方法，这里不能用waitGroup\n\ttime.Sleep(1 * time.Minute)\n}\n"
  },
  {
    "path": "ztimer/timerscheduler.go",
    "content": "package ztimer\n\n/**\n* @Author: Aceld\n* @Date: 2019/5/8 17:43\n* @Mail: danbing.at@gmail.com\n*\n*  时间轮调度器\n*   依赖模块，delayfunc.go  timer.go timewheel.go\n */\n\nimport (\n\t\"math\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zlog\"\n)\n\nconst (\n\t//MaxChanBuff 默认缓冲触发函数队列大小\n\tMaxChanBuff = 2048\n\t//MaxTimeDelay 默认最大误差时间\n\tMaxTimeDelay = 100\n)\n\n// TimerScheduler 计时器调度器\ntype TimerScheduler struct {\n\t//当前调度器的最高级时间轮\n\ttw *TimeWheel\n\t//定时器编号累加器\n\tIDGen uint32\n\t//已经触发定时器的channel\n\ttriggerChan chan *DelayFunc\n\t//互斥锁\n\tsync.RWMutex\n}\n\n// NewTimerScheduler 返回一个定时器调度器 ，主要创建分层定时器，并做关联，并依次启动\nfunc NewTimerScheduler() *TimerScheduler {\n\n\t//创建秒级时间轮\n\tsecondTw := NewTimeWheel(SecondName, SecondInterval, SecondScales, TimersMaxCap)\n\t//创建分钟级时间轮\n\tminuteTw := NewTimeWheel(MinuteName, MinuteInterval, MinuteScales, TimersMaxCap)\n\t//创建小时级时间轮\n\thourTw := NewTimeWheel(HourName, HourInterval, HourScales, TimersMaxCap)\n\n\t//将分层时间轮做关联\n\thourTw.AddTimeWheel(minuteTw)\n\tminuteTw.AddTimeWheel(secondTw)\n\n\t//时间轮运行\n\tsecondTw.Run()\n\tminuteTw.Run()\n\thourTw.Run()\n\n\treturn &TimerScheduler{\n\t\ttw:          hourTw,\n\t\ttriggerChan: make(chan *DelayFunc, MaxChanBuff),\n\t}\n}\n\n// CreateTimerAt 创建一个定点Timer 并将Timer添加到分层时间轮中， 返回Timer的tID\nfunc (ts *TimerScheduler) CreateTimerAt(df *DelayFunc, unixNano int64) (uint32, error) {\n\tts.Lock()\n\tdefer ts.Unlock()\n\n\tts.IDGen++\n\treturn ts.IDGen, ts.tw.AddTimer(ts.IDGen, NewTimerAt(df, unixNano))\n}\n\n// CreateTimerAfter 创建一个延迟Timer 并将Timer添加到分层时间轮中， 返回Timer的tID\nfunc (ts *TimerScheduler) CreateTimerAfter(df *DelayFunc, duration time.Duration) (uint32, error) {\n\tts.Lock()\n\tdefer ts.Unlock()\n\n\tts.IDGen++\n\treturn ts.IDGen, ts.tw.AddTimer(ts.IDGen, NewTimerAfter(df, duration))\n}\n\n// CancelTimer 删除timer\nfunc (ts *TimerScheduler) CancelTimer(tID uint32) {\n\tts.Lock()\n\tdefer ts.Unlock()\n\n\ttw := ts.tw\n\tfor tw != nil {\n\t\ttw.RemoveTimer(tID)\n\t\ttw = tw.nextTimeWheel\n\t}\n}\n\n// GetTriggerChan 获取计时结束的延迟执行函数通道\nfunc (ts *TimerScheduler) GetTriggerChan() chan *DelayFunc {\n\treturn ts.triggerChan\n}\n\n// Start 非阻塞的方式启动timerSchedule\nfunc (ts *TimerScheduler) Start() {\n\tgo func() {\n\t\tfor {\n\t\t\t//当前时间\n\t\t\tnow := UnixMilli()\n\t\t\t//获取最近MaxTimeDelay 毫秒的超时定时器集合\n\t\t\ttimerList := ts.tw.GetTimerWithIn(MaxTimeDelay * time.Millisecond)\n\t\t\tfor _, timer := range timerList {\n\t\t\t\tif math.Abs(float64(now-timer.unixts)) > MaxTimeDelay {\n\t\t\t\t\t//已经超时的定时器，报警\n\t\t\t\t\tzlog.Error(\"want call at \", timer.unixts, \"; real call at\", now, \"; delay \", now-timer.unixts)\n\t\t\t\t}\n\t\t\t\tts.triggerChan <- timer.delayFunc\n\t\t\t}\n\t\t\ttime.Sleep(MaxTimeDelay / 2 * time.Millisecond)\n\t\t}\n\t}()\n}\n\n// NewAutoExecTimerScheduler 时间轮定时器 自动调度\nfunc NewAutoExecTimerScheduler() *TimerScheduler {\n\t//创建一个调度器\n\tautoExecScheduler := NewTimerScheduler()\n\t//启动调度器\n\tautoExecScheduler.Start()\n\n\t//永久从调度器中获取超时 触发的函数 并执行\n\tgo func() {\n\t\tdelayFuncChan := autoExecScheduler.GetTriggerChan()\n\t\tfor df := range delayFuncChan {\n\t\t\tgo df.Call()\n\t\t}\n\t}()\n\n\treturn autoExecScheduler\n}\n"
  },
  {
    "path": "ztimer/timerscheduler_test.go",
    "content": "package ztimer\n\n/**\n* @Author: Aceld(刘丹冰)\n* @Date: 2019/5/9 10:14\n* @Mail: danbing.at@gmail.com\n*\n*  时间轮定时器调度器单元测试\n */\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"testing\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zlog\"\n)\n\n// 触发函数\nfunc foo(args ...interface{}) {\n\tfmt.Printf(\"I am No. %d function, delay %d ms\\n\", args[0].(int), args[1].(int))\n}\n\n// 手动创建调度器运转时间轮\nfunc TestNewTimerScheduler(t *testing.T) {\n\ttimerScheduler := NewTimerScheduler()\n\ttimerScheduler.Start()\n\n\t//在scheduler中添加timer\n\tfor i := 1; i < 2000; i++ {\n\t\tf := NewDelayFunc(foo, []interface{}{i, i * 3})\n\t\ttID, err := timerScheduler.CreateTimerAfter(f, time.Duration(3*i)*time.Millisecond)\n\t\tif err != nil {\n\t\t\tzlog.Error(\"create timer error\", tID, err)\n\t\t\tbreak\n\t\t}\n\t}\n\n\t//执行调度器触发函数\n\tgo func() {\n\t\tdelayFuncChan := timerScheduler.GetTriggerChan()\n\t\tfor df := range delayFuncChan {\n\t\t\tdf.Call()\n\t\t}\n\t}()\n\n\t//阻塞等待\n\tselect {}\n}\n\n// 采用自动调度器运转时间轮\nfunc TestNewAutoExecTimerScheduler(t *testing.T) {\n\tautoTS := NewAutoExecTimerScheduler()\n\n\t//给调度器添加Timer\n\tfor i := 0; i < 2000; i++ {\n\t\tf := NewDelayFunc(foo, []interface{}{i, i * 3})\n\t\ttID, err := autoTS.CreateTimerAfter(f, time.Duration(3*i)*time.Millisecond)\n\t\tif err != nil {\n\t\t\tzlog.Error(\"create timer error\", tID, err)\n\t\t\tbreak\n\t\t}\n\t}\n\n\t//阻塞等待\n\tselect {}\n}\n\n// 测试取消一个定时器\nfunc TestCancelTimerScheduler(t *testing.T) {\n\tScheduler := NewAutoExecTimerScheduler()\n\tf1 := NewDelayFunc(foo, []interface{}{3, 3})\n\tf2 := NewDelayFunc(foo, []interface{}{5, 5})\n\ttimerID1, err := Scheduler.CreateTimerAfter(f1, time.Duration(3)*time.Second)\n\tif nil != err {\n\t\tt.Log(\"Scheduler.CreateTimerAfter(f1, time.Duration(3)*time.Second)\", \"err：\", err)\n\t}\n\ttimerID2, err := Scheduler.CreateTimerAfter(f2, time.Duration(5)*time.Second)\n\tif nil != err {\n\t\tt.Log(\"Scheduler.CreateTimerAfter(f1, time.Duration(3)*time.Second)\", \"err：\", err)\n\t}\n\tlog.Printf(\"timerID1=%d ,timerID2=%d\\n\", timerID1, timerID2)\n\tScheduler.CancelTimer(timerID1) //删除timerID1\n\n\t//阻塞等待\n\tselect {}\n}\n"
  },
  {
    "path": "ztimer/timewheel.go",
    "content": "package ztimer\n\n/**\n* @Author: Aceld\n* @Date: 2019/4/30 11:57\n* @Mail: danbing.at@gmail.com\n */\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/aceld/zinx/zlog\"\n)\n\n/*\n  tips:\n\t一个网络服务程序时需要管理大量客户端连接的，\n\t其中每个客户端连接都需要管理它的 timeout 时间。\n\t通常连接的超时管理一般设置为30~60秒不等，并不需要太精确的时间控制。\n\t另外由于服务端管理着多达数万到数十万不等的连接数，\n\t因此我们没法为每个连接使用一个Timer，那样太消耗资源不现实。\n\n\t用时间轮的方式来管理和维护大量的timer调度，会解决上面的问题。\n*/\n\n// TimeWheel 时间轮\ntype TimeWheel struct {\n\t//TimeWheel的名称\n\tname string\n\t//刻度的时间间隔，单位ms\n\tinterval int64\n\t//每个时间轮上的刻度数\n\tscales int\n\t//当前时间指针的指向\n\tcurIndex int\n\t//每个刻度所存放的timer定时器的最大容量\n\tmaxCap int\n\t//当前时间轮上的所有timer\n\ttimerQueue map[int]map[uint32]*Timer //map[int] VALUE  其中int表示当前时间轮的刻度,\n\t// map[int] map[uint32] *Timer, uint32表示Timer的ID号\n\t//下一层时间轮\n\tnextTimeWheel *TimeWheel\n\t//互斥锁（继承RWMutex的 RWLock,UnLock 等方法）\n\tsync.RWMutex\n}\n\n// NewTimeWheel  创建一个时间轮\nfunc NewTimeWheel(name string, interval int64, scales int, maxCap int) *TimeWheel {\n\t// name：时间轮的名称\n\t// interval：每个刻度之间的duration时间间隔\n\t// scales:当前时间轮的轮盘一共多少个刻度(如我们正常的时钟就是12个刻度)\n\t// maxCap: 每个刻度所最大保存的Timer定时器个数\n\n\ttw := &TimeWheel{\n\t\tname:       name,\n\t\tinterval:   interval,\n\t\tscales:     scales,\n\t\tmaxCap:     maxCap,\n\t\ttimerQueue: make(map[int]map[uint32]*Timer, scales),\n\t}\n\t//初始化map\n\tfor i := 0; i < scales; i++ {\n\t\ttw.timerQueue[i] = make(map[uint32]*Timer, maxCap)\n\t}\n\n\tzlog.Ins().InfoF(\"Init timerWhell name = %s is Done!\", tw.name)\n\treturn tw\n}\n\n/*\n将一个timer定时器加入到分层时间轮中\ntID: 每个定时器timer的唯一标识\nt: 当前被加入时间轮的定时器\nforceNext: 是否强制的将定时器添加到下一层时间轮\n\n我们采用的算法是：\n如果当前timer的超时时间间隔 大于一个刻度，那么进行hash计算 找到对应的刻度上添加\n如果当前的timer的超时时间间隔 小于一个刻度 :\n\n\t如果没有下一轮时间轮\n*/\nfunc (tw *TimeWheel) addTimer(tID uint32, t *Timer, forceNext bool) error {\n\tdefer func() error {\n\t\tif err := recover(); err != nil {\n\t\t\terrstr := fmt.Sprintf(\"addTimer function err : %s\", err)\n\t\t\tzlog.Ins().ErrorF(\"addTimer function err : %s\", err)\n\t\t\treturn errors.New(errstr)\n\t\t}\n\t\treturn nil\n\t}()\n\n\t//得到当前的超时时间间隔(ms)毫秒为单位\n\tdelayInterval := t.unixts - UnixMilli()\n\n\t//如果当前的超时时间 大于一个刻度的时间间隔\n\tif delayInterval >= tw.interval {\n\t\t//得到需要跨越几个刻度\n\t\tdn := delayInterval / tw.interval\n\t\t//在对应的刻度上的定时器Timer集合map加入当前定时器(由于是环形，所以要求余)\n\t\ttw.timerQueue[(tw.curIndex+int(dn))%tw.scales][tID] = t\n\n\t\treturn nil\n\t}\n\n\t//如果当前的超时时间,小于一个刻度的时间间隔，并且当前时间轮没有下一层，经度最小的时间轮\n\tif delayInterval < tw.interval && tw.nextTimeWheel == nil {\n\t\tif forceNext == true {\n\t\t\t//如果设置为强制移至下一个刻度，那么将定时器移至下一个刻度\n\t\t\t//这种情况，主要是时间轮自动轮转的情况\n\t\t\t//因为这是底层时间轮，该定时器在转动的时候，如果没有被调度者取走的话，该定时器将不会再被发现\n\t\t\t//因为时间轮刻度已经过去，如果不强制把该定时器Timer移至下时刻，就永远不会被取走并触发调用\n\t\t\t//所以这里强制将timer移至下个刻度的集合中，等待调用者在下次轮转之前取走该定时器\n\t\t\ttw.timerQueue[(tw.curIndex+1)%tw.scales][tID] = t\n\t\t} else {\n\t\t\t//如果手动添加定时器，那么直接将timer添加到对应底层时间轮的当前刻度集合中\n\t\t\ttw.timerQueue[tw.curIndex][tID] = t\n\t\t}\n\t\treturn nil\n\t}\n\n\t//如果当前的超时时间，小于一个刻度的时间间隔，并且有下一层时间轮\n\tif delayInterval < tw.interval {\n\t\treturn tw.nextTimeWheel.AddTimer(tID, t)\n\t}\n\n\treturn nil\n}\n\n// AddTimer 添加一个timer到一个时间轮中(非时间轮自转情况)\nfunc (tw *TimeWheel) AddTimer(tID uint32, t *Timer) error {\n\ttw.Lock()\n\tdefer tw.Unlock()\n\n\treturn tw.addTimer(tID, t, false)\n}\n\n// RemoveTimer 删除一个定时器，根据定时器的ID\nfunc (tw *TimeWheel) RemoveTimer(tID uint32) {\n\ttw.Lock()\n\tdefer tw.Unlock()\n\n\tfor i := 0; i < tw.scales; i++ {\n\t\tif _, ok := tw.timerQueue[i][tID]; ok {\n\t\t\tdelete(tw.timerQueue[i], tID)\n\t\t}\n\t}\n}\n\n// AddTimeWheel 给一个时间轮添加下层时间轮 比如给小时时间轮添加分钟时间轮，给分钟时间轮添加秒时间轮\nfunc (tw *TimeWheel) AddTimeWheel(next *TimeWheel) {\n\ttw.nextTimeWheel = next\n\tzlog.Ins().InfoF(\"Add timerWhell[%s]'s next [%s] is succ!\", tw.name, next.name)\n}\n\n/*\n启动时间轮\n*/\nfunc (tw *TimeWheel) run() {\n\tfor {\n\t\t//时间轮每间隔interval一刻度时间，触发转动一次\n\t\ttime.Sleep(time.Duration(tw.interval) * time.Millisecond)\n\n\t\ttw.Lock()\n\t\t//取出挂载在当前刻度的全部定时器\n\t\tcurTimers := tw.timerQueue[tw.curIndex]\n\t\t//当前定时器要重新添加 所给当前刻度再重新开辟一个map Timer容器\n\t\ttw.timerQueue[tw.curIndex] = make(map[uint32]*Timer, tw.maxCap)\n\t\tfor tID, timer := range curTimers {\n\t\t\t//这里属于时间轮自动转动，forceNext设置为true\n\t\t\ttw.addTimer(tID, timer, true)\n\t\t}\n\n\t\t//取出下一个刻度 挂载的全部定时器 进行重新添加 (为了安全起见,待考慮)\n\t\tnextTimers := tw.timerQueue[(tw.curIndex+1)%tw.scales]\n\t\ttw.timerQueue[(tw.curIndex+1)%tw.scales] = make(map[uint32]*Timer, tw.maxCap)\n\t\tfor tID, timer := range nextTimers {\n\t\t\ttw.addTimer(tID, timer, true)\n\t\t}\n\n\t\t//当前刻度指针 走一格\n\t\ttw.curIndex = (tw.curIndex + 1) % tw.scales\n\n\t\ttw.Unlock()\n\t}\n}\n\n// Run 非阻塞的方式让时间轮转起来\nfunc (tw *TimeWheel) Run() {\n\tgo tw.run()\n\tzlog.Ins().InfoF(\"timerwheel name = %s is running...\", tw.name)\n}\n\n// GetTimerWithIn 获取定时器在一段时间间隔内的Timer\nfunc (tw *TimeWheel) GetTimerWithIn(duration time.Duration) map[uint32]*Timer {\n\t//最终触发定时器的一定是挂载最底层时间轮上的定时器\n\t//1 找到最底层时间轮\n\tleaftw := tw\n\tfor leaftw.nextTimeWheel != nil {\n\t\tleaftw = leaftw.nextTimeWheel\n\t}\n\n\tleaftw.Lock()\n\tdefer leaftw.Unlock()\n\t//返回的Timer集合\n\ttimerList := make(map[uint32]*Timer)\n\n\tnow := UnixMilli()\n\n\t//取出当前时间轮刻度内全部Timer\n\tfor tID, timer := range leaftw.timerQueue[leaftw.curIndex] {\n\t\tif timer.unixts-now < int64(duration/1e6) {\n\t\t\t//当前定时器已经超时\n\t\t\ttimerList[tID] = timer\n\t\t\t//定时器已经超时被取走，从当前时间轮上 摘除该定时器\n\t\t\tdelete(leaftw.timerQueue[leaftw.curIndex], tID)\n\t\t}\n\t}\n\n\treturn timerList\n}\n"
  },
  {
    "path": "ztimer/timewheel_test.go",
    "content": "/**\n* @Author: Aceld\n* @Date: 2019/5/7 18:00\n* @Mail: danbing.at@gmail.com\n*\n*  针对 timer_wheel.go 时间轮api 做单元测试, 主要测试时间轮运转功能\n*  依赖模块 delayFunc.go timer.go\n */\npackage ztimer\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestTimerWheel(t *testing.T) {\n\t//创建秒级时间轮\n\tsecondTw := NewTimeWheel(SecondName, SecondInterval, SecondScales, TimersMaxCap)\n\t//创建分钟级时间轮\n\tminuteTw := NewTimeWheel(MinuteName, MinuteInterval, MinuteScales, TimersMaxCap)\n\t//创建小时级时间轮\n\thourTw := NewTimeWheel(HourName, HourInterval, HourScales, TimersMaxCap)\n\n\t// 将分层时间轮做关联\n\thourTw.AddTimeWheel(minuteTw)\n\tminuteTw.AddTimeWheel(secondTw)\n\n\tfmt.Println(\"init timewheels done!\")\n\n\t//===== > 以上为初始化分层时间轮 <====\n\n\t//给时间轮添加定时器\n\ttimer1 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{1, 10}), 10*time.Second)\n\t_ = hourTw.AddTimer(1, timer1)\n\tfmt.Println(\"add timer 1 done!\")\n\n\t//给时间轮添加定时器\n\ttimer2 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{2, 20}), 20*time.Second)\n\t_ = hourTw.AddTimer(2, timer2)\n\tfmt.Println(\"add timer 2 done!\")\n\n\t//给时间轮添加定时器\n\ttimer3 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{3, 30}), 30*time.Second)\n\t_ = hourTw.AddTimer(3, timer3)\n\tfmt.Println(\"add timer 3 done!\")\n\n\t//给时间轮添加定时器\n\ttimer4 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{4, 40}), 40*time.Second)\n\t_ = hourTw.AddTimer(4, timer4)\n\tfmt.Println(\"add timer 4 done!\")\n\n\t//给时间轮添加定时器\n\ttimer5 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{5, 50}), 50*time.Second)\n\t_ = hourTw.AddTimer(5, timer5)\n\tfmt.Println(\"add timer 5 done!\")\n\n\t//时间轮运行\n\tsecondTw.Run()\n\tminuteTw.Run()\n\thourTw.Run()\n\n\tfmt.Println(\"timewheels are run!\")\n\n\tgo func() {\n\t\tn := 0.0\n\t\tfor {\n\t\t\tfmt.Println(\"tick...\", n)\n\n\t\t\t//取出近1ms的超时定时器有哪些\n\t\t\ttimers := hourTw.GetTimerWithIn(1000 * time.Millisecond)\n\t\t\tfor _, timer := range timers {\n\t\t\t\t//调用定时器方法\n\t\t\t\ttimer.delayFunc.Call()\n\t\t\t}\n\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t\tn += 0.5\n\t\t}\n\t}()\n\n\t//主进程等待其他go，由于Run()方法是用一个新的go承载延迟方法，这里不能用waitGroup\n\ttime.Sleep(10 * time.Minute)\n}\n"
  },
  {
    "path": "zutils/hash.go",
    "content": "package zutils\n\nconst (\n\tPrime   = 16777619\n\tHashVal = 2166136261\n)\n\ntype IHash interface {\n\tSum(string) uint32\n}\n\ntype Fnv32Hash struct{}\n\nfunc DefaultHash() IHash {\n\treturn &Fnv32Hash{}\n}\n\n// fnv32 algorithm\nfunc (f *Fnv32Hash) Sum(key string) uint32 {\n\thashVal := uint32(HashVal)\n\tprime := uint32(Prime)\n\tkeyLength := len(key)\n\tfor i := 0; i < keyLength; i++ {\n\t\thashVal *= prime\n\t\thashVal ^= uint32(key[i])\n\t}\n\treturn hashVal\n}\n"
  },
  {
    "path": "zutils/shard_lock_map.go",
    "content": "// Package zutils provides utility functions and data structures for the Zinx framework.\n// This package includes a high-performance sharded concurrent map implementation.\npackage zutils\n\nimport (\n\t\"encoding/json\"\n\t\"sync\"\n)\n\n// DefaultShardCount is the default number of shards for the concurrent map.\n// A higher number reduces lock contention but increases memory overhead.\nvar DefaultShardCount = 32\n\n// ShardLockMaps is a thread-safe map of type string:Anything.\n// To avoid lock bottlenecks, this map is divided into several shards.\n// Each shard has its own read-write mutex, allowing concurrent access\n// to different shards without blocking.\ntype ShardLockMaps struct {\n\tshards     []*SingleShardMap\n\thash       IHash\n\tshardCount int\n}\n\n// SingleShardMap is a thread-safe string to anything map.\n// It represents a single shard within the ShardLockMaps.\ntype SingleShardMap struct {\n\titems map[string]interface{}\n\tsync.RWMutex\n}\n\n// createShardLockMaps Creates a new concurrent map.\nfunc createShardLockMaps(hash IHash, shardCount int) ShardLockMaps {\n\tslm := ShardLockMaps{\n\t\tshards:     make([]*SingleShardMap, shardCount),\n\t\thash:       hash,\n\t\tshardCount: shardCount,\n\t}\n\tfor i := 0; i < shardCount; i++ {\n\t\tslm.shards[i] = &SingleShardMap{items: make(map[string]interface{})}\n\t}\n\treturn slm\n}\n\n// NewShardLockMaps creates a new ShardLockMaps with default shard count.\n// Example usage:\n//\n//\tm := NewShardLockMaps()\n//\tm.Set(\"key\", \"value\")\n//\tif val, ok := m.Get(\"key\"); ok {\n//\t    fmt.Println(val)\n//\t}\nfunc NewShardLockMaps() ShardLockMaps {\n\treturn createShardLockMaps(DefaultHash(), DefaultShardCount)\n}\n\n// NewShardLockMapsWithCount creates a new ShardLockMaps with custom shard count.\n// Use this when you need to tune performance based on your workload.\n// More shards = less lock contention but more memory overhead.\nfunc NewShardLockMapsWithCount(shardCount int) ShardLockMaps {\n\treturn createShardLockMaps(DefaultHash(), shardCount)\n}\n\n// NewWithCustomHash creates a new ShardLockMaps with custom hash function.\n// Use this when you need a different hash distribution strategy.\nfunc NewWithCustomHash(hash IHash) ShardLockMaps {\n\treturn createShardLockMaps(hash, DefaultShardCount)\n}\n\n// NewWithCustomHashAndCount creates a new ShardLockMaps with custom hash function and shard count.\n// This provides maximum flexibility for performance tuning.\nfunc NewWithCustomHashAndCount(hash IHash, shardCount int) ShardLockMaps {\n\treturn createShardLockMaps(hash, shardCount)\n}\n\n// GetShard returns shard under given key\nfunc (slm ShardLockMaps) GetShard(key string) *SingleShardMap {\n\treturn slm.shards[slm.hash.Sum(key)%uint32(slm.shardCount)]\n}\n\n// Count returns the number of elements within the map.\nfunc (slm ShardLockMaps) Count() int {\n\tcount := 0\n\tfor i := 0; i < slm.shardCount; i++ {\n\t\tshard := slm.shards[i]\n\t\tshard.RLock()\n\t\tcount += len(shard.items)\n\t\tshard.RUnlock()\n\t}\n\treturn count\n}\n\n// Get retrieves an element from map under given key.\nfunc (slm ShardLockMaps) Get(key string) (interface{}, bool) {\n\tshard := slm.GetShard(key)\n\tshard.RLock()\n\tval, ok := shard.items[key]\n\tshard.RUnlock()\n\treturn val, ok\n}\n\n// Set Sets the given value under the specified key.\nfunc (slm ShardLockMaps) Set(key string, value interface{}) {\n\tshard := slm.GetShard(key)\n\tshard.Lock()\n\tshard.items[key] = value\n\tshard.Unlock()\n}\n\n// SetNX Sets the given value under the specified key if no value was associated with it.\nfunc (slm ShardLockMaps) SetNX(key string, value interface{}) bool {\n\tshard := slm.GetShard(key)\n\tshard.Lock()\n\t_, ok := shard.items[key]\n\tif !ok {\n\t\tshard.items[key] = value\n\t}\n\tshard.Unlock()\n\treturn !ok\n}\n\n// MSet Sets the given value under the specified key.\nfunc (slm ShardLockMaps) MSet(data map[string]interface{}) {\n\tfor key, value := range data {\n\t\tshard := slm.GetShard(key)\n\t\tshard.Lock()\n\t\tshard.items[key] = value\n\t\tshard.Unlock()\n\t}\n}\n\n// Has Looks up an item under specified key\nfunc (slm ShardLockMaps) Has(key string) bool {\n\tshard := slm.GetShard(key)\n\tshard.RLock()\n\t_, ok := shard.items[key]\n\tshard.RUnlock()\n\treturn ok\n}\n\n// Remove removes an element from the map.\nfunc (slm ShardLockMaps) Remove(key string) {\n\tshard := slm.GetShard(key)\n\tshard.Lock()\n\tdelete(shard.items, key)\n\tshard.Unlock()\n}\n\n// RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held\n// If returns true, the element will be removed from the map\ntype RemoveCb func(key string, v interface{}, exists bool) bool\n\n// RemoveCb locks the shard containing the key, retrieves its current value and calls the callback with those params\n// If callback returns true and element exists, it will remove it from the map\n// Returns the value returned by the callback (even if element was not present in the map)\nfunc (slm ShardLockMaps) RemoveCb(key string, cb RemoveCb) bool {\n\n\tshard := slm.GetShard(key)\n\tshard.Lock()\n\tv, ok := shard.items[key]\n\tremove := cb(key, v, ok)\n\tif remove && ok {\n\t\tdelete(shard.items, key)\n\t}\n\tshard.Unlock()\n\treturn remove\n}\n\n// Pop removes an element from the map and returns it\nfunc (slm ShardLockMaps) Pop(key string) (v interface{}, exists bool) {\n\tshard := slm.GetShard(key)\n\tshard.Lock()\n\tv, exists = shard.items[key]\n\tdelete(shard.items, key)\n\tshard.Unlock()\n\treturn v, exists\n}\n\n// GetOrSet gets the value for the given key, or sets it if it doesn't exist.\n// Returns the value and whether it was set (true) or already existed (false).\nfunc (slm ShardLockMaps) GetOrSet(key string, value interface{}) (interface{}, bool) {\n\tif v, ok := slm.Get(key); ok {\n\t\treturn v, false\n\t}\n\treturn slm.doSetWithLockCheck(key, value)\n}\n\n// GetOrSetFunc gets the value for the given key, or sets it using the provided function if it doesn't exist.\n// The function f is called outside the lock to avoid deadlocks.\n// Returns the value and whether it was set (true) or already existed (false).\nfunc (slm ShardLockMaps) GetOrSetFunc(key string, f func(key string) interface{}) (interface{}, bool) {\n\tif v, ok := slm.Get(key); ok {\n\t\treturn v, false\n\t}\n\treturn slm.doSetWithLockCheck(key, f(key))\n}\n\n// GetOrSetFuncLock gets the value for the given key, or sets it using the provided function if it doesn't exist.\n// The function f is called inside the lock for atomic operations.\n// WARNING: Do not perform operations on the container within f to avoid deadlocks.\n// Returns the value and whether it was set (true) or already existed (false).\nfunc (slm ShardLockMaps) GetOrSetFuncLock(key string, f func(key string) interface{}) (interface{}, bool) {\n\tif v, ok := slm.Get(key); ok {\n\t\treturn v, false\n\t}\n\treturn slm.doSetWithLockCheckWithFunc(key, f)\n}\n\n// doSetWithLockCheck performs a set operation with lock checking\nfunc (slm ShardLockMaps) doSetWithLockCheck(key string, val interface{}) (interface{}, bool) {\n\tshard := slm.GetShard(key)\n\tshard.Lock()\n\tdefer shard.Unlock()\n\n\tif got, ok := shard.items[key]; ok {\n\t\treturn got, false\n\t}\n\n\tshard.items[key] = val\n\treturn val, true\n}\n\n// doSetWithLockCheckWithFunc performs a set operation with function execution inside lock\nfunc (slm ShardLockMaps) doSetWithLockCheckWithFunc(key string, f func(key string) interface{}) (interface{}, bool) {\n\tshard := slm.GetShard(key)\n\tshard.Lock()\n\tdefer shard.Unlock()\n\n\tif got, ok := shard.items[key]; ok {\n\t\treturn got, false\n\t}\n\n\tval := f(key)\n\tshard.items[key] = val\n\treturn val, true\n}\n\n// Clear removes all items from map.\nfunc (slm ShardLockMaps) Clear() {\n\tfor item := range slm.IterBuffered() {\n\t\tslm.Remove(item.Key)\n\t}\n}\n\n// LockFuncWithKey executes a function with write lock on the shard containing the key.\n// WARNING: Do not perform operations on the container within f to avoid deadlocks.\nfunc (slm ShardLockMaps) LockFuncWithKey(key string, f func(shardData map[string]interface{})) {\n\tshard := slm.GetShard(key)\n\tshard.Lock()\n\tdefer shard.Unlock()\n\tf(shard.items)\n}\n\n// RLockFuncWithKey executes a function with read lock on the shard containing the key.\n// WARNING: Do not perform write operations on the container within f to avoid deadlocks.\nfunc (slm ShardLockMaps) RLockFuncWithKey(key string, f func(shardData map[string]interface{})) {\n\tshard := slm.GetShard(key)\n\tshard.RLock()\n\tdefer shard.RUnlock()\n\tf(shard.items)\n}\n\n// LockFunc executes a function with write lock on all shards.\n// WARNING: Do not perform operations on the container within f to avoid deadlocks.\nfunc (slm ShardLockMaps) LockFunc(f func(shardData map[string]interface{})) {\n\tfor _, shard := range slm.shards {\n\t\tshard.Lock()\n\t\tf(shard.items)\n\t\tshard.Unlock()\n\t}\n}\n\n// RLockFunc executes a function with read lock on all shards.\n// WARNING: Do not perform write operations on the container within f to avoid deadlocks.\nfunc (slm ShardLockMaps) RLockFunc(f func(shardData map[string]interface{})) {\n\tfor _, shard := range slm.shards {\n\t\tshard.RLock()\n\t\tf(shard.items)\n\t\tshard.RUnlock()\n\t}\n}\n\n// ClearWithFuncLock clears all items with a callback function executed under lock.\n// WARNING: Do not perform operations on the container within onClear to avoid deadlocks.\nfunc (slm ShardLockMaps) ClearWithFuncLock(onClear func(key string, val interface{})) {\n\tfor _, shard := range slm.shards {\n\t\tshard.Lock()\n\t\tfor key, val := range shard.items {\n\t\t\tonClear(key, val)\n\t\t}\n\t\tshard.items = make(map[string]interface{})\n\t\tshard.Unlock()\n\t}\n}\n\n// IsEmpty checks if map is empty.\nfunc (slm ShardLockMaps) IsEmpty() bool {\n\treturn slm.Count() == 0\n}\n\n// MGet retrieves multiple elements from the map.\nfunc (slm ShardLockMaps) MGet(keys ...string) map[string]interface{} {\n\tdata := make(map[string]interface{})\n\tfor _, key := range keys {\n\t\tif val, ok := slm.Get(key); ok {\n\t\t\tdata[key] = val\n\t\t}\n\t}\n\treturn data\n}\n\n// GetAll returns a copy of all items in the map.\nfunc (slm ShardLockMaps) GetAll() map[string]interface{} {\n\tdata := make(map[string]interface{})\n\tfor _, shard := range slm.shards {\n\t\tshard.RLock()\n\t\tfor key, val := range shard.items {\n\t\t\tdata[key] = val\n\t\t}\n\t\tshard.RUnlock()\n\t}\n\treturn data\n}\n\n// Tuple Used by the IterBuffered functions to wrap two variables together over a channel,\ntype Tuple struct {\n\tKey string\n\tVal interface{}\n}\n\n// Returns a array of channels that contains elements in each shard,\n// which likely takes a snapshot of `slm`.\n// It returns once the size of each buffered channel is determined,\n// before all the channels are populated using goroutines.\nfunc snapshot(slm ShardLockMaps) (chanList []chan Tuple) {\n\tchanList = make([]chan Tuple, slm.shardCount)\n\twg := sync.WaitGroup{}\n\twg.Add(slm.shardCount)\n\tfor index, shard := range slm.shards {\n\t\tgo func(index int, shard *SingleShardMap) {\n\t\t\tshard.RLock()\n\t\t\tchanList[index] = make(chan Tuple, len(shard.items))\n\t\t\twg.Done()\n\t\t\tfor key, val := range shard.items {\n\t\t\t\tchanList[index] <- Tuple{key, val}\n\t\t\t}\n\t\t\tshard.RUnlock()\n\t\t\tclose(chanList[index])\n\t\t}(index, shard)\n\t}\n\twg.Wait()\n\treturn chanList\n}\n\n// fanIn reads elements from channels `chanList` into channel `out`\nfunc fanIn(chanList []chan Tuple, out chan Tuple) {\n\twg := sync.WaitGroup{}\n\twg.Add(len(chanList))\n\tfor _, ch := range chanList {\n\t\tgo func(ch chan Tuple) {\n\t\t\tfor t := range ch {\n\t\t\t\tout <- t\n\t\t\t}\n\t\t\twg.Done()\n\t\t}(ch)\n\t}\n\twg.Wait()\n\tclose(out)\n}\n\n// IterBuffered returns a buffered iterator which could be used in a for range loop.\nfunc (slm ShardLockMaps) IterBuffered() <-chan Tuple {\n\tchanList := snapshot(slm)\n\ttotal := 0\n\tfor _, c := range chanList {\n\t\ttotal += cap(c)\n\t}\n\tch := make(chan Tuple, total)\n\tgo fanIn(chanList, ch)\n\treturn ch\n}\n\n// Items returns all items as map[string]interface{}\nfunc (slm ShardLockMaps) Items() map[string]interface{} {\n\ttmp := make(map[string]interface{})\n\n\tfor item := range slm.IterBuffered() {\n\t\ttmp[item.Key] = item.Val\n\t}\n\n\treturn tmp\n}\n\n// Keys returns all keys as []string\nfunc (slm ShardLockMaps) Keys() []string {\n\tcount := slm.Count()\n\tch := make(chan string, count)\n\tgo func() {\n\t\twg := sync.WaitGroup{}\n\t\twg.Add(slm.shardCount)\n\t\tfor _, shard := range slm.shards {\n\t\t\tgo func(shard *SingleShardMap) {\n\t\t\t\tshard.RLock()\n\t\t\t\tfor key := range shard.items {\n\t\t\t\t\tch <- key\n\t\t\t\t}\n\t\t\t\tshard.RUnlock()\n\t\t\t\twg.Done()\n\t\t\t}(shard)\n\t\t}\n\t\twg.Wait()\n\t\tclose(ch)\n\t}()\n\n\tkeys := make([]string, 0, count)\n\tfor k := range ch {\n\t\tkeys = append(keys, k)\n\t}\n\treturn keys\n}\n\n// IterCb Iterator callback,called for every key,value found in maps.\n// RLock is held for all calls for a given shard\n// therefore callback sess consistent view of a shard,\n// but not across the shards\ntype IterCb func(key string, v interface{})\n\n// IterCb Callback based iterator, cheapest way to read\n// all elements in a map.\nfunc (slm ShardLockMaps) IterCb(fn IterCb) {\n\tfor idx := range slm.shards {\n\t\tshard := (slm.shards)[idx]\n\t\tshard.RLock()\n\t\tfor key, value := range shard.items {\n\t\t\tfn(key, value)\n\t\t}\n\t\tshard.RUnlock()\n\t}\n}\n\n// MarshalJSON Reviles ConcurrentMap \"private\" variables to json marshal.\nfunc (slm ShardLockMaps) MarshalJSON() ([]byte, error) {\n\ttmp := make(map[string]interface{})\n\n\tfor item := range slm.IterBuffered() {\n\t\ttmp[item.Key] = item.Val\n\t}\n\treturn json.Marshal(tmp)\n}\n\n// UnmarshalJSON Reverse process of Marshal.\nfunc (slm ShardLockMaps) UnmarshalJSON(b []byte) (err error) {\n\ttmp := make(map[string]interface{})\n\n\tif err := json.Unmarshal(b, &tmp); err != nil {\n\t\treturn err\n\t}\n\n\tfor key, val := range tmp {\n\t\tslm.Set(key, val)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "zutils/shard_lock_map_bench_test.go",
    "content": "package zutils\n\nimport (\n\t\"strconv\"\n\t\"sync\"\n\t\"testing\"\n)\n\nfunc BenchmarkItems(b *testing.B) {\n\tslm := NewShardLockMaps()\n\n\tfor i := 0; i < 10000; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\tslm.Items()\n\t}\n}\n\nfunc BenchmarkKeys(b *testing.B) {\n\tslm := NewShardLockMaps()\n\n\tfor i := 0; i < 10000; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\tslm.Keys()\n\t}\n}\n\nfunc BenchmarkMarshalJson(b *testing.B) {\n\tslm := NewShardLockMaps()\n\n\tfor i := 0; i < 10000; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\t_, err := slm.MarshalJSON()\n\t\tif err != nil {\n\t\t\tb.FailNow()\n\t\t}\n\t}\n}\n\nfunc BenchmarkStrconv(b *testing.B) {\n\tfor i := 0; i < b.N; i++ {\n\t\tstrconv.Itoa(i)\n\t}\n}\n\nfunc BenchmarkSingleInsertAbsent(b *testing.B) {\n\tslm := NewShardLockMaps()\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tslm.Set(strconv.Itoa(i), \"value\")\n\t}\n}\n\nfunc BenchmarkSingleInsertAbsentSyncMap(b *testing.B) {\n\tvar m sync.Map\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tm.Store(strconv.Itoa(i), \"value\")\n\t}\n}\n\nfunc BenchmarkSingleInsertPresent(b *testing.B) {\n\tslm := NewShardLockMaps()\n\tslm.Set(\"key\", \"value\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tslm.Set(\"key\", \"value\")\n\t}\n}\n\nfunc BenchmarkSingleInsertPresentSyncMap(b *testing.B) {\n\tvar m sync.Map\n\tm.Store(\"key\", \"value\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tm.Store(\"key\", \"value\")\n\t}\n}\n\nfunc BenchmarkMultiInsertDifferentSyncMap(b *testing.B) {\n\tvar m sync.Map\n\tfinished := make(chan struct{}, b.N)\n\t_, set := GetSetSyncMap(&m, finished)\n\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo set(strconv.Itoa(i), \"value\")\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc BenchmarkMultiInsertDifferent_1_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(1)\n\tbenchmarkMultiInsertDifferentWithMap(slm, b)\n}\nfunc BenchmarkMultiInsertDifferent_16_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(16)\n\tbenchmarkMultiInsertDifferentWithMap(slm, b)\n}\nfunc BenchmarkMultiInsertDifferent_32_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(32)\n\tbenchmarkMultiInsertDifferentWithMap(slm, b)\n}\nfunc BenchmarkMultiInsertDifferent_256_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(256)\n\tbenchmarkMultiInsertDifferentWithMap(slm, b)\n}\n\nfunc BenchmarkMultiInsertSame(b *testing.B) {\n\tslm := NewShardLockMaps()\n\tfinished := make(chan struct{}, b.N)\n\t_, set := GetSet(slm, finished)\n\tslm.Set(\"key\", \"value\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo set(\"key\", \"value\")\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc BenchmarkMultiInsertSameSyncMap(b *testing.B) {\n\tvar m sync.Map\n\tfinished := make(chan struct{}, b.N)\n\t_, set := GetSetSyncMap(&m, finished)\n\tm.Store(\"key\", \"value\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo set(\"key\", \"value\")\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc BenchmarkMultiGetSame(b *testing.B) {\n\tslm := NewShardLockMaps()\n\tfinished := make(chan struct{}, b.N)\n\tget, _ := GetSet(slm, finished)\n\tslm.Set(\"key\", \"value\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo get(\"key\", \"value\")\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc BenchmarkMultiGetSameSyncMap(b *testing.B) {\n\tvar m sync.Map\n\tfinished := make(chan struct{}, b.N)\n\tget, _ := GetSetSyncMap(&m, finished)\n\tm.Store(\"key\", \"value\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo get(\"key\", \"value\")\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc BenchmarkMultiGetSetDifferentSyncMap(b *testing.B) {\n\tvar m sync.Map\n\tfinished := make(chan struct{}, 2*b.N)\n\tget, set := GetSetSyncMap(&m, finished)\n\tm.Store(\"-1\", \"value\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo set(strconv.Itoa(i-1), \"value\")\n\t\tgo get(strconv.Itoa(i), \"value\")\n\t}\n\tfor i := 0; i < 2*b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc BenchmarkMultiGetSetDifferent_1_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(1)\n\tbenchmarkMultiGetSetDifferentWithMap(slm, b)\n}\nfunc BenchmarkMultiGetSetDifferent_16_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(16)\n\tbenchmarkMultiGetSetDifferentWithMap(slm, b)\n}\nfunc BenchmarkMultiGetSetDifferent_32_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(32)\n\tbenchmarkMultiGetSetDifferentWithMap(slm, b)\n}\nfunc BenchmarkMultiGetSetDifferent_256_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(256)\n\tbenchmarkMultiGetSetDifferentWithMap(slm, b)\n}\n\nfunc BenchmarkMultiGetSetBlockSyncMap(b *testing.B) {\n\tvar m sync.Map\n\tfinished := make(chan struct{}, 2*b.N)\n\tget, set := GetSetSyncMap(&m, finished)\n\tfor i := 0; i < b.N; i++ {\n\t\tm.Store(strconv.Itoa(i%100), \"value\")\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo set(strconv.Itoa(i%100), \"value\")\n\t\tgo get(strconv.Itoa(i%100), \"value\")\n\t}\n\tfor i := 0; i < 2*b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc BenchmarkMultiGetSetBlock_1_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(1)\n\tbenchmarkMultiGetSetBlockWithMap(slm, b)\n}\nfunc BenchmarkMultiGetSetBlock_16_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(16)\n\tbenchmarkMultiGetSetBlockWithMap(slm, b)\n}\nfunc BenchmarkMultiGetSetBlock_32_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(32)\n\tbenchmarkMultiGetSetBlockWithMap(slm, b)\n}\nfunc BenchmarkMultiGetSetBlock_256_Shard(b *testing.B) {\n\tslm := NewShardLockMapsWithCount(256)\n\tbenchmarkMultiGetSetBlockWithMap(slm, b)\n}\n\nfunc benchmarkMultiInsertDifferent(b *testing.B) {\n\tslm := NewShardLockMaps()\n\tfinished := make(chan struct{}, b.N)\n\t_, set := GetSet(slm, finished)\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo set(strconv.Itoa(i), \"value\")\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc benchmarkMultiGetSetDifferent(b *testing.B) {\n\tslm := NewShardLockMaps()\n\tfinished := make(chan struct{}, 2*b.N)\n\tget, set := GetSet(slm, finished)\n\tslm.Set(\"-1\", \"value\")\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo set(strconv.Itoa(i-1), \"value\")\n\t\tgo get(strconv.Itoa(i), \"value\")\n\t}\n\tfor i := 0; i < 2*b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc benchmarkMultiGetSetBlock(b *testing.B) {\n\tslm := NewShardLockMaps()\n\tfinished := make(chan struct{}, 2*b.N)\n\tget, set := GetSet(slm, finished)\n\tfor i := 0; i < b.N; i++ {\n\t\tslm.Set(strconv.Itoa(i%100), \"value\")\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo set(strconv.Itoa(i%100), \"value\")\n\t\tgo get(strconv.Itoa(i%100), \"value\")\n\t}\n\tfor i := 0; i < 2*b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc GetSet(slm ShardLockMaps, finished chan struct{}) (set func(key, value string), get func(key, value string)) {\n\treturn func(key, value string) {\n\t\t\tfor i := 0; i < 10; i++ {\n\t\t\t\tslm.Get(key)\n\t\t\t}\n\t\t\tfinished <- struct{}{}\n\t\t}, func(key, value string) {\n\t\t\tfor i := 0; i < 10; i++ {\n\t\t\t\tslm.Set(key, value)\n\t\t\t}\n\t\t\tfinished <- struct{}{}\n\t\t}\n}\n\nfunc GetSetSyncMap(m *sync.Map, finished chan struct{}) (get func(key, value string), set func(key, value string)) {\n\tget = func(key, value string) {\n\t\tfor i := 0; i < 10; i++ {\n\t\t\tm.Load(key)\n\t\t}\n\t\tfinished <- struct{}{}\n\t}\n\tset = func(key, value string) {\n\t\tfor i := 0; i < 10; i++ {\n\t\t\tm.Store(key, value)\n\t\t}\n\t\tfinished <- struct{}{}\n\t}\n\treturn\n}\n\nfunc benchmarkMultiInsertDifferentWithMap(slm ShardLockMaps, b *testing.B) {\n\tfinished := make(chan struct{}, b.N)\n\t_, set := GetSet(slm, finished)\n\tfor i := 0; i < b.N; i++ {\n\t\tset(strconv.Itoa(i), strconv.Itoa(i))\n\t}\n\tfor i := 0; i < b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc benchmarkMultiGetSetDifferentWithMap(slm ShardLockMaps, b *testing.B) {\n\tfinished := make(chan struct{}, 2*b.N)\n\tget, set := GetSet(slm, finished)\n\tfor i := 0; i < b.N; i++ {\n\t\tgo get(strconv.Itoa(i), \"value\")\n\t\tgo set(strconv.Itoa(i), \"value\")\n\t}\n\tfor i := 0; i < 2*b.N; i++ {\n\t\t<-finished\n\t}\n}\n\nfunc benchmarkMultiGetSetBlockWithMap(slm ShardLockMaps, b *testing.B) {\n\tfinished := make(chan struct{}, 2*b.N)\n\tget, set := GetSet(slm, finished)\n\tfor i := 0; i < 100; i++ {\n\t\tset(strconv.Itoa(i), \"value\")\n\t}\n\tb.ResetTimer()\n\tfor i := 0; i < b.N; i++ {\n\t\tgo set(strconv.Itoa(i%100), \"value\")\n\t\tgo get(strconv.Itoa(i%100), \"value\")\n\t}\n\tfor i := 0; i < 2*b.N; i++ {\n\t\t<-finished\n\t}\n}\n"
  },
  {
    "path": "zutils/shard_lock_map_test.go",
    "content": "package zutils\n\nimport (\n\t\"fmt\"\n\t\"hash/fnv\"\n\t\"sort\"\n\t\"strconv\"\n\t\"testing\"\n)\n\ntype TestUser struct {\n\tname string\n}\n\nfunc TestCreatMap(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tif slm.shards == nil {\n\t\tt.Error(\"shardLockMaps is null.\")\n\t}\n\n\tif slm.Count() != 0 {\n\t\tt.Error(\"new shardLockMaps should be empty.\")\n\t}\n}\n\nfunc TestSet(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tslm.Set(\"user\", \"14March\")\n\tmData := make(map[string]interface{})\n\tmData[\"aaa\"] = 111\n\tmData[\"bbb\"] = \"222\"\n\tslm.MSet(mData)\n\tslm.SetNX(\"user\", \"14March\")\n\tbo := TestUser{\"bo\"}\n\tslm.SetNX(\"bo\", bo)\n\n\tif slm.Count() != 4 {\n\t\tt.Error(\"shardLockMaps should contain exactly one elements.\")\n\t}\n\n}\n\nfunc TestGet(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tval, ok := slm.Get(\"user\")\n\n\tif ok == true {\n\t\tt.Error(\"ok should be false when item is missing from map.\")\n\t}\n\n\tif val != nil {\n\t\tt.Error(\"Missing values should return as null.\")\n\t}\n\n\ttony := TestUser{\"tony\"}\n\tslm.Set(\"tony\", tony)\n\n\ttmp, ok := slm.Get(\"tony\")\n\tif ok == false {\n\t\tt.Error(\"ok should be true for item stored within the map.\")\n\t}\n\n\ttony, ok = tmp.(TestUser)\n\tif !ok {\n\t\tt.Error(\"expecting an element, not null.\")\n\t}\n\n\tif tony.name != \"tony\" {\n\t\tt.Error(\"item was modified.\")\n\t}\n\n}\n\nfunc TestHas(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tif slm.Has(\"Money\") == true {\n\t\tt.Error(\"element shouldn't exists\")\n\t}\n\n\tslm.Set(\"user\", \"14March\")\n\n\tif slm.Has(\"user\") == false {\n\t\tt.Error(\"element exists, user Has to return True.\")\n\t}\n\n}\n\nfunc TestCount(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tfor i := 0; i < 100; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\n\tif slm.Count() != 100 {\n\t\tt.Error(\"Expecting 100 element within map.\")\n\t}\n\n}\n\nfunc TestRemove(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tdavid := TestUser{\"David\"}\n\tslm.Set(\"david\", david)\n\n\tslm.Remove(\"david\")\n\n\tif slm.Count() != 0 {\n\t\tt.Error(\"Expecting count to be zero once item was removed.\")\n\t}\n\n\ttemp, ok := slm.Get(\"david\")\n\n\tif ok != false {\n\t\tt.Error(\"Expecting ok to be false for missing items.\")\n\t}\n\n\tif temp != nil {\n\t\tt.Error(\"Expecting item to be nil after its removal.\")\n\t}\n\n\tslm.Remove(\"user\")\n\n\tisEmpty := slm.IsEmpty()\n\tif !isEmpty {\n\t\tt.Error(\"map should be empty.\")\n\t}\n\n}\n\nfunc TestRemoveCb(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\ttony := TestUser{\"tony\"}\n\tslm.Set(\"tony\", tony)\n\tdavid := TestUser{\"david\"}\n\tslm.Set(\"david\", david)\n\n\tvar (\n\t\tmapKey   string\n\t\tmapVal   interface{}\n\t\twasFound bool\n\t)\n\tcb := func(key string, val interface{}, exists bool) bool {\n\t\tmapKey = key\n\t\tmapVal = val\n\t\twasFound = exists\n\n\t\tif user, ok := val.(TestUser); ok {\n\t\t\treturn user.name == \"tony\"\n\t\t}\n\t\treturn false\n\t}\n\n\tresult := slm.RemoveCb(\"tony\", cb)\n\tif !result {\n\t\tt.Errorf(\"Result was not true\")\n\t}\n\n\tif mapKey != \"tony\" {\n\t\tt.Error(\"Wrong key was provided to the callback\")\n\t}\n\n\tif mapVal != tony {\n\t\tt.Errorf(\"Wrong value was provided to the value\")\n\t}\n\n\tif !wasFound {\n\t\tt.Errorf(\"Key was not found\")\n\t}\n\n\tif slm.Has(\"tony\") {\n\t\tt.Errorf(\"Key was not removed\")\n\t}\n\n\tresult = slm.RemoveCb(\"david\", cb)\n\tif result {\n\t\tt.Errorf(\"Result was true\")\n\t}\n\n\tif mapKey != \"david\" {\n\t\tt.Error(\"Wrong key was provided to the callback\")\n\t}\n\n\tif mapVal != david {\n\t\tt.Errorf(\"Wrong value was provided to the value\")\n\t}\n\n\tif !wasFound {\n\t\tt.Errorf(\"Key was not found\")\n\t}\n\n\tif !slm.Has(\"david\") {\n\t\tt.Errorf(\"Key was removed\")\n\t}\n\n\tresult = slm.RemoveCb(\"danny\", cb)\n\tif result {\n\t\tt.Errorf(\"Result was true\")\n\t}\n\n\tif mapKey != \"danny\" {\n\t\tt.Error(\"Wrong key was provided to the callback\")\n\t}\n\n\tif mapVal != nil {\n\t\tt.Errorf(\"Wrong value was provided to the value\")\n\t}\n\n\tif wasFound {\n\t\tt.Errorf(\"Key was found\")\n\t}\n\n\tif slm.Has(\"danny\") {\n\t\tt.Errorf(\"Key was created\")\n\t}\n}\n\nfunc TestPop(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\t_, exists := slm.Pop(\"user\")\n\tif exists {\n\t\tt.Error(\"user should be not exists.\")\n\t}\n\n\tslm.Set(\"user\", \"14March\")\n\n\tval, exists := slm.Pop(\"user\")\n\tif exists {\n\t\tt.Logf(\"user should be exists %v.\", val)\n\t}\n\n}\n\nfunc TestBufferedIterator(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tfor i := 0; i < 100; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\n\tcounter := 0\n\tfor item := range slm.IterBuffered() {\n\t\tval := item.Val\n\n\t\tif val == nil {\n\t\t\tt.Error(\"Expecting an object.\")\n\t\t}\n\t\tcounter++\n\t}\n\n\tif counter != 100 {\n\t\tt.Error(\"We should have counted 100 elements.\")\n\t}\n\n}\n\nfunc TestClear(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tfor i := 0; i < 100; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\n\tslm.Clear()\n\n\tif slm.Count() != 0 {\n\t\tt.Error(\"should have 0 elements.\")\n\t}\n}\n\nfunc TestIterCb(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tfor i := 0; i < 100; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\n\tcounter := 0\n\tslm.IterCb(func(key string, v interface{}) {\n\t\t_, ok := v.(TestUser)\n\t\tif !ok {\n\t\t\tt.Error(\"Expecting an user object\")\n\t\t}\n\n\t\tcounter++\n\t})\n\tif counter != 100 {\n\t\tt.Error(\"We should have counted 100 elements.\")\n\t}\n}\n\nfunc TestItems(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tfor i := 0; i < 100; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\n\titems := slm.Items()\n\n\tif len(items) != 100 {\n\t\tt.Error(\"We should have counted 100 elements.\")\n\t}\n\n}\n\nfunc TestJsonMarshal(t *testing.T) {\n\texpected := \"{\\\"a\\\":1,\\\"b\\\":2}\"\n\tslm := NewShardLockMapsWithCount(2)\n\tslm.Set(\"a\", 1)\n\tslm.Set(\"b\", 2)\n\tj, err := slm.MarshalJSON()\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif string(j) != expected {\n\t\tt.Error(\"json\", string(j), \"differ from expected\", expected)\n\t\treturn\n\t}\n}\n\nfunc TestUnmarshalJSON(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tbytes := []byte(\"{\\\"ccc\\\":1,\\\"ddd\\\":2}\")\n\n\terr := slm.UnmarshalJSON(bytes)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tif slm.Count() != 2 {\n\t\tt.Error(\"Expecting count to be 2 once item was removed.\")\n\t}\n\n\tfor _, shard := range slm.shards {\n\t\tfmt.Printf(\"%+v \\n\", shard.items)\n\t}\n\n}\n\nfunc TestKeys(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tfor i := 0; i < 100; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\n\tkeys := slm.Keys()\n\tif len(keys) != 100 {\n\t\tt.Error(\"We should have counted 100 elements.\")\n\t}\n}\n\nfunc TestFnv32(t *testing.T) {\n\tkey := []byte(\"ABC\")\n\n\thasher := fnv.New32()\n\t_, err := hasher.Write(key)\n\tif err != nil {\n\t\tt.Errorf(\"%v\", err)\n\t}\n\tif (&Fnv32Hash{}).Sum(string(key)) != hasher.Sum32() {\n\t\tt.Errorf(\"Bundled fnv32 produced %d, expected result from hash/fnv32 is %d\", (&Fnv32Hash{}).Sum(string(key)), hasher.Sum32())\n\t}\n\n}\n\nfunc TestKeysWhenRemoving(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tTotal := 100\n\tfor i := 0; i < Total; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\n\tNum := 10\n\tfor i := 0; i < Num; i++ {\n\t\tgo func(c *ShardLockMaps, n int) {\n\t\t\tc.Remove(strconv.Itoa(n))\n\t\t}(&slm, i)\n\t}\n\tkeys := slm.Keys()\n\tfor _, k := range keys {\n\t\tif k == \"\" {\n\t\t\tt.Error(\"Empty keys returned\")\n\t\t}\n\t}\n}\n\nfunc TestUnDrainedIterBuffered(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\tTotal := 100\n\tfor i := 0; i < Total; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\tcounter := 0\n\n\t// Iterate over elements.\n\tch := slm.IterBuffered()\n\tfor item := range ch {\n\t\tval := item.Val\n\n\t\tif val == nil {\n\t\t\tt.Error(\"Expecting an object.\")\n\t\t}\n\t\tcounter++\n\t\tif counter == 42 {\n\t\t\tbreak\n\t\t}\n\t}\n\tfor i := Total; i < 2*Total; i++ {\n\t\tslm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)})\n\t}\n\tfor item := range ch {\n\t\tval := item.Val\n\n\t\tif val == nil {\n\t\t\tt.Error(\"Expecting an object.\")\n\t\t}\n\t\tcounter++\n\t}\n\n\tif counter != 100 {\n\t\tt.Error(\"We should have been right where we stopped\")\n\t}\n\n\tcounter = 0\n\tfor item := range slm.IterBuffered() {\n\t\tval := item.Val\n\n\t\tif val == nil {\n\t\t\tt.Error(\"Expecting an object.\")\n\t\t}\n\t\tcounter++\n\t}\n\n\tif counter != 200 {\n\t\tt.Error(\"We should have counted 200 elements.\")\n\t}\n}\n\nfunc TestConcurrent(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tch := make(chan int)\n\tconst iterations = 1000\n\tvar a [iterations]int\n\n\t// Using go routines insert 1000 ints into our map.\n\tgo func() {\n\t\tfor i := 0; i < iterations/2; i++ {\n\t\t\t// Add item to map.\n\t\t\tslm.Set(strconv.Itoa(i), i)\n\n\t\t\t// Retrieve item from map.\n\t\t\tval, _ := slm.Get(strconv.Itoa(i))\n\n\t\t\t// Write to channel inserted value.\n\t\t\tch <- val.(int)\n\t\t} // Call go routine with current index.\n\t}()\n\n\tgo func() {\n\t\tfor i := iterations / 2; i < iterations; i++ {\n\t\t\t// Add item to map.\n\t\t\tslm.Set(strconv.Itoa(i), i)\n\n\t\t\t// Retrieve item from map.\n\t\t\tval, _ := slm.Get(strconv.Itoa(i))\n\n\t\t\t// Write to channel inserted value.\n\t\t\tch <- val.(int)\n\t\t} // Call go routine with current index.\n\t}()\n\n\t// Wait for all go routines to finish.\n\tcounter := 0\n\tfor elem := range ch {\n\t\ta[counter] = elem\n\t\tcounter++\n\t\tif counter == iterations {\n\t\t\tbreak\n\t\t}\n\t}\n\n\t// Sorts array, will make is simpler to verify all inserted values we're returned.\n\tsort.Ints(a[0:iterations])\n\n\t// Make sure map contains 1000 elements.\n\tif slm.Count() != iterations {\n\t\tt.Error(\"Expecting 1000 elements.\")\n\t}\n\n\t// Make sure all inserted values we're fetched from map.\n\tfor i := 0; i < iterations; i++ {\n\t\tif i != a[i] {\n\t\t\tt.Error(\"missing value\", i)\n\t\t}\n\t}\n}\n\n// TestGetOrSet tests the GetOrSet method\nfunc TestGetOrSet(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\t// Test setting a new value\n\tvalue, isSet := slm.GetOrSet(\"key1\", \"value1\")\n\tif !isSet {\n\t\tt.Error(\"Expected isSet to be true for new key\")\n\t}\n\tif value != \"value1\" {\n\t\tt.Error(\"Expected value to be 'value1'\")\n\t}\n\n\t// Test getting existing value\n\tvalue, isSet = slm.GetOrSet(\"key1\", \"value2\")\n\tif isSet {\n\t\tt.Error(\"Expected isSet to be false for existing key\")\n\t}\n\tif value != \"value1\" {\n\t\tt.Error(\"Expected value to be 'value1'\")\n\t}\n}\n\n// TestGetOrSetFunc tests the GetOrSetFunc method\nfunc TestGetOrSetFunc(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\t// Test setting a new value with function\n\tvalue, isSet := slm.GetOrSetFunc(\"key1\", func(key string) interface{} {\n\t\treturn \"generated_\" + key\n\t})\n\tif !isSet {\n\t\tt.Error(\"Expected isSet to be true for new key\")\n\t}\n\tif value != \"generated_key1\" {\n\t\tt.Error(\"Expected value to be 'generated_key1'\")\n\t}\n\n\t// Test getting existing value\n\tvalue, isSet = slm.GetOrSetFunc(\"key1\", func(key string) interface{} {\n\t\treturn \"should_not_be_called\"\n\t})\n\tif isSet {\n\t\tt.Error(\"Expected isSet to be false for existing key\")\n\t}\n\tif value != \"generated_key1\" {\n\t\tt.Error(\"Expected value to be 'generated_key1'\")\n\t}\n}\n\n// TestGetOrSetFuncLock tests the GetOrSetFuncLock method\nfunc TestGetOrSetFuncLock(t *testing.T) {\n\tslm := NewShardLockMaps()\n\n\t// Test setting a new value with function in lock\n\tvalue, isSet := slm.GetOrSetFuncLock(\"key1\", func(key string) interface{} {\n\t\treturn \"locked_\" + key\n\t})\n\tif !isSet {\n\t\tt.Error(\"Expected isSet to be true for new key\")\n\t}\n\tif value != \"locked_key1\" {\n\t\tt.Error(\"Expected value to be 'locked_key1'\")\n\t}\n\n\t// Test getting existing value\n\tvalue, isSet = slm.GetOrSetFuncLock(\"key1\", func(key string) interface{} {\n\t\treturn \"should_not_be_called\"\n\t})\n\tif isSet {\n\t\tt.Error(\"Expected isSet to be false for existing key\")\n\t}\n\tif value != \"locked_key1\" {\n\t\tt.Error(\"Expected value to be 'locked_key1'\")\n\t}\n}\n\n// TestMGet tests the MGet method\nfunc TestMGet(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tslm.Set(\"key1\", \"value1\")\n\tslm.Set(\"key2\", \"value2\")\n\tslm.Set(\"key3\", \"value3\")\n\n\t// Test getting multiple existing keys\n\tvalues := slm.MGet(\"key1\", \"key2\", \"key3\")\n\tif len(values) != 3 {\n\t\tt.Error(\"Expected 3 values\")\n\t}\n\tif values[\"key1\"] != \"value1\" || values[\"key2\"] != \"value2\" || values[\"key3\"] != \"value3\" {\n\t\tt.Error(\"Expected correct values\")\n\t}\n\n\t// Test getting mix of existing and non-existing keys\n\tvalues = slm.MGet(\"key1\", \"nonexistent\", \"key2\")\n\tif len(values) != 2 {\n\t\tt.Error(\"Expected 2 values for mix of existing and non-existing keys\")\n\t}\n\tif values[\"key1\"] != \"value1\" || values[\"key2\"] != \"value2\" {\n\t\tt.Error(\"Expected correct values for existing keys\")\n\t}\n}\n\n// TestGetAll tests the GetAll method\nfunc TestGetAll(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tslm.Set(\"key1\", \"value1\")\n\tslm.Set(\"key2\", \"value2\")\n\n\tall := slm.GetAll()\n\tif len(all) != 2 {\n\t\tt.Error(\"Expected 2 items in GetAll\")\n\t}\n\tif all[\"key1\"] != \"value1\" || all[\"key2\"] != \"value2\" {\n\t\tt.Error(\"Expected correct values in GetAll\")\n\t}\n}\n\n// TestLockFuncWithKey tests the LockFuncWithKey method\nfunc TestLockFuncWithKey(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tslm.Set(\"key1\", \"value1\")\n\tslm.Set(\"key2\", \"value2\")\n\n\t// Test modifying a specific key's shard\n\tslm.LockFuncWithKey(\"key1\", func(data map[string]interface{}) {\n\t\tif val, ok := data[\"key1\"]; ok {\n\t\t\tdata[\"key1\"] = val.(string) + \"_modified\"\n\t\t}\n\t})\n\n\tvalue, ok := slm.Get(\"key1\")\n\tif !ok || value != \"value1_modified\" {\n\t\tt.Error(\"Expected key1 to be modified\")\n\t}\n}\n\n// TestRLockFuncWithKey tests the RLockFuncWithKey method\nfunc TestRLockFuncWithKey(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tslm.Set(\"key1\", \"value1\")\n\tslm.Set(\"key2\", \"value2\")\n\n\t// Test reading from a specific key's shard\n\tvar found bool\n\tslm.RLockFuncWithKey(\"key1\", func(data map[string]interface{}) {\n\t\tif _, ok := data[\"key1\"]; ok {\n\t\t\tfound = true\n\t\t}\n\t})\n\n\tif !found {\n\t\tt.Error(\"Expected to find key1 in shard\")\n\t}\n}\n\n// TestLockFunc tests the LockFunc method\nfunc TestLockFunc(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tslm.Set(\"key1\", \"value1\")\n\tslm.Set(\"key2\", \"value2\")\n\n\t// Test modifying all shards\n\tslm.LockFunc(func(data map[string]interface{}) {\n\t\tfor key, val := range data {\n\t\t\tif str, ok := val.(string); ok {\n\t\t\t\tdata[key] = str + \"_modified\"\n\t\t\t}\n\t\t}\n\t})\n\n\tvalue1, _ := slm.Get(\"key1\")\n\tvalue2, _ := slm.Get(\"key2\")\n\tif value1 != \"value1_modified\" || value2 != \"value2_modified\" {\n\t\tt.Error(\"Expected all values to be modified\")\n\t}\n}\n\n// TestRLockFunc tests the RLockFunc method\nfunc TestRLockFunc(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tslm.Set(\"key1\", \"value1\")\n\tslm.Set(\"key2\", \"value2\")\n\n\t// Test reading from all shards\n\tvar count int\n\tslm.RLockFunc(func(data map[string]interface{}) {\n\t\tcount += len(data)\n\t})\n\n\tif count != 2 {\n\t\tt.Error(\"Expected to find 2 items across all shards\")\n\t}\n}\n\n// TestClearWithFuncLock tests the ClearWithFuncLock method\nfunc TestClearWithFuncLock(t *testing.T) {\n\tslm := NewShardLockMaps()\n\tslm.Set(\"key1\", \"value1\")\n\tslm.Set(\"key2\", \"value2\")\n\n\tvar clearedKeys []string\n\tslm.ClearWithFuncLock(func(key string, val interface{}) {\n\t\tclearedKeys = append(clearedKeys, key)\n\t})\n\n\tif len(clearedKeys) != 2 {\n\t\tt.Error(\"Expected 2 keys to be cleared\")\n\t}\n\tif slm.Count() != 0 {\n\t\tt.Error(\"Expected map to be empty after ClearWithFuncLock\")\n\t}\n}\n"
  },
  {
    "path": "zutils/snowflake_uuid.go",
    "content": "package zutils\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n)\n\n/*\n\tworkerID 的大小为 10 位，因此可以生成的最大 workerID 数量为 2^10 = 1024。\n\tsequenceBits 的大小为 12 位，因此对于每个工作器，可以在同一毫秒内生成的最大序列号数为 2^12 = 4096。\n\t因此，在同一毫秒内，每个工作者最多可以生成 4096 个分布式 ID。\n\t由于时间戳使用的是毫秒级别的时间戳，因此每个工作者在一秒钟内最多可以生成 4096 * 1000 = 4,096,000 个分布式 ID。\n\t总体上，如果 workerID 保持唯一，这个算法可以生成多达 1024 x 4,096,000 = 4,194,304,000 个分布式 ID。\n*/\n\nconst (\n\tworkerBits     uint8 = 10\n\tmaxWorker      int64 = -1 ^ (-1 << workerBits)\n\tsequenceBits   uint8 = 12\n\tsequenceMask   int64 = -1 ^ (-1 << sequenceBits)\n\tworkerShift    uint8 = sequenceBits\n\ttimestampShift uint8 = sequenceBits + workerBits\n)\n\ntype IDWorker struct {\n\tsequence      int64\n\tlastTimestamp int64\n\tworkerId      int64\n\tmutex         sync.Mutex\n}\n\nfunc NewIDWorker(workerId int64) (*IDWorker, error) {\n\tif workerId < 0 || workerId > maxWorker {\n\t\treturn nil, fmt.Errorf(\"worker ID can't be greater than %d or less than 0\", maxWorker)\n\t}\n\n\treturn &IDWorker{\n\t\tworkerId:      workerId,\n\t\tlastTimestamp: -1,\n\t\tsequence:      0,\n\t}, nil\n}\n\nfunc (w *IDWorker) NextID() (int64, error) {\n\tw.mutex.Lock()\n\tdefer w.mutex.Unlock()\n\n\ttimestamp := time.Now().UnixNano() / 1000000\n\n\tif timestamp < w.lastTimestamp {\n\t\treturn 0, fmt.Errorf(\"clock moved backwards\")\n\t}\n\n\tif timestamp == w.lastTimestamp {\n\t\tw.sequence = (w.sequence + 1) & sequenceMask\n\t\tif w.sequence == 0 {\n\t\t\ttimestamp = w.nextMillisecond(timestamp)\n\t\t}\n\t} else {\n\t\tw.sequence = 0\n\t}\n\n\tw.lastTimestamp = timestamp\n\n\treturn (timestamp << timestampShift) | (w.workerId << workerShift) | w.sequence, nil\n}\n\nfunc (w *IDWorker) nextMillisecond(currentTimestamp int64) int64 {\n\tfor currentTimestamp <= w.lastTimestamp {\n\t\tcurrentTimestamp = time.Now().UnixNano() / 1000000\n\t}\n\treturn currentTimestamp\n}\n"
  },
  {
    "path": "zutils/snowflake_uuid_test.go",
    "content": "package zutils\n\nimport (\n\t\"fmt\"\n\t\"testing\"\n)\n\nfunc TestSnowFlakeUUID(t *testing.T) {\n\tworker, err := NewIDWorker(1)\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tid, err := worker.NextID()\n\tif err != nil {\n\t\tfmt.Println(err)\n\t\treturn\n\t}\n\n\tfmt.Println(\"ID:\", id)\n}\n"
  },
  {
    "path": "zutils/witer.go",
    "content": "package zutils\n\nimport (\n\t\"archive/zip\"\n\t\"bufio\"\n\t\"bytes\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/fs\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n)\n\nconst (\n\tsizeMiB    = 1024 * 1024\n\tdefMaxAge  = 31\n\tdefMaxSize = 64 //MiB\n)\n\nvar _ io.WriteCloser = (*Writer)(nil)\n\ntype Writer struct {\n\tmaxAge    int       // 最大保留天数\n\tmaxSize   int64     // 单个日志最大容量 默认 64MB\n\tsize      int64     // 累计大小\n\tfpath     string    // 文件目录 完整路径 fpath=fdir+fname+fsuffix\n\tfdir      string    //\n\tfname     string    // 文件名\n\tfsuffix   string    // 文件后缀名 默认 .log\n\tzipsuffix string    // 文件后缀名 默认 .log\n\tcreated   time.Time // 文件创建日期\n\tcreates   []byte    // 文件创建日期\n\tcons      bool      // 标准输出  默认 false\n\tfile      *os.File\n\tbw        *bufio.Writer\n\tmu        sync.Mutex\n}\n\nfunc New(path string) *Writer {\n\tw := &Writer{\n\t\tfpath: path, //dir1/dir2/app.log\n\t\tmu:    sync.Mutex{},\n\t}\n\tw.fdir = filepath.Dir(w.fpath)                                  //dir1/dir2\n\tw.fsuffix = filepath.Ext(w.fpath)                               //.log\n\tw.fname = strings.TrimSuffix(filepath.Base(w.fpath), w.fsuffix) //app\n\tif w.fsuffix == \"\" {\n\t\tw.fsuffix = \".log\"\n\t}\n\tif w.zipsuffix == \"\" {\n\t\tw.zipsuffix = \".zip\"\n\t}\n\tw.maxSize = sizeMiB * defMaxSize\n\tw.maxAge = defMaxAge\n\tos.MkdirAll(filepath.Dir(w.fpath), 0755)\n\tgo w.daemon()\n\treturn w\n}\nfunc (w *Writer) daemon() {\n\tfor range time.NewTicker(time.Second * 5).C {\n\t\tw.flush()\n\t}\n}\n\n// SetMaxAge 最大保留天数\nfunc (w *Writer) SetMaxAge(ma int) {\n\tw.mu.Lock()\n\tw.maxAge = ma\n\tw.mu.Unlock()\n}\n\n// SetMaxSize 单个日志最大容量\nfunc (w *Writer) SetMaxSize(ms int64) {\n\tif ms < 1 {\n\t\treturn\n\t}\n\tw.mu.Lock()\n\tw.maxSize = ms\n\tw.mu.Unlock()\n}\n\n// SetCons 同时输出控制台\nfunc (w *Writer) SetCons(b bool) {\n\tw.mu.Lock()\n\tw.cons = b\n\tw.mu.Unlock()\n}\n\nfunc (w *Writer) Write(p []byte) (n int, err error) {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\tif w.cons {\n\t\tos.Stderr.Write(p)\n\t}\n\tif w.file == nil {\n\t\tif err := w.rotate(); err != nil {\n\t\t\tos.Stderr.Write(p)\n\t\t\treturn 0, err\n\t\t}\n\t}\n\n\tt := time.Now()\n\tb := t.AppendFormat(nil, time.RFC3339)\n\n\t// 按天切割\n\tif !bytes.Equal(w.creates[:10], b[:10]) { //2023-04-05\n\t\tgo w.delete() // 每天检测一次旧文件\n\t\tif err := w.rotate(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\t// 按大小切割,需要考虑bw缓冲区中未写入的数据\n\tif w.size+int64(len(p))+int64(w.bw.Buffered()) >= w.maxSize {\n\t\tif err := w.rotate(); err != nil {\n\t\t\treturn 0, err\n\t\t}\n\t}\n\t// n, err = w.file.Write(p)\n\tn, err = w.bw.Write(p)\n\tw.size += int64(n)\n\tif err != nil {\n\t\treturn n, err\n\t}\n\treturn\n}\n\n// rotate 切割文件\nfunc (w *Writer) rotate() error {\n\tnow := time.Now()\n\tif w.file != nil {\n\t\tw.bw.Flush()\n\t\tw.file.Sync()\n\t\tw.file.Close()\n\t\t// 保存\n\t\tfbak := w.fname + w.time2name(w.created)\n\t\tfbakname := fbak + w.fsuffix\n\t\terr := os.Rename(w.fpath, filepath.Join(w.fdir, fbakname))\n\t\tif err == nil {\n\t\t\terr1 := ZipToFile(filepath.Join(w.fdir, fbak+\".zip\"), filepath.Join(w.fdir, fbakname))\n\t\t\tif err1 == nil {\n\t\t\t\tos.Remove(filepath.Join(w.fdir, fbakname))\n\t\t\t} else {\n\t\t\t\tfmt.Println(err1)\n\t\t\t}\n\t\t}\n\n\t\tw.size = 0\n\t}\n\tfinfo, err := os.Stat(w.fpath)\n\tw.created = now\n\tif err == nil {\n\t\tw.size = finfo.Size()\n\t\tw.created = finfo.ModTime()\n\t}\n\tw.creates = w.created.AppendFormat(nil, time.RFC3339)\n\tfout, err := os.OpenFile(w.fpath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666)\n\tif err != nil {\n\t\treturn err\n\t}\n\tw.file = fout\n\tw.bw = bufio.NewWriter(w.file)\n\treturn nil\n}\n\n// 删除旧日志\nfunc (w *Writer) delete() {\n\tif w.maxAge <= 0 {\n\t\treturn\n\t}\n\tdir := filepath.Dir(w.fpath)\n\tfakeNow := time.Now().AddDate(0, 0, -w.maxAge)\n\tdirs, err := os.ReadDir(dir)\n\tif err != nil {\n\t\treturn\n\t}\n\tfor _, path := range dirs {\n\t\tname := path.Name()\n\t\tif path.IsDir() {\n\t\t\tcontinue\n\t\t}\n\t\tt, err := w.name2time(name)\n\t\t// 只删除满足格式的文件\n\t\tif err == nil && t.Before(fakeNow) {\n\t\t\tos.Remove(filepath.Join(dir, name))\n\t\t}\n\t}\n}\n\nfunc (w *Writer) name2time(name string) (time.Time, error) {\n\tname = strings.TrimPrefix(name, filepath.Base(w.fname))\n\tname = strings.TrimSuffix(name, w.zipsuffix)\n\t// 改为微秒级别的文件后缀，避免1s内大量写入造成多次rotate，而覆盖丢失之前的日志文件\n\treturn time.Parse(\".2006-01-02-150405.000000\", name)\n}\nfunc (w *Writer) time2name(t time.Time) string {\n\treturn t.Format(\".2006-01-02-150405.000000\")\n}\n\nfunc (w *Writer) Close() error {\n\tw.flush()\n\treturn w.close()\n}\n\n// close closes the file if it is open.\nfunc (w *Writer) close() error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\tif w.file == nil {\n\t\treturn nil\n\t}\n\tw.file.Sync()\n\terr := w.file.Close()\n\tw.file = nil\n\treturn err\n}\n\nfunc (w *Writer) flush() error {\n\tw.mu.Lock()\n\tdefer w.mu.Unlock()\n\tif w.bw == nil {\n\t\treturn nil\n\t}\n\treturn w.bw.Flush()\n}\n\n// ZipToFile 压缩至文件\n// @params dst string 压缩文件目标路径\n// @params src string 待压缩源文件/目录路径\n// @return     error  错误信息\nfunc ZipToFile(dst, src string) error {\n\t// 创建一个ZIP文件\n\tfw, err := os.Create(filepath.Clean(dst))\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer fw.Close()\n\n\t// 执行压缩\n\treturn Zip(fw, src)\n}\n\n// Zip 压缩文件或目录\n// @params dst io.Writer 压缩文件可写流\n// @params src string    待压缩源文件/目录路径\nfunc Zip(dst io.Writer, src string) error {\n\t// 强转一下路径\n\tsrc = filepath.Clean(src)\n\t// 提取最后一个文件或目录的名称\n\tbaseFile := filepath.Base(src)\n\t// 判断src是否存在\n\t_, err := os.Stat(src)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// 通文件流句柄创建一个ZIP压缩包\n\tzw := zip.NewWriter(dst)\n\t// 延迟关闭这个压缩包\n\tdefer zw.Close()\n\n\t// 通过filepath封装的Walk来递归处理源路径到压缩文件中\n\treturn filepath.Walk(src, func(path string, info fs.FileInfo, err error) error {\n\t\t// 是否存在异常\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// 通过原始文件头信息，创建zip文件头信息\n\t\tzfh, err := zip.FileInfoHeader(info)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// 赋值默认的压缩方法，否则不压缩\n\t\tzfh.Method = zip.Deflate\n\n\t\t// 移除绝对路径\n\t\ttmpPath := path\n\t\tindex := strings.Index(tmpPath, baseFile)\n\t\tif index > -1 {\n\t\t\ttmpPath = tmpPath[index:]\n\t\t}\n\t\t// 替换文件名，并且去除前后 \"\\\" 或 \"/\"\n\t\ttmpPath = strings.Trim(tmpPath, string(filepath.Separator))\n\t\t// 替换一下分隔符，zip不支持 \"\\\\\"\n\t\tzfh.Name = strings.ReplaceAll(tmpPath, \"\\\\\", \"/\")\n\t\t// 目录需要拼上一个 \"/\" ，否则会出现一个和目录一样的文件在压缩包中\n\t\tif info.IsDir() {\n\t\t\tzfh.Name += \"/\"\n\t\t}\n\n\t\t// 写入文件头信息，并返回一个ZIP文件写入句柄\n\t\tzfw, err := zw.CreateHeader(zfh)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// 仅在他是标准文件时进行文件内容写入\n\t\tif zfh.Mode().IsRegular() {\n\t\t\t// 打开要压缩的文件\n\t\t\tsfr, err := os.Open(path)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t\tdefer sfr.Close()\n\n\t\t\t// 将srcFileReader拷贝到zipFilWrite中\n\t\t\t_, err = io.Copy(zfw, sfr)\n\t\t\tif err != nil {\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\n\t\t// 搞定\n\t\treturn nil\n\t})\n}\n"
  }
]