Repository: aceld/zinx Branch: master Commit: 6fb5bd58d731 Files: 233 Total size: 532.8 KB Directory structure: gitextract_ynwx33qg/ ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ └── workflows/ │ └── reviewdog.yml ├── .gitignore ├── .golangci.yaml ├── LICENSE ├── Makefile ├── README-CN.md ├── README.md ├── examples/ │ ├── zinx_RequestPollMode/ │ │ ├── client/ │ │ │ └── client.go │ │ ├── no_pool_mode_server/ │ │ │ └── NoPoolModeServer.go │ │ └── pool_mode_server/ │ │ └── PoolModeServer.go │ ├── zinx_async_op/ │ │ ├── async_op_apis/ │ │ │ └── user_async_api.go │ │ ├── client/ │ │ │ └── client.go │ │ ├── db_model/ │ │ │ └── user_dao.go │ │ ├── msg_struct/ │ │ │ └── user_login.go │ │ ├── router/ │ │ │ └── login.go │ │ └── server/ │ │ └── server.go │ ├── zinx_client/ │ │ ├── Makefile │ │ ├── build.sh │ │ ├── c_router/ │ │ │ ├── hello.go │ │ │ └── ping.go │ │ ├── main.go │ │ └── version │ ├── zinx_client_old/ │ │ ├── Makefile │ │ ├── build.sh │ │ ├── c_router/ │ │ │ ├── hello.go │ │ │ └── ping.go │ │ ├── main.go │ │ └── version │ ├── zinx_closecallback/ │ │ ├── client/ │ │ │ └── client.go │ │ ├── router/ │ │ │ └── ping_router.go │ │ └── server/ │ │ └── server.go │ ├── zinx_decoder/ │ │ ├── README.MD │ │ ├── bili/ │ │ │ ├── README.MD │ │ │ ├── main.go │ │ │ └── router/ │ │ │ ├── bili0x10router.go │ │ │ ├── bili0x13router.go │ │ │ ├── bili0x14router.go │ │ │ ├── bili0x15router.go │ │ │ └── bili0x16router.go │ │ ├── client/ │ │ │ └── client.go │ │ ├── router/ │ │ │ ├── htlvcrcbusinessrouter.go │ │ │ └── tlvbusinessrouter.go │ │ └── server/ │ │ └── server.go │ ├── zinx_dynamic_bind/ │ │ ├── client/ │ │ │ └── client.go │ │ └── server/ │ │ ├── conf/ │ │ │ └── zinx.json │ │ └── server.go │ ├── zinx_heartbeat/ │ │ ├── client/ │ │ │ └── client.go │ │ ├── client_default/ │ │ │ └── client_default.go │ │ ├── server/ │ │ │ ├── conf/ │ │ │ │ └── zinx.json │ │ │ └── server.go │ │ └── server_default/ │ │ ├── conf/ │ │ │ └── zinx.json │ │ └── server_default.go │ ├── zinx_interceptor/ │ │ ├── client/ │ │ │ └── client.go │ │ ├── interceptors/ │ │ │ └── interceptor_1.go │ │ ├── router/ │ │ │ └── route.go │ │ └── server/ │ │ └── server.go │ ├── zinx_kcp/ │ │ ├── client/ │ │ │ └── kcp_client.go │ │ └── server/ │ │ └── server.go │ ├── zinx_logger/ │ │ ├── client/ │ │ │ └── client.go │ │ └── server/ │ │ ├── my_logger.go │ │ └── server.go │ ├── zinx_metrics/ │ │ ├── client/ │ │ │ ├── c1/ │ │ │ │ └── client.go │ │ │ └── c2/ │ │ │ └── client.go │ │ └── server/ │ │ ├── conf/ │ │ │ └── zinx.json │ │ └── server.go │ ├── zinx_mutiport/ │ │ ├── client8999/ │ │ │ └── client.go │ │ ├── client9000/ │ │ │ └── client.go │ │ └── server/ │ │ └── server.go │ ├── zinx_new_router/ │ │ ├── client/ │ │ │ └── client.go │ │ └── server/ │ │ ├── conf/ │ │ │ └── zinx.json │ │ └── server.go │ ├── zinx_protobuf/ │ │ ├── client/ │ │ │ └── client.go │ │ └── server/ │ │ └── server.go │ ├── zinx_routerSlices/ │ │ ├── client/ │ │ │ └── client.go │ │ ├── default_func_server/ │ │ │ └── server.go │ │ ├── router_func_server/ │ │ │ └── server.go │ │ └── router_group_server/ │ │ └── server.go │ ├── zinx_server/ │ │ ├── Makefile │ │ ├── build.sh │ │ ├── conf/ │ │ │ └── zinx.json │ │ ├── dockerfile │ │ ├── main.go │ │ ├── s_router/ │ │ │ ├── hello.go │ │ │ └── ping.go │ │ └── version │ ├── zinx_tls/ │ │ ├── client/ │ │ │ └── client.go │ │ └── server/ │ │ └── server.go │ ├── zinx_version_ex/ │ │ ├── ZinxV0.10Test/ │ │ │ ├── client0/ │ │ │ │ └── Client0.go │ │ │ ├── client1/ │ │ │ │ └── Client1.go │ │ │ └── server/ │ │ │ ├── Server.go │ │ │ └── conf/ │ │ │ └── zinx.json │ │ ├── ZinxV0.11Test/ │ │ │ ├── client0/ │ │ │ │ └── Client0.go │ │ │ ├── client1/ │ │ │ │ └── Client1.go │ │ │ └── server/ │ │ │ ├── Server.go │ │ │ └── conf/ │ │ │ └── zinx.json │ │ ├── ZinxV0.1Test/ │ │ │ ├── client/ │ │ │ │ └── client.go │ │ │ └── server/ │ │ │ └── server.go │ │ ├── ZinxV0.2Test/ │ │ │ ├── client/ │ │ │ │ └── client.go │ │ │ └── server/ │ │ │ └── server.go │ │ ├── ZinxV0.3Test/ │ │ │ ├── client/ │ │ │ │ └── client.go │ │ │ └── server/ │ │ │ └── server.go │ │ ├── ZinxV0.4Test/ │ │ │ ├── client/ │ │ │ │ └── client.go │ │ │ └── server/ │ │ │ ├── conf/ │ │ │ │ └── zinx.json │ │ │ └── server.go │ │ ├── ZinxV0.5Test/ │ │ │ ├── client/ │ │ │ │ └── client.go │ │ │ └── server/ │ │ │ ├── conf/ │ │ │ │ └── zinx.json │ │ │ └── server.go │ │ ├── ZinxV0.6Test-V0.7Test/ │ │ │ ├── client0/ │ │ │ │ └── Client0.go │ │ │ ├── client1/ │ │ │ │ └── Client1.go │ │ │ └── server/ │ │ │ ├── Server.go │ │ │ └── conf/ │ │ │ └── zinx.json │ │ ├── ZinxV0.8Test/ │ │ │ ├── client0/ │ │ │ │ └── Client0.go │ │ │ ├── client1/ │ │ │ │ └── Client1.go │ │ │ └── server/ │ │ │ ├── Server.go │ │ │ └── conf/ │ │ │ └── zinx.json │ │ ├── ZinxV0.9Test/ │ │ │ ├── client0/ │ │ │ │ └── Client0.go │ │ │ ├── client1/ │ │ │ │ └── Client1.go │ │ │ └── server/ │ │ │ ├── Server.go │ │ │ └── conf/ │ │ │ └── zinx.json │ │ ├── datapackDemo/ │ │ │ ├── client/ │ │ │ │ └── client.go │ │ │ └── server/ │ │ │ └── server.go │ │ └── protoDemo/ │ │ ├── main.go │ │ └── pb/ │ │ ├── Person.pb.go │ │ └── Person.proto │ └── zinx_websocket/ │ ├── client/ │ │ └── client.go │ ├── minicode/ │ │ ├── .eslintrc.js │ │ ├── app.js │ │ ├── app.json │ │ ├── app.wxss │ │ ├── index/ │ │ │ ├── index.js │ │ │ ├── index.json │ │ │ ├── index.wxml │ │ │ └── index.wxss │ │ ├── package.json │ │ ├── project.config.json │ │ ├── project.private.config.json │ │ └── sitemap.json │ └── server/ │ └── server.go ├── go.mod ├── go.sum ├── logo/ │ └── zinxlogo.go ├── zasync_op/ │ ├── async_op.go │ ├── async_op_result.go │ └── async_worker.go ├── zconf/ │ ├── env.go │ ├── userconf.go │ └── zconf.go ├── zdecoder/ │ ├── crc.go │ ├── htlvcrcdecoder.go │ ├── ltvdecoder_little.go │ └── tlvdecoder.go ├── ziface/ │ ├── iclient.go │ ├── iconnection.go │ ├── iconnmanager.go │ ├── idatapack.go │ ├── idecoder.go │ ├── iheartbeat.go │ ├── iinterceptor.go │ ├── ilengthfield.go │ ├── ilogger.go │ ├── imessage.go │ ├── imsghandler.go │ ├── inotify.go │ ├── irequest.go │ ├── irouter.go │ ├── iserver.go │ └── options.go ├── zinterceptor/ │ ├── chain.go │ ├── framedecoder.go │ └── interceptor.go ├── zinx_app_demo/ │ └── mmo_game/ │ ├── README-CN.md │ ├── README.md │ ├── api/ │ │ ├── move.go │ │ └── world_chat.go │ ├── client_AI_robot.go │ ├── conf/ │ │ └── zinx.json │ ├── core/ │ │ ├── aoi.go │ │ ├── aoi_test.go │ │ ├── grid.go │ │ ├── player.go │ │ └── world_manager.go │ ├── pb/ │ │ ├── build.sh │ │ ├── msg.pb.go │ │ └── msg.proto │ └── server.go ├── zlog/ │ ├── default.go │ ├── logger_core.go │ ├── stdzlog.go │ └── zlog_test.go ├── znet/ │ ├── acceptdelay.go │ ├── acceptdelay_test.go │ ├── callbacks.go │ ├── callbacks_test.go │ ├── chainbuilder.go │ ├── client.go │ ├── connection.go │ ├── connmanager.go │ ├── defaultrouterfunc.go │ ├── heartbeat.go │ ├── kcp_connection.go │ ├── msghandler.go │ ├── options.go │ ├── request.go │ ├── request_func.go │ ├── router.go │ ├── routerSilces_test.go │ ├── server.go │ ├── server_test.go │ └── ws_connection.go ├── znotify/ │ ├── notify.go │ └── notify_test.go ├── zpack/ │ ├── datapack_ltv_littleendian.go │ ├── datapack_tlv_bigendian.go │ ├── datapack_tlv_bigendian_test.go │ ├── message.go │ └── packfactory.go ├── ztimer/ │ ├── delayfunc.go │ ├── delayfunc_test.go │ ├── timer.go │ ├── timer_test.go │ ├── timerscheduler.go │ ├── timerscheduler_test.go │ ├── timewheel.go │ └── timewheel_test.go └── zutils/ ├── hash.go ├── shard_lock_map.go ├── shard_lock_map_bench_test.go ├── shard_lock_map_test.go ├── snowflake_uuid.go ├── snowflake_uuid_test.go └── witer.go ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.aspx linguist-language=Go ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: [aceld] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry #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'] ================================================ FILE: .github/workflows/reviewdog.yml ================================================ name: reviewdog on: [pull_request] permissions: contents: read pull-requests: write jobs: golangci-lint: runs-on: ubuntu-latest name: runner / golangci-lint steps: - name: Check out code into the Go module directory uses: actions/checkout@v3 - name: golangci-lint uses: reviewdog/action-golangci-lint@dd3fda91790ca90e75049e5c767509dc0ec7d99b with: github_token: ${{ secrets.github_token }} # Change reviewdog reporter if you need [github-pr-check,github-check,github-pr-review]. reporter: github-pr-review # Report all results. filter_mode: nofilter # Exit with 1 when it finds at least one finding. fail_on_error: true #golangci_lint_flags golangci_lint_version: v1.64.7 ================================================ FILE: .gitignore ================================================ .idea .vscode /zinx_app_demo/mmo_game/game_client/client_Data/ /zinx_app_demt/mmo_game/mmo_game_log/ *.log ### bin examples/zinx_server/zinx_server examples/zinx_server/server examples/zinx_client/client .DS_Store log /examples/zinx_websocket/minicode/node_modules /examples/zinx_websocket/minicode/miniprogram_npm rebase ================================================ FILE: .golangci.yaml ================================================ run: timeout: 30m skip-dirs: - examples linters: disable-all: true enable: #- unused - ineffassign - goimports - gofmt - misspell - unparam - unconvert - govet # - errcheck - staticcheck linters-settings: staticcheck: go: "1.17" checks: - "all" - "-SA1019" unused: go: "1.17" ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2024 Aceld(刘丹冰) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ .PHONY: build SERVICE := zinx CUR_PWD := $(shell pwd) SERVER_DEMO_PATH := $(CUR_PWD)/examples/zinx_server CLIENT_DEMO_PATH := $(CUR_PWD)/examples/zinx_client SERVER_DEMO_BIN := $(SERVER_DEMO_PATH)/server CLIENT_DEMO_BIN := $(CLIENT_DEMO_PATH)/client AUTHOR := $(shell git log --pretty=format:"%an"|head -n 1) VERSION := $(shell git rev-list HEAD | head -1) BUILD_INFO := $(shell git log --pretty=format:"%s" | head -1) BUILD_DATE := $(shell date +%Y-%m-%d\ %H:%M:%S) export GO111MODULE=on LD_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)"' TEST_FILES := znet/acceptdelay_test.go znet/acceptdelay.go default: build build: go build -ldflags $(LD_FLAGS) -gcflags "-N" -o $(SERVER_DEMO_BIN) $(SERVER_DEMO_PATH)/main.go go build -ldflags $(LD_FLAGS) -gcflags "-N" -o $(CLIENT_DEMO_BIN) $(CLIENT_DEMO_PATH)/main.go test: go test -v -cover $(TEST_FILES) clean: rm $(SERVER_DEMO_BIN) rm $(CLIENT_DEMO_BIN) ================================================ FILE: README-CN.md ================================================ # [English](README.md) | 简体中文 [![License](https://img.shields.io/badge/License-MIT-black.svg)](LICENSE) [![Discord](https://img.shields.io/badge/zinx-Discord在线社区-blue.svg)](https://discord.gg/xQ8Xxfyfcz) [![Gitter](https://img.shields.io/badge/zinx-Gitter在线交流-green.svg)](https://gitter.im/zinx_go/community) [![zinx tutorial](https://img.shields.io/badge/Zinx教程-YuQue-red.svg)](https://www.yuque.com/aceld/npyr8s/bgftov) [![Original Book of Zinx](https://img.shields.io/badge/原创书籍-YuQue-black.svg)](https://www.yuque.com/aceld) Zinx 是一个基于Golang的轻量级并发服务器框架 ## 开发者文档 [ < Zinx Wiki : English > ](https://github.com/aceld/zinx/wiki) [ < Zinx 文档 : 简体中文> ](https://www.yuque.com/aceld/tsgooa/sbvzgczh3hqz8q3l) > **说明**:目前zinx已经在很多企业进行开发使用,具体使用领域包括:后端模块的消息中转、长连接游戏服务器、Web框架中的消息处理插件等。zinx的定位是代码简洁,让更多的开发者迅速的了解框架的内脏细节并且可以快速基于zinx DIY(二次开发)一款适合自己企业场景的模块。 --- ## zinx源码地址 | platform | Entry | | ---- | ---- | |Github| https://github.com/aceld/zinx | |Gitcode|https://gitcode.com/aceld/zinx| |Gitee|https://gitee.com/Aceld/zinx| ### 官网 http://zinx.me --- ## 在线开发教程 ### 文字教程 | platform | Entry | | ---- | ---- | | | [Zinx Framework tutorial-Lightweight server based on Golang](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)| ||[《Golang轻量级并发服务器框架zinx》](https://www.yuque.com/aceld)| ### 视频教程 | platform | online video | | ---- | ---- | | | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.bilibili.com/video/av71067087)| | | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.douyin.com/video/6983301202939333891) | | | [![zinx-youtube](https://s2.ax1x.com/2019/10/14/KSurCR.jpg)](https://www.youtube.com/watch?v=U95iF-HMWsU&list=PL_GrAPKmuajzeNI8HBTi-k5NQO1g0rM-A)| ## 一、写在前面 我们为什么要做Zinx,Golang目前在服务器的应用框架很多,但是应用在游戏领域或者其他长连接的领域的轻量级企业框架甚少。 设计Zinx的目的是我们可以通过Zinx框架来了解基于Golang编写一个TCP服务器的整体轮廓,让更多的Golang爱好者能深入浅出的去学习和认识这个领域。 Zinx框架的项目制作采用编码和学习教程同步进行,将开发的全部递进和迭代思维带入教程中,而不是一下子给大家一个非常完整的框架去学习,让很多人一头雾水,不知道该如何学起。 教程会一个版本一个版本迭代,每个版本的添加功能都是微小的,让一个服务框架小白,循序渐进的曲线方式了解服务器框架的领域。 当然,最后希望Zinx会有更多的人加入,给我们提出宝贵的意见,让Zinx成为真正的解决企业的服务器框架!在此感谢您的关注! ## 二、初探Zinx架构 ![Zinx框架](https://user-images.githubusercontent.com/7778936/220058446-0ad45112-2225-4b71-b0d8-69a7f3cee5ca.jpg) ![流程图](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) ![zinx-start](https://user-images.githubusercontent.com/7778936/126594039-98dddd10-ec6a-4881-9e06-a09ec34f1af7.gif) ## 三、Zinx开发接口文档 ### (1)快速开始 [](https://github.com/xxl6097/tcptest) **版本** Golang 1.17+ DownLoad zinx Source ```bash $go get github.com/aceld/zinx ``` > note: Golang Version 1.17+ #### Zinx-Server ```go package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // PingRouter MsgId=1的路由 type PingRouter struct { znet.BaseRouter } //Ping Handle MsgId=1的路由处理方法 func (r *PingRouter) Handle(request ziface.IRequest) { //读取客户端的数据 fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) } func main() { //1 创建一个server服务 s := znet.NewServer() //2 配置路由 s.AddRouter(1, &PingRouter{}) //3 启动服务 s.Serve() } ``` Run Server ```bash $ go run server.go ``` ```bash ██ ▀▀ ████████ ████ ██▄████▄ ▀██ ██▀ ▄█▀ ██ ██▀ ██ ████ ▄█▀ ██ ██ ██ ▄██▄ ▄██▄▄▄▄▄ ▄▄▄██▄▄▄ ██ ██ ▄█▀▀█▄ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀ ▀▀▀ ┌──────────────────────────────────────────────────────┐ │ [Github] https://github.com/aceld │ │ [tutorial] https://www.yuque.com/aceld/npyr8s/bgftov │ └──────────────────────────────────────────────────────┘ [Zinx] Version: V1.0, MaxConn: 12000, MaxPacketSize: 4096 ===== Zinx Global Config ===== TCPServer: Host: 0.0.0.0 TCPPort: 8999 Name: ZinxServerApp Version: V1.0 MaxPacketSize: 4096 MaxConn: 12000 WorkerPoolSize: 10 MaxWorkerTaskLen: 1024 MaxMsgChanLen: 1024 ConfFilePath: /Users/Aceld/go/src/zinx-usage/quick_start/conf/zinx.json LogDir: /Users/Aceld/go/src/zinx-usage/quick_start/log LogFile: LogIsolationLevel: 0 HeartbeatMax: 10 ============================== 2023/03/09 18:39:49 [INFO]msghandler.go:61: Add api msgID = 1 2023/03/09 18:39:49 [INFO]server.go:112: [START] Server name: ZinxServerApp,listenner at IP: 0.0.0.0, Port 8999 is starting 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 0 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 1 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 3 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 2 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 4 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 6 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 7 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 8 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 9 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 5 is started. 2023/03/09 18:39:49 [INFO]server.go:134: [START] start Zinx server ZinxServerApp succ, now listenning... ``` #### Zinx-Client ```go package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" "time" ) //客户端自定义业务 func pingLoop(conn ziface.IConnection) { for { err := conn.SendMsg(1, []byte("Ping...Ping...Ping...[FromClient]")) if err != nil { fmt.Println(err) break } time.Sleep(1 * time.Second) } } //创建连接的时候执行 func onClientStart(conn ziface.IConnection) { fmt.Println("onClientStart is Called ... ") go pingLoop(conn) } func main() { //创建Client客户端 client := znet.NewClient("127.0.0.1", 8999) //设置连接建立成功后的钩子函数 client.SetOnConnStart(onClientStart) //启动客户端 client.Start() //防止进程退出,等待中断信号 select {} } ``` Run Client ```bash $ go run client.go 2023/03/09 19:04:54 [INFO]client.go:73: [START] Zinx Client LocalAddr: 127.0.0.1:55294, RemoteAddr: 127.0.0.1:8999 2023/03/09 19:04:54 [INFO]connection.go:354: ZINX CallOnConnStart.... ``` Terminal of Zinx Print: ```bash recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] ... ``` ### (2)Zinx配置文件 ```json { "Name":"zinx v-0.10 demoApp", "Host":"0.0.0.0", "TCPPort":9090, "MaxConn":3, "WorkerPoolSize":10, "LogDir": "./mylog", "LogFile":"app.log", "LogSaveDays":15, "LogCons": true, "LogIsolationLevel":0 } ``` `Name`:服务器应用名称 `Host`:服务器IP `TcpPort`:服务器监听端口 `MaxConn`:允许的客户端连接最大数量 `WorkerPoolSize`:工作任务池最大工作Goroutine数量 `LogDir`: 日志文件夹 `LogFile`: 日志文件名称(如果不提供,则日志信息打印到Stderr) `LogIsolationLevel`: 日志隔离级别 0:全开, 1:关debug, 2:关debug/info, 3:关debug/info/warn --- #### 开发者 | **Zinx** | **开发者** | |--------------------------------------------------------| ---- | | [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))| | [moke-kit(微服务框架)](https://github.com/GStones/moke-kit) |GStones([@GStones](https://github.com/GStones))| | [zinx(C++)](https://github.com/marklion/zinx) |刘洋([@marklion](https://github.com/marklion))| | [zinx(Lua)](https://github.com/huqitt/zinx-lua) |胡琪([@huqitt](https://github.com/huqitt))| | [ginx(Java)](https://github.com/ModuleCode/ginx) |ModuleCode([@ModuleCode](https://github.com/ModuleCode))| --- 感谢所有为zinx贡献的开发者 --- ### 关于作者: 作者:`Aceld(刘丹冰)` `mail`: [danbing.at@gmail.com](mailto:danbing.at@gmail.com) `github`: [https://github.com/aceld](https://github.com/aceld) `原创书籍`: [https://www.yuque.com/aceld](https://www.yuque.com/aceld) ### 加入Zinx技术社区 | platform | Entry | | ---- | ---- | | | https://discord.gg/xQ8Xxfyfcz| | | 加微信: `ace_ld` 或扫二维码,备注`zinx`即可。
| || **WeChat Public Account** | || **QQ Group** | ================================================ FILE: README.md ================================================ # English | [简体中文](README-CN.md) [![License](https://img.shields.io/badge/License-MIT-black.svg)](LICENSE) [![Discord](https://img.shields.io/badge/zinx-Discord-blue.svg)](https://discord.gg/xQ8Xxfyfcz) [![Gitter](https://img.shields.io/badge/zinx-Gitter-green.svg)](https://gitter.im/zinx_go/community) [![zinx tutorial](https://img.shields.io/badge/ZinxTutorial-YuQue-red.svg)](https://www.yuque.com/aceld/npyr8s/bgftov) [![Original Book of Zinx](https://img.shields.io/badge/OriginalBook-YuQue-black.svg)](https://www.yuque.com/aceld) Zinx is a lightweight concurrent server framework based on Golang. ## Document [ < Zinx Wiki : English > ](https://github.com/aceld/zinx/wiki) [ < Zinx 文档 : 简体中文> ](https://www.yuque.com/aceld/tsgooa/sbvzgczh3hqz8q3l) > **Note**: > 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. > 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. --- ## Source of Zinx | platform | Entry | | ---- | ---- | |Github| https://github.com/aceld/zinx | |Gitcode|https://gitcode.com/aceld/zinx| |Gitee|https://gitee.com/Aceld/zinx| ### Website http://zinx.me --- ## Online Tutorial | platform | Entry | | ---- | ---- | | | [Zinx Framework tutorial-Lightweight server based on Golang](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)| ||[《Golang轻量级并发服务器框架zinx》](https://www.yuque.com/aceld)| ## Online Tutorial Video | platform | online video | | ---- | ---- | | | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.bilibili.com/video/av71067087)| | | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.douyin.com/video/6983301202939333891) | | | [![zinx-youtube](https://s2.ax1x.com/2019/10/14/KSurCR.jpg)](https://www.youtube.com/watch?v=U95iF-HMWsU&list=PL_GrAPKmuajzeNI8HBTi-k5NQO1g0rM-A)| ## I. One word that has been said before Why 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. The 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. The 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. The 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. Of 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! ## II. Zinx architecture ![Zinx框架](https://user-images.githubusercontent.com/7778936/220058446-0ad45112-2225-4b71-b0d8-69a7f3cee5ca.jpg) ![流程图](https://raw.githubusercontent.com/wenyoufu/testaaaaaa/master/%E6%B5%81%E7%A8%8B%E5%9B%BE-en.jpg) ![zinx-start](https://user-images.githubusercontent.com/7778936/126594039-98dddd10-ec6a-4881-9e06-a09ec34f1af7.gif) ## III. Zinx development API documentation ### (1) QuickStart [](https://github.com/xxl6097/tcptest) DownLoad zinx Source ```bash $go get github.com/aceld/zinx ``` > note: Golang Version 1.17+ #### Zinx-Server ```go package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // PingRouter MsgId=1 type PingRouter struct { znet.BaseRouter } //Ping Handle MsgId=1 func (r *PingRouter) Handle(request ziface.IRequest) { //read client data fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) } func main() { //1 Create a server service s := znet.NewServer() //2 configure routing s.AddRouter(1, &PingRouter{}) //3 start service s.Serve() } ``` Run Server ```bash $ go run server.go ``` ```bash ██ ▀▀ ████████ ████ ██▄████▄ ▀██ ██▀ ▄█▀ ██ ██▀ ██ ████ ▄█▀ ██ ██ ██ ▄██▄ ▄██▄▄▄▄▄ ▄▄▄██▄▄▄ ██ ██ ▄█▀▀█▄ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀ ▀▀▀ ┌──────────────────────────────────────────────────────┐ │ [Github] https://github.com/aceld │ │ [tutorial] https://www.yuque.com/aceld/npyr8s/bgftov │ └──────────────────────────────────────────────────────┘ [Zinx] Version: V1.0, MaxConn: 12000, MaxPacketSize: 4096 ===== Zinx Global Config ===== Host: 0.0.0.0 TCPPort: 8999 Name: ZinxServerApp Version: V1.0 MaxPacketSize: 4096 MaxConn: 12000 WorkerPoolSize: 10 MaxWorkerTaskLen: 1024 MaxMsgChanLen: 1024 ConfFilePath: /Users/Aceld/go/src/zinx-usage/quick_start/conf/zinx.json LogDir: /Users/Aceld/go/src/zinx-usage/quick_start/log LogFile: LogIsolationLevel: 0 HeartbeatMax: 10 ============================== 2023/03/09 18:39:49 [INFO]msghandler.go:61: Add api msgID = 1 2023/03/09 18:39:49 [INFO]server.go:112: [START] Server name: ZinxServerApp,listenner at IP: 0.0.0.0, Port 8999 is starting 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 0 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 1 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 3 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 2 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 4 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 6 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 7 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 8 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 9 is started. 2023/03/09 18:39:49 [INFO]msghandler.go:66: Worker ID = 5 is started. 2023/03/09 18:39:49 [INFO]server.go:134: [START] start Zinx server ZinxServerApp succ, now listenning... ``` #### Zinx-Client ```go package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" "time" ) //Client custom business func pingLoop(conn ziface.IConnection) { for { err := conn.SendMsg(1, []byte("Ping...Ping...Ping...[FromClient]")) if err != nil { fmt.Println(err) break } time.Sleep(1 * time.Second) } } //Executed when a connection is created func onClientStart(conn ziface.IConnection) { fmt.Println("onClientStart is Called ... ") go pingLoop(conn) } func main() { //Create a client client client := znet.NewClient("127.0.0.1", 8999) //Set the hook function after the link is successfully established client.SetOnConnStart(onClientStart) //start the client client.Start() //Prevent the process from exiting, waiting for an interrupt signal select {} } ``` Run Client ```bash $ go run client.go 2023/03/09 19:04:54 [INFO]client.go:73: [START] Zinx Client LocalAddr: 127.0.0.1:55294, RemoteAddr: 127.0.0.1:8999 2023/03/09 19:04:54 [INFO]connection.go:354: ZINX CallOnConnStart.... ``` Terminal of Zinx Print: ```bash recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] recv from client : msgId= 1 , data= Ping...Ping...Ping...[FromClient] ... ``` ### (2) Zinx configuration file ```json { "Name":"zinx v-0.10 demoApp", "Host":"0.0.0.0", "TCPPort":9090, "MaxConn":3, "WorkerPoolSize":10, "LogDir": "./mylog", "LogFile":"app.log", "LogSaveDays":15, "LogCons": true, "LogIsolationLevel":0 } ``` `Name`:Server Application Name `Host`:Server IP `TcpPort`:Server listening port `MaxConn`:Maximum number of client links allowed `WorkerPoolSize`:Maximum number of working Goroutines in the work task pool `LogDir`: Log folder `LogFile`: Log file name (if not provided, log information is printed to Stderr) `LogIsolationLevel`: Log Isolation Level -0: Full On 1: Off debug 2: Off debug/info 3: Off debug/info/warn --- #### Developers | **Zinx** | **Authors** | |----------------------------------------------------------------| ---- | | [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))| | [moke-kit(Microservices)](https://github.com/GStones/moke-kit) |GStones([@GStones](https://github.com/GStones))| | [zinx(C++)](https://github.com/marklion/zinx) |刘洋([@marklion](https://github.com/marklion))| | [zinx(Lua)](https://github.com/huqitt/zinx-lua) |胡琪([@huqitt](https://github.com/huqitt))| | [ginx(Java)](https://github.com/ModuleCode/ginx) |ModuleCode([@ModuleCode](https://github.com/ModuleCode))| --- Thanks to all the developers who contributed to Zinx! --- ### About the author `name`:`Aceld(刘丹冰)` `mail`: [danbing.at@gmail.com](mailto:danbing.at@gmail.com) `github`: [https://github.com/aceld](https://github.com/aceld) `original work`: [https://www.yuque.com/aceld](https://www.yuque.com/aceld) ### Join the Zinx community | platform | Entry | | ---- | ---- | | | https://discord.gg/xQ8Xxfyfcz| | | 加微信: `ace_ld` 或扫二维码,备注`zinx`即可。
| || **WeChat Public Account** | || **QQ Group** | ================================================ FILE: examples/zinx_RequestPollMode/client/client.go ================================================ package main import ( "fmt" "os" "os/signal" "time" "github.com/aceld/zinx/examples/zinx_client/c_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) // Custom business logic of the client (客户端自定义业务) func business(conn ziface.IConnection) { for i := 0; i < 100; i++ { err := conn.SendMsg(1, []byte("Ping...[FromClient]")) if err != nil { fmt.Println(err) zlog.Error(err) break } err = conn.SendMsg(2, []byte("Ping...[FromClient]")) if err != nil { fmt.Println(err) zlog.Error(err) break } } } // Function to execute when the connection is created (创建连接的时候执行) func DoClientConnectedBegin(conn ziface.IConnection) { zlog.Debug("DoConnectionBegin is Called ... ") // Set two connection properties after the connection is created (设置两个连接属性,在连接创建之后) conn.SetProperty("Name", "刘丹冰Aceld") conn.SetProperty("Home", "https://yuque.com/aceld") go business(conn) } // Function to execute when the connection is lost (连接断开的时候执行) func DoClientConnectedLost(conn ziface.IConnection) { // Get the Name and Home properties of the connection before it is destroyed // (在连接销毁之前,查询conn的Name,Home属性) if name, err := conn.GetProperty("Name"); err == nil { zlog.Debug("Conn Property Name = ", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Debug("Conn Property Home = ", home) } zlog.Debug("DoClientConnectedLost is Called ... ") } func main() { // Create a client handle using Zinx's Method (创建一个Client句柄,使用Zinx的方法) client := znet.NewClient("127.0.0.1", 8999) // Set the business logic to execute when the connection is created or lost // (添加首次建立连接时的业务) client.SetOnConnStart(DoClientConnectedBegin) client.SetOnConnStop(DoClientConnectedLost) // Register routers for the messages received from the server // (注册收到服务器消息业务路由) client.AddRouter(2, &c_router.PingRouter{}) client.AddRouter(3, &c_router.HelloRouter{}) // Start the client client.Start() // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) client.Stop() time.Sleep(time.Second * 2) } ================================================ FILE: examples/zinx_RequestPollMode/no_pool_mode_server/NoPoolModeServer.go ================================================ package main import ( "fmt" "time" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // 如果不使用对象池模式则可以直接传递但是产生大量的 Request 对象 func NoPoll1(request ziface.IRequest) { request.Set("num", 1) go NoPoll2(request) } func NoPoll2(request ziface.IRequest) { time.Sleep(time.Second * 3) get, _ := request.Get("num") fmt.Printf("num:%v \n", get) } func NoPoll4(request ziface.IRequest) { // 非对象池模式永远不会影响原本的 Request request.Set("num", 3) } func main() { // 关闭 Request 对象池模式 server := znet.NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: "127.0.0.1", RequestPoolMode: false}) server.AddRouterSlices(1, NoPoll1) server.AddRouterSlices(2, NoPoll4) server.Serve() } ================================================ FILE: examples/zinx_RequestPollMode/pool_mode_server/PoolModeServer.go ================================================ package main import ( "fmt" "time" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) func Poll1(request ziface.IRequest) { // 如果需要连接信息 request.Set("conn", request.GetConnection()) request.Set("num", 1) fmt.Printf("request 1 addr:%p,conn:%p \n", &request, request.GetConnection()) // 需要新线程同时也需要上下文的情况,则需要调用 copy 方法拷贝一份 cp := request.Copy() go Poll2(cp) // 如果不使用 copy 方法拷贝对象则会出现同一个对象但是信息可能不一致的问题,不启动 poll2 会更直接 go Poll3(request) } func Poll2(request ziface.IRequest) { defer func() { if err := recover(); err != nil { // 接收一个panic fmt.Println(err) } }() get_conn, ok := request.Get("conn") if ok { // 如果直接取用则会导致空指针 request.GetConnection().GetConnID() // 打印出的 Request 对象的地址是不一致的 conn := get_conn.(ziface.IConnection) fmt.Printf("request copy addr:%p,conn:%p \n", &request, conn) // conn.sendMsg() } } // 如果请求的次数多,则开启对象池且直接传递不copy Request 就可能导致值不一致 func Poll3(request ziface.IRequest) { time.Sleep(time.Second * 3) get, _ := request.Get("num") // 池化对象如果直接传递被影响可能随机打印被修改的值 3 fmt.Printf("num:%v \n", get) } func Poll4(request ziface.IRequest) { // 影响原本的 request 对象 request.Set("num", 3) } func main() { // 开启 Request 对象池模式 server := znet.NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: "127.0.0.1", RequestPoolMode: true}) server.AddRouterSlices(1, Poll1) server.AddRouterSlices(2, Poll4) server.Serve() } ================================================ FILE: examples/zinx_async_op/async_op_apis/user_async_api.go ================================================ package async_op_apis import ( "github.com/aceld/zinx/examples/zinx_async_op/db_model" "github.com/aceld/zinx/zasync_op" "github.com/aceld/zinx/ziface" ) func AsyncUserSaveData(request ziface.IRequest) *zasync_op.AsyncOpResult { opId := 1 // player's unique identifier Id (玩家的唯一标识Id) asyncResult := zasync_op.NewAsyncOpResult(request.GetConnection()) zasync_op.Process( int(opId), func() { // perform db operation (执行db操作) user := db_model.SaveUserData() // set async return result (设置异步返回结果) asyncResult.SetReturnedObj(user) // test active exception (测试主动异常) /* a := 0 b := 1 c := b / a fmt.Println(c) */ }, ) return asyncResult } ================================================ FILE: examples/zinx_async_op/client/client.go ================================================ package main import ( "fmt" "io" "net" "github.com/aceld/zinx/zpack" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:8999") if err != nil { fmt.Println("client start err, exit!", err) return } dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte("async_op_router test=========>"))) _, err = conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } for { headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) if err != nil { fmt.Println("client read head err: ", err) return } msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("client unpack head err: ", err) return } if msgHead.GetDataLen() > 0 { msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("client unpack data err") return } fmt.Printf("==> Client receive Msg: ID = %d, len = %d , data = %s\n", msg.ID, msg.DataLen, msg.Data) } } } ================================================ FILE: examples/zinx_async_op/db_model/user_dao.go ================================================ package db_model import ( "time" "github.com/aceld/zinx/zlog" ) type UserModel struct { UserId uint32 UpdateTime int64 Name string } func SaveUserData() *UserModel { zlog.Debug("SaveUserData IN=================>222") time.Sleep(time.Second * 2) // 模拟db操作需要2秒时间 user := &UserModel{1, time.Now().Unix(), "14March"} zlog.Debug("SaveUserData OUT==================>222") return user } ================================================ FILE: examples/zinx_async_op/msg_struct/user_login.go ================================================ package msg_struct type MsgLoginResponse struct { UserId uint32 UserName string ErrorCode int } ================================================ FILE: examples/zinx_async_op/router/login.go ================================================ package router import ( "encoding/json" "time" "github.com/aceld/zinx/examples/zinx_async_op/async_op_apis" "github.com/aceld/zinx/examples/zinx_async_op/db_model" "github.com/aceld/zinx/examples/zinx_async_op/msg_struct" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) type LoginRouter struct { znet.BaseRouter } func (hr *LoginRouter) Handle(request ziface.IRequest) { zlog.Debug("AsyncOpRouter Handle IN ===>111") asyncResult := async_op_apis.AsyncUserSaveData(request) // // Test DB asynchronous operation(测试DB异步操作) // 测试:执行了一大推业务逻辑,才设置回调函数 // Test: A lot of business logic is executed before setting the callback function time.Sleep(1 * time.Second) // Asynchronous callback (异步回调) asyncResult.OnComplete(func() { zlog.Debug("OnComplete IN===>333") returnedObj := asyncResult.GetReturnedObj() if returnedObj == nil { zlog.Debug("The asynchronous result has not been set when registering the callback function.") return } user := returnedObj.(*db_model.UserModel) userLoginRsp := &msg_struct.MsgLoginResponse{ UserId: user.UserId, UserName: user.Name, ErrorCode: 0, } marshalData, marErr := json.Marshal(userLoginRsp) if marErr != nil { zlog.Error("LoginRouter marErr", marErr.Error()) return } // Send response to the client conn := request.GetConnection() if sendErr := conn.SendMsg(1, marshalData); sendErr != nil { zlog.Error("LoginRouter sendErr", sendErr.Error()) return } zlog.Debug("OnComplete OUT===>333") // Test actively throwing an exception (测试主动异常) /* a := 0 b := 1 c := b / a fmt.Println(c) */ }) // Test: The original thread is blocked for 3 seconds, and the callback function is executed in the original thread, // so it will be executed after 3 seconds // 测试:原来所属的线程阻塞3秒,回调函数因为是回到原来所属的线程里执行的,所以一定在3秒后执行. time.Sleep(time.Second * 3) zlog.Debug("AsyncOpRouter Handle OUT ===>111") } ================================================ FILE: examples/zinx_async_op/server/server.go ================================================ package main import ( "github.com/aceld/zinx/examples/zinx_async_op/router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) func OnConnectionAdd(conn ziface.IConnection) { zlog.Debug("zinx_async_op OnConnectionAdd ===>") } func OnConnectionLost(conn ziface.IConnection) { zlog.Debug("zinx_async_op OnConnectionLost ===>") } func main() { s := znet.NewServer() s.SetOnConnStart(OnConnectionAdd) s.SetOnConnStop(OnConnectionLost) s.AddRouter(1, &router.LoginRouter{}) s.Serve() } ================================================ FILE: examples/zinx_client/Makefile ================================================ PROJECT_NAME:=zinx_client VERSION:=v1 .PHONY: image run build clean build: bash build.sh ${PROJECT_NAME} image: docker build -t ${PROJECT_NAME}:${VERSION} . run: docker run -itd \ -p 8999:8999 \ ${PROJECT_NAME}:${VERSION} clean: rm -rf ${PROJECT_NAME} ================================================ FILE: examples/zinx_client/build.sh ================================================ #!/bin/bash set -e APP_NAME=$1 APP_VERSION=v$(cat version) BUILD_VERSION=$(git log -1 --oneline) BUILD_TIME=$(date "+%FT%T%z") GIT_REVISION=$(git rev-parse --short HEAD) GIT_BRANCH=$(git name-rev --name-only HEAD) GO_VERSION=$(go version) go build -ldflags " \ -X 'main.AppName=${APP_NAME}' \ -X 'main.AppVersion=${APP_VERSION}' \ -X 'main.BuildVersion=${BUILD_VERSION//\'/_}' \ -X 'main.BuildTime=${BUILD_TIME}' \ -X 'main.GitRevision=${GIT_REVISION}' \ -X 'main.GitBranch=${GIT_BRANCH}' \ -X 'main.GoVersion=${GO_VERSION}' \ " -o $APP_NAME ================================================ FILE: examples/zinx_client/c_router/hello.go ================================================ package c_router import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) type HelloRouter struct { znet.BaseRouter } // HelloZinxRouter Handle func (this *HelloRouter) Handle(request ziface.IRequest) { zlog.Debug("Call HelloZinxRouter Handle") zlog.Debug("recv from server : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) } ================================================ FILE: examples/zinx_client/c_router/ping.go ================================================ package c_router import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) // Ping test custom routing. type PingRouter struct { znet.BaseRouter } // Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { zlog.Debug("Call PingRouter Handle") zlog.Debug("recv from server : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) if err := request.GetConnection().SendBuffMsg(1, []byte("Hello[from client]")); err != nil { zlog.Error(err) } } ================================================ FILE: examples/zinx_client/main.go ================================================ /** * @Author: Aceld * @Date: 2023/03/02 * @Mail: danbing.at@gmail.com * zinx client demo */ package main import ( "fmt" "github.com/aceld/zinx/examples/zinx_client/c_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "os" "os/signal" "time" ) // Custom business logic of the client (客户端自定义业务) func business(conn ziface.IConnection) { for { err := conn.SendMsg(100, []byte("Ping...[FromClient]")) if err != nil { fmt.Println(err) zlog.Error(err) break } time.Sleep(1 * time.Second) } } // Function to execute when the connection is created (创建连接的时候执行) func DoClientConnectedBegin(conn ziface.IConnection) { zlog.Debug("DoConnectionBegin is Called ... ") // Set two connection properties after the connection is created (设置两个连接属性,在连接创建之后) conn.SetProperty("Name", "刘丹冰Aceld") conn.SetProperty("Home", "https://yuque.com/aceld") go business(conn) } // Function to execute when the connection is lost (连接断开的时候执行) func DoClientConnectedLost(conn ziface.IConnection) { // Get the Name and Home properties of the connection before it is destroyed // (在连接销毁之前,查询conn的Name,Home属性) if name, err := conn.GetProperty("Name"); err == nil { zlog.Debug("Conn Property Name = ", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Debug("Conn Property Home = ", home) } zlog.Debug("DoClientConnectedLost is Called ... ") } func main() { // Create a client handle using Zinx's Method (创建一个Client句柄,使用Zinx的方法) client := znet.NewClient("127.0.0.1", 8999) // Set the business logic to execute when the connection is created or lost // (添加首次建立连接时的业务) client.SetOnConnStart(DoClientConnectedBegin) client.SetOnConnStop(DoClientConnectedLost) // Register routers for the messages received from the server // (注册收到服务器消息业务路由) client.AddRouter(2, &c_router.PingRouter{}) client.AddRouter(3, &c_router.HelloRouter{}) // Start the client client.Start() // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) client.Stop() time.Sleep(time.Second * 2) } ================================================ FILE: examples/zinx_client/version ================================================ v 1.0.0 ================================================ FILE: examples/zinx_client_old/Makefile ================================================ PROJECT_NAME:=zinx_client VERSION:=v1 .PHONY: image run build clean build: bash build.sh ${PROJECT_NAME} image: docker build -t ${PROJECT_NAME}:${VERSION} . run: docker run -itd \ -p 8999:8999 \ ${PROJECT_NAME}:${VERSION} clean: rm -rf ${PROJECT_NAME} ================================================ FILE: examples/zinx_client_old/build.sh ================================================ #!/bin/bash set -e APP_NAME=$1 APP_VERSION=v$(cat version) BUILD_VERSION=$(git log -1 --oneline) BUILD_TIME=$(date "+%FT%T%z") GIT_REVISION=$(git rev-parse --short HEAD) GIT_BRANCH=$(git name-rev --name-only HEAD) GO_VERSION=$(go version) go build -ldflags " \ -X 'main.AppName=${APP_NAME}' \ -X 'main.AppVersion=${APP_VERSION}' \ -X 'main.BuildVersion=${BUILD_VERSION//\'/_}' \ -X 'main.BuildTime=${BUILD_TIME}' \ -X 'main.GitRevision=${GIT_REVISION}' \ -X 'main.GitBranch=${GIT_BRANCH}' \ -X 'main.GoVersion=${GO_VERSION}' \ " -o $APP_NAME ================================================ FILE: examples/zinx_client_old/c_router/hello.go ================================================ package c_router import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) type HelloRouter struct { znet.BaseRouter } // HelloZinxRouter Handle func (this *HelloRouter) Handle(request ziface.IRequest) { zlog.Debug("Call HelloZinxRouter Handle") zlog.Debug("recv from server : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) } ================================================ FILE: examples/zinx_client_old/c_router/ping.go ================================================ package c_router import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { zlog.Debug("Call PingRouter Handle") zlog.Debug("recv from server : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) if err := request.GetConnection().SendBuffMsg(1, []byte("Hello[from client]")); err != nil { zlog.Error(err) } } ================================================ FILE: examples/zinx_client_old/main.go ================================================ /** * @Author: Aceld * @Date: 2023/03/02 * @Mail: danbing.at@gmail.com * zinx client demo */ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:8999") if err != nil { fmt.Println("client start err, exit!", err) return } for { dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(100, []byte("ZinxPing"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) if err != nil { fmt.Println("read head error") break } msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Test Router:[Ping] Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_client_old/version ================================================ v 1.0.0 ================================================ FILE: examples/zinx_closecallback/client/client.go ================================================ package main import ( "fmt" "os" "os/signal" "time" "github.com/aceld/zinx/examples/zinx_closecallback/router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // business handles the main business logic for sending ping messages // business 处理发送ping消息的主要业务逻辑 func business(conn ziface.IConnection) { for i := 0; i < 3; i++ { err := conn.SendMsg(1, []byte(fmt.Sprintf("Ping %d", i+1))) if err != nil { fmt.Println("SendMsg error:", err) break } time.Sleep(1 * time.Second) } // Actively disconnect after sending is complete / 发送完成后主动断开连接 fmt.Println("Client actively disconnects") conn.Stop() } // DoClientConnectedBegin is the callback function when connection starts // DoClientConnectedBegin 是连接开始时的回调函数 func DoClientConnectedBegin(conn ziface.IConnection) { fmt.Println("Client connection started") // Set connection properties / 设置连接属性 conn.SetProperty("StartTime", time.Now()) // Add close callback function - record connection statistics / 添加关闭回调函数 - 记录连接统计 conn.AddCloseCallback("stats", "connection-stats", func() { if startTime, err := conn.GetProperty("StartTime"); err == nil { duration := time.Since(startTime.(time.Time)) fmt.Printf("Client connection duration: %v\n", duration) } }) // Start business processing / 启动业务处理 go business(conn) } // DoClientConnectedLost is the callback function when connection is lost // DoClientConnectedLost 是连接断开时的回调函数 func DoClientConnectedLost(conn ziface.IConnection) { fmt.Println("Client connection lost") } func main() { // Create client / 创建客户端 client := znet.NewClient("127.0.0.1", 8999) // Set connection start and stop callbacks / 设置连接开始和断开的回调 client.SetOnConnStart(DoClientConnectedBegin) client.SetOnConnStop(DoClientConnectedLost) // Add router / 添加路由 client.AddRouter(0, &router.PingRouter{}) // Start client / 启动客户端 fmt.Println("Client starting") client.Start() // Wait for interrupt signal / 等待中断信号 c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) <-c } ================================================ FILE: examples/zinx_closecallback/router/ping_router.go ================================================ package router import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // PingRouter handles ping messages // PingRouter 处理ping消息 type PingRouter struct { znet.BaseRouter } // PreHandle processes the request before the main handler // PreHandle 在主处理器之前处理请求 func (r *PingRouter) PreHandle(req ziface.IRequest) { } // Handle processes the main ping message and sends pong response // Handle 处理主要的ping消息并发送pong响应 func (r *PingRouter) Handle(req ziface.IRequest) { // Reply to client / 回复客户端 if err := req.GetConnection().SendMsg(0, []byte("Pong")); err != nil { fmt.Println("SendMsg error:", err) } } // PostHandle processes the request after the main handler // PostHandle 在主处理器之后处理请求 func (r *PingRouter) PostHandle(req ziface.IRequest) { } ================================================ FILE: examples/zinx_closecallback/server/server.go ================================================ package main import ( "fmt" "time" "github.com/aceld/zinx/examples/zinx_closecallback/router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // DoConnectionBegin is the callback function when connection starts // DoConnectionBegin 是连接开始时的回调函数 func DoConnectionBegin(conn ziface.IConnection) { fmt.Println("Server connection started") // Set connection properties / 设置连接属性 conn.SetProperty("StartTime", time.Now()) // Add close callback function - cleanup resources / 添加关闭回调函数 - 清理资源 conn.AddCloseCallback("cleanup", "resources", func() { fmt.Printf("Cleanup resources for connection %d\n", conn.GetConnID()) }) // Add close callback function - record statistics / 添加关闭回调函数 - 记录统计信息 conn.AddCloseCallback("stats", "record", func() { if startTime, err := conn.GetProperty("StartTime"); err == nil { duration := time.Since(startTime.(time.Time)) fmt.Printf("Connection %d duration: %v\n", conn.GetConnID(), duration) } }) // Add close callback function - notify other components / 添加关闭回调函数 - 通知其他组件 conn.AddCloseCallback("notification", "broadcast", func() { fmt.Printf("Notify other components: connection %d disconnected\n", conn.GetConnID()) }) } // DoConnectionLost is the callback function when connection is lost // DoConnectionLost 是连接断开时的回调函数 func DoConnectionLost(conn ziface.IConnection) { fmt.Println("Server connection lost") } func main() { // Create server / 创建服务器 s := znet.NewServer() // Set connection start and stop callbacks / 设置连接开始和断开的回调 s.SetOnConnStart(DoConnectionBegin) s.SetOnConnStop(DoConnectionLost) // Add router / 添加路由 s.AddRouter(1, &router.PingRouter{}) // Start server / 启动服务器 fmt.Println("Server starting") s.Serve() } ================================================ FILE: examples/zinx_decoder/README.MD ================================================ # LengthFieldFrameDecoder使用详解 >LengthFieldFrameDecoder是一个基于长度字段的解码器,比较难理解的解码器,它主要有5个核心的参数配置: >maxFrameLength: 数据包最大长度 >lengthFieldOffset: 长度字段偏移量 >lengthFieldLength: 长度字段所占的字节数 >lengthAdjustment: 长度的调整值 >initialBytesToStrip:解码后跳过的字节数 ## 示例讲解 #### TLV格式协议 TLV,即Tag(Type)—Length—Value,是一种简单实用的数据传输方案。在TLV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)。这里的长度域的值实际上就是内容域的长度。 ``` 解码前 (20 bytes) 解码后 (20 bytes) +------------+------------+-----------------+ +------------+------------+-----------------+ | Tag | Length | Value |----->| Tag | Length | Value | | 0x00000001 | 0x0000000C | "HELLO, WORLD" | | 0x00000001 | 0x0000000C | "HELLO, WORLD" | +------------+------------+-----------------+ +------------+------------+-----------------+ ``` > Tag: uint32类型,占4字节,Tag作为MsgId,暂定为1
> Length:uint32类型,占4字节,Length标记Value长度12(hex:0x0000000C)
> Value: 共12个字符,占12字节
``` 说明: lengthFieldOffset = 4 (Length的字节位索引下标是4) 长度字段的偏差 lengthFieldLength = 4 (Length是4个byte) 长度字段占的字节数 lengthAdjustment = 0 (Length只表示Value长度,程序只会读取Length个字节就结束,后面没有来,故为0,若Value后面还有crc占2字节的话,那么此处就是2。若Length标记的是Tag+Length+Value总长度,那么此处是-8) initialBytesToStrip = 0 (这个0表示返回完整的协议内容Tag+Length+Value,如果只想返回Value内容,去掉Tag的4字节和Length的4字节,此处就是8) 从解码帧中第一次去除的字节数 maxFrameLength = 2^32 + 4 + 4 (Length为uint类型,故2^32次方表示Value最大长度,此外Tag和Length各占4字节) ``` #### HTLV+CRC格式协议 HTLV+CRC,H头码,T功能码,L数据长度,V数据内容 ``` +------+-------+---------+--------+--------+ | 头码 | 功能码 | 数据长度 | 数据内容 | CRC校验 | | 1字节 | 1字节 | 1字节 | N字节 | 2字节 | +------+-------+---------+--------+--------+ ``` 数据示例 ``` 头码 功能码 数据长度 Body CRC A2 10 0E 0102030405060708091011121314 050B ``` ``` 说明: 1.数据长度len是14(0E),这里的len仅仅指Body长度; lengthFieldOffset = 2 (len的索引下标是2,下标从0开始) 长度字段的偏差 lengthFieldLength = 1 (len是1个byte) 长度字段占的字节数 lengthAdjustment = 2 (len只表示Body长度,程序只会读取len个字节就结束,但是CRC还有2byte没读呢,所以为2) initialBytesToStrip = 0 (这个0表示完整的协议内容,如果不想要A2,那么这里就是1) 从解码帧中第一次去除的字节数 maxFrameLength = 255 + 4(起始码、功能码、CRC) (len是1个byte,所以最大长度是无符号1个byte的最大值) ``` ## 案例分析 以下7种案例足以满足所有协议,只处理断粘包,并不能处理错包,包的完整性需要依靠协议自身定义CRC来校验 #### 案例1: ``` lengthFieldOffset =0 长度字段从0开始 lengthFieldLength =2 长度字段本身占2个字节 lengthAdjustment =0 需要调整0字节 initialBytesToStrip=0 解码后跳过0字节 解码前 (14 bytes) 解码后 (14 bytes) +--------+----------------+ +--------+----------------+ | Length | Actual Content |----->| Length | Actual Content | | 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" | +--------+----------------+ +--------+----------------+ ``` > Length为0x000C,这个是十六进制,0x000C转化十进制就是14 #### 案例2: ``` lengthFieldOffset =0 长度字段从0开始 lengthFieldLength =2 长度字段本身占2个字节 lengthAdjustment =0 需要调整0字节 initialBytesToStrip=2 解码后跳过2字节 解码前 (14 bytes) 解码后 (12 bytes) +--------+----------------+ +----------------+ | Length | Actual Content |----->| Actual Content | | 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" | +--------+----------------+ +----------------+ ``` >这时initialBytesToStrip字段起作用了,在解码后会将前面的2字节跳过,所以解码后就只剩余了数据部分。 #### 案例3: ``` lengthFieldOffset =0 长度字段从0开始 lengthFieldLength =2 长度字段本身占2个字节 lengthAdjustment =-2 需要调整 -2 字节 initialBytesToStrip=0 解码后跳过2字节 解码前 (14 bytes) 解码后 (14 bytes) +--------+----------------+ +--------+----------------+ | Length | Actual Content |----->| Length | Actual Content | | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" | +--------+----------------+ +--------+----------------+ ``` >这时lengthAdjustment起作用了,因为长度字段的值包含了长度字段本身的2字节, 如果要获取数据的字节数,需要加上lengthAdjustment的值,就是 14+(-2)=12,这样才算出来数据的长度。 #### 案例4: ``` lengthFieldOffset =2 长度字段从第2个字节开始 lengthFieldLength =3 长度字段本身占3个字节 lengthAdjustment =0 需要调整0字节 initialBytesToStrip=0 解码后跳过0字节 解码前 (17 bytes) 解码后 (17 bytes) +----------+----------+----------------+ +----------+----------+----------------+ | Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content | | 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" | +----------+----------+----------------+ +----------+----------+----------------+ ``` >由于数据包最前面加了2个字节的Header,所以lengthFieldOffset为2, 说明长度字段是从第2个字节开始的。然后lengthFieldLength为3,说明长度字段本身占了3个字节。 #### 案例5: ``` lengthFieldOffset =0 长度字段从第0个字节开始 lengthFieldLength =3 长度字段本身占3个字节 lengthAdjustment =2 需要调整2字节 initialBytesToStrip=0 解码后跳过0字节 解码前 (17 bytes) 解码后 (17 bytes) +----------+----------+----------------+ +----------+----------+----------------+ | Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content | | 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" | +----------+----------+----------------+ +----------+----------+----------------+ ``` >lengthFieldOffset为0,所以长度字段从0字节开始。lengthFieldLength为3,长度总共占3字节。 因为长度字段后面还剩余14字节的总数据,但是长度字段的值为12,只表示了数据的长度,不包含头的长度, 所以lengthAdjustment为2,就是12+2=14,计算出Header+Content的总长度。 #### 案例6: ``` lengthFieldOffset =1 长度字段从第1个字节开始 lengthFieldLength =2 长度字段本身占2个字节 lengthAdjustment =1 需要调整1字节 initialBytesToStrip=3 解码后跳过3字节 解码前 (16 bytes) 解码后 (13 bytes) +------+--------+------+----------------+ +------+----------------+ | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | +------+--------+------+----------------+ +------+----------------+ ``` >这一次将Header分为了两个1字节的部分,lengthFieldOffset为1表示长度从第1个字节开始,lengthFieldLength为2表示长度字段占2个字节。 因为长度字段的值为12,只表示了数据的长度,所以lengthAdjustment为1,12+1=13, 表示Header的第二部分加上数据的总长度为13。因为initialBytesToStrip为3,所以解码后跳过前3个字节。 #### 案例7: ``` lengthFieldOffset =1 长度字段从第1个字节开始 lengthFieldLength =2 长度字段本身占2个字节 lengthAdjustment =-3 需要调整 -3 字节 initialBytesToStrip=3 解码后跳过3字节 解码前 (16 bytes) 解码后 (13 bytes) +------+--------+------+----------------+ +------+----------------+ | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | +------+--------+------+----------------+ +------+----------------+ ``` >这一次长度字段的值为16,表示包的总长度,所以lengthAdjustment为 -3 ,16+ (-3)=13, 表示Header的第二部分加数据部分的总长度为13字节。initialBytesToStrip为3,解码后跳过前3个字节。 ================================================ FILE: examples/zinx_decoder/bili/README.MD ================================================ # 一款水机设备服务端模拟程序 ```shell HTLV+CRC,H头码,T功能码,L数据长度,V数据内容 +------+-------+---------+--------+--------+ | 头码 | 功能码 | 数据长度 | 数据内容 | CRC校验 | | 1字节 | 1字节 | 1字节 | N字节 | 2字节 | +------+-------+---------+--------+--------+ 头码 功能码 数据长度 Body CRC A2 10 0E 0102030405060708091011121314 050B 说明: 1.数据长度len是14(0E),这里的len仅仅指Body长度; lengthFieldOffset = 2 (len的索引下标是2,下标从0开始) 长度字段的偏差 lengthFieldLength = 1 (len是1个byte) 长度字段占的字节数 lengthAdjustment = 2 (len只表示Body长度,程序只会读取len个字节就结束,但是CRC还有2byte没读呢,所以为2) initialBytesToStrip = 0 (这个0表示完整的协议内容,如果不想要A2,那么这里就是1) 从解码帧中第一次去除的字节数 maxFrameLength = 255 + 4(起始码、功能码、CRC) (len是1个byte,所以最大长度是无符号1个byte的最大值) ``` ================================================ FILE: examples/zinx_decoder/bili/main.go ================================================ package main import ( "github.com/aceld/zinx/examples/zinx_decoder/bili/router" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) func DoConnectionBegin(conn ziface.IConnection) { } func DoConnectionLost(conn ziface.IConnection) { } func main() { server := znet.NewServer(func(s *znet.Server) { s.Port = 9090 /* s.LengthField = ziface.LengthField{ MaxFrameLength: math.MaxUint8 + 4, LengthFieldOffset: 2, LengthFieldLength: 1, LengthAdjustment: 2, InitialBytesToStrip: 0, } */ }) server.SetOnConnStart(DoConnectionBegin) server.SetOnConnStop(DoConnectionLost) server.AddInterceptor(zdecoder.NewHTLVCRCDecoder()) server.AddRouter(0x10, &router.Data0x10Router{}) server.AddRouter(0x13, &router.Data0x13Router{}) server.AddRouter(0x14, &router.Data0x14Router{}) server.AddRouter(0x15, &router.Data0x15Router{}) server.AddRouter(0x16, &router.Data0x16Router{}) server.Serve() } ================================================ FILE: examples/zinx_decoder/bili/router/bili0x10router.go ================================================ package router import ( "bytes" "encoding/hex" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) type Data0x10Router struct { znet.BaseRouter } func (this *Data0x10Router) Handle(request ziface.IRequest) { zlog.Ins().DebugF("Data0x10Router Handle %s \n", hex.EncodeToString(request.GetMessage().GetData())) _response := request.GetResponse() if _response != nil { switch _response.(type) { case zdecoder.HtlvCrcDecoder: _data := _response.(zdecoder.HtlvCrcDecoder) //zlog.Ins().DebugF("Data0x10Router %v \n", _data) buffer := pack10(_data) request.GetConnection().Send(buffer) } } } // 头码 功能码 数据长度 Body CRC // A2 10 0E 0102030405060708091011121314 050B // HeadCode FuncCode DataLen Body CRC // A2 10 0E 0102030405060708091011121314 050B func pack10(_data zdecoder.HtlvCrcDecoder) []byte { buffer := bytes.NewBuffer([]byte{}) buffer.WriteByte(0xA1) buffer.WriteByte(_data.Funcode) buffer.WriteByte(0x1E) //3~9:唯一设备码 将IMEI码转换为16进制 //3~9: Unique device code: Convert the IMEI code to hexadecimal buffer.Write(_data.Body[:7]) //10~14:园区代码 后台根据幼儿园生成的唯一代码 //10~14: Park code: A unique code generated by the background according to the kindergarten buffer.Write([]byte{10, 11, 12, 13, 14}) //15~18:时间戳 实际当前北京时间的时间戳,转换为16进制 //15~18: Timestamp: The actual current Beijing time stamp, converted to hexadecimal buffer.Write([]byte{15, 16, 17, 18}) //19:RFID模块工作模式 0x01-离线工作模式(默认工作模式)0x02-在线工作模式 //19: RFID module working mode: 0x01-offline working mode (default working mode) 0x02-online working mode buffer.WriteByte(0x02) //20~27:通讯密匙 预留,全填0x00 //20~27: Communication key: Reserved, fill with 0x00 buffer.Write([]byte{20, 21, 22, 23, 24, 25, 26, 27}) //28:出水方式 0x00-放杯出水,取杯停止出水 0x01-刷一下卡出水,再刷停止出水【数联默认】 //28: Water outlet mode: 0x00-put cup and stop water, 0x01-swipe card for water, swipe card again to stop water [Suliandu default] buffer.WriteByte(0x01) //29~32:预留 全填0x00 //29~32: Reserved: Fill with 0x00 buffer.Write([]byte{29, 30, 31, 32}) crc := zdecoder.GetCrC(buffer.Bytes()) buffer.Write(crc) return buffer.Bytes() } ================================================ FILE: examples/zinx_decoder/bili/router/bili0x13router.go ================================================ package router import ( "bytes" "fmt" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) type Data0x13Router struct { znet.BaseRouter } func (this *Data0x13Router) Handle(request ziface.IRequest) { fmt.Println("Data0x13Router Handle", request.GetMessage().GetData()) _response := request.GetResponse() if _response != nil { switch _response.(type) { case zdecoder.HtlvCrcDecoder: _data := _response.(zdecoder.HtlvCrcDecoder) fmt.Println("Data0x13Router", _data) buffer := pack13(_data) request.GetConnection().Send(buffer) } } } // 头码 功能码 数据长度 Body CRC // A2 10 0E 0102030405060708091011121314 050B // HeadCode FuncCode DataLen Body CRC // A2 10 0E 0102030405060708091011121314 050B func pack13(_data zdecoder.HtlvCrcDecoder) []byte { buffer := bytes.NewBuffer([]byte{}) buffer.WriteByte(0xA1) buffer.WriteByte(_data.Funcode) buffer.WriteByte(0x0E) //3~9:3~6:用户卡号 用户IC卡卡号 //3~9:3~6: User card number: User IC card number buffer.Write(_data.Body[:4]) //7:卡状态: 0x00-未绑定(如服务器未查询到该IC卡时) //0x01-已绑定 //0x02-解除绑定(如服务器查询到该IC卡解除绑定时下发) //7: Card Status: 0x00-Unbound (when the card is not found in the server) //0x01-Bound //0x02-Unbound (when the server sends a command to unbind the card) buffer.WriteByte(0x01) //8~9:剩余使用天数 该用户的剩余流量天数 //8~9: Remaining usage days: the remaining number of days of usage for the user's data plan. buffer.Write([]byte{8, 9}) //10~11:每次最大出水量 单位mL,实际出水量 //10~11: Maximum dispensing amount per use, unit: mL, actual dispensing amount. buffer.Write([]byte{10, 11}) //12~16:预留 全填0x00 //12~16: Reserved, all filled with 0x00. buffer.Write([]byte{12, 13, 14, 15, 16}) crc := zdecoder.GetCrC(buffer.Bytes()) buffer.Write(crc) return buffer.Bytes() } ================================================ FILE: examples/zinx_decoder/bili/router/bili0x14router.go ================================================ package router import ( "bytes" "fmt" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) type Data0x14Router struct { znet.BaseRouter } func (this *Data0x14Router) Handle(request ziface.IRequest) { fmt.Println("Data0x14Router Handle", request.GetMessage().GetData()) _response := request.GetResponse() if _response != nil { switch _response.(type) { case zdecoder.HtlvCrcDecoder: _data := _response.(zdecoder.HtlvCrcDecoder) fmt.Println("Data0x14Router", _data) buffer := pack14(_data) request.GetConnection().Send(buffer) } } } // 头码 功能码 数据长度 Body CRC // A2 10 0E 0102030405060708091011121314 050B // HeadCode FuncCode DataLen Body CRC // A2 10 0E 0102030405060708091011121314 050B func pack14(_data zdecoder.HtlvCrcDecoder) []byte { _data.Data[0] = 0xA1 buffer := bytes.NewBuffer(_data.Data[:len(_data.Data)-2]) crc := zdecoder.GetCrC(buffer.Bytes()) buffer.Write(crc) return buffer.Bytes() } ================================================ FILE: examples/zinx_decoder/bili/router/bili0x15router.go ================================================ package router import ( "bytes" "fmt" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) type Data0x15Router struct { znet.BaseRouter } func (this *Data0x15Router) Handle(request ziface.IRequest) { fmt.Println("Data0x15Router Handle", request.GetMessage().GetData()) _response := request.GetResponse() if _response != nil { switch _response.(type) { case zdecoder.HtlvCrcDecoder: _data := _response.(zdecoder.HtlvCrcDecoder) fmt.Println("Data0x15Router", _data) buffer := pack15(_data) request.GetConnection().Send(buffer) } } } // 头码 功能码 数据长度 Body CRC // A2 10 0E 0102030405060708091011121314 050B // HeaderCode FunctionCode DataLength Body CRC // A2 10 0E 0102030405060708091011121314 050B func pack15(_data zdecoder.HtlvCrcDecoder) []byte { buffer := bytes.NewBuffer([]byte{}) buffer.WriteByte(0xA1) buffer.WriteByte(_data.Funcode) buffer.WriteByte(0x26) //3~9: Device Code Convert IMEI code to hex (3~9:设备代码, 将IMEI码转换为16进制) buffer.Write(_data.Body[:7]) //10: Model Code A8 (Hot Type Kindergarten Machine) , 10: 机型代码 A8(即热式幼儿园机) buffer.WriteByte(0xA8) //11:主机状态1 //Bit0:0-待机中,1-运行中 //Bit1:0-非智控,1-智控【本设备按智控】 //Bit2:0-不能饮用,1-可以饮用 //Bit3:0-无人用水,1-有人用水 //Bit4:0-上电进水中,1-正常工作中 //Bit5:0-消毒未启动,1-消毒进行中 //Bit6:0-低压开关断开(无水),1-低压开关接通(有水) //Bit7:0-主机不带RO,1-主机带RO // 11: Host Status 1 // Bit0: 0-Standby, 1-Running // Bit1: 0-Non-intelligent control, 1-Intelligent control [This device follows intelligent control] // Bit2: 0-Cannot be drunk, 1-Can be drunk // Bit3: 0-No one uses water, 1-Someone uses water // Bit4: 0-Entering water at power-on, 1-Working normally // Bit5: 0-Disinfection not started, 1-Disinfection in progress // Bit6: 0-Low voltage switch off (no water), 1-Low voltage switch on (with water) // Bit7: 0-Master without RO, 1-Master with RO buffer.WriteByte(0x01) //12:主机状态2 Bit0:0-RO机不允许启动水泵,1-RO机允许启动水泵 //Bit1:0-开水无人用,1-开水有人用 //Bit2:0-高压开关断开(满水),1-高压开关接通(缺水) //Bit3:0-冰水无人用,1-冰水有人用【本设备无意义】 //Bit4:0-无漏水信号,1-有漏水信号 //Bit5:0-紫外灯未启动,1-紫外线灯杀菌中 //Bit6:预留 //Bit7:预留 // 12: Host Status 2 // Bit0: 0-RO machine does not allow pump to start, 1-RO machine allows pump to start // Bit1: 0-No one uses hot water, 1-Someone uses hot water // Bit2: 0-High-voltage switch off (full water), 1-High-voltage switch on (lack of water) // Bit3: 0-No one uses ice water, 1-Someone uses ice water [meaningless for this device] // Bit4: 0-No water leakage signal, 1-Water leakage signal // Bit5: 0-Ultraviolet lamp not started, 1-Ultraviolet lamp sterilizing // Bit6: Reserved // Bit7: Reserved buffer.WriteByte(0x01) //13:水位状态 //(即热水位) Bit0:低水位,0-代表无水,1-代表有水【本设备低水位有水即表示水满】 //Bit1:中水位,0-代表无水,1-代表有水【本设备无意义】 //Bit2:高水位,0-代表无水,1-代表有水【本设备无意义】 //Bit3:溢出水位,0-代表无水,1-代表有水【本设备无意义】 //Bit4:预留 //Bit5:预留 //Bit6:预留 //Bit7:预留 // 13: Water level status (i.e. hot water level) // Bit0: Low water level, 0-represents no water, 1-represents water [Low water level with water indicates full water for this device] // Bit1: Medium water level, 0-represents no water, 1-represents water [Meaningless for this device] // Bit2: High water level, 0-represents no water, 1-represents water [Meaningless for this device] // Bit3: Overflow water level, 0-represents no water, 1-represents water [Meaningless for this device] // Bit4: Reserved // Bit5: Reserved // Bit6: Reserved // Bit7: Reserved buffer.WriteByte(0x01) buffer.WriteByte(0x01) // 14: Hot water temperature 0℃100℃, indicating the current hot water temperature //14:开水温度 0℃~100℃,表示当前开水温度 buffer.WriteByte(0x1A) // 15: Stop heating temperature of current system 30~98℃, actual value // 15:当前系统的停止加热温度 30~98℃,实际数值 buffer.WriteByte(0x27) //16:负载状态 //Bit0:加热,0-未加热,1-加热中 //Bit1:进水,0-未进水,1-进水中 //Bit2:换水或消毒,0-未换水,1-换水或消毒 //Bit3:冲洗,0-未冲洗,1-冲洗中 //Bit4:增压泵和RO进水阀,0-未启动,1-启动增压泵和RO进水阀 //Bit5:RO进水阀2,0-未启动,1-启动中【本设备无意义】 //Bit6:开水出水阀1,0-未启动,1-启动中 //Bit7:净化水出水阀1,0-未启动,1-启动中【本设备无意义】 // 16: Load status // Bit0: Heating, 0 - Not heating, 1 - Heating // Bit1: Inlet water, 0 - No inlet water, 1 - Inlet water // Bit2: Water change or disinfection, 0 - No change, 1 - Water change or disinfection // Bit3: Flushing, 0 - Not flushing, 1 - Flushing // Bit4: Booster pump and RO inlet valve, 0 - Not started, 1 - Started booster pump and RO inlet valve // Bit5: RO inlet valve 2, 0 - Not started, 1 - Started 【Irrelevant to this device】 // Bit6: Hot water outlet valve 1, 0 - Not started, 1 - Started // Bit7: Purified water outlet valve 1, 0 - Not started, 1 - Started 【Irrelevant to this device】 buffer.WriteByte(0x01) //17:负载状态2 预留,填0x00 //17: Load State 2, reserved, fill with 0x00. buffer.WriteByte(0x00) //18:故障状态 //Bit0:故障1,0-无故障,1-有故障 //Bit1:故障2,0-无故障,1-有故障 //Bit2:障保3,0-无故障,1-有故障 //Bit3:故障4 ,0-无故障,1-有故障 //Bit4:故障5 ,0-无故障,1-有故障 //Bit5:故障6,0-无故障,1-有故障 //Bit6:故障7,0-无故障,1-有故障 //Bit7:故障8,0-无故障,1-有故障 //18: Fault status //Bit0: Fault 1, 0-no fault, 1-fault //Bit1: Fault 2, 0-no fault, 1-fault //Bit2: Fault 3, 0-no fault, 1-fault //Bit3: Fault 4, 0-no fault, 1-fault //Bit4: Fault 5, 0-no fault, 1-fault //Bit5: Fault 6, 0-no fault, 1-fault //Bit6: Fault 7, 0-no fault, 1-fault //Bit7: Fault 8, 0-no fault, 1-fault buffer.WriteByte(0x00) //19:故障状态2 //Bit0:故障A,0-无故障,1-有故障 //Bit1:故障B,0-无故障,1-有故障 //Bit2:障保C,0-无故障,1-有故障 //Bit3:故障9,0-无故障,1-有故障 //Bit4:故障D,0-无故障,1-有故障 //Bit5:故障E,0-无故障,1-有故障 //Bit6:预留 //Bit7:预留 //19: Fault status 2 //Bit0: Fault A, 0 - no fault, 1 - there is a fault //Bit1: Fault B, 0 - no fault, 1 - there is a fault //Bit2: Fault C, 0 - no fault, 1 - there is a fault //Bit3: Fault 9, 0 - no fault, 1 - there is a fault //Bit4: Fault D, 0 - no fault, 1 - there is a fault //Bit5: Fault E, 0 - no fault, 1 - there is a fault //Bit6: Reserved //Bit7: Reserved buffer.WriteByte(0x00) //20:主板软件版本 实际数值1~255 //20: Mainboard software version, actual value 1~255. buffer.WriteByte(0x01) //21:水位状态2 //Bit0:纯水箱低水位,0-代表无水,1-代表有水【本设备无意义】 //Bit1:纯水箱中水位,0-代表无水,1-代表有水【本设备无意义】 //Bit2:纯水箱高水位,0-代表无水,1-代表有水【本设备无意义】 //Bit3:保温箱低水位,0-代表无水,1-代表有水 //Bit4:保温箱中水位,0-代表无水,1-代表有水【本设备无意义】 //Bit5:保温箱高水位,0-代表无水,1-代表有水 //Bit6:保温箱溢水位,0-代表无水,1-代表有水 //Bit7:预留 //21: Water level status 2 //Bit0: Low water level in pure water tank, 0 - no water, 1 - water present (not applicable for this device) //Bit1: Medium water level in pure water tank, 0 - no water, 1 - water present (not applicable for this device) //Bit2: High water level in pure water tank, 0 - no water, 1 - water present (not applicable for this device) //Bit3: Low water level in insulation box, 0 - no water, 1 - water present //Bit4: Medium water level in insulation box, 0 - no water, 1 - water present (not applicable for this device) //Bit5: High water level in insulation box, 0 - no water, 1 - water present //Bit6: Overflow water level in insulation box, 0 - no water, 1 - water present //Bit7: Reserved buffer.WriteByte(0x01) //22:温开水温度 0~100℃ //22: Hot water temperature in Celsius degree, range from 0 to 100℃. buffer.WriteByte(0x30) //23~24:剩余滤芯寿命 单位:小时,实际数值 //23~24: Remaining filter life, unit: hours, actual numerical value buffer.Write([]byte{23, 24}) //25~26:剩余紫外线灯寿命 //25~26: Remaining UV lamp life, measured in hours, actual numerical value. buffer.Write([]byte{25, 26}) //27~28:源水TDS值 0x0000-无此功能 ,实际数值,单位,ppm //27~28: Source water TDS value, 0x0000 - no such function, actual value in ppm buffer.Write([]byte{27, 28}) //29:净水TDS值 0x00-无此功能, 实际数值,单位,ppm //29: TDS value of purified water 0x00 - No such function, actual value, unit: ppm buffer.WriteByte(0x00) //30~33:耗电量 0xFFFFFFFF-无此功能, 实际数值,高位在前,低位在后,单位wh //30~33: Power consumption, 0xFFFFFFFF - not supported, actual value, high byte first, low byte last, unit is Wh. buffer.Write([]byte{30, 31, 32, 33}) //34:信号强度 0x01~0x28 //0x01~0x0A对应:-81~-90dbm=极差 //0x0B~0x14对应:-71~-80dbm=差 //0x15~0x1E对应-61~-70dbm=好 //0x1F~0x28对应:-41以上~-50dbm=良好 //34: Signal strength 0x01~0x28 //0x010x0A correspond to -81~-90dbm=poor //0x0B0x14 correspond to -71-80dbm=weak //0x150x1E correspond to -61-70dbm=good //0x1F0x28 correspond to -41 and above-50dbm=excellent buffer.WriteByte(0x30) //35~40:预留 全填0x00 //35~40: Reserved. All filled with 0x00. buffer.Write([]byte{0x00, 0x00}) crc := zdecoder.GetCrC(buffer.Bytes()) buffer.Write(crc) return buffer.Bytes() } ================================================ FILE: examples/zinx_decoder/bili/router/bili0x16router.go ================================================ package router import ( "bytes" "fmt" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) type Data0x16Router struct { znet.BaseRouter } func (this *Data0x16Router) Handle(request ziface.IRequest) { fmt.Println("Data0x16Router Handle", request.GetMessage().GetData()) _response := request.GetResponse() if _response != nil { switch _response.(type) { case zdecoder.HtlvCrcDecoder: _data := _response.(zdecoder.HtlvCrcDecoder) fmt.Println("Data0x16Router", _data) buffer := pack16(_data) request.GetConnection().Send(buffer) } } } // Pack a complete 0x16 protocol data // Format: // HeadCode FuncCode DataLen Body CRC // A2 10 0E 0102030405060708091011121314 050B // 头码 功能码 数据长度 Body CRC // A2 10 0E 0102030405060708091011121314 050B func pack16(_data zdecoder.HtlvCrcDecoder) []byte { _data.Data[0] = 0xA1 buffer := bytes.NewBuffer(_data.Data[:len(_data.Data)-2]) crc := zdecoder.GetCrC(buffer.Bytes()) buffer.Write(crc) return buffer.Bytes() } ================================================ FILE: examples/zinx_decoder/client/client.go ================================================ package main import ( "encoding/binary" "encoding/hex" "fmt" "os" "os/signal" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) // Use this method to generate mock data. (使用该方法生成模拟数据) func getTLVPackData() []byte { msgID := 1 tag := make([]byte, 4) binary.BigEndian.PutUint32(tag, uint32(msgID)) str := "HELLO, WORLD" var value = []byte(str) length := make([]byte, 4) binary.BigEndian.PutUint32(length, uint32(len(value))) _data := make([]byte, 0) _data = append(_data, tag...) _data = append(_data, length...) _data = append(_data, value...) fmt.Println("--->", len(_data), hex.EncodeToString(_data)) return _data } func getTLVData(index int) []byte { // Get a complete TLV simulated data package by using the getTLVPackData() method: 000000010000000c48454c4c4f2c20574f524c44. // (通过 getTLVPackData()方法,获得一段完整的TLV模拟数据包:000000010000000c48454c4c4f2c20574f524c44) tlvPackData := []string{ "000000010000000c48454c4c4f2c20574f524c44000000010000000c", //one and a half packets(一包半) "48454c4c4f2c20574f524c44", //the remaining half of the packet(剩下的半包) "000000010000000c48454c4c4f2c20574f524c44000000010000000c48454c4c4f2c20574f524c44", //two packets(两包) } // The simulation sequence here is: two complete packages, one half package, and the remaining half package. // (此处模拟顺序如:两包一包半剩下的半包) index = index % 3 if index == 0 { fmt.Println("Simulation-Data - Sticking (粘包)") index = 2 //Simulate a situation of packet sticking, where two packets of data are combined together. (模拟粘包情况,两包数据一起) } else { // Simulate the situation of message fragmentation, with one and a half packages and the remaining half package // (模拟断包情况,一包半+剩下的半包) index = index / 2 % 2 fmt.Println("Simulation-Data - Fragmentation(断包)") } arr, _ := hex.DecodeString(tlvPackData[index]) return arr } func getHTLVCRCData(index int) []byte { // A complete HTLVCRC simulation data packet: A2100E0102030405060708091011121314050B // (一段完整的HTLVCRC模拟数据包:A2100E0102030405060708091011121314050B) tlvPackData := []string{ "a21018686574000004d30000000000000000000000000000000000e7a2a2130e686574000004d300000001", //one and a half packets(一包半) "00000040c3", //剩下的半包 "a21018686574000004d30000000000000000000000000000000000e7a2a2130e686574000004d30000000100000040c3", //two packets(两包) } // Simulated sequence here: two complete packages, one half package, and the remaining half package. // (此处模拟顺序如:两包一包半剩下的半包) index = index % 3 if index == 0 { fmt.Println("Simulation-Data - Sticking (粘包)") index = 2 //Simulate a situation of packet sticking, where two packets of data are combined together. (模拟粘包情况,两包数据一起) } else { // Simulate the situation of message fragmentation, with one and a half packages and the remaining half package // (模拟断包情况,一包半+剩下的半包) index = index / 2 % 2 fmt.Println("Simulation-Data - Fragmentation(断包)") } arr, _ := hex.DecodeString(tlvPackData[index]) return arr } func business(conn ziface.IConnection) { var i int for { //buffer := getTLVData(i) buffer := getHTLVCRCData(i) conn.Send(buffer) i++ time.Sleep(1 * time.Second) } } func DoClientConnectedBegin(conn ziface.IConnection) { zlog.Debug("DoConnectionBegin is Called ... ") go business(conn) } func main() { client := znet.NewClient("127.0.0.1", 8999) client.SetOnConnStart(DoClientConnectedBegin) client.Start() // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) } ================================================ FILE: examples/zinx_decoder/router/htlvcrcbusinessrouter.go ================================================ package router import ( "encoding/hex" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) type HtlvCrcBusinessRouter struct { znet.BaseRouter } func (this *HtlvCrcBusinessRouter) Handle(request ziface.IRequest) { //MsgID msgID := request.GetMessage().GetMsgID() zlog.Ins().DebugF("Call HtlvCrcBusinessRouter Handle %d %s\n", msgID, hex.EncodeToString(request.GetMessage().GetData())) resp := request.GetResponse() if resp == nil { return } tlvData := resp.(zdecoder.HtlvCrcDecoder) zlog.Ins().DebugF("do msgid=0x10 data business %+v\n", tlvData) } ================================================ FILE: examples/zinx_decoder/router/tlvbusinessrouter.go ================================================ package router import ( "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) type TLVBusinessRouter struct { znet.BaseRouter } func (this *TLVBusinessRouter) Handle(request ziface.IRequest) { msgID := request.GetMessage().GetMsgID() zlog.Ins().DebugF("Call TLVRouter Handle %d %+v\n", msgID, request.GetMessage().GetData()) resp := request.GetResponse() if resp == nil { return } tlvData := resp.(zdecoder.TLVDecoder) zlog.Ins().DebugF("do msgid=0x00000001 data business %+v\n", tlvData) } ================================================ FILE: examples/zinx_decoder/server/server.go ================================================ package main import ( "github.com/aceld/zinx/examples/zinx_decoder/router" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) func DoConnectionBegin(conn ziface.IConnection) { zlog.Ins().InfoF("DoConnectionBegin is Called ...") } func DoConnectionLost(conn ziface.IConnection) { zlog.Ins().InfoF("Conn is Lost") } func main() { //zlog.SetLogFile("./logs", "app.log") s := znet.NewServer() s.SetOnConnStart(DoConnectionBegin) s.SetOnConnStop(DoConnectionLost) // TLV protocol corresponding to business function // TLV协议对应业务功能 s.AddRouter(0x00000001, &router.TLVBusinessRouter{}) // Process HTLVCRC protocol data // 处理HTLVCRC协议数据 s.SetDecoder(zdecoder.NewHTLVCRCDecoder()) // TLV protocol corresponding to business function, because the funcode field in client.go is 0x10 // TLV协议对应业务功能,因为client.go中模拟数据funcode字段为0x10 s.AddRouter(0x10, &router.HtlvCrcBusinessRouter{}) // TLV protocol corresponding to business function, because the funcode field in client.go is 0x13 // TLV协议对应业务功能,因为client.go中模拟数据funcode字段为0x13 s.AddRouter(0x13, &router.HtlvCrcBusinessRouter{}) //开启服务 s.Serve() } ================================================ FILE: examples/zinx_dynamic_bind/client/client.go ================================================ package main import ( "os" "os/signal" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) const ( PingType = 1 PongType = 2 ) // ping response router type PongRouter struct { znet.BaseRouter client string } // Hash 工作模式下,需要等待接受到client1的pong后,才会收到client2和client3的pong // DynamicBind工作模式下,client2, client3 都会立马收到pong, 但client1的pong会被阻塞十秒后才收到 func (p *PongRouter) Handle(request ziface.IRequest) { //read server pong data zlog.Infof("---------client:%s, recv from server:%s, msgId=%d, data=%s ----------\n", p.client, request.GetConnection().RemoteAddr(), request.GetMsgID(), string(request.GetData())) } func onClient1Start(conn ziface.IConnection) { zlog.Infof("client1 connection start, %s->%s\n", conn.LocalAddrString(), conn.RemoteAddrString()) //send ping err := conn.SendMsg(PingType, []byte("Ping From Client1")) if err != nil { zlog.Error(err) } } func onClient2Start(conn ziface.IConnection) { zlog.Infof("client2 connection start, %s->%s\n", conn.LocalAddrString(), conn.RemoteAddrString()) //send ping err := conn.SendMsg(PingType, []byte("Ping From Client2")) if err != nil { zlog.Error(err) } } func onClient3Start(conn ziface.IConnection) { zlog.Infof("client3 connection start, %s->%s\n", conn.LocalAddrString(), conn.RemoteAddrString()) //send ping err := conn.SendMsg(PingType, []byte("Ping From Client3")) if err != nil { zlog.Error(err) } } func main() { //Create a client client client1 := znet.NewClient("127.0.0.1", 8999) client1.SetOnConnStart(onClient1Start) client1.AddRouter(PongType, &PongRouter{client: "client1"}) client1.Start() time.Sleep(time.Second) client2 := znet.NewClient("127.0.0.1", 8999) client2.SetOnConnStart(onClient2Start) client2.AddRouter(PongType, &PongRouter{client: "client2"}) client2.Start() time.Sleep(time.Second) client3 := znet.NewClient("127.0.0.1", 8999) client3.SetOnConnStart(onClient3Start) client3.AddRouter(PongType, &PongRouter{client: "client3"}) client3.Start() //Prevent the process from exiting, waiting for an interrupt signal signalChan := make(chan os.Signal, 1) signal.Notify(signalChan, os.Interrupt) <-signalChan client1.Stop() client2.Stop() client3.Stop() time.Sleep(time.Second) } ================================================ FILE: examples/zinx_dynamic_bind/server/conf/zinx.json ================================================ { "Name":"zinx server DynamicBind Mode Demo", "Host":"127.0.0.1", "TCPPort":8999, "MaxConn":12000, "WorkerPoolSize":1, "MaxWorkerTaskLen":50, "WorkerMode":"DynamicBind" } ================================================ FILE: examples/zinx_dynamic_bind/server/server.go ================================================ package main import ( "sync/atomic" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) func OnConnectionAdd(conn ziface.IConnection) { zlog.Debug("OnConnectionAdd:", conn.GetConnection().RemoteAddr()) } func OnConnectionLost(conn ziface.IConnection) { zlog.Debug("OnConnectionLost:", conn.GetConnection().RemoteAddr()) } type blockRouter struct { znet.BaseRouter } var Block = int32(1) // 模拟阻塞操作 func (r *blockRouter) Handle(request ziface.IRequest) { //read client data zlog.Infof("recv from client:%s, msgId=%d, data=%s\n", request.GetConnection().RemoteAddr(), request.GetMsgID(), string(request.GetData())) // 第一次处理时,模拟任务阻塞操作, Hash 模式下,后面的连接的任务得不到处理 // DynamicBind 模式下,看后面的连接的任务会得到即使处理,不会因为前面连接的任务阻塞而得不到处理 // 这里只模拟一次阻塞操作。 if atomic.CompareAndSwapInt32(&Block, 1, 0) { zlog.Infof("blockRouter handle start, msgId=%d, remote:%v\n", request.GetMsgID(), request.GetConnection().RemoteAddr()) time.Sleep(time.Second * 10) //阻塞操作结束 zlog.Infof("blockRouter handle end, msgId=%d, remote:%v\n", request.GetMsgID(), request.GetConnection().RemoteAddr()) } err := request.GetConnection().SendMsg(2, []byte("pong from server")) if err != nil { zlog.Error(err) return } zlog.Infof("send pong over, client:%s\n", request.GetConnection().RemoteAddr()) } func main() { s := znet.NewServer() s.SetOnConnStart(OnConnectionAdd) s.SetOnConnStop(OnConnectionLost) s.AddRouter(1, &blockRouter{}) s.Serve() } ================================================ FILE: examples/zinx_heartbeat/client/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" "time" ) // User-defined heartbeat message processing method // 用户自定义的心跳检测消息处理方法 func myClientHeartBeatMsg(conn ziface.IConnection) []byte { return []byte("heartbeat, I am Client, I am alive") } // User-defined handling method for remote connection not alive. // 用户自定义的远程连接不存活时的处理方法 func myClientOnRemoteNotAlive(conn ziface.IConnection) { fmt.Println("myClientOnRemoteNotAlive is Called, connID=", conn.GetConnID(), "remoteAddr = ", conn.RemoteAddr()) //关闭连接 conn.Stop() } // 用户自定义的心跳检测消息处理方法 type myClientHeartBeatRouter struct { znet.BaseRouter } func (r *myClientHeartBeatRouter) Handle(request ziface.IRequest) { fmt.Println("in myClientHeartBeatRouter Handle, recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) } func main() { client := znet.NewClient("127.0.0.1", 8999) myHeartBeatMsgID := 88888 // Start heartbeating detection. (启动心跳检测) client.StartHeartBeatWithOption(3*time.Second, &ziface.HeartBeatOption{ MakeMsg: myClientHeartBeatMsg, OnRemoteNotAlive: myClientOnRemoteNotAlive, Router: &myClientHeartBeatRouter{}, HeartBeatMsgID: uint32(myHeartBeatMsgID), }) client.Start() select {} } ================================================ FILE: examples/zinx_heartbeat/client_default/client_default.go ================================================ package main import ( "time" "github.com/aceld/zinx/znet" ) func main() { client := znet.NewClient("127.0.0.1", 8999) // Start heartbeating detection. client.StartHeartBeat(3 * time.Second) client.Start() // wait select {} } ================================================ FILE: examples/zinx_heartbeat/server/conf/zinx.json ================================================ { "Name":"Zinx Heartbeat", "Host":"127.0.0.1", "TcpPort":8999, "MaxConn":3, "WorkerPoolSize":10, "LogIsolationLevel": 1, "HeartbeatMax": 10 } ================================================ FILE: examples/zinx_heartbeat/server/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" "time" ) // User-defined heartbeat message processing method // 用户自定义的心跳检测消息处理方法 func myHeartBeatMsg(conn ziface.IConnection) []byte { return []byte("heartbeat, I am server, I am alive") } // User-defined handling method for remote connection not alive. // 用户自定义的远程连接不存活时的处理方法 func myOnRemoteNotAlive(conn ziface.IConnection) { fmt.Println("myOnRemoteNotAlive is Called, connID=", conn.GetConnID(), "remoteAddr = ", conn.RemoteAddr()) //关闭连接 conn.Stop() } // User-defined method for handling heartbeat messages (用户自定义的心跳检测消息处理方法) type myHeartBeatRouter struct { znet.BaseRouter } func (r *myHeartBeatRouter) Handle(request ziface.IRequest) { fmt.Println("in MyHeartBeatRouter Handle, recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) } func main() { s := znet.NewServer() myHeartBeatMsgID := 88888 // Start heartbeating detection. (启动心跳检测) s.StartHeartBeatWithOption(1*time.Second, &ziface.HeartBeatOption{ MakeMsg: myHeartBeatMsg, OnRemoteNotAlive: myOnRemoteNotAlive, Router: &myHeartBeatRouter{}, HeartBeatMsgID: uint32(myHeartBeatMsgID), }) s.Serve() } ================================================ FILE: examples/zinx_heartbeat/server_default/conf/zinx.json ================================================ { "Name":"Zinx Heartbeat", "Host":"127.0.0.1", "TcpPort":8999, "MaxConn":3, "WorkerPoolSize":10, "LogIsolationLevel": 1, "HeartbeatMax": 10 } ================================================ FILE: examples/zinx_heartbeat/server_default/server_default.go ================================================ package main import ( "github.com/aceld/zinx/znet" "time" ) func main() { s := znet.NewServer() // Start heartbeating detection. s.StartHeartBeat(5 * time.Second) s.Serve() } ================================================ FILE: examples/zinx_interceptor/client/client.go ================================================ package main import ( "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) func main() { client := znet.NewClient("127.0.0.1", 8999) client.SetOnConnStart(func(connection ziface.IConnection) { _ = connection.SendMsg(1, []byte("hello zinx")) }) client.Start() time.Sleep(time.Second) } ================================================ FILE: examples/zinx_interceptor/interceptors/interceptor_1.go ================================================ package interceptors import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" ) // Custom Interceptor 1 type MyInterceptor struct{} func (m *MyInterceptor) Intercept(chain ziface.IChain) ziface.IcResp { request := chain.Request() // This layer is the custom interceptor processing logic, which simply prints the input. // (这一层是自定义拦截器处理逻辑,这里只是简单打印输入) iRequest := request.(ziface.IRequest) zlog.Ins().InfoF("MyInterceptor, Recv:%s", iRequest.GetData()) return chain.Proceed(chain.Request()) } ================================================ FILE: examples/zinx_interceptor/router/route.go ================================================ package router import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) type HelloRouter struct { znet.BaseRouter } func (hr *HelloRouter) Handle(request ziface.IRequest) { zlog.Ins().InfoF(string(request.GetData())) } ================================================ FILE: examples/zinx_interceptor/server/server.go ================================================ package main import ( "github.com/aceld/zinx/examples/zinx_interceptor/interceptors" "github.com/aceld/zinx/examples/zinx_interceptor/router" "github.com/aceld/zinx/znet" ) func main() { server := znet.NewServer() server.AddRouter(1, &router.HelloRouter{}) // Add Custom Interceptor server.AddInterceptor(&interceptors.MyInterceptor{}) server.Serve() } ================================================ FILE: examples/zinx_kcp/client/kcp_client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zpack" "github.com/xtaci/kcp-go" "io" "time" ) // 模拟客户端 func main() { fmt.Println("Client Test ... start") // Replace net.Dial with kcp.DialWithOptions conn, err := kcp.Dial("127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } dp := zpack.Factory().NewPack(ziface.ZinxDataPack) sendMsg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte("client test message"))) _, err = conn.Write(sendMsg) if err != nil { fmt.Println("client write err: ", err) return } for { // Read the "head" section from the stream first. (先读出流中的head部分) headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) if err != nil { fmt.Println("client read head err: ", err) return } // Unpack the headData byte stream into msg. (将headData字节流 拆包到msg中) msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("client unpack head err: ", err) return } if msgHead.GetDataLen() > 0 { // Read the "data" section from the stream. (再读出流中的data部分) msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) // read from io.Reader into msg.Data (根据dataLen从io中读取字节流) _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("client unpack data err") return } fmt.Printf("==> Client receive Msg: ID = %d, len = %d , data = %s\n", msg.ID, msg.DataLen, msg.Data) time.Sleep(1 * time.Second) _, err = conn.Write(sendMsg) if err != nil { fmt.Println("client write err: ", err) return } } } } ================================================ FILE: examples/zinx_kcp/server/server.go ================================================ package main import ( "errors" "fmt" "github.com/aceld/zinx/zconf" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) type TestRouter struct { znet.BaseRouter } var dealTimes = 0 // PreHandle - func (t *TestRouter) PreHandle(req ziface.IRequest) { start := time.Now() fmt.Println("--> Call PreHandle") if err := req.GetConnection().SendMsg(0, []byte("test1")); err != nil { fmt.Println(err) } elapsed := time.Since(start) fmt.Println("cost time:", elapsed) } // Handle - func (t *TestRouter) Handle(req ziface.IRequest) { fmt.Println("--> Call Handle") if err := Err(); err != nil { req.Abort() fmt.Println("Insufficient permission") } dealTimes++ req.GetConnection().AddCloseCallback(nil, nil, func() { fmt.Println("run close callback") }) if err := req.GetConnection().SendMsg(0, []byte("test2")); err != nil { fmt.Println(err) } if dealTimes == 5 { req.GetConnection().Stop() } time.Sleep(1 * time.Millisecond) } // PostHandle - func (t *TestRouter) PostHandle(req ziface.IRequest) { fmt.Println("--> Call PostHandle") if err := req.GetConnection().SendMsg(0, []byte("test3")); err != nil { fmt.Println(err) } } func Err() error { //Specific Business Operation (具体业务操作) return errors.New("Test") } func main() { s := znet.NewUserConfServer(&zconf.Config{ Mode: "kcp", KcpPort: 7777, KcpRecvWindow: 128, KcpSendWindow: 128, KcpStreamMode: true, KcpACKNoDelay: false, LogDir: "./", LogFile: "test.log", KcpFecDataShards: 10, //代表每10个原始数据块 发3个校验数据块 KcpFecParityShards: 3, }) s.AddRouter(1, &TestRouter{}) s.SetOnConnStart(func(conn ziface.IConnection) { fmt.Println("--> OnConnStart") }) s.SetOnConnStop(func(conn ziface.IConnection) { fmt.Println("--> OnConnStop") }) s.Serve() } ================================================ FILE: examples/zinx_logger/client/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/examples/zinx_client/c_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "os" "os/signal" "time" ) func business(conn ziface.IConnection) { for { err := conn.SendMsg(1, []byte("Ping...[FromClient]")) if err != nil { fmt.Println(err) zlog.Error(err) break } time.Sleep(1 * time.Second) } } func DoClientConnectedBegin(conn ziface.IConnection) { zlog.Debug("DoConnectionBegin is Called ... ") conn.SetProperty("Name", "刘丹冰") conn.SetProperty("Home", "https://yuque.com/aceld") go business(conn) } func DoClientConnectedLost(conn ziface.IConnection) { if name, err := conn.GetProperty("Name"); err == nil { zlog.Error("Conn Property Name = ", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Error("Conn Property Home = ", home) } zlog.Debug("DoClientConnectedLost is Called ... ") } func main() { client := znet.NewClient("127.0.0.1", 8999) client.SetOnConnStart(DoClientConnectedBegin) client.SetOnConnStop(DoClientConnectedLost) client.AddRouter(0, &c_router.PingRouter{}) client.Start() // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) } ================================================ FILE: examples/zinx_logger/server/my_logger.go ================================================ package main import ( "context" "fmt" ) // User-defined logging method // The internal engine logging method of zinx can be reset by the logging method of its own business. // In this example, fmt.Println is used. // 用户自定义日志方式, // 可以通过自身业务的日志方式,来重置zinx内部引擎的日志打印方式 // 本例以fmt.Println为例 type MyLogger struct{} // Without context logging interface func (l *MyLogger) InfoF(format string, v ...interface{}) { fmt.Printf(format, v...) } func (l *MyLogger) ErrorF(format string, v ...interface{}) { fmt.Printf(format, v...) } func (l *MyLogger) DebugF(format string, v ...interface{}) { fmt.Printf(format, v...) } // Logging interface with context func (l *MyLogger) InfoFX(ctx context.Context, format string, v ...interface{}) { fmt.Println(ctx) fmt.Printf(format, v...) } func (l *MyLogger) ErrorFX(ctx context.Context, format string, v ...interface{}) { fmt.Println(ctx) fmt.Printf(format, v...) } func (l *MyLogger) DebugFX(ctx context.Context, format string, v ...interface{}) { fmt.Println(ctx) fmt.Printf(format, v...) } ================================================ FILE: examples/zinx_logger/server/server.go ================================================ package main import ( "fmt" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) type TestRouter struct { znet.BaseRouter } // PreHandle - func (t *TestRouter) PreHandle(req ziface.IRequest) { start := time.Now() fmt.Println("--> Call PreHandle") if err := req.GetConnection().SendMsg(0, []byte("test1")); err != nil { fmt.Println(err) } elapsed := time.Since(start) fmt.Println("elapsed:", elapsed) } // Handle - func (t *TestRouter) Handle(req ziface.IRequest) { fmt.Println("--> Call Handle") if err := req.GetConnection().SendMsg(0, []byte("test2")); err != nil { fmt.Println(err) } } // PostHandle - func (t *TestRouter) PostHandle(req ziface.IRequest) { fmt.Println("--> Call PostHandle") if err := req.GetConnection().SendMsg(0, []byte("test3")); err != nil { fmt.Println(err) } } func main() { s := znet.NewServer() s.AddRouter(1, &TestRouter{}) zlog.SetLogger(new(MyLogger)) s.Serve() } ================================================ FILE: examples/zinx_metrics/client/c1/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/examples/zinx_client/c_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "os" "os/signal" "time" ) func business(conn ziface.IConnection) { for { err := conn.SendMsg(100, []byte("Ping...[FromClient]")) if err != nil { fmt.Println(err) zlog.Error(err) break } time.Sleep(1 * time.Second) } } func DoClientConnectedBegin(conn ziface.IConnection) { zlog.Debug("DoConnectionBegin is Called ... ") //设置两个连接属性,在连接创建之后 conn.SetProperty("Name", "刘丹冰Aceld") conn.SetProperty("Home", "https://yuque.com/aceld") go business(conn) } func DoClientConnectedLost(conn ziface.IConnection) { if name, err := conn.GetProperty("Name"); err == nil { zlog.Debug("Conn Property Name = ", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Debug("Conn Property Home = ", home) } zlog.Debug("DoClientConnectedLost is Called ... ") } func main() { client := znet.NewClient("127.0.0.1", 8999) client.SetOnConnStart(DoClientConnectedBegin) client.SetOnConnStop(DoClientConnectedLost) client.AddRouter(2, &c_router.PingRouter{}) client.AddRouter(3, &c_router.HelloRouter{}) client.Start() // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) client.Stop() time.Sleep(time.Second * 2) } ================================================ FILE: examples/zinx_metrics/client/c2/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/examples/zinx_client/c_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "os" "os/signal" "time" ) func business(conn ziface.IConnection) { for { err := conn.SendMsg(100, []byte("Ping...[FromClient]")) if err != nil { fmt.Println(err) zlog.Error(err) break } time.Sleep(1 * time.Second) } } func DoClientConnectedBegin(conn ziface.IConnection) { zlog.Debug("DoConnectionBegin is Called ... ") conn.SetProperty("Name", "刘丹冰Aceld") conn.SetProperty("Home", "https://yuque.com/aceld") go business(conn) } func DoClientConnectedLost(conn ziface.IConnection) { if name, err := conn.GetProperty("Name"); err == nil { zlog.Debug("Conn Property Name = ", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Debug("Conn Property Home = ", home) } zlog.Debug("DoClientConnectedLost is Called ... ") } func main() { client := znet.NewClient("127.0.0.1", 8999) client.SetOnConnStart(DoClientConnectedBegin) client.SetOnConnStop(DoClientConnectedLost) client.AddRouter(2, &c_router.PingRouter{}) client.AddRouter(3, &c_router.HelloRouter{}) client.Start() // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) client.Stop() time.Sleep(time.Second * 2) } ================================================ FILE: examples/zinx_metrics/server/conf/zinx.json ================================================ { "Name":"MyZinxServer001", "Host":"0.0.0.0", "TCPPort":8999, "MaxConn":3, "WorkerPoolSize":10, "LogIsolationLevel": 0, "PrometheusMetricsEnable": true, "PrometheusServer": true, "PrometheusListen": "0.0.0.0:20004" } ================================================ FILE: examples/zinx_metrics/server/server.go ================================================ package main import ( "github.com/aceld/zinx/examples/zinx_server/s_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) func DoConnectionBegin(conn ziface.IConnection) { zlog.Ins().InfoF("DoConnectionBegin is Called ...") conn.SetProperty("Name", "Aceld") conn.SetProperty("Home", "https://yuque.com/aceld") err := conn.SendMsg(2, []byte("DoConnection BEGIN...")) if err != nil { zlog.Error(err) } } func DoConnectionLost(conn ziface.IConnection) { if name, err := conn.GetProperty("Name"); err == nil { zlog.Ins().InfoF("Conn Property Name = %v", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Ins().InfoF("Conn Property Home = %v", home) } zlog.Ins().InfoF("Conn is Lost") } // usage:$ curl 0.0.0.0:20004/metrics // to get Metrics func main() { s := znet.NewServer() s.SetOnConnStart(DoConnectionBegin) s.SetOnConnStop(DoConnectionLost) s.AddRouter(100, &s_router.PingRouter{}) s.AddRouter(1, &s_router.HelloZinxRouter{}) s.Serve() } ================================================ FILE: examples/zinx_mutiport/client8999/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/examples/zinx_client/c_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "os" "os/signal" "time" ) func business(conn ziface.IConnection) { for { err := conn.SendMsg(100, []byte("Ping...[FromClient]")) if err != nil { fmt.Println(err) zlog.Error(err) break } time.Sleep(1 * time.Second) } } func DoClientConnectedBegin(conn ziface.IConnection) { zlog.Debug("DoConnectionBegin is Called ... ") //设置两个连接属性,在连接创建之后 conn.SetProperty("Name", "刘丹冰Aceld") conn.SetProperty("Home", "https://yuque.com/aceld") go business(conn) } func DoClientConnectedLost(conn ziface.IConnection) { if name, err := conn.GetProperty("Name"); err == nil { zlog.Debug("Conn Property Name = ", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Debug("Conn Property Home = ", home) } zlog.Debug("DoClientConnectedLost is Called ... ") } func main() { client := znet.NewClient("127.0.0.1", 8999) client.SetOnConnStart(DoClientConnectedBegin) client.SetOnConnStop(DoClientConnectedLost) client.AddRouter(2, &c_router.PingRouter{}) client.AddRouter(3, &c_router.HelloRouter{}) client.Start() // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) client.Stop() time.Sleep(time.Second * 2) } ================================================ FILE: examples/zinx_mutiport/client9000/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/examples/zinx_client/c_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "os" "os/signal" "time" ) func business(conn ziface.IConnection) { for { err := conn.SendMsg(100, []byte("Ping...[FromClient]")) if err != nil { fmt.Println(err) zlog.Error(err) break } time.Sleep(1 * time.Second) } } func DoClientConnectedBegin(conn ziface.IConnection) { zlog.Debug("DoConnectionBegin is Called ... ") conn.SetProperty("Name", "刘丹冰Aceld") conn.SetProperty("Home", "https://yuque.com/aceld") go business(conn) } func DoClientConnectedLost(conn ziface.IConnection) { if name, err := conn.GetProperty("Name"); err == nil { zlog.Debug("Conn Property Name = ", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Debug("Conn Property Home = ", home) } zlog.Debug("DoClientConnectedLost is Called ... ") } func main() { client := znet.NewClient("127.0.0.1", 9000) client.SetOnConnStart(DoClientConnectedBegin) client.SetOnConnStop(DoClientConnectedLost) client.AddRouter(2, &c_router.PingRouter{}) client.AddRouter(3, &c_router.HelloRouter{}) client.Start() // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) client.Stop() time.Sleep(time.Second * 2) } ================================================ FILE: examples/zinx_mutiport/server/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/examples/zinx_server/s_router" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "os" "os/signal" ) // Execute when creating a connection (创建连接的时候执行) func DoConnectionBegin(conn ziface.IConnection) { zlog.Ins().InfoF("DoConnectionBegin is Called ...") //设置两个连接属性,在连接创建之后 conn.SetProperty("Name", "Aceld") conn.SetProperty("Home", "https://yuque.com/@aceld") err := conn.SendMsg(2, []byte("DoConnection BEGIN...")) if err != nil { zlog.Error(err) } } // Execute when connection lost (连接断开的时候执行) func DoConnectionLost(conn ziface.IConnection) { // Query the Name and Home properties of the conn before destroying the connectio // 在连接销毁之前,查询conn的Name,Home属性 if name, err := conn.GetProperty("Name"); err == nil { zlog.Ins().InfoF("Conn Property Name = %v", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Ins().InfoF("Conn Property Home = %v", home) } zlog.Ins().InfoF("Conn is Lost") } func main() { var i = 0 for i < 2 { port := 8999 + i s := znet.NewUserConfServer(&zconf.Config{ TCPPort: port, Name: fmt.Sprintf("MyZinxServer-port:%d", port), }) s.SetOnConnStart(DoConnectionBegin) s.SetOnConnStop(DoConnectionLost) s.AddRouter(100, &s_router.PingRouter{}) s.AddRouter(1, &s_router.HelloZinxRouter{}) go s.Serve() i++ } // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) } ================================================ FILE: examples/zinx_new_router/client/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zpack" "io" "net" "time" ) // 模拟客户端 func main() { fmt.Println("Client Test ... start") // Send a test request after 3 seconds to give the server a chance to start the service. (3秒之后发起测试请求,给服务端开启服务的机会) time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } dp := zpack.Factory().NewPack(ziface.ZinxDataPack) msg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte("client test message"))) _, err = conn.Write(msg) if err != nil { fmt.Println("client write err: ", err) return } for { // Read the "head" section from the stream first. (先读出流中的head部分) headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) if err != nil { fmt.Println("client read head err: ", err) return } // Unpack the headData byte stream into msg. (将headData字节流 拆包到msg中) msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("client unpack head err: ", err) return } if msgHead.GetDataLen() > 0 { // Read the "data" section from the stream. (再读出流中的data部分) msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) // read from io.Reader into msg.Data (根据dataLen从io中读取字节流) _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("client unpack data err") return } fmt.Printf("==> Client receive Msg: ID = %d, len = %d , data = %s\n", msg.ID, msg.DataLen, msg.Data) } } } ================================================ FILE: examples/zinx_new_router/server/conf/zinx.json ================================================ { "Name":"zinx v-0.10 demoApp", "Host":"127.0.0.1", "TCPPort":7777, "MaxConn":3, "WorkerPoolSize":10, "LogDir": "./mylog", "LogFile":"zinx.log" } ================================================ FILE: examples/zinx_new_router/server/server.go ================================================ package main import ( "errors" "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" "time" ) type TestRouter struct { znet.BaseRouter } // PreHandle - func (t *TestRouter) PreHandle(req ziface.IRequest) { start := time.Now() fmt.Println("--> Call PreHandle") if err := req.GetConnection().SendMsg(0, []byte("test1")); err != nil { fmt.Println(err) } elapsed := time.Since(start) fmt.Println("cost time:", elapsed) } // Handle - func (t *TestRouter) Handle(req ziface.IRequest) { fmt.Println("--> Call Handle") // Simulated scenario - In the event of an expected error such as incorrect permissions or incorrect information, // subsequent function execution will be stopped, but this function will be fully executed. // 模拟场景- 出现意料之中的错误 如权限不对或者信息错误 则停止后续函数执行,但是次函数会执行完毕 if err := Err(); err != nil { req.Abort() fmt.Println("Insufficient permission") } // Simulation scenario - In case of a certain situation, repeat the above operation. // 模拟场景- 出现某种情况,重复上面的操作 /* if err := Err(); err != nil { req.Goto(znet.PRE_HANDLE) fmt.Println("repeat") } */ if err := req.GetConnection().SendMsg(0, []byte("test2")); err != nil { fmt.Println(err) } time.Sleep(1 * time.Millisecond) } // PostHandle - func (t *TestRouter) PostHandle(req ziface.IRequest) { fmt.Println("--> Call PostHandle") if err := req.GetConnection().SendMsg(0, []byte("test3")); err != nil { fmt.Println(err) } } func Err() error { //Specific Business Operation (具体业务操作) return errors.New("Test") } func main() { s := znet.NewServer() s.AddRouter(1, &TestRouter{}) s.Serve() } ================================================ FILE: examples/zinx_protobuf/client/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zinx_app_demo/mmo_game/pb" "github.com/aceld/zinx/znet" "github.com/golang/protobuf/proto" "os" "os/signal" "time" ) type PositionClientRouter struct { znet.BaseRouter } func (this *PositionClientRouter) Handle(request ziface.IRequest) { fmt.Println("Handle....") msg := &pb.Position{} err := proto.Unmarshal(request.GetData(), msg) if err != nil { fmt.Println("Position Unmarshal error ", err, " data = ", request.GetData()) return } fmt.Printf("recv from server : msgId=%+v, data=%+v\n", request.GetMsgID(), msg) } // 客户端自定义业务 func business(conn ziface.IConnection) { for { msg := &pb.Position{} msg.X = 1 msg.Y = 2 msg.Z = 3 msg.V = 4 data, err := proto.Marshal(msg) if err != nil { fmt.Println("proto Marshal error = ", err, " msg = ", msg) break } err = conn.SendMsg(0, data) if err != nil { fmt.Println(err) break } time.Sleep(1 * time.Second) } } func DoClientConnectedBegin(conn ziface.IConnection) { conn.SetProperty("Name", "刘丹冰Aceld") conn.SetProperty("Home", "https://yuque.com/aceld") go business(conn) } func wait() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) } func main() { client := znet.NewClient("127.0.0.1", 8999) client.SetOnConnStart(DoClientConnectedBegin) client.AddRouter(0, &PositionClientRouter{}) client.Start() wait() } ================================================ FILE: examples/zinx_protobuf/server/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zinx_app_demo/mmo_game/pb" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "github.com/golang/protobuf/proto" ) type PositionServerRouter struct { znet.BaseRouter } // Ping Handle func (this *PositionServerRouter) Handle(request ziface.IRequest) { msg := &pb.Position{} err := proto.Unmarshal(request.GetData(), msg) if err != nil { fmt.Println("Position Unmarshal error ", err, " data = ", request.GetData()) return } fmt.Printf("recv from client : msgId=%+v, data=%+v\n", request.GetMsgID(), msg) msg.X += 1 msg.Y += 1 msg.Z += 1 msg.V += 1 data, err := proto.Marshal(msg) if err != nil { fmt.Println("proto Marshal error = ", err, " msg = ", msg) return } err = request.GetConnection().SendMsg(0, data) if err != nil { zlog.Error(err) } } func main() { s := znet.NewServer() s.AddRouter(0, &PositionServerRouter{}) s.Serve() } ================================================ FILE: examples/zinx_routerSlices/client/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "net" ) func main() { conn, err := net.Dial("tcp", "127.0.0.1:8999") if err != nil { fmt.Println("client start err, exit!", err) return } dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte("ZinxPing"))) _, err = conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } } ================================================ FILE: examples/zinx_routerSlices/default_func_server/server.go ================================================ package main import ( "fmt" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) func DefaultTest1(request ziface.IRequest) { fmt.Println("test1") } func DefaultTest2(request ziface.IRequest) { time.Sleep(1) arr := make([]int, 1) fmt.Println(arr[1]) } func main() { s := znet.NewDefaultRouterSlicesServer() s.AddRouterSlices(1, DefaultTest1, DefaultTest2) s.Serve() } ================================================ FILE: examples/zinx_routerSlices/router_func_server/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) func Auth1(request ziface.IRequest) { // Verify business, default to pass. (验证业务 默认固定放行) fmt.Println("I am the Auth1, I will always pass.") // I am validation handler 1, and I must pass. // Note that the next function will start executing and return here after all functions are executed. // (注意是进入下一个函数开始执行,全部执行完后 回到此处) request.RouterSlicesNext() } func Auth2(request ziface.IRequest) { // I am the validation handler 2, and by default, I do not allow the request to pass.(验证业务 默认固定不放行) // Terminate execution function, no more handlers will be executed after this one.(终结执行函数,再这个处理器结束后不会在执行后面的处理器) request.Abort() fmt.Println("I am the Auth2, I will definitely not pass.") fmt.Println("The business terminates here and the subsequent handlers will not be executed.") } func Auth3(request ziface.IRequest) { fmt.Println("I am the group validation function.") } // I am a business function. func TestFunc(request ziface.IRequest) { fmt.Println("I am a business function.") } func main() { // New version usage and explanation.(新版本使用方法以及说明) server := znet.NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: "127.0.0.1"}) // Simulation scenario 1: A normal business that only executes a single operation function separately. // 模拟场景 1,普通业务单独只执行一个操作函数 //server.AddRouterSlices(1, TestFunc) // Simulated scenario 2: All operations below require verification of request permissions, so a verification function is needed. // the verification component has been added to all the routes under the use method, such as 1 and 2. // 模拟场景 2, 以下所有操作都需要验证请求权限,所以需要一个验证函数 // 将验证组件添加到了所有再use方法下的所有路由中了 如1,2 中都会带有 //routerSlices := server.Use(Auth1) //routerSlices.AddHandler(1, TestFunc) //routerSlices.AddHandler(2, TestFunc) // Equivalent to the following:(等价于下面:) //routerSlices.AddHandler(1, Auth1, TestFunc) // Simulated scenario 3: Authorization is required, but some route operations require additional verification. // 模拟场景3 需要权限,但是某些路由操作需要更多额外验证 server.Use(Auth1) group1 := server.Group(1, 2, Auth3) { // MsgId=1, there will be Auth3 and Auth1. (1中就会有Auth3和Auth1) group1.AddHandler(1, TestFunc) // More specific scenario: Some operations within the group require an additional validation process. // 更特殊的情况,组内另一些操作还需要另一道校验处理 group1.Use(Auth2) // MsgId=2, Auth3 and Auth1 will be added to all routes under the use method. // 2中就会有Auth1和Auth3以及Auth2 group1.AddHandler(2, TestFunc) } // MsgId=3, Auth3 will not be included. (3中就不会有Auth3) server.AddRouterSlices(3, TestFunc) server.Serve() } ================================================ FILE: examples/zinx_routerSlices/router_group_server/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) func Test1(request ziface.IRequest) { fmt.Println("test1") } func Test2(request ziface.IRequest) { fmt.Println("Test2") } func Test3(request ziface.IRequest) { fmt.Println("Test3") } func Test4(request ziface.IRequest) { fmt.Println("Test4") } func Test5(request ziface.IRequest) { fmt.Println("Test5") } func Test6(request ziface.IRequest) { fmt.Println("Test6") } type router struct { znet.BaseRouter } func (r *router) PreHandle(req ziface.IRequest) { fmt.Println(" hello router1") } func (r *router) Handle(req ziface.IRequest) { req.Abort() fmt.Println(" hello router2") } func (r *router) PostHandle(req ziface.IRequest) { fmt.Println(" hello router3") } func main() { // Old version router method (旧版本路由方法) //{ // server := znet.NewUserConfServer(&zconf.Config{TCPPort: 8999, Host: "127.0.0.1"}) // // // Even without manually calling the router mode, the default is 1 (old version) 即使不手动调路由模式也可以,默认是1(旧版本) // //server := znet.NewServer() // // // Old version runs normally(旧版正常执行) // r := &router{} // server.AddRouter(1, r) // server.Serve() //} //{ // New version usage and explanation(新版本使用方法以及说明) { server := znet.NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: "127.0.0.1"}) // Grouping(分组) group := server.Group(3, 10, Test1) // Add router. Will panic if not within the group range.(添加路由 如果不在组范围会直接panic) //group.AddHandler(11, Test2) // Within the group, not affected by Use, has processors 1 and 2.(在组中 不受Use影响 有 1 2 处理器) group.AddHandler(3, Test2) // Not within the group and before Use, only has its own processor 3.(既不在组里也在Use之前只会有自己的处理器 3) server.AddRouterSlices(1, Test3) // If you want the group processor to have priority, you should do the following before Use. // You can manually add it via group.AddHandler(5, Test4, Test5,Test2, Test3, Test6) // 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. // 如果希望group处理器优先,应当在Use之前如下操作 // 可以手动添加 入 group.AddHandler(5, Test4, Test5,Test2, Test3, Test6) // 或者如下使用Group的Use方法 那么就是 1 4 5 6的顺序 不被use影响 group.Use(Test2, Test3) group.AddHandler(5, Test4, Test5, Test6) // Common components, but not affected by the groups or routers before Use. (公共组件,但是,在使用Use之前的组或者路由不会影响到) router := server.Use(Test4, Test5) // Add router. Not within the group but is affected by Use, has processors 4, 5, and 6. // (添加路由 不在组中但是收Use影响 有4 5 6处理器) router.AddHandler(2, Test6) // 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. // (在组里也受到Use影响 有所有处理器 且顺序应该是 4 5 1 2 3 6 因为use中的处理器始终在最前端) group.AddHandler(4, Test6) server.Serve() } } ================================================ FILE: examples/zinx_server/Makefile ================================================ PROJECT_NAME:=zinx_server VERSION:=v1 .PHONY: image run build clean build: bash build.sh ${PROJECT_NAME} image: docker build -t ${PROJECT_NAME}:${VERSION} . run: docker run -itd \ -p 8999:8999 \ ${PROJECT_NAME}:${VERSION} clean: rm -rf ${PROJECT_NAME} ================================================ FILE: examples/zinx_server/build.sh ================================================ #!/bin/bash set -e APP_NAME=$1 APP_VERSION=v$(cat version) BUILD_VERSION=$(git log -1 --oneline) BUILD_TIME=$(date "+%FT%T%z") GIT_REVISION=$(git rev-parse --short HEAD) GIT_BRANCH=$(git name-rev --name-only HEAD) GO_VERSION=$(go version) go build -ldflags " \ -X 'main.AppName=${APP_NAME}' \ -X 'main.AppVersion=${APP_VERSION}' \ -X 'main.BuildVersion=${BUILD_VERSION//\'/_}' \ -X 'main.BuildTime=${BUILD_TIME}' \ -X 'main.GitRevision=${GIT_REVISION}' \ -X 'main.GitBranch=${GIT_BRANCH}' \ -X 'main.GoVersion=${GO_VERSION}' \ " -o $APP_NAME ================================================ FILE: examples/zinx_server/conf/zinx.json ================================================ { "Name":"zinx server Demo", "Host":"127.0.0.1", "TCPPort":8999, "MaxConn":3, "WorkerMode": "", "WorkerPoolSize":10, "LogDir": "./mylog", "LogFile":"zinx.log", "LogIsolationLevel": 0, "Mode":"kcp" } ================================================ FILE: examples/zinx_server/dockerfile ================================================ FROM centos:8 COPY zinx_server /zinx-server COPY /conf/zinx.json /conf/zinx.json WORKDIR / EXPOSE 8999 ENTRYPOINT [ "/zinx-server" ] ================================================ FILE: examples/zinx_server/main.go ================================================ /** * @Author: Aceld * @Date: 2020/12/24 00:24 * @Mail: danbing.at@gmail.com * zinx server demo */ package main import ( "github.com/aceld/zinx/examples/zinx_server/s_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) // DoConnectionBegin Executed when creating a connection. // 创建连接的时候执行 func DoConnectionBegin(conn ziface.IConnection) { zlog.Ins().InfoF("DoConnectionBegin is Called ...") //设置两个连接属性,在连接创建之后 conn.SetProperty("Name", "Aceld") conn.SetProperty("Home", "https://www.kancloud.cn/@aceld") err := conn.SendMsg(2, []byte("DoConnection BEGIN...")) if err != nil { zlog.Error(err) } } // 连接断开的时候执行 // DoConnectionLost Executed when the connection is closed. func DoConnectionLost(conn ziface.IConnection) { //在连接销毁之前,查询conn的Name,Home属性 // Query the Name and Home properties of conn before destroying the connection. if name, err := conn.GetProperty("Name"); err == nil { zlog.Ins().InfoF("Conn Property Name = %v", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Ins().InfoF("Conn Property Home = %v", home) } zlog.Ins().InfoF("Conn is Lost") } func main() { // Create a server s := znet.NewServer() // Register a hook callback function for the connection s.SetOnConnStart(DoConnectionBegin) s.SetOnConnStop(DoConnectionLost) // Configure routing. s.AddRouter(100, &s_router.PingRouter{}) s.AddRouter(1, &s_router.HelloZinxRouter{}) // Start Service s.Serve() } ================================================ FILE: examples/zinx_server/s_router/hello.go ================================================ package s_router import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) type HelloZinxRouter struct { znet.BaseRouter } // HelloZinxRouter Handle func (this *HelloZinxRouter) Handle(request ziface.IRequest) { zlog.Ins().DebugF("Call HelloZinxRouter Handle") // Read the data from the client first, then send back "ping...ping...ping" zlog.Ins().DebugF("recv from client : msgId=%d, data=%+v, len=%d", request.GetMsgID(), string(request.GetData()), len(request.GetData())) err := request.GetConnection().SendBuffMsg(3, []byte("Hello Zinx Router[FromServer]")) if err != nil { zlog.Error(err) } } ================================================ FILE: examples/zinx_server/s_router/ping.go ================================================ package s_router import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { zlog.Ins().DebugF("Call PingRouter Handle") // Read the data from the client first, then send back "ping...ping...ping". zlog.Ins().DebugF("recv from client : msgId=%d, data=%+v, len=%d", request.GetMsgID(), string(request.GetData()), len(request.GetData())) err := request.GetConnection().SendMsg(2, []byte("pong-server")) if err != nil { zlog.Error(err) } } ================================================ FILE: examples/zinx_server/version ================================================ v 1.0.0 ================================================ FILE: examples/zinx_tls/client/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "os" "os/signal" "time" ) // PongRouter pong test 自定义路由 type PongRouter struct { znet.BaseRouter } // Handle Pong Handle func (this *PongRouter) Handle(request ziface.IRequest) { zlog.Debug("Call PongRouter Handle") //先读取服务器返回的数据 zlog.Debug("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) } func wait() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) } func main() { // Create a TLS client. c := znet.NewTLSClient("127.0.0.1", 8899) c.SetOnConnStart(func(connection ziface.IConnection) { go func() { for { err := connection.SendMsg(1, []byte("Ping with TLS")) if err != nil { fmt.Println(err) break } time.Sleep(1 * time.Second) } }() }) c.AddRouter(2, &PongRouter{}) c.Start() wait() } ================================================ FILE: examples/zinx_tls/server/server.go ================================================ package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "math/big" "os" "time" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) // PingRouter ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Handle Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { zlog.Debug("Call PingRouter Handle") zlog.Debug("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendBuffMsg(2, []byte("Pong with TLS"), ziface.WithSendMsgTimeout(time.Millisecond*10)) if err != nil { zlog.Error(err) } } // genExampleCrtAndKeyFile // Generate certificate and key files for testing purposes only! Please customize this function or use openssl to generate them for actual use. // (仅测试时生成证书和密钥文件!!实际使用请自定义该函数或者用openssl自行生成) // Reference for generating certificate and private key using openssl : https://blog.csdn.net/qq_44637753/article/details/124152315 // (openssl生成证书和私钥方法参考 https://blog.csdn.net/qq_44637753/article/details/124152315) func genExampleCrtAndKeyFile(crtFileName, KeyFileName string) (err error) { // If already exists, regenerate.(如果已存在则重新生成) _ = os.Remove(crtFileName) _ = os.Remove(KeyFileName) defer func() { if err != nil { // If there is an error during the process, delete the generated certificate and private key files. // (如果期间发生错误,删除以及生成的证书和私钥文件) _ = os.Remove(crtFileName) _ = os.Remove(KeyFileName) } }() // Generating a private key.(生成私钥) privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) // Creating a certificate template.(创建证书模板) serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { return err } template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"Beijing University of Post and Telecommunication"}, }, NotBefore: time.Now(), NotAfter: time.Now().Add(24 * time.Hour * 365 * 10), // The certificate is valid for ten years. (证书十年之内有效) KeyUsage: x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, } // Generating a certificate.(生成证书) derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey) if err != nil { return err } // serialize the certificate file.(序列化证书文件) pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) if pemCert == nil { return err } if err := os.WriteFile(crtFileName, pemCert, 0644); err != nil { return err } // Generating private key file(生成私钥文件) privateBytes, err := x509.MarshalPKCS8PrivateKey(privateKey) if err != nil { return err } pemKey := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateBytes}) if pemKey == nil { return err } if err := os.WriteFile(KeyFileName, pemKey, 0600); err != nil { return err } return nil } func main() { // Generate certificate and key files for testing purposes only!! Please customize this function or use openssl to generate them yourself in actual use. // Refer to this link for how to generate certificates and private keys using openssl: https://blog.csdn.net/qq_44637753/article/details/124152315 // 生成测试用的证书和密钥文件!!仅测试时生成证书和密钥文件!!实际使用请自定义该函数或者用openssl自行生成 // openssl生成证书和私钥方法参考 https://blog.csdn.net/qq_44637753/article/details/124152315 certFile := "cert.pem" keyFile := "key.pem" err := genExampleCrtAndKeyFile(certFile, keyFile) if err != nil { panic(err) } defer func() { // example中的证书和私钥文件仅作测试时使用 测试结束后删除 // The certificate and private key files in the example are only used for testing purposes. Please delete them after the test is completed. _ = os.Remove(certFile) _ = os.Remove(keyFile) }() // Create a server, and if CertFile and PrivateKeyFile are specified, the server will start in TLS mode. // 创建一个server,当指定了CertFile和PrivateKeyFile时服务器开启TLS模式 s := znet.NewUserConfServer(&zconf.Config{ TCPPort: 8899, CertFile: certFile, // 证书文件 PrivateKeyFile: keyFile, // 密钥文件 }) s.AddRouter(1, &PingRouter{}) s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.10Test/client0/Client0.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte("Zinx V0.8 Client0 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.10Test/client1/Client1.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for n := 3; n >= 0; n-- { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte("Zinx V0.8 Client1 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.10Test/server/Server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { fmt.Println("Call PingRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping")) if err != nil { fmt.Println(err) } } type HelloZinxRouter struct { znet.BaseRouter } // HelloZinxRouter Handle func (this *HelloZinxRouter) Handle(request ziface.IRequest) { fmt.Println("Call HelloZinxRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendBuffMsg(1, []byte("Hello Zinx Router V0.10")) if err != nil { fmt.Println(err) } } // 创建连接的时候执行 func DoConnectionBegin(conn ziface.IConnection) { fmt.Println("DoConnectionBegin is Called ... ") //设置两个连接属性,在连接创建之后 fmt.Println("Set conn Name, Home done!") conn.SetProperty("Name", "Aceld") conn.SetProperty("Home", "https://www.jianshu.com/u/35261429b7f1") err := conn.SendMsg(2, []byte("DoConnection BEGIN...")) if err != nil { fmt.Println(err) } } // 连接断开的时候执行 func DoConnectionLost(conn ziface.IConnection) { //在连接销毁之前,查询conn的Name,Home属性 if name, err := conn.GetProperty("Name"); err == nil { fmt.Println("Conn Property Name = ", name) } if home, err := conn.GetProperty("Home"); err == nil { fmt.Println("Conn Property Home = ", home) } fmt.Println("DoConneciotnLost is Called ... ") } func main() { //创建一个server句柄 s := znet.NewServer() //注册连接hook回调函数 s.SetOnConnStart(DoConnectionBegin) s.SetOnConnStop(DoConnectionLost) //配置路由 s.AddRouter(0, &PingRouter{}) s.AddRouter(1, &HelloZinxRouter{}) //开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.10Test/server/conf/zinx.json ================================================ { "Name":"zinx v-0.10 demoApp", "Host":"127.0.0.1", "TcpPort":7777, "MaxConn":3, "WorkerPoolSize":10 } ================================================ FILE: examples/zinx_version_ex/ZinxV0.11Test/client0/Client0.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte("Zinx V0.8 Client0 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.11Test/client1/Client1.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for n := 3; n >= 0; n-- { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte("Zinx V0.8 Client1 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.11Test/server/Server.go ================================================ /** * @Author: Aceld * @Date: 2019/4/30 17:42 * @Mail: danbing.at@gmail.com * ZinxV0.11测试,测试Zinx 日志模块功能 zlog模块 */ package main import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { zlog.Debug("Call PingRouter Handle") //先读取客户端的数据,再回写ping...ping...ping zlog.Debug("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping")) if err != nil { zlog.Error(err) } } type HelloZinxRouter struct { znet.BaseRouter } // HelloZinxRouter Handle func (this *HelloZinxRouter) Handle(request ziface.IRequest) { zlog.Debug("Call HelloZinxRouter Handle") //先读取客户端的数据,再回写ping...ping...ping zlog.Debug("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendBuffMsg(1, []byte("Hello Zinx Router V0.10")) if err != nil { zlog.Error(err) } } // 创建连接的时候执行 func DoConnectionBegin(conn ziface.IConnection) { zlog.Debug("DoConnectionBegin is Called ... ") //设置两个连接属性,在连接创建之后 zlog.Debug("Set conn Name, Home done!") conn.SetProperty("Name", "Aceld") conn.SetProperty("Home", "https://www.jianshu.com/u/35261429b7f1") err := conn.SendMsg(2, []byte("DoConnection BEGIN...")) if err != nil { zlog.Error(err) } } // 连接断开的时候执行 func DoConnectionLost(conn ziface.IConnection) { //在连接销毁之前,查询conn的Name,Home属性 if name, err := conn.GetProperty("Name"); err == nil { zlog.Error("Conn Property Name = ", name) } if home, err := conn.GetProperty("Home"); err == nil { zlog.Error("Conn Property Home = ", home) } zlog.Debug("DoConneciotnLost is Called ... ") } func main() { //创建一个server句柄 s := znet.NewServer() //注册连接hook回调函数 s.SetOnConnStart(DoConnectionBegin) s.SetOnConnStop(DoConnectionLost) //配置路由 s.AddRouter(0, &PingRouter{}) s.AddRouter(1, &HelloZinxRouter{}) //开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.11Test/server/conf/zinx.json ================================================ { "Name":"zinx v-0.10 demoApp", "Host":"127.0.0.1", "TcpPort":7777, "MaxConn":3, "WorkerPoolSize":10, "LogDir": "./mylog", "LogFile":"zinx.log" } ================================================ FILE: examples/zinx_version_ex/ZinxV0.1Test/client/client.go ================================================ package main import ( "fmt" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { _, err := conn.Write([]byte("hahaha")) if err != nil { fmt.Println("write error err ", err) return } buf := make([]byte, 512) cnt, err := conn.Read(buf) if err != nil { fmt.Println("read buf error ") return } fmt.Printf(" server call back : %s, cnt = %d\n", buf, cnt) time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.1Test/server/server.go ================================================ package main import ( "github.com/aceld/zinx/znet" ) // Server 模块的测试函数 func main() { /* 服务端测试 */ //1 创建一个server 句柄 s // s := znet.NewServer("[zinx V0.1]") s := znet.NewServer() //2 开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.2Test/client/client.go ================================================ package main import ( "fmt" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { _, err := conn.Write([]byte("hahaha")) if err != nil { fmt.Println("write error err ", err) return } buf := make([]byte, 512) cnt, err := conn.Read(buf) if err != nil { fmt.Println("read buf error ") return } fmt.Printf(" server call back : %s, cnt = %d\n", buf, cnt) time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.2Test/server/server.go ================================================ package main import ( "github.com/aceld/zinx/znet" ) // Server 模块的测试函数 func main() { /* 服务端测试 */ //1 创建一个server 句柄 s // s := znet.NewServer("[zinx V0.2]") s := znet.NewServer() //2 开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.3Test/client/client.go ================================================ package main import ( "fmt" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { _, err := conn.Write([]byte("Zinx V0.3")) if err != nil { fmt.Println("write error err ", err) return } buf := make([]byte, 512) cnt, err := conn.Read(buf) if err != nil { fmt.Println("read buf error ") return } fmt.Printf(" server call back : %s, cnt = %d\n", buf, cnt) time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.3Test/server/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Test PreHandle func (this *PingRouter) PreHandle(request ziface.IRequest) { fmt.Println("Call Router PreHandle") _, err := request.GetConnection().GetTCPConnection().Write([]byte("before ping ....\n")) if err != nil { fmt.Println("call back ping ping ping error") } } // Test Handle func (this *PingRouter) Handle(request ziface.IRequest) { fmt.Println("Call PingRouter Handle") _, err := request.GetConnection().GetTCPConnection().Write([]byte("ping...ping...ping\n")) if err != nil { fmt.Println("call back ping ping ping error") } } // Test PostHandle func (this *PingRouter) PostHandle(request ziface.IRequest) { fmt.Println("Call Router PostHandle") _, err := request.GetConnection().GetTCPConnection().Write([]byte("After ping .....\n")) if err != nil { fmt.Println("call back ping ping ping error") } } func main() { //创建一个server句柄 // s := znet.NewServer("[zinx V0.3]") s := znet.NewServer() // s.AddRouter(&PingRouter{}) s.AddRouter(3, &PingRouter{}) //2 开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.4Test/client/client.go ================================================ package main import ( "fmt" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { _, err := conn.Write([]byte("Zinx V0.3")) if err != nil { fmt.Println("write error err ", err) return } buf := make([]byte, 512) cnt, err := conn.Read(buf) if err != nil { fmt.Println("read buf error ") return } fmt.Printf(" server call back : %s, cnt = %d\n", buf, cnt) time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.4Test/server/conf/zinx.json ================================================ { "Name":"zinx v-0.4 demoApp", "Host":"127.0.0.1", "TcpPort":7777, "MaxConn":3 } ================================================ FILE: examples/zinx_version_ex/ZinxV0.4Test/server/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Test PreHandle func (this *PingRouter) PreHandle(request ziface.IRequest) { fmt.Println("Call Router PreHandle") _, err := request.GetConnection().GetTCPConnection().Write([]byte("before ping ....\n")) if err != nil { fmt.Println("call back ping ping ping error") } } // Test Handle func (this *PingRouter) Handle(request ziface.IRequest) { fmt.Println("Call PingRouter Handle") _, err := request.GetConnection().GetTCPConnection().Write([]byte("ping...ping...ping\n")) if err != nil { fmt.Println("call back ping ping ping error") } } // Test PostHandle func (this *PingRouter) PostHandle(request ziface.IRequest) { fmt.Println("Call Router PostHandle") _, err := request.GetConnection().GetTCPConnection().Write([]byte("After ping .....\n")) if err != nil { fmt.Println("call back ping ping ping error") } } func main() { //创建一个server句柄 s := znet.NewServer() //配置路由 // s.AddRouter(&PingRouter{}) s.AddRouter(4, &PingRouter{}) //开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.5Test/client/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte("Zinx V0.5 Client Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.5Test/server/conf/zinx.json ================================================ { "Name":"zinx v-0.5 demoApp", "Host":"127.0.0.1", "TcpPort":7777, "MaxConn":3 } ================================================ FILE: examples/zinx_version_ex/ZinxV0.5Test/server/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Test Handle func (this *PingRouter) Handle(request ziface.IRequest) { fmt.Println("Call PingRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) //回写数据 /* _, err := request.GetConnection().GetTCPConnection().Write([]byte("ping...ping...ping\n")) if err != nil { fmt.Println("call back ping ping ping error") } */ err := request.GetConnection().SendMsg(1, []byte("ping...ping...ping")) if err != nil { fmt.Println(err) } } func main() { //创建一个server句柄 s := znet.NewServer() //配置路由 s.AddRouter(5, &PingRouter{}) //开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.6Test-V0.7Test/client0/Client0.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte("Zinx V0.6 Client0 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.6Test-V0.7Test/client1/Client1.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte("Zinx V0.6 Client1 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.6Test-V0.7Test/server/Server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { fmt.Println("Call PingRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendMsg(0, []byte("ping...ping...ping")) if err != nil { fmt.Println(err) } } type HelloZinxRouter struct { znet.BaseRouter } // HelloZinxRouter Handle func (this *HelloZinxRouter) Handle(request ziface.IRequest) { fmt.Println("Call HelloZinxRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendMsg(1, []byte("Hello Zinx Router V0.6")) if err != nil { fmt.Println(err) } } func main() { //创建一个server句柄 s := znet.NewServer() //配置路由 s.AddRouter(0, &PingRouter{}) s.AddRouter(1, &HelloZinxRouter{}) //开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.6Test-V0.7Test/server/conf/zinx.json ================================================ { "Name":"zinx v-0.6 demoApp", "Host":"127.0.0.1", "TcpPort":7777, "MaxConn":3 } ================================================ FILE: examples/zinx_version_ex/ZinxV0.8Test/client0/Client0.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte("Zinx V0.8 Client0 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.8Test/client1/Client1.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte("Zinx V0.8 Client1 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.8Test/server/Server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { fmt.Println("Call PingRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendMsg(0, []byte("ping...ping...ping")) if err != nil { fmt.Println(err) } } type HelloZinxRouter struct { znet.BaseRouter } // HelloZinxRouter Handle func (this *HelloZinxRouter) Handle(request ziface.IRequest) { fmt.Println("Call HelloZinxRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendMsg(1, []byte("Hello Zinx Router V0.8")) if err != nil { fmt.Println(err) } } func main() { //创建一个server句柄 s := znet.NewServer() //配置路由 s.AddRouter(0, &PingRouter{}) s.AddRouter(1, &HelloZinxRouter{}) //开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.8Test/server/conf/zinx.json ================================================ { "Name":"zinx v-0.8 demoApp", "Host":"127.0.0.1", "TcpPort":7777, "MaxConn":3, "WorkerPoolSize":10 } ================================================ FILE: examples/zinx_version_ex/ZinxV0.9Test/client0/Client0.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(0, []byte("Zinx V0.8 Client0 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.9Test/client1/Client1.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" "time" ) /* 模拟客户端 */ func main() { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client start err, exit!") return } for n := 3; n >= 0; n-- { //发封包message消息 dp := zpack.NewDataPack() msg, _ := dp.Pack(zpack.NewMsgPackage(1, []byte("Zinx V0.8 Client1 Test Message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("write error err ", err) return } //先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } time.Sleep(1 * time.Second) } } ================================================ FILE: examples/zinx_version_ex/ZinxV0.9Test/server/Server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) // ping test 自定义路由 type PingRouter struct { znet.BaseRouter } // Ping Handle func (this *PingRouter) Handle(request ziface.IRequest) { fmt.Println("Call PingRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping")) if err != nil { fmt.Println(err) } } type HelloZinxRouter struct { znet.BaseRouter } // HelloZinxRouter Handle func (this *HelloZinxRouter) Handle(request ziface.IRequest) { fmt.Println("Call HelloZinxRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendBuffMsg(1, []byte("Hello Zinx Router V0.8")) if err != nil { fmt.Println(err) } } // 创建连接的时候执行 func DoConnectionBegin(conn ziface.IConnection) { fmt.Println("DoConnectionBegin is Called ... ") err := conn.SendMsg(2, []byte("DoConnection BEGIN...")) if err != nil { fmt.Println(err) } } // 连接断开的时候执行 func DoConnectionLost(conn ziface.IConnection) { fmt.Println("DoConneciotnLost is Called ... ") } func main() { //创建一个server句柄 s := znet.NewServer() //注册连接hook回调函数 s.SetOnConnStart(DoConnectionBegin) s.SetOnConnStop(DoConnectionLost) //配置路由 s.AddRouter(0, &PingRouter{}) s.AddRouter(1, &HelloZinxRouter{}) //开启服务 s.Serve() } ================================================ FILE: examples/zinx_version_ex/ZinxV0.9Test/server/conf/zinx.json ================================================ { "Name":"zinx v-0.8 demoApp", "Host":"127.0.0.1", "TcpPort":7777, "MaxConn":3, "WorkerPoolSize":10 } ================================================ FILE: examples/zinx_version_ex/datapackDemo/client/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "net" ) func main() { //客户端goroutine,负责模拟粘包的数据,然后进行发送 conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client dial err:", err) return } //创建一个封包对象 dp dp := zpack.NewDataPack() //封装一个msg1包 msg1 := &zpack.Message{ ID: 0, DataLen: 5, Data: []byte{'h', 'e', 'l', 'l', 'o'}, } sendData1, err := dp.Pack(msg1) if err != nil { fmt.Println("client pack msg1 err:", err) return } msg2 := &zpack.Message{ ID: 1, DataLen: 7, Data: []byte{'w', 'o', 'r', 'l', 'd', '!', '!'}, } sendData2, err := dp.Pack(msg2) if err != nil { fmt.Println("client temp msg2 err:", err) return } //将sendData1,和 sendData2 拼接一起,组成粘包 sendData1 = append(sendData1, sendData2...) //向服务器端写数据 conn.Write(sendData1) //客户端阻塞 select {} } ================================================ FILE: examples/zinx_version_ex/datapackDemo/server/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zpack" "io" "net" ) // 只是负责测试datapack拆包,封包功能 func main() { //创建socket TCP Server listener, err := net.Listen("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("server listen err:", err) return } //创建服务器gotoutine,负责从客户端goroutine读取粘包的数据,然后进行解析 for { conn, err := listener.Accept() if err != nil { fmt.Println("server accept err:", err) } //处理客户端请求 go func(conn net.Conn) { //创建封包拆包对象dp dp := zpack.NewDataPack() for { //1 先读出流中的head部分 headData := make([]byte, dp.GetHeadLen()) _, err := io.ReadFull(conn, headData) //ReadFull 会把msg填充满为止 if err != nil { fmt.Println("read head error") break } //将headData字节流 拆包到msg中 msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { //msg 是有data数据的,需要再次读取data数据 msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) //根据dataLen从io中读取字节流 _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } } }(conn) } //阻塞 select {} } ================================================ FILE: examples/zinx_version_ex/protoDemo/main.go ================================================ package main import ( "encoding/hex" "fmt" "github.com/aceld/zinx/examples/zinx_version_ex/protoDemo/pb" "github.com/golang/protobuf/proto" ) func main() { person := &pb.Person{ Name: "XiaoYuer", Age: 16, Emails: []string{"xiao_yu_er@sina.com", "yu_er@sina.cn"}, Phones: []*pb.PhoneNumber{ { Number: "13113111311", Type: pb.PhoneType_MOBILE, }, { Number: "14141444144", Type: pb.PhoneType_HOME, }, { Number: "19191919191", Type: pb.PhoneType_WORK, }, }, } data, err := proto.Marshal(person) if err != nil { fmt.Println("marshal err:", err) } fmt.Println(hex.EncodeToString(data)) newdata := &pb.Person{} err = proto.Unmarshal(data, newdata) if err != nil { fmt.Println("unmarshal err:", err) } fmt.Println(newdata) } ================================================ FILE: examples/zinx_version_ex/protoDemo/pb/Person.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // source: Person.proto package pb import ( fmt "fmt" proto "github.com/golang/protobuf/proto" math "math" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package // enum为关键字,作用为定义一种枚举类型 type PhoneType int32 const ( PhoneType_MOBILE PhoneType = 0 PhoneType_HOME PhoneType = 1 PhoneType_WORK PhoneType = 2 ) var PhoneType_name = map[int32]string{ 0: "MOBILE", 1: "HOME", 2: "WORK", } var PhoneType_value = map[string]int32{ "MOBILE": 0, "HOME": 1, "WORK": 2, } func (x PhoneType) String() string { return proto.EnumName(PhoneType_name, int32(x)) } func (PhoneType) EnumDescriptor() ([]byte, []int) { return fileDescriptor_841ab6396175eaf3, []int{0} } // message为关键字,作用为定义一种消息类型 type Person struct { Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age,omitempty"` Emails []string `protobuf:"bytes,3,rep,name=emails,proto3" json:"emails,omitempty"` Phones []*PhoneNumber `protobuf:"bytes,4,rep,name=phones,proto3" json:"phones,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Person) Reset() { *m = Person{} } func (m *Person) String() string { return proto.CompactTextString(m) } func (*Person) ProtoMessage() {} func (*Person) Descriptor() ([]byte, []int) { return fileDescriptor_841ab6396175eaf3, []int{0} } func (m *Person) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Person.Unmarshal(m, b) } func (m *Person) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Person.Marshal(b, m, deterministic) } func (m *Person) XXX_Merge(src proto.Message) { xxx_messageInfo_Person.Merge(m, src) } func (m *Person) XXX_Size() int { return xxx_messageInfo_Person.Size(m) } func (m *Person) XXX_DiscardUnknown() { xxx_messageInfo_Person.DiscardUnknown(m) } var xxx_messageInfo_Person proto.InternalMessageInfo func (m *Person) GetName() string { if m != nil { return m.Name } return "" } func (m *Person) GetAge() int32 { if m != nil { return m.Age } return 0 } func (m *Person) GetEmails() []string { if m != nil { return m.Emails } return nil } func (m *Person) GetPhones() []*PhoneNumber { if m != nil { return m.Phones } return nil } // message为关键字,作用为定义一种消息类型可以被另外的消息类型嵌套使用 type PhoneNumber struct { Number string `protobuf:"bytes,1,opt,name=number,proto3" json:"number,omitempty"` Type PhoneType `protobuf:"varint,2,opt,name=type,proto3,enum=pb.PhoneType" json:"type,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *PhoneNumber) Reset() { *m = PhoneNumber{} } func (m *PhoneNumber) String() string { return proto.CompactTextString(m) } func (*PhoneNumber) ProtoMessage() {} func (*PhoneNumber) Descriptor() ([]byte, []int) { return fileDescriptor_841ab6396175eaf3, []int{1} } func (m *PhoneNumber) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_PhoneNumber.Unmarshal(m, b) } func (m *PhoneNumber) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_PhoneNumber.Marshal(b, m, deterministic) } func (m *PhoneNumber) XXX_Merge(src proto.Message) { xxx_messageInfo_PhoneNumber.Merge(m, src) } func (m *PhoneNumber) XXX_Size() int { return xxx_messageInfo_PhoneNumber.Size(m) } func (m *PhoneNumber) XXX_DiscardUnknown() { xxx_messageInfo_PhoneNumber.DiscardUnknown(m) } var xxx_messageInfo_PhoneNumber proto.InternalMessageInfo func (m *PhoneNumber) GetNumber() string { if m != nil { return m.Number } return "" } func (m *PhoneNumber) GetType() PhoneType { if m != nil { return m.Type } return PhoneType_MOBILE } func init() { proto.RegisterEnum("pb.PhoneType", PhoneType_name, PhoneType_value) proto.RegisterType((*Person)(nil), "pb.Person") proto.RegisterType((*PhoneNumber)(nil), "pb.PhoneNumber") } func init() { proto.RegisterFile("Person.proto", fileDescriptor_841ab6396175eaf3) } var fileDescriptor_841ab6396175eaf3 = []byte{ // 209 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x4c, 0x8f, 0xcd, 0x4a, 0xc4, 0x30, 0x14, 0x85, 0x4d, 0x13, 0x83, 0xbd, 0xe3, 0x4f, 0xb8, 0x0b, 0xc9, 0x32, 0xce, 0xc6, 0xa0, 0xd0, 0xc5, 0xf8, 0x06, 0xc2, 0xc0, 0x88, 0x8e, 0x1d, 0x82, 0xe0, 0xba, 0x81, 0xa0, 0x82, 0x4d, 0x42, 0x3b, 0x2e, 0xfa, 0xf6, 0x92, 0x34, 0x94, 0xd9, 0x7d, 0xf7, 0x3b, 0x70, 0x0e, 0x17, 0x2e, 0x0f, 0x6e, 0x18, 0x83, 0x6f, 0xe2, 0x10, 0x8e, 0x01, 0xab, 0x68, 0xd7, 0x01, 0xf8, 0xec, 0x10, 0x81, 0xf9, 0xae, 0x77, 0x92, 0x28, 0xa2, 0x6b, 0x93, 0x19, 0x05, 0xd0, 0xee, 0xcb, 0xc9, 0x4a, 0x11, 0x7d, 0x6e, 0x12, 0xe2, 0x2d, 0x70, 0xd7, 0x77, 0x3f, 0xbf, 0xa3, 0xa4, 0x8a, 0xea, 0xda, 0x94, 0x0b, 0xef, 0x81, 0xc7, 0xef, 0xe0, 0xdd, 0x28, 0x99, 0xa2, 0x7a, 0xb5, 0xb9, 0x69, 0xa2, 0x6d, 0x0e, 0xc9, 0xbc, 0xff, 0xf5, 0xd6, 0x0d, 0xa6, 0xc4, 0xeb, 0x1d, 0xac, 0x4e, 0x74, 0xea, 0xf3, 0x99, 0xca, 0x6e, 0xb9, 0xf0, 0x0e, 0xd8, 0x71, 0x8a, 0xf3, 0xf4, 0xf5, 0xe6, 0x6a, 0x69, 0xfb, 0x98, 0xa2, 0x33, 0x39, 0x7a, 0x78, 0x84, 0x7a, 0x51, 0x08, 0xc0, 0xf7, 0xed, 0xf3, 0xcb, 0xdb, 0x56, 0x9c, 0xe1, 0x05, 0xb0, 0x5d, 0xbb, 0xdf, 0x0a, 0x92, 0xe8, 0xb3, 0x35, 0xaf, 0xa2, 0xb2, 0x3c, 0xbf, 0xfc, 0xf4, 0x1f, 0x00, 0x00, 0xff, 0xff, 0xdf, 0x3c, 0xf1, 0xb8, 0x02, 0x01, 0x00, 0x00, } ================================================ FILE: examples/zinx_version_ex/protoDemo/pb/Person.proto ================================================ syntax = "proto3"; //指定版本信息,不指定会报错 package pb; //后期生成go文件的包名 //message为关键字,作用为定义一种消息类型 message Person { string name = 1; //姓名 int32 age = 2; //年龄 repeated string emails = 3; //电子邮件(repeated表示字段允许重复) repeated PhoneNumber phones = 4; //手机号 } //enum为关键字,作用为定义一种枚举类型 enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } //message为关键字,作用为定义一种消息类型可以被另外的消息类型嵌套使用 message PhoneNumber { string number = 1; PhoneType type = 2; } ================================================ FILE: examples/zinx_websocket/client/client.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/examples/zinx_client/c_router" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" "os" "os/signal" "time" ) type PositionClientRouter struct { znet.BaseRouter } func (this *PositionClientRouter) Handle(request ziface.IRequest) { } // 客户端自定义业务 func business(conn ziface.IConnection) { for { err := conn.SendMsg(1, []byte("ping ping ping ...")) if err != nil { fmt.Println(err) } time.Sleep(1 * time.Second) } } // 创建连接的时候执行 func DoClientConnectedBegin(conn ziface.IConnection) { //设置两个连接属性,在连接创建之后 conn.SetProperty("Name", "刘丹冰") conn.SetProperty("Home", "https://yuque.com/aceld") go business(conn) } func wait() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) } func main() { // Create a Client. client := znet.NewWsClient("127.0.0.1", 9000) // Add business logic for when the connection is first established.(添加首次建立连接时的业务) client.SetOnConnStart(DoClientConnectedBegin) // Register business routing for receiving messages from the server.(注册收到服务器消息业务路由) client.AddRouter(2, &c_router.PingRouter{}) client.AddRouter(3, &c_router.HelloRouter{}) // Start the client. client.Start() select { case err := <-client.GetErrChan(): // Handle the errors returned by the client.(处理客户端返回的错误) zlog.Ins().ErrorF("client err:%v", err) } // close c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, os.Kill) sig := <-c fmt.Println("===exit===", sig) // Clean up the client.(清理客户端) client.Stop() time.Sleep(time.Second * 2) } ================================================ FILE: examples/zinx_websocket/minicode/.eslintrc.js ================================================ /* * Eslint config file * Documentation: https://eslint.org/docs/user-guide/configuring/ * Install the Eslint extension before using this feature. */ module.exports = { env: { es6: true, browser: true, node: true, }, ecmaFeatures: { modules: true, }, parserOptions: { ecmaVersion: 2018, sourceType: 'module', }, globals: { wx: true, App: true, Page: true, getCurrentPages: true, getApp: true, Component: true, requirePlugin: true, requireMiniProgram: true, }, // extends: 'eslint:recommended', rules: {}, } ================================================ FILE: examples/zinx_websocket/minicode/app.js ================================================ const Buffer = require("buffer").Buffer; App({ onLaunch() { const socket = wx.connectSocket({ url: 'ws://localhost:9000', protocols:["12321","321321321",32132121] }) wx.onSocketOpen((result) => { console.log("连接成功") wx.sendSocketMessage({ data: this.encodeTLV(1, "hello"), }) }) socket.onMessage(result => { let message = this.decodeTLV(result.data) console.log(message) }) }, globalData: { TYPE_LENGTH: 4, LENGTH_LENGTH: 4, }, // 将数据编码为 TLV 格式 encodeTLV(type, value) { const length = value.length; const typeBuffer = Buffer.alloc(this.globalData.TYPE_LENGTH); const lengthBuffer = Buffer.alloc(this.globalData.LENGTH_LENGTH); const valueBuffer = Buffer.from(value); typeBuffer.writeUInt32BE(type, 0); lengthBuffer.writeUInt32BE(length, 0); return Buffer.concat([typeBuffer, lengthBuffer, valueBuffer], this.globalData.TYPE_LENGTH + this.globalData.LENGTH_LENGTH + length); }, // 从 TLV 格式解码数据 decodeTLV(buffer) { // 解析包头长度 let data = Buffer.from(buffer) const tag = Buffer.alloc(this.globalData.TYPE_LENGTH); let offset = 0; data.copy(tag, 0, offset, 4) const dataLen = Buffer.alloc(this.globalData.LENGTH_LENGTH); offset += 4; data.copy(dataLen, 0, offset, offset + 4) // 解析数据包内容 const body = new Buffer(dataLen.readInt32BE()) offset += 4 data.copy(body, 0, offset, offset + dataLen.readInt32BE()); let message = { tag: tag.readUInt32BE(), dataLen: dataLen.readUInt32BE(), data: body } return message } }) ================================================ FILE: examples/zinx_websocket/minicode/app.json ================================================ { "pages": [ "index/index" ], "window": { "backgroundTextStyle": "light", "navigationBarBackgroundColor": "#fff", "navigationBarTitleText": "Weixin", "navigationBarTextStyle": "black" }, "style": "v2", "sitemapLocation": "sitemap.json", "lazyCodeLoading": "requiredComponents" } ================================================ FILE: examples/zinx_websocket/minicode/app.wxss ================================================ ================================================ FILE: examples/zinx_websocket/minicode/index/index.js ================================================ const app = getApp() Page({ data: { }, onLoad() { console.log('代码片段是一种迷你、可分享的小程序或小游戏项目,可用于分享小程序和小游戏的开发经验、展示组件和 API 的使用、复现开发问题和 Bug 等。可点击以下连接查看代码片段的详细文档:') console.log('https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/devtools.html') }, }) ================================================ FILE: examples/zinx_websocket/minicode/index/index.json ================================================ { "usingComponents": {} } ================================================ FILE: examples/zinx_websocket/minicode/index/index.wxml ================================================ 欢迎使用代码片段,可在控制台查看代码片段的说明和文档 ================================================ FILE: examples/zinx_websocket/minicode/index/index.wxss ================================================ .intro { margin: 30px; text-align: center; } ================================================ FILE: examples/zinx_websocket/minicode/package.json ================================================ { "dependencies": { "buffer": "^6.0.3" } } ================================================ FILE: examples/zinx_websocket/minicode/project.config.json ================================================ { "appid": "wx0b3c079df9f406b6", "compileType": "miniprogram", "libVersion": "2.30.4", "packOptions": { "ignore": [], "include": [] }, "setting": { "coverView": true, "es6": true, "postcss": true, "minified": true, "enhance": true, "showShadowRootInWxmlPanel": true, "packNpmRelationList": [], "babelSetting": { "ignore": [], "disablePlugins": [], "outputPath": "" }, "condition": false }, "condition": {}, "editorSetting": { "tabIndent": "insertSpaces", "tabSize": 4 } } ================================================ FILE: examples/zinx_websocket/minicode/project.private.config.json ================================================ { "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", "projectname": "minicode-15", "setting": { "compileHotReLoad": true, "urlCheck": false } } ================================================ FILE: examples/zinx_websocket/minicode/sitemap.json ================================================ { "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", "rules": [{ "action": "allow", "page": "*" }] } ================================================ FILE: examples/zinx_websocket/server/server.go ================================================ package main import ( "github.com/aceld/zinx/examples/zinx_server/s_router" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/znet" ) func main() { // Set up as WebSocket before starting. (在启动之前设置为 websocket) zconf.GlobalObject.Mode = "" zconf.GlobalObject.LogFile = "" s := znet.NewServer() s.AddRouter(100, &s_router.PingRouter{}) s.AddRouter(1, &s_router.HelloZinxRouter{}) s.Serve() } ================================================ FILE: go.mod ================================================ module github.com/aceld/zinx go 1.23.0 require ( github.com/gorilla/websocket v1.5.0 github.com/stretchr/testify v1.8.1 github.com/xtaci/kcp-go v5.4.20+incompatible golang.org/x/net v0.38.0 // indirect google.golang.org/protobuf v1.33.0 // indirect ) require github.com/golang/protobuf v1.5.0 require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/klauspost/cpuid/v2 v2.1.1 // indirect github.com/klauspost/reedsolomon v1.11.8 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 // indirect github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/sys v0.31.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) ================================================ FILE: go.sum ================================================ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/klauspost/cpuid/v2 v2.1.1 h1:t0wUqjowdm8ezddV5k0tLWVklVuvLJpoHeb4WBdydm0= github.com/klauspost/cpuid/v2 v2.1.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= github.com/klauspost/reedsolomon v1.11.8 h1:s8RpUW5TK4hjr+djiOpbZJB4ksx+TdYbRH7vHQpwPOY= github.com/klauspost/reedsolomon v1.11.8/go.mod h1:4bXRN+cVzMdml6ti7qLouuYi32KHJ5MGv0Qd8a47h6A= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161 h1:89CEmDvlq/F7SJEOqkIdNDGJXrQIhuIx9D2DBXjavSU= github.com/templexxx/cpufeat v0.0.0-20180724012125-cef66df7f161/go.mod h1:wM7WEvslTq+iOEAMDLSzhVuOt5BRZ05WirO+b09GHQU= github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b h1:fj5tQ8acgNUr6O8LEplsxDhUIe2573iLkJc+PqnzZTI= github.com/templexxx/xor v0.0.0-20191217153810-f85b25db303b/go.mod h1:5XA7W9S6mni3h5uvOC75dA3m9CCCaS83lltmc0ukdi4= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/xtaci/kcp-go v5.4.20+incompatible h1:TN1uey3Raw0sTz0Fg8GkfM0uH3YwzhnZWQ1bABv5xAg= github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37 h1:EWU6Pktpas0n8lLQwDsRyZfmkPeRbdgPtW609es+/9E= github.com/xtaci/lossyconn v0.0.0-20200209145036-adba10fffc37/go.mod h1:HpMP7DB2CyokmAh4lp0EQnnWhmycP/TvwBGzvuie+H0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= ================================================ FILE: logo/zinxlogo.go ================================================ package logo import ( "fmt" "github.com/aceld/zinx/zconf" ) var zinxLogo = ` ██ ▀▀ ████████ ████ ██▄████▄ ▀██ ██▀ ▄█▀ ██ ██▀ ██ ████ ▄█▀ ██ ██ ██ ▄██▄ ▄██▄▄▄▄▄ ▄▄▄██▄▄▄ ██ ██ ▄█▀▀█▄ ▀▀▀▀▀▀▀▀ ▀▀▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀ ▀▀▀ ` var topLine = `┌──────────────────────────────────────────────────────┐` var borderLine = `│` var bottomLine = `└──────────────────────────────────────────────────────┘` func PrintLogo() { fmt.Println(zinxLogo) fmt.Println(topLine) fmt.Println(fmt.Sprintf("%s [Github] https://github.com/aceld %s", borderLine, borderLine)) fmt.Println(fmt.Sprintf("%s [tutorial] https://www.yuque.com/aceld/npyr8s/bgftov %s", borderLine, borderLine)) fmt.Println(fmt.Sprintf("%s [document] https://www.yuque.com/aceld/tsgooa %s", borderLine, borderLine)) fmt.Println(bottomLine) fmt.Printf("[Zinx] Version: %s, MaxConn: %d, MaxPacketSize: %d\n", zconf.GlobalObject.Version, zconf.GlobalObject.MaxConn, zconf.GlobalObject.MaxPacketSize) } ================================================ FILE: zasync_op/async_op.go ================================================ /* Package zasync_op @Author:14March @File:async_op.go */ package zasync_op import ( "sync" ) /* <异步IO模块简介> 1.业务线程执行业务操作,发送一个IO请求,由IO线程来完成写库,如果写完库之后,还有其他操作呢? a.接下来的逻辑就在 IO 线程里执行了; b.回到不是原来的业务线程,而是另一个业务线程执行; 这2种情况,就相当于一部分业务逻辑在 A 线程里,一部分业务逻辑在 B 线程了;两个线程同时操作一块内存区域,会出现脏读写问题。 2.因此,必须回到原本所属的业务线程里执行,意思就是说,业务逻辑原先是由谁来执行的,那么 IO 操作完成之后,继续交还给原来的人去执行。 3.使用: a.调用 Process 选择一个异步worker进行异步IO操作逻辑; b.在异步IO逻辑中设置需要共享的变量,及异步返回结果:asyncResult.SetReturnedObj c.注册设置异步回调,即回到原本的业务线程里继续进行后续的操作:asyncResult.OnComplete */ /* 1.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? a. The next logic will be executed in the IO thread; b. Back to a different business thread instead of the original one. 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. 2.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. 3.Usage: a. Call Process to select an asynchronous worker for asynchronous IO operation logic; b. Set the variables that need to be shared in the asynchronous IO logic and the asynchronous return result: asyncResult.SetReturnedObj c. Register and set the asynchronous callback, that is, return to the original business thread to continue subsequent operations: asyncResult.OnComplete */ // Asynchronous worker group (异步worker组) var asyncWorkerArray = [2048]*AsyncWorker{} var onceArray = [2048]sync.Once{} // 每个元素对应一个 worker 的 Once func Process(opId int, asyncOp func()) { if asyncOp == nil { return } curWorker := getCurWorker(opId) if curWorker != nil { curWorker.process(asyncOp) } } func getCurWorker(opId int) *AsyncWorker { // If opId is less than 0, convert it to its absolute value to ensure opId is positive // (如果 opId 小于 0,则取其绝对值,确保 opId 为正数) if opId < 0 { opId = -opId } // 使用 opId 对工作者数组长度取模,确保根据 opId 获取到一个有效的工作者索引 // (Use opId % len(asyncWorkerArray) to calculate a valid worker index) workerIndex := opId % len(asyncWorkerArray) // Use sync.Once to ensure initialization happens only once for each worker // (使用 sync.Once 确保对每个工作者的初始化操作只会执行一次) onceArray[workerIndex].Do(func() { // Initialization: create a task queue for the worker at the current index and start a goroutine to execute tasks // (初始化操作:为当前索引的工作者创建任务队列,并启动一个 goroutine 执行任务) asyncWorkerArray[workerIndex] = &AsyncWorker{ taskQ: make(chan func(), 2048), // Create a task queue with a capacity of 2048 } // Start a goroutine to repeatedly execute tasks for the worker go asyncWorkerArray[workerIndex].loopExecTask() }) // Return the worker at the corresponding index return asyncWorkerArray[workerIndex] } ================================================ FILE: zasync_op/async_op_result.go ================================================ /* Package zasync_op @Author:14March @File:async_op_result.go */ package zasync_op import ( "sync/atomic" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" ) type AsyncOpResult struct { // Player connection (玩家连接) conn ziface.IConnection // Returned object (已返回对象) returnedObj interface{} // Completion callback function(完成回调函数) completeFunc atomic.Value // Whether the return value has been set(是否已有返回值) hasReturnedObj int32 // Whether the completion callback function has been set(是否已有回调函数) hasCompleteFunc int32 // Whether the completion function has already been called(是否已经调用过完成函数) completeFuncHasAlreadyBeenCalled int32 // Default value = 0, not yet called (默认值 = 0, 没被调用过) } // GetReturnedObj returns the return value (获取返回值) func (aor *AsyncOpResult) GetReturnedObj() interface{} { return aor.returnedObj } // SetReturnedObj sets the return value(设置返回值) func (aor *AsyncOpResult) SetReturnedObj(val interface{}) { if atomic.CompareAndSwapInt32(&aor.hasReturnedObj, 0, 1) { aor.returnedObj = val // **** 防止未调用回调函数问题: 设置处理结果时,直接调用回调函数:1.回调函数未绑定,调用空;2.回调函数已绑定,立马调用 **** // **** Prevent the problem of not calling the completion callback function: // Call the callback function directly when setting the processing result: // 1. If the callback function is not bound, it will be called with nil; // 2. If the callback function is bound, it will be called immediately. **** aor.doComplete() } } // OnComplete sets the completion callback function(完成回调函数) func (aor *AsyncOpResult) OnComplete(val func()) { if atomic.CompareAndSwapInt32(&aor.hasCompleteFunc, 0, 1) { aor.completeFunc.Store(val) // **** 防止未调用回调函数问题:设置回调函数时,发现已经有处理结果了,直接调用 **** // **** Prevent the problem of not calling the completion callback function: // If a processing result already exists when setting the callback function, // call the callback function directly. **** if atomic.LoadInt32(&aor.hasReturnedObj) == 1 { aor.doComplete() } } } // 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 // (执行完成回调,在设置处理结果和回调函数的时候双重检查,杜绝未调用回调函数的可能性) func (aor *AsyncOpResult) doComplete() { v := aor.completeFunc.Load() if v == nil { return } cf := v.(func()) // Prevent re-entry problems (防止可重入问题) if atomic.CompareAndSwapInt32(&aor.completeFuncHasAlreadyBeenCalled, 0, 1) { // Prevent cross-thread calling problems, // throw it to the corresponding business thread to execute // (防止跨线程调用问题,扔到所属业务线程里去执行) request := znet.NewFuncRequest(aor.conn, cf) aor.conn.GetMsgHandler().SendMsgToTaskQueue(request) } } // NewAsyncOpResult creates a new asynchronous result(新建异步结果) func NewAsyncOpResult(conn ziface.IConnection) *AsyncOpResult { result := &AsyncOpResult{} result.conn = conn return result } ================================================ FILE: zasync_op/async_worker.go ================================================ /* Package zasync_op @Author:14March @File:async_worker.go */ package zasync_op import "github.com/aceld/zinx/zlog" type AsyncWorker struct { taskQ chan func() } func (aw *AsyncWorker) process(asyncOp func()) { if asyncOp == nil { zlog.Error("Async operation is empty.") return } if aw.taskQ == nil { zlog.Error("Task queue has not been initialized.") return } aw.taskQ <- func() { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("async process panic: %v", err) } }() // Execute async operation.(执行异步操作) asyncOp() } } func (aw *AsyncWorker) loopExecTask() { if aw.taskQ == nil { zlog.Error("The task queue has not been initialized.") return } for { task := <-aw.taskQ if task != nil { task() } } } ================================================ FILE: zconf/env.go ================================================ /** * User: coder.sdp@gmail.com * Date: 2023/9/8 * Time: 14:18 */ package zconf import ( "os" "path/filepath" ) const ( // EnvConfigFilePathKey (Set configuration file path export ZINX_CONFIG_FILE_PATH = xxxxxxzinx.json) // (设置配置文件路径 export ZINX_CONFIG_FILE_PATH = xxx/xxx/zinx.json) EnvConfigFilePathKey = "ZINX_CONFIG_FILE_PATH" EnvDefaultConfigFilePath = "/conf/zinx.json" ) var env = new(zEnv) type zEnv struct { configFilePath string } func init() { configFilePath := os.Getenv(EnvConfigFilePathKey) if configFilePath == "" { pwd, err := os.Getwd() if err != nil { panic(err) } configFilePath = filepath.Join(pwd, EnvDefaultConfigFilePath) } var err error configFilePath, err = filepath.Abs(configFilePath) if err != nil { panic(err) } env.configFilePath = configFilePath } func GetConfigFilePath() string { return env.configFilePath } ================================================ FILE: zconf/userconf.go ================================================ package zconf import "github.com/aceld/zinx/zlog" // UserConfToGlobal, Note that if UserConf is used, // the method should be called to synchronize with GlobalConfObject // because other parameters are called from this structure parameter. // (注意如果使用UserConf应该调用方法同步至 GlobalConfObject 因为其他参数是调用的此结构体参数) func UserConfToGlobal(config *Config) { // Server if config.Name != "" { GlobalObject.Name = config.Name } if config.Host != "" { GlobalObject.Host = config.Host } if config.TCPPort != 0 { GlobalObject.TCPPort = config.TCPPort } // Zinx if config.Version != "" { GlobalObject.Version = config.Version } if config.MaxPacketSize != 0 { GlobalObject.MaxPacketSize = config.MaxPacketSize } if config.MaxConn != 0 { GlobalObject.MaxConn = config.MaxConn } if config.WorkerPoolSize != 0 { GlobalObject.WorkerPoolSize = config.WorkerPoolSize } if config.MaxWorkerTaskLen != 0 { GlobalObject.MaxWorkerTaskLen = config.MaxWorkerTaskLen } if config.WorkerMode != "" { GlobalObject.WorkerMode = config.WorkerMode } if config.MaxMsgChanLen != 0 { GlobalObject.MaxMsgChanLen = config.MaxMsgChanLen } if config.IOReadBuffSize != 0 { GlobalObject.IOReadBuffSize = config.IOReadBuffSize } // logger // By default, it is False. If the config is not initialized, the default configuration will be used. // (默认是False, config没有初始化即使用默认配置) GlobalObject.LogIsolationLevel = config.LogIsolationLevel if GlobalObject.LogIsolationLevel > zlog.LogDebug { zlog.SetLogLevel(GlobalObject.LogIsolationLevel) } // Different from the required fields mentioned above, the logging module should use the default configuration if it is not configured. // (不同于上方必填项 日志目前如果没配置应该使用默认配置) if config.LogDir != "" { GlobalObject.LogDir = config.LogDir } if config.LogFile != "" { GlobalObject.LogFile = config.LogFile zlog.SetLogFile(GlobalObject.LogDir, GlobalObject.LogFile) } if config.LogSaveDays > 0 { GlobalObject.LogSaveDays = config.LogSaveDays } if config.LogFileSize > 0 { GlobalObject.LogFileSize = config.LogFileSize } if config.LogCons { GlobalObject.LogCons = config.LogCons } // Keepalive if config.HeartbeatMax != 0 { GlobalObject.HeartbeatMax = config.HeartbeatMax } // TLS if config.CertFile != "" { GlobalObject.CertFile = config.CertFile } if config.PrivateKeyFile != "" { GlobalObject.PrivateKeyFile = config.PrivateKeyFile } if config.Mode != "" { GlobalObject.Mode = config.Mode } if config.WsPort != 0 { GlobalObject.WsPort = config.WsPort } if config.WsPath != "" { GlobalObject.WsPath = config.WsPath } if config.RouterSlicesMode { GlobalObject.RouterSlicesMode = config.RouterSlicesMode } if config.RequestPoolMode { GlobalObject.RequestPoolMode = config.RequestPoolMode } if config.KcpPort != 0 { GlobalObject.KcpPort = config.KcpPort } if config.KcpACKNoDelay { GlobalObject.KcpACKNoDelay = config.KcpACKNoDelay } if !config.KcpStreamMode { GlobalObject.KcpStreamMode = config.KcpStreamMode } if config.KcpNoDelay != 0 { GlobalObject.KcpNoDelay = config.KcpNoDelay } if config.KcpInterval != 0 { GlobalObject.KcpInterval = config.KcpInterval } if config.KcpResend != 0 { GlobalObject.KcpResend = config.KcpResend } if config.KcpNc != 0 { GlobalObject.KcpNc = config.KcpNc } if config.KcpSendWindow != 0 { GlobalObject.KcpSendWindow = config.KcpSendWindow } if config.KcpRecvWindow != 0 { GlobalObject.KcpRecvWindow = config.KcpRecvWindow } if config.KcpFecDataShards != 0 { GlobalObject.KcpFecDataShards = config.KcpFecDataShards } if config.KcpFecParityShards != 0 { GlobalObject.KcpFecParityShards = config.KcpFecParityShards } } ================================================ FILE: zconf/zconf.go ================================================ // @Title globalobj.go // @Description 相关配置文件定义及加载方式 // defines a configuration structure named "Config" along with its methods. // The package is named "zconf", and the file is named "globalobj.go". // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package zconf import ( "encoding/json" "fmt" "os" "reflect" "testing" "time" "github.com/aceld/zinx/zlog" ) const ( ServerModeTcp = "tcp" ServerModeWebsocket = "websocket" ServerModeKcp = "kcp" ) const ( WorkerModeHash = "Hash" // By default, the round-robin average allocation rule is used.(默认使用取余的方式) WorkerModeBind = "Bind" // Bind a worker to each connection.(为每个连接分配一个worker) //Hash 模式有阻塞的问题(虽然有异步的方式可解决)。 //Bind 模式下,如果配置MaxConn值比较大的话 就会后台就会起很多worker在等着,当服务器接入连接较少时, 很多worker都是空闲; MaxConn 设置小的话,服务器能接入的连接就会受限。 //WorkerModeDynamicBind 也是一个连接对应一个worker, 给连接分配worker时, 如果工作池里初始化的worker已经用完了,就动态创建一个worker绑定到每个连接。这个临时创建的worker, 会在连接断开后销毁。 //跟WorkerModeHash的区别是,如果业务层回调有阻塞操作的话,也不影响其他连接的业务层处理。 //跟WorkerModeBind的区别是,不需要像Bind模式那样一开始就创建很多worker,而是根据连接数动态创建worker,这样可以避免闲置worker数量过多导致的资源浪费。 WorkerModeDynamicBind = "DynamicBind" // Dynamic binding of a worker to each connection when there is no worker in worker pool.(临时动态创建一个worker绑定到每个连接) ) /* Store all global parameters related to the Zinx framework for use by other modules. Some parameters can also be configured by the user based on the zinx.json file. (存储一切有关Zinx框架的全局参数,供其他模块使用 一些参数也可以通过 用户根据 zinx.json来配置) */ type Config struct { /* Server */ Host string // The IP address of the current server. (当前服务器主机IP) TCPPort int // The port number on which the server listens for TCP connections.(当前服务器主机监听端口号) WsPort int // The port number on which the server listens for WebSocket connections.(当前服务器主机websocket监听端口) WsPath string // The path of the WebSocket connection.(websocket连接路径) Name string // The name of the current server.(当前服务器名称) KcpPort int // he port number on which the server listens for KCP connections.(当前服务器主机监听端口号) /* ServerConfig */ KcpACKNoDelay bool // changes ack flush option, set true to flush ack immediately, KcpStreamMode bool // toggles the stream mode on/off KcpNoDelay int // Whether nodelay mode is enabled, 0 is not enabled; 1 enabled. KcpInterval int // Protocol internal work interval, in milliseconds, such as 10 ms or 20 ms. KcpResend int // Fast retransmission mode, 0 represents off by default, 2 can be set (2 ACK spans will result in direct retransmission) KcpNc int // Whether to turn off flow control, 0 represents “Do not turn off” by default, 1 represents “Turn off”. KcpSendWindow int // SND_BUF, this unit is the packet, default 32. KcpRecvWindow int // RCV_BUF, this unit is the packet, default 32. KcpFecDataShards int // The number of data shards in the FEC.(FEC数据分片), default 0. KcpFecParityShards int // The number of parity shards in the FEC.(FEC校验分片) default 0. /* Zinx */ Version string // The version of the Zinx framework.(当前Zinx版本号) MaxPacketSize uint32 // The maximum size of the packets that can be sent or received.(读写数据包的最大值) MaxConn int // The maximum number of connections that the server can handle.(当前服务器主机允许的最大连接个数) WorkerPoolSize uint32 // The number of worker pools in the business logic.(业务工作Worker池的数量) MaxWorkerTaskLen uint32 // The maximum number of tasks that a worker pool can handle.(业务工作Worker对应负责的任务队列最大任务存储数量) WorkerMode string // The way to assign workers to connections.(为连接分配worker的方式) MaxMsgChanLen uint32 // The maximum length of the send buffer message queue.(SendBuffMsg发送消息的缓冲最大长度) IOReadBuffSize uint32 // The maximum size of the read buffer for each IO operation.(每次IO最大的读取长度) //The server mode, which can be "tcp" or "websocket". If it is empty, both modes are enabled. //"tcp":tcp监听, "websocket":websocket 监听 为空时同时开启 Mode string // A boolean value that indicates whether the new or old version of the router is used. The default value is false. // 路由模式 false为旧版本路由,true为启用新版本的路由 默认使用旧版本 RouterSlicesMode bool // 是否开启 Request 对象池模式 RequestPoolMode bool /* logger */ LogDir string // The directory where log files are stored. The default value is "./log".(日志所在文件夹 默认"./log") // The name of the log file. If it is empty, the log information will be printed to stderr. // (日志文件名称 默认"" --如果没有设置日志文件,打印信息将打印至stderr) LogFile string LogSaveDays int // 日志最大保留天数 LogFileSize int64 // 日志单个日志最大容量 默认 64MB,单位:字节,记得一定要换算成MB(1024 * 1024) LogCons bool // 日志标准输出 默认 false // 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. // 日志隔离级别 -- 0:全开 1:关debug 2:关debug/info 3:关debug/info/warn ... LogIsolationLevel int /* Keepalive */ // The maximum interval for heartbeat detection in seconds. // 最长心跳检测间隔时间(单位:秒),超过改时间间隔,则认为超时,从配置文件读取 HeartbeatMax int /* TLS */ CertFile string // The name of the certificate file. If it is empty, TLS encryption is not enabled.(证书文件名称 默认"") PrivateKeyFile string // The name of the private key file. If it is empty, TLS encryption is not enabled.(私钥文件名称 默认"" --如果没有设置证书和私钥文件,则不启用TLS加密) } // GlobalObject Define a global object.(定义一个全局的对象) var GlobalObject *Config // PathExists Check if a file exists.(判断一个文件是否存在) func PathExists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } if os.IsNotExist(err) { return false, nil } return false, err } // Reload 读取用户的配置文件 // This method is used to reload the configuration file. // It reads the configuration file specified in the command-line arguments, // and updates the fields of the "Config" structure accordingly. // If the configuration file does not exist, it prints an error message to the log and returns. func (g *Config) Reload() { confFilePath := GetConfigFilePath() if confFileExists, _ := PathExists(confFilePath); confFileExists != true { // The configuration file may not exist, // in which case the default parameters should be used to initialize the logging module configuration. // (配置文件不存在也需要用默认参数初始化日志模块配置) g.InitLogConfig() zlog.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) return } data, err := os.ReadFile(confFilePath) if err != nil { panic(err) } err = json.Unmarshal(data, g) if err != nil { panic(err) } g.InitLogConfig() } // Show Zinx Config Info func (g *Config) Show() { objVal := reflect.ValueOf(g).Elem() objType := reflect.TypeOf(*g) fmt.Println("===== Zinx Global Config =====") for i := 0; i < objVal.NumField(); i++ { field := objVal.Field(i) typeField := objType.Field(i) fmt.Printf("%s: %v\n", typeField.Name, field.Interface()) } fmt.Println("==============================") } func (g *Config) HeartbeatMaxDuration() time.Duration { return time.Duration(g.HeartbeatMax) * time.Second } func (g *Config) InitLogConfig() { if g.LogFile != "" { zlog.SetLogFile(g.LogDir, g.LogFile) zlog.SetCons(g.LogCons) } if g.LogSaveDays > 0 { zlog.SetMaxAge(g.LogSaveDays) } if g.LogFileSize > 0 { zlog.SetMaxSize(g.LogFileSize) } if g.LogIsolationLevel > zlog.LogDebug { zlog.SetLogLevel(g.LogIsolationLevel) } } /* init, set default value */ func init() { pwd, err := os.Getwd() if err != nil { pwd = "." } // Note: Prevent errors like "flag provided but not defined: -test.paniconexit0" from occurring in go test. // (防止 go test 出现"flag provided but not defined: -test.paniconexit0"等错误) testing.Init() // Initialize the GlobalObject variable and set some default values. // (初始化GlobalObject变量,设置一些默认值) GlobalObject = &Config{ Name: "ZinxServerApp", Version: "V1.0", TCPPort: 8999, WsPort: 9000, WsPath: "/", KcpPort: 9001, Host: "0.0.0.0", MaxConn: 12000, MaxPacketSize: 4096, WorkerPoolSize: 10, MaxWorkerTaskLen: 1024, WorkerMode: "", MaxMsgChanLen: 1024, LogDir: pwd + "/log", LogFile: "", // if set "", print to Stderr(默认日志文件为空,打印到stderr) LogIsolationLevel: 0, HeartbeatMax: 10, // The default maximum interval for heartbeat detection is 10 seconds. (默认心跳检测最长间隔为10秒) IOReadBuffSize: 1024, CertFile: "", PrivateKeyFile: "", Mode: ServerModeTcp, RouterSlicesMode: false, RequestPoolMode: false, KcpACKNoDelay: false, KcpStreamMode: true, //Normal Mode: ikcp_nodelay(kcp, 0, 40, 0, 0); //Turbo Mode: ikcp_nodelay(kcp, 1, 10, 2, 1); KcpNoDelay: 1, KcpInterval: 10, KcpResend: 2, KcpNc: 1, KcpRecvWindow: 32, KcpSendWindow: 32, KcpFecDataShards: 0, KcpFecParityShards: 0, } // Note: Load some user-configured parameters from the configuration file. // (从配置文件中加载一些用户配置的参数) GlobalObject.Reload() } ================================================ FILE: zdecoder/crc.go ================================================ package zdecoder var crc16_h = []byte{ 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, } var crc16_l = []byte{ 0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09, 0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3, 0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A, 0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26, 0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F, 0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5, 0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C, 0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C, 0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80, 0x40, } func GetCrC(buff []byte) []byte { var hi uint16 = 0x00ff var low uint16 = 0x00ff for i := 0; i < len(buff); i++ { pos := (low ^ uint16(buff[i])) & 0x00ff low = hi ^ uint16(crc16_h[pos]) hi = uint16(crc16_l[pos]) } var d_crc = ((hi & 0x00ff) << 8) | (low&0x00ff)&0xffff //var d_crcArr = [2]byte{byte((d_crc & 0xff)), byte((d_crc >> 8) & 0xff)} d_crcArr := make([]byte, 0) d_crcArr = append(d_crcArr, byte((d_crc & 0xff)), byte((d_crc>>8)&0xff)) return d_crcArr } func IsComplete(src []byte, dst []byte) bool { if src != nil && dst != nil { if len(src) == 2 && len(dst) == 2 { if src[0] == dst[0] && src[1] == dst[1] { return true } } } return false } func CheckCRC(src []byte, crc []byte) bool { return IsComplete(GetCrC(src), crc) } ================================================ FILE: zdecoder/htlvcrcdecoder.go ================================================ // HTLV+CRC, H header code, T function code, L data length, V data content //+------+-------+-------+--------+--------+ //| H | T | L | V | CRC | //| 1Byte| 1Byte | 1Byte | NBytes | 2Bytes | //+------+-------+-------+--------+--------+ // HeaderCode FunctionCode DataLength Body CRC // A2 10 0E 0102030405060708091011121314 050B // // // Explanation: // 1. The data length len is 14 (0E), where len only refers to the length of the Body. // // // lengthFieldOffset = 2 (the index of len is 2, starting from 0) The offset of the length field // lengthFieldLength = 1 (len is 1 byte) The length of the length field in bytes // 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) // 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 // 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) // [简体中文] // // HTLV+CRC,H头码,T功能码,L数据长度,V数据内容 //+------+-------+---------+--------+--------+ //| 头码 | 功能码 | 数据长度 | 数据内容 | CRC校验 | //| 1字节 | 1字节 | 1字节 | N字节 | 2字节 | //+------+-------+---------+--------+--------+ //头码 功能码 数据长度 Body CRC //A2 10 0E 0102030405060708091011121314 050B // // // 说明: // 1.数据长度len是14(0E),这里的len仅仅指Body长度; // // // lengthFieldOffset = 2 (len的索引下标是2,下标从0开始) 长度字段的偏差 // lengthFieldLength = 1 (len是1个byte) 长度字段占的字节数 // lengthAdjustment = 2 (len只表示Body长度,程序只会读取len个字节就结束,但是CRC还有2byte没读呢,所以为2) // initialBytesToStrip = 0 (这个0表示完整的协议内容,如果不想要A2,那么这里就是1) 从解码帧中第一次去除的字节数 // maxFrameLength = 255 + 4(起始码、功能码、CRC) (len是1个byte,所以最大长度是无符号1个byte的最大值) package zdecoder import ( "encoding/hex" "math" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" ) const HEADER_SIZE = 5 type HtlvCrcDecoder struct { Head byte //HeaderCode(头码) Funcode byte //FunctionCode(功能码) Length byte //DataLength(数据长度) Body []byte //BodyData(数据内容) Crc []byte //CRC校验 Data []byte //// Original data content(原始数据内容) } func NewHTLVCRCDecoder() ziface.IDecoder { return &HtlvCrcDecoder{} } func (hcd *HtlvCrcDecoder) GetLengthField() *ziface.LengthField { //+------+-------+---------+--------+--------+ //| 头码 | 功能码 | 数据长度 | 数据内容 | CRC校验 | //| 1字节 | 1字节 | 1字节 | N字节 | 2字节 | //+------+-------+---------+--------+--------+ //头码 功能码 数据长度 Body CRC //A2 10 0E 0102030405060708091011121314 050B //说明: // 1.数据长度len是14(0E),这里的len仅仅指Body长度; // // lengthFieldOffset = 2 (len的索引下标是2,下标从0开始) 长度字段的偏差 // lengthFieldLength = 1 (len是1个byte) 长度字段占的字节数 // lengthAdjustment = 2 (len只表示Body长度,程序只会读取len个字节就结束,但是CRC还有2byte没读呢,所以为2) // initialBytesToStrip = 0 (这个0表示完整的协议内容,如果不想要A2,那么这里就是1) 从解码帧中第一次去除的字节数 // maxFrameLength = 255 + 4(起始码、功能码、CRC) (len是1个byte,所以最大长度是无符号1个byte的最大值) return &ziface.LengthField{ MaxFrameLength: math.MaxUint8 + 4, LengthFieldOffset: 2, LengthFieldLength: 1, LengthAdjustment: 2, InitialBytesToStrip: 0, } } func (hcd *HtlvCrcDecoder) decode(data []byte) *HtlvCrcDecoder { datasize := len(data) htlvData := HtlvCrcDecoder{ Data: data, } // Parse the header htlvData.Head = data[0] htlvData.Funcode = data[1] htlvData.Length = data[2] htlvData.Body = data[3 : datasize-2] htlvData.Crc = data[datasize-2 : datasize] // CRC if !CheckCRC(data[:datasize-2], htlvData.Crc) { zlog.Ins().DebugF("crc check error %s %s\n", hex.EncodeToString(data), hex.EncodeToString(htlvData.Crc)) return nil } //zlog.Ins().DebugF("2htlvData %s \n", hex.EncodeToString(htlvData.data)) //zlog.Ins().DebugF("HTLVCRC-DecodeData size:%d data:%+v\n", unsafe.Sizeof(htlvData), htlvData) return &htlvData } func (hcd *HtlvCrcDecoder) Intercept(chain ziface.IChain) ziface.IcResp { //1. Get the IMessage of zinx iMessage := chain.GetIMessage() if iMessage == nil { // Go to the next layer in the chain of responsibility return chain.ProceedWithIMessage(iMessage, nil) } //2. Get Data data := iMessage.GetData() //zlog.Ins().DebugF("HTLVCRC-RawData size:%d data:%s\n", len(data), hex.EncodeToString(data)) //3. If the amount of data read is less than the length of the header, proceed to the next layer directly. // (读取的数据不超过包头,直接进入下一层) if len(data) < HEADER_SIZE { return chain.ProceedWithIMessage(iMessage, nil) } //4. HTLV+CRC Decode htlvData := hcd.decode(data) //5. Set the decoded data back to the IMessage, the Zinx Router needs MsgID for addressing // (将解码后的数据重新设置到IMessage中, Zinx的Router需要MsgID来寻址) iMessage.SetMsgID(uint32(htlvData.Funcode)) //6. Pass the decoded data to the next layer. // (将解码后的数据进入下一层) return chain.ProceedWithIMessage(iMessage, *htlvData) } ================================================ FILE: zdecoder/ltvdecoder_little.go ================================================ // LTV, which stands for Length-Tag(Type)-Value, is a simple and practical data transmission scheme. // In the definition of LTV, there are three fields: Tag field, Length field, and Value field. // The value of the Length field is actually the length of the Value field. // // Before decoding (20 bytes) After decoding (20 bytes) //+------------+------------+-----------------+ +------------+------------+-----------------+ //| Length | Tag | Value |----->| Length | Tag | Value | //| 0x0000000C | 0x00000001 | "HELLO, WORLD" | | 0x0000000C | 0x00000001 | "HELLO, WORLD" | //+------------+------------+-----------------+ +------------+------------+-----------------+ // Length: uint32 type, 4 bytes, indicating the length of Value is 12 (hex: 0x0000000C). // Tag: uint32 type, 4 bytes, serves as MsgId, temporarily set to 1. // Value: 12 characters in total, 12 bytes in length. // // Explanation: // lengthFieldOffset = 0 (The index of Length byte is 0) Length field offset. // lengthFieldLength = 4 (Length takes up 4 bytes) Length field length. // lengthAdjustment = 4 (Length only represents the length of Value. The program will only read Length bytes and end. // If there is a crc of 2 bytes after Value, then it is 2 here. If Length represents the total // length of Tag+Length+Value, then it is -8 here.) // initialBytesToStrip = 0 (0 means returning the complete protocol content Tag+Length+Value. If you only want to // return the Value content and remove the 4 bytes of Tag and 4 bytes of Length, it is 8 here.) // The number of bytes removed from the decoded frame for the first time. // maxFrameLength = 2^32 + 4 + 4 (Since Length is of uint type, 2^32 represents the maximum length of Value. // In addition, Tag and Length each take up 4 bytes.) // [简体中文] // LTV,即Length-Tag(Type)-Value,是一种简单实用的数据传输方案。 //在LTV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)。这里的长度域的值实际上就是内容域的长度。 // //解码前 (20 bytes) 解码后 (20 bytes) //+------------+------------+-----------------+ +------------+------------+-----------------+ //| Length | Tag | Value |----->| Length | Tag | Value | //| 0x0000000C | 0x00000001 | "HELLO, WORLD" | | 0x0000000C | 0x00000001 | "HELLO, WORLD" | //+------------+------------+-----------------+ +------------+------------+-----------------+ // Length:uint32类型,占4字节,Length标记Value长度12(hex:0x0000000C) // Tag: uint32类型,占4字节,Tag作为MsgId,暂定为1 // Value: 共12个字符,占12字节 // // 说明: // lengthFieldOffset = 0 (Length的字节位索引下标是0) 长度字段的偏差 // lengthFieldLength = 4 (Length是4个byte) 长度字段占的字节数 // lengthAdjustment = 4 (Length只表示Value长度,程序只会读取Length个字节就结束,后面没有来,故为0, // 若Value后面还有crc占2字节的话,那么此处就是2。若Length标记的是Tag+Length+Value总长度, // 那么此处是-8) // initialBytesToStrip = 0 (这个0表示返回完整的协议内容Tag+Length+Value,如果只想返回Value内容,去掉Tag的4字节和Length // 的4字节,此处就是8) 从解码帧中第一次去除的字节数 // maxFrameLength = 2^32 + 4 + 4 (Length为uint类型,故2^32次方表示Value最大长度,此外Tag和Length各占4字节) package zdecoder import ( "bytes" "encoding/binary" "math" "github.com/aceld/zinx/ziface" ) const LTV_HEADER_SIZE = 8 //表示TLV空包长度 type LTV_Little_Decoder struct { Length uint32 //L Tag uint32 //T Value []byte //V } func NewLTV_Little_Decoder() ziface.IDecoder { return <V_Little_Decoder{} } func (ltv *LTV_Little_Decoder) GetLengthField() *ziface.LengthField { // +---------------+---------------+---------------+ // | Length | Tag | Value | // | uint32(4byte) | uint32(4byte) | n byte | // +---------------+---------------+---------------+ // Length:uint32类型,占4字节,Length标记Value长度 // Tag: uint32类型,占4字节 // Value: 占n字节 // //说明: // lengthFieldOffset = 0 (Length的字节位索引下标是4) 长度字段的偏差 // lengthFieldLength = 4 (Length是4个byte) 长度字段占的字节数 // lengthAdjustment = 4 (Length只表示Value长度,程序只会读取Length个字节就结束,后面没有来,故为0,若Value后面还有crc占2字节的话,那么此处就是2。若Length标记的是Tag+Length+Value总长度,那么此处是-8) // initialBytesToStrip = 0 (这个0表示返回完整的协议内容Tag+Length+Value,如果只想返回Value内容,去掉Tag的4字节和Length的4字节,此处就是8) 从解码帧中第一次去除的字节数 // maxFrameLength = 2^32 + 4 + 4 (Length为uint32类型,故2^32次方表示Value最大长度,此外Tag和Length各占4字节) //默认使用TLV封包方式 return &ziface.LengthField{ MaxFrameLength: math.MaxUint32 + 4 + 4, LengthFieldOffset: 0, LengthFieldLength: 4, LengthAdjustment: 4, InitialBytesToStrip: 0, //注意现在默认是大端,使用小端需要指定编码方式 Order: binary.LittleEndian, } } func (ltv *LTV_Little_Decoder) decode(data []byte) *LTV_Little_Decoder { ltvData := LTV_Little_Decoder{} //Get L ltvData.Length = binary.LittleEndian.Uint32(data[0:4]) //Get T ltvData.Tag = binary.LittleEndian.Uint32(data[4:8]) //Determine the length of V. (确定V的长度) ltvData.Value = make([]byte, ltvData.Length) //5. Get V binary.Read(bytes.NewBuffer(data[8:8+ltvData.Length]), binary.LittleEndian, ltvData.Value) return <vData } func (ltv *LTV_Little_Decoder) Intercept(chain ziface.IChain) ziface.IcResp { //1. Get the IMessage of zinx iMessage := chain.GetIMessage() if iMessage == nil { // Go to the next layer in the chain of responsibility return chain.ProceedWithIMessage(iMessage, nil) } //2. Get Data data := iMessage.GetData() //zlog.Ins().DebugF("LTV-RawData size:%d data:%s\n", len(data), hex.EncodeToString(data)) //3. If the amount of data read is less than the length of the header, proceed to the next layer directly. // (读取的数据不超过包头,直接进入下一层) if len(data) < LTV_HEADER_SIZE { return chain.ProceedWithIMessage(iMessage, nil) } //4. LTV Decode ltvData := ltv.decode(data) //5. Set the decoded data back to the IMessage, the Zinx Router needs MsgID for addressing // (将解码后的数据重新设置到IMessage中, Zinx的Router需要MsgID来寻址) iMessage.SetDataLen(ltvData.Length) iMessage.SetMsgID(ltvData.Tag) iMessage.SetData(ltvData.Value) //6. Pass the decoded data to the next layer. // (将解码后的数据进入下一层) return chain.ProceedWithIMessage(iMessage, *ltvData) } ================================================ FILE: zdecoder/tlvdecoder.go ================================================ // TLV, which stands for Tag(Type)-Length-Value, is a simple and practical data transmission scheme. // In the definition of TLV, it can be seen that it consists of three fields: the tag field (Tag), the length // field (Length), and the value field (Value). // The value of the length field is actually the length of the content field. // // Before decoding (20 bytes) After decoding (20 bytes) // +------------+------------+-----------------+ +------------+------------+-----------------+ // | Tag | Length | Value |-----> | Tag | Length | Value | // | 0x00000001 | 0x0000000C | "HELLO, WORLD" | | 0x00000001 | 0x0000000C | "HELLO, WORLD" | // +------------+------------+-----------------+ +------------+------------+-----------------+ // Tag: uint32 type, occupies 4 bytes, Tag is set as MsgId, temporarily set to 1 // Length: uint32 type, occupies 4 bytes, Length marks the length of Value, which is 12(hex:0x0000000C) // Value: 12 characters in total, occupies 12 bytes // // Explanation: // lengthFieldOffset = 4 (The byte index of Length is 4) Length field offset // lengthFieldLength = 4 (Length is 4 bytes) Length field length in bytes // lengthAdjustment = 0 (Length only represents the length of Value. The program will read only Length bytes and end. // If there is a crc of 2 bytes after Value, then this is 2. If Length marks the total length of // Tag+Length+Value, then this is -8) // initialBytesToStrip = 0 (This 0 means that the complete protocol content Tag+Length+Value is returned. If you only // want to return the Value content, remove the 4 bytes of Tag and 4 bytes of Length, and // this is 8) Number of bytes to strip from the decoded frame // maxFrameLength = 2^32 + 4 + 4 (Since Length is of uint type, 2^32 represents the maximum length of Value. In addition, // Tag and Length each occupy 4 bytes.) // [简体中文] // TLV,即Tag(Type)—Length—Value,是一种简单实用的数据传输方案。 //在TLV的定义中,可以知道它包括三个域,分别为:标签域(Tag),长度域(Length),内容域(Value)。这里的长度域的值实际上就是内容域的长度。 // //解码前 (20 bytes) 解码后 (20 bytes) //+------------+------------+-----------------+ +------------+------------+-----------------+ //| Tag | Length | Value |----->| Tag | Length | Value | //| 0x00000001 | 0x0000000C | "HELLO, WORLD" | | 0x00000001 | 0x0000000C | "HELLO, WORLD" | //+------------+------------+-----------------+ +------------+------------+-----------------+ // Tag: uint32类型,占4字节,Tag作为MsgId,暂定为1 // Length:uint32类型,占4字节,Length标记Value长度12(hex:0x0000000C) // Value: 共12个字符,占12字节 // // 说明: // lengthFieldOffset = 4 (Length的字节位索引下标是4) 长度字段的偏差 // lengthFieldLength = 4 (Length是4个byte) 长度字段占的字节数 // lengthAdjustment = 0 (Length只表示Value长度,程序只会读取Length个字节就结束,后面没有来,故为0, // 若Value后面还有crc占2字节的话,那么此处就是2。若Length标记的是Tag+Length+Value总长度, // 那么此处是-8) // initialBytesToStrip = 0 (这个0表示返回完整的协议内容Tag+Length+Value,如果只想返回Value内容,去掉Tag的4字节和Length // 的4字节,此处就是8) 从解码帧中第一次去除的字节数 // maxFrameLength = 2^32 + 4 + 4 (Length为uint类型,故2^32次方表示Value最大长度,此外Tag和Length各占4字节) package zdecoder import ( "bytes" "encoding/binary" "math" "github.com/aceld/zinx/ziface" ) const TLV_HEADER_SIZE = 8 //表示TLV空包长度 type TLVDecoder struct { Tag uint32 //T Length uint32 //L Value []byte //V } func NewTLVDecoder() ziface.IDecoder { return &TLVDecoder{} } func (tlv *TLVDecoder) GetLengthField() *ziface.LengthField { // +---------------+---------------+---------------+ // | Tag | Length | Value | // | uint32(4byte) | uint32(4byte) | n byte | // +---------------+---------------+---------------+ // Length:uint32类型,占4字节,Length标记Value长度 // Tag: uint32类型,占4字节 // Value: 占n字节 // //说明: // lengthFieldOffset = 4 (Length的字节位索引下标是4) 长度字段的偏差 // lengthFieldLength = 4 (Length是4个byte) 长度字段占的字节数 // lengthAdjustment = 0 (Length只表示Value长度,程序只会读取Length个字节就结束,后面没有来,故为0,若Value后面还有crc占2字节的话,那么此处就是2。若Length标记的是Tag+Length+Value总长度,那么此处是-8) // initialBytesToStrip = 0 (这个0表示返回完整的协议内容Tag+Length+Value,如果只想返回Value内容,去掉Tag的4字节和Length的4字节,此处就是8) 从解码帧中第一次去除的字节数 // maxFrameLength = 2^32 + 4 + 4 (Length为uint32类型,故2^32次方表示Value最大长度,此外Tag和Length各占4字节) //默认使用TLV封包方式 return &ziface.LengthField{ MaxFrameLength: math.MaxUint32 + 4 + 4, LengthFieldOffset: 4, LengthFieldLength: 4, LengthAdjustment: 0, InitialBytesToStrip: 0, } } func (tlv *TLVDecoder) decode(data []byte) *TLVDecoder { tlvData := TLVDecoder{} //Get T tlvData.Tag = binary.BigEndian.Uint32(data[0:4]) //Get L tlvData.Length = binary.BigEndian.Uint32(data[4:8]) //Determine the length of V. (确定V的长度) tlvData.Value = make([]byte, tlvData.Length) //Get V binary.Read(bytes.NewBuffer(data[8:8+tlvData.Length]), binary.BigEndian, tlvData.Value) //zlog.Ins().DebugF("TLV-DecodeData size:%d data:%+v\n", unsafe.Sizeof(data), tlvData) return &tlvData } func (tlv *TLVDecoder) Intercept(chain ziface.IChain) ziface.IcResp { //1. Get the IMessage of zinx iMessage := chain.GetIMessage() if iMessage == nil { // Go to the next layer in the chain of responsibility return chain.ProceedWithIMessage(iMessage, nil) } //2. Get Data data := iMessage.GetData() //zlog.Ins().DebugF("TLV-RawData size:%d data:%s\n", len(data), hex.EncodeToString(data)) //3. If the amount of data read is less than the length of the header, proceed to the next layer directly. // (读取的数据不超过包头,直接进入下一层) if len(data) < TLV_HEADER_SIZE { return chain.ProceedWithIMessage(iMessage, nil) } //4. TLV Decode tlvData := tlv.decode(data) //5. Set the decoded data back to the IMessage, the Zinx Router needs MsgID for addressing // (将解码后的数据重新设置到IMessage中, Zinx的Router需要MsgID来寻址) iMessage.SetMsgID(tlvData.Tag) iMessage.SetData(tlvData.Value) iMessage.SetDataLen(tlvData.Length) //6. Pass the decoded data to the next layer. // (将解码后的数据进入下一层) return chain.ProceedWithIMessage(iMessage, *tlvData) } ================================================ FILE: ziface/iclient.go ================================================ // @Title iclient.go // @Description Provides all interface declarations for the Client abstraction layer. // @Author Aceld - 2023-2-28 package ziface import ( "net/http" "net/url" "time" ) type IClient interface { Restart() Start() Stop() AddRouter(msgID uint32, router IRouter) Conn() IConnection // SetOnConnStart Set the Hook function to be called when a connection is created for this Client // (设置该Client的连接创建时Hook函数) SetOnConnStart(func(IConnection)) // SetOnConnStop Set the Hook function to be called when a connection is closed for this Client // (设置该Client的连接断开时的Hook函数) SetOnConnStop(func(IConnection)) // GetOnConnStart Get the Hook function that is called when a connection is created for this Client // (获取该Client的连接创建时Hook函数) GetOnConnStart() func(IConnection) // GetOnConnStop Get the Hook function that is called when a connection is closed for this Client // (设置该Client的连接断开时的Hook函数) GetOnConnStop() func(IConnection) // GetPacket Get the data protocol packet binding method for this Client // (获取Client绑定的数据协议封包方式) GetPacket() IDataPack // SetPacket Set the data protocol packet binding method for this Client // (设置Client绑定的数据协议封包方式) SetPacket(IDataPack) // GetMsgHandler Get the message handling module bound to this Client // (获取Client绑定的消息处理模块) GetMsgHandler() IMsgHandle // StartHeartBeat Start heartbeat detection(启动心跳检测) StartHeartBeat(time.Duration) // StartHeartBeatWithOption Start heartbeat detection with custom callbacks 启动心跳检测(自定义回调) StartHeartBeatWithOption(time.Duration, *HeartBeatOption) // GetLengthField Get the length field of this Client GetLengthField() *LengthField // SetDecoder Set the decoder for this Client 设置解码器 SetDecoder(IDecoder) // AddInterceptor Add an interceptor for this Client 添加拦截器 AddInterceptor(IInterceptor) // Get the error channel for this Client 获取客户端错误管道 GetErrChan() <-chan error // Set the name of this Clien // 设置客户端Client名称 SetName(string) // Get the name of this Client // 获取客户端Client名称 GetName() string SetUrl(url *url.URL) GetUrl() *url.URL // Set custom headers for WebSocket connection // 设置WebSocket连接的自定义请求头 SetWsHeader(http.Header) // Get custom headers for WebSocket connection // 获取WebSocket连接的自定义请求头 GetWsHeader() http.Header } ================================================ FILE: ziface/iconnection.go ================================================ // @Title iconnection.go // @Description Declaration of all connection-related methods // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package ziface import ( "context" "net" "github.com/gorilla/websocket" ) // IConnection Define connection interface type IConnection interface { // Start the connection, make the current connection start working // (启动连接,让当前连接开始工作) Start() // Stop the connection and end the current connection state // (停止连接,结束当前连接状态) Stop() // Returns ctx, used by user-defined go routines to obtain connection exit status // (返回ctx,用于用户自定义的go程获取连接退出状态) Context() context.Context GetName() string // Get the current connection name (获取当前连接名称) GetConnection() net.Conn // Get the original socket from the current connection(从当前连接获取原始的socket) GetWsConn() *websocket.Conn // Get the original websocket connection from the current connection(从当前连接中获取原始的websocket连接) // Deprecated: use GetConnection instead GetTCPConnection() net.Conn // Get the original socket TCPConn from the current connection (从当前连接获取原始的socket TCPConn) GetConnID() uint64 // Get the current connection ID (获取当前连接ID) GetConnIdStr() string // Get the current connection ID for string (获取当前字符串连接ID) GetMsgHandler() IMsgHandle // Get the message handler (获取消息处理器) GetWorkerID() uint32 // Get Worker ID(获取workerid) RemoteAddr() net.Addr // Get the remote address information of the connection (获取连接远程地址信息) LocalAddr() net.Addr // Get the local address information of the connection (获取连接本地地址信息) LocalAddrString() string // Get the local address information of the connection as a string RemoteAddrString() string // Get the remote address information of the connection as a string Send(data []byte) error // Send data directly to the remote TCP client (without buffering) SendToQueue(data []byte, opts ...MsgSendOption) error // Send data to the message queue to be sent to the remote TCP client later // Send Message data directly to the remote TCP client (without buffering) // 直接将Message数据发送数据给远程的TCP客户端(无缓冲) SendMsg(msgID uint32, data []byte) error // Send Message data to the message queue to be sent to the remote TCP client later (with buffering) // 直接将Message数据发送给远程的TCP客户端(有缓冲) SendBuffMsg(msgID uint32, data []byte, opts ...MsgSendOption) error SetProperty(key string, value interface{}) // Set connection property GetProperty(key string) (interface{}, error) // Get connection property RemoveProperty(key string) // Remove connection property IsAlive() bool // Check if the current connection is alive(判断当前连接是否存活) SetHeartBeat(checker IHeartbeatChecker) // Set the heartbeat detector (设置心跳检测器) AddCloseCallback(handler, key interface{}, callback func()) // Add a close callback function (添加关闭回调函数) RemoveCloseCallback(handler, key interface{}) // Remove a close callback function (删除关闭回调函数) InvokeCloseCallbacks() // Trigger the close callback function (触发关闭回调函数,独立协程完成) } ================================================ FILE: ziface/iconnmanager.go ================================================ // @Title iconnmanager.go // @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. // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package ziface // IConnManager Connection Management Abstract Layer type IConnManager interface { Add(IConnection) // Add connection Remove(IConnection) // Remove connection Get(uint64) (IConnection, error) // Get a connection by ConnID Get2(string) (IConnection, error) // Get a connection by string ConnID Len() int // Get current number of connections ClearConn() // Remove and stop all connections GetAllConnID() []uint64 // Get all connection IDs GetAllConnIdStr() []string // Get all string connection IDs Range(func(uint64, IConnection, interface{}) error, interface{}) error // Traverse all connections Range2(func(string, IConnection, interface{}) error, interface{}) error // Traverse all connections 2 } ================================================ FILE: ziface/idatapack.go ================================================ // @Title idatapack.go // @Description Message packing and unpacking methods // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package ziface // IDataPack Package and unpack data. // Operating on the data stream of TCP connections, add header information to transfer data, and solve TCP sticky packets. // (封包数据和拆包数据, 直接面向TCP连接中的数据流,为传输数据添加头部信息,用于处理TCP粘包问题。) type IDataPack interface { GetHeadLen() uint32 // Get the length of the message header(获取包头长度方法) Pack(msg IMessage) ([]byte, error) // Package message (封包方法) Unpack([]byte) (IMessage, error) // Unpackage message(拆包方法) } const ( // Zinx standard packing and unpacking method (Zinx 标准封包和拆包方式) ZinxDataPack string = "zinx_pack_tlv_big_endian" ZinxDataPackOld string = "zinx_pack_ltv_little_endian" //...(+) //// Custom packing method can be added here(自定义封包方式在此添加) ) const ( // Zinx default standard message protocol format(Zinx 默认标准报文协议格式) ZinxMessage string = "zinx_message" ) ================================================ FILE: ziface/idecoder.go ================================================ package ziface type IDecoder interface { IInterceptor GetLengthField() *LengthField } ================================================ FILE: ziface/iheartbeat.go ================================================ package ziface type IHeartbeatChecker interface { SetOnRemoteNotAlive(OnRemoteNotAlive) SetHeartbeatMsgFunc(HeartBeatMsgFunc) SetHeartbeatFunc(HeartBeatFunc) BindRouter(uint32, IRouter) BindRouterSlices(uint32, ...RouterHandler) Start() Stop() SendHeartBeatMsg() error BindConn(IConnection) Clone() IHeartbeatChecker MsgID() uint32 Router() IRouter RouterSlices() []RouterHandler } // User-defined method for handling heartbeat detection messages // (用户自定义的心跳检测消息处理方法) type HeartBeatMsgFunc func(IConnection) []byte // HeartBeatFunc User-defined heartbeat function // (用户自定义心跳函数) type HeartBeatFunc func(IConnection) error // OnRemoteNotAlive User-defined method for handling remote connections that are not alive // 用户自定义的远程连接不存活时的处理方法 type OnRemoteNotAlive func(IConnection) type HeartBeatOption struct { MakeMsg HeartBeatMsgFunc // User-defined method for handling heartbeat detection messages(用户自定义的心跳检测消息处理方法) OnRemoteNotAlive OnRemoteNotAlive // User-defined method for handling remote connections that are not alive(用户自定义的远程连接不存活时的处理方法) HeartBeatMsgID uint32 // User-defined ID for heartbeat detection messages(用户自定义的心跳检测消息ID) Router IRouter // User-defined business processing route for heartbeat detection messages(用户自定义的心跳检测消息业务处理路由) RouterSlices []RouterHandler //新版本的路由处理函数的集合 } const ( HeartBeatDefaultMsgID uint32 = 99999 ) ================================================ FILE: ziface/iinterceptor.go ================================================ /** * @author uuxia * @date 15:54 2023/3/10 * @description **/ package ziface // Input data for interceptor // (拦截器输入数据) type IcReq interface{} // Output data for interceptor // (拦截器输出数据) type IcResp interface{} // Interceptor // (拦截器) type IInterceptor interface { Intercept(IChain) IcResp // The interception method of the interceptor (defined by the developer) // (拦截器的拦截处理方法,由开发者定义) } // Responsibility chain // (责任链) type IChain interface { Request() IcReq // Get the request data in the current chain (current interceptor)-获取当前责任链中的请求数据(当前拦截器) GetIMessage() IMessage // Get IMessage from Chain (从Chain中获取IMessage) Proceed(IcReq) IcResp // Enter and execute the next interceptor, and pass the request data to the next interceptor (进入并执行下一个拦截器,且将请求数据传递给下一个拦截器) ProceedWithIMessage(IMessage, IcReq) IcResp } ================================================ FILE: ziface/ilengthfield.go ================================================ package ziface import "encoding/binary" type IFrameDecoder interface { Decode(buff []byte) [][]byte } // ILengthField Basic attributes possessed by ILengthField // (具备的基础属性) type LengthField struct { /* Note: Big-endian: the most significant byte (the "big end") of a word is placed at the byte with the lowest address; the rest of the bytes are placed in order of decreasing significance towards the byte with the highest address. Little-endian: the least significant byte (the "little end") of a word is placed at the byte with the lowest address; the rest of the bytes are placed in order of increasing significance towards the byte with the highest address. (大端模式:是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,地址由小向大增加,而数据从高位往低位放; 小端模式:是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,高地址部分权值高,低地址部分权值低,和我们的日常逻辑方法一致。) */ Order binary.ByteOrder //The byte order: BigEndian or LittleEndian(大小端) MaxFrameLength uint64 //The maximum length of a frame(最大帧长度) LengthFieldOffset int //The offset of the length field(长度字段偏移量) LengthFieldLength int //The length of the length field in bytes(长度域字段的字节数) LengthAdjustment int //The length adjustment(长度调整) InitialBytesToStrip int //The number of bytes to strip from the decoded frame(需要跳过的字节数) } ================================================ FILE: ziface/ilogger.go ================================================ package ziface import "context" type ILogger interface { //without context InfoF(format string, v ...interface{}) ErrorF(format string, v ...interface{}) DebugF(format string, v ...interface{}) //with context InfoFX(ctx context.Context, format string, v ...interface{}) ErrorFX(ctx context.Context, format string, v ...interface{}) DebugFX(ctx context.Context, format string, v ...interface{}) } ================================================ FILE: ziface/imessage.go ================================================ // @Title imessage.go // @Description Provides basic methods for messages // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package ziface // IMessage Package ziface defines an abstract interface for encapsulating a request message into a message type IMessage interface { GetDataLen() uint32 // Gets the length of the message data segment(获取消息数据段长度) GetMsgID() uint32 // Gets the ID of the message(获取消息ID) GetData() []byte // Gets the content of the message(获取消息内容) GetRawData() []byte // Gets the raw data of the message(获取原始数据) SetMsgID(uint32) // Sets the ID of the message(设计消息ID) SetData([]byte) // Sets the content of the message(设计消息内容) SetDataLen(uint32) // Sets the length of the message data segment(设置消息数据段长度) } ================================================ FILE: ziface/imsghandler.go ================================================ // @Title imsghandler.go // @Description Provides interfaces for worker startup and handling message business calls // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package ziface // IMsgHandle Abstract layer of message management(消息管理抽象层) type IMsgHandle interface { // Add specific handling logic for messages, msgID supports int and string types // (为消息添加具体的处理逻辑, msgID,支持整型,字符串) AddRouter(msgID uint32, router IRouter) AddRouterSlices(msgId uint32, handler ...RouterHandler) IRouterSlices Group(start, end uint32, Handlers ...RouterHandler) IGroupRouterSlices Use(Handlers ...RouterHandler) IRouterSlices StartWorkerPool() // Start the worker pool SendMsgToTaskQueue(request IRequest) // Pass the message to the TaskQueue for processing by the worker(将消息交给TaskQueue,由worker进行处理) Execute(request IRequest) // Execute interceptor methods on the responsibility chain(执行责任链上的拦截器方法) // Register the entry point of the responsibility chain. After each interceptor is processed, // the data is passed to the next interceptor, so that the message can be handled and passed layer by layer, // the order depends on the registration order // (注册责任链任务入口,每个拦截器处理完后,数据都会传递至下一个拦截器,使得消息可以层层处理层层传递,顺序取决于注册顺序) AddInterceptor(interceptor IInterceptor) // SetHeadInterceptor sets the head interceptor of the responsibility chain, which is the first interceptor to be executed // (SetHeadInterceptor 设置责任链的头拦截器,也就是第一个要执行的拦截器) // 默认情况下,责任链的头拦截器是msg decoder // this function will replace the head interceptor SetHeadInterceptor(interceptor IInterceptor) } ================================================ FILE: ziface/inotify.go ================================================ package ziface type Inotify interface { // Whether there is a connection with this id // (是否有这个id) HasIdConn(id uint64) bool // Get the number of connections stored // (存储的map长度) ConnNums() int // Add a connection // (添加连接) SetNotifyID(Id uint64, conn IConnection) // Get a connection by id // (得到某个连接) GetNotifyByID(Id uint64) (IConnection, error) // Delete a connection by id // (删除某个连接) DelNotifyByID(Id uint64) // Notify a connection with the given id // (通知某个id的方法) NotifyToConnByID(Id uint64, MsgId uint32, data []byte) error // Notify all connections // (通知所有人) NotifyAll(MsgId uint32, data []byte) error // Notify a connection with the given id using a buffer queue // (通过缓冲队列通知某个id的方法) NotifyBuffToConnByID(Id uint64, MsgId uint32, data []byte) error // Notify all connections using a buffer queue // (缓冲队列通知所有人) NotifyBuffAll(MsgId uint32, data []byte) error } ================================================ FILE: ziface/irequest.go ================================================ // @Title irequest.go // @Description Provides all interface declarations for connection requests // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package ziface type HandleStep int // IFuncRequest function message interface (函数消息接口) type IFuncRequest interface { CallFunc() } // IRequest interface: // It actually packages the connection information and request data of the client request into Request // (实际上是把客户端请求的连接信息 和 请求的数据 包装到了 Request里) type IRequest interface { GetConnection() IConnection // Get the connection information of the request(获取请求连接信息) GetData() []byte // Get the data of the request message(获取请求消息的数据) GetMsgID() uint32 // Get the message ID of the request(获取请求的消息ID) GetMessage() IMessage // Get the raw data of the request message (获取请求消息的原始数据 add by uuxia 2023-03-10) GetResponse() IcResp // Get the serialized data after parsing(获取解析完后序列化数据) SetResponse(IcResp) // Set the serialized data after parsing(设置解析完后序列化数据) BindRouter(router IRouter) // Bind which router handles this request(绑定这次请求由哪个路由处理) // Move on to the next handler to start execution, but the function that calls this method will execute in reverse order of their order // (转进到下一个处理器开始执行 但是调用此方法的函数会根据先后顺序逆序执行) Call() //erminate the execution of the processing function, but the function that calls this method will be executed until completion // 终止处理函数的运行 但调用此方法的函数会执行完毕 Abort() //Specify which Handler function to execute next in the Handle // (指定接下来的Handle去执行哪个Handler函数) // Be careful, it will cause loop calling // (慎用,会导致循环调用) Goto(HandleStep) // New router operation // (新路由操作) BindRouterSlices([]RouterHandler) // Execute the next function // (执行下一个函数) RouterSlicesNext() // 重置一个 Request //Reset(conn IConnection, msg IMessage) // 复制一份 Request 对象 Copy() IRequest //Set 在 Request 中存放一个上下文 Set(key string, value interface{}) //Get 从 Request 中获取一个上下文信息 Get(key string) (value interface{}, exists bool) } type BaseRequest struct{} func (br *BaseRequest) GetConnection() IConnection { return nil } func (br *BaseRequest) GetData() []byte { return nil } func (br *BaseRequest) GetMsgID() uint32 { return 0 } func (br *BaseRequest) GetMessage() IMessage { return nil } func (br *BaseRequest) GetResponse() IcResp { return nil } func (br *BaseRequest) SetResponse(resp IcResp) {} func (br *BaseRequest) BindRouter(router IRouter) {} func (br *BaseRequest) Call() {} func (br *BaseRequest) Abort() {} func (br *BaseRequest) Goto(HandleStep) {} func (br *BaseRequest) BindRouterSlices([]RouterHandler) {} func (br *BaseRequest) RouterSlicesNext() {} func (br *BaseRequest) Copy() IRequest { return nil } func (br *BaseRequest) Set(key string, value interface{}) {} func (br *BaseRequest) Get(key string) (value interface{}, exists bool) { return nil, false } ================================================ FILE: ziface/irouter.go ================================================ // @Title irouter.go // @Description Provides all interface declarations for message routing // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package ziface // IRouter is the interface for message routing. The route is the processing // business method set by the framework user for this connection. The IRequest // in the route includes the connection information and the request data // information for this connection. // (路由接口, 这里面路由是 使用框架者给该连接自定的 处理业务方法 // 路由里的IRequest 则包含用该连接的连接信息和该连接的请求数据信息) type IRouter interface { PreHandle(request IRequest) //Hook method before processing conn business(在处理conn业务之前的钩子方法) Handle(request IRequest) //Method for processing conn business(处理conn业务的方法) PostHandle(request IRequest) //Hook method after processing conn business(处理conn业务之后的钩子方法) } // RouterHandler is a method slice collection style router. Unlike the old version, // the new version only saves the router method collection, and the specific execution // is handed over to the IRequest of each request. // (方法切片集合式路路由 // 不同于旧版 新版本仅保存路由方法集合,具体执行交给每个请求的 IRequest) type RouterHandler func(request IRequest) type IRouterSlices interface { // Add global components (添加全局组件) Use(Handlers ...RouterHandler) // Add a route (添加业务处理器集合) AddHandler(msgId uint32, handlers ...RouterHandler) // Router group management (路由分组管理,并且会返回一个组管理器) Group(start, end uint32, Handlers ...RouterHandler) IGroupRouterSlices // Get the method set collection for processing (获得当前的所有注册在MsgId的处理器集合) GetHandlers(MsgId uint32) ([]RouterHandler, bool) } type IGroupRouterSlices interface { // Add global components (添加全局组件) Use(Handlers ...RouterHandler) // Add group routing components (添加业务处理器集合) AddHandler(MsgId uint32, Handlers ...RouterHandler) } ================================================ FILE: ziface/iserver.go ================================================ // @Title iserver.go // @Description Provides all interface declarations for the Server abstraction layer // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package ziface import ( "net/http" "time" ) // Defines the server interface type IServer interface { Start() // Start the server method(启动服务器方法) Stop() // Stop the server method (停止服务器方法) Serve() // Start the business service method(开启业务服务方法) // Routing feature: register a routing business method for the current service for client link processing use //(路由功能:给当前服务注册一个路由业务方法,供客户端连接处理使用) AddRouter(msgID uint32, router IRouter) // New version of routing (新版路由方式) AddRouterSlices(msgID uint32, router ...RouterHandler) IRouterSlices // Route group management (路由组管理) Group(start, end uint32, Handlers ...RouterHandler) IGroupRouterSlices // Common component management (公共组件管理) Use(Handlers ...RouterHandler) IRouterSlices // Get connection management (得到连接管理) GetConnMgr() IConnManager // Set Hook function when the connection is created for the Server (设置该Server的连接创建时Hook函数) SetOnConnStart(func(IConnection)) // Set Hook function when the connection is disconnected for the Server // (设置该Server的连接断开时的Hook函数) SetOnConnStop(func(IConnection)) // Get Hook function when the connection is created for the Server // (得到该Server的连接创建时Hook函数) GetOnConnStart() func(IConnection) // Get Hook function when the connection is disconnected for the Server // (得到该Server的连接断开时的Hook函数) GetOnConnStop() func(IConnection) // Get the data protocol packet binding method for the Server // (获取Server绑定的数据协议封包方式) GetPacket() IDataPack // Get the message processing module binding method for the Server // (获取Server绑定的消息处理模块) GetMsgHandler() IMsgHandle // Set the data protocol packet binding method for the Server // (设置Server绑定的数据协议封包方式) SetPacket(IDataPack) // Start the heartbeat check // (启动心跳检测) StartHeartBeat(time.Duration) // Start the heartbeat check (custom callback) // 启动心跳检测(自定义回调) StartHeartBeatWithOption(time.Duration, *HeartBeatOption) // Get the heartbeat checker // (获取心跳检测器) GetHeartBeat() IHeartbeatChecker GetLengthField() *LengthField SetDecoder(IDecoder) AddInterceptor(IInterceptor) // Add WebSocket authentication method // (添加websocket认证方法) SetWebsocketAuth(func(r *http.Request) error) // Get the server name (获取服务器名称) ServerName() string } ================================================ FILE: ziface/options.go ================================================ package ziface import "time" type MsgSendOptionObj struct { Timeout time.Duration } type MsgSendOption func(opt *MsgSendOptionObj) func WithSendMsgTimeout(timeout time.Duration) MsgSendOption { return func(opt *MsgSendOptionObj) { opt.Timeout = timeout } } ================================================ FILE: zinterceptor/chain.go ================================================ /** * @author uuxia * @date 15:56 2023/3/10 * @description 责任链模式 **/ package zinterceptor import "github.com/aceld/zinx/ziface" type Chain struct { req ziface.IcReq position int interceptors []ziface.IInterceptor } func NewChain(list []ziface.IInterceptor, pos int, req ziface.IcReq) ziface.IChain { return &Chain{ req: req, position: pos, interceptors: list, } } func (c *Chain) Request() ziface.IcReq { return c.req } func (c *Chain) Proceed(request ziface.IcReq) ziface.IcResp { if c.position < len(c.interceptors) { chain := NewChain(c.interceptors, c.position+1, request) interceptor := c.interceptors[c.position] response := interceptor.Intercept(chain) return response } return request } // GetIMessage Get IMessage from the Chain (从Chain中获取IMessage) func (c *Chain) GetIMessage() ziface.IMessage { req := c.Request() if req == nil { return nil } iRequest := c.ShouldIRequest(req) if iRequest == nil { return nil } return iRequest.GetMessage() } // Next Enter the next chain task with IMessage and decoded data,iMessage is the decoded IMessage response is the decoded data // (Next 通过IMessage和解码后数据进入下一个责任链任务, iMessage 为解码后的IMessage, response 为解码后的数据) func (c *Chain) ProceedWithIMessage(iMessage ziface.IMessage, response ziface.IcReq) ziface.IcResp { if iMessage == nil || response == nil { return c.Proceed(c.Request()) } req := c.Request() if req == nil { return c.Proceed(c.Request()) } iRequest := c.ShouldIRequest(req) if iRequest == nil { return c.Proceed(c.Request()) } // Set the request of chain for the next request // (设置chain的request下一次请求) iRequest.SetResponse(response) return c.Proceed(iRequest) } // ShouldIRequest Determine if it is IRequest(判断是否是IRequest) func (c *Chain) ShouldIRequest(icReq ziface.IcReq) ziface.IRequest { if icReq == nil { return nil } switch icReq.(type) { case ziface.IRequest: return icReq.(ziface.IRequest) default: return nil } } ================================================ FILE: zinterceptor/framedecoder.go ================================================ /** * @author uuxia * @date 15:57 2023/3/10 * @description 通用解码器 **/ package zinterceptor import ( "bytes" "encoding/binary" "fmt" "github.com/aceld/zinx/ziface" "math" ) // FrameDecoder // A decoder that splits the received {@link ByteBuf}s dynamically by the // value of the length field in the message. It is particularly useful when you // decode a binary message which has an integer header field that represents the // length of the message body or the whole message. // // ziface.LengthField has many configuration parameters so // that it can decode any message with a length field, which is often seen in // proprietary client-server protocols. Here are some example that will give // you the basic idea on which option does what. // // // // The value of the length field in this example is 12 (0x0C) which // represents the length of "HELLO, WORLD". By default, the decoder assumes // that the length field represents the number of the bytes that follows the // length field. Therefore, it can be decoded with the simplistic parameter // combination. // // LengthFieldOffset = 0 // LengthFieldLength = 2 // LengthAdjustment = 0 // InitialBytesToStrip = 0 (= do not strip header) // // BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) // +--------+----------------+ +--------+----------------+ // | Length | Actual Content |----->| Length | Actual Content | // | 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" | // +--------+----------------+ +--------+----------------+ // // // // // Because we can get the length of the content by calling // {@link ByteBuf#readableBytes()}, you might want to strip the length // field by specifying `InitialBytesToStrip`. In this example, we // specified `2`, that is same with the length of the length field, to // strip the first two bytes. // // LengthFieldOffset = 0 // LengthFieldLength = 2 // LengthAdjustment = 0 // InitialBytesToStrip = 2 (= the length of the Length field) // // BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes) // +--------+----------------+ +----------------+ // | Length | Actual Content |----->| Actual Content | // | 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" | // +--------+----------------+ +----------------+ // // // // represents the length of the whole message // // In most cases, the length field represents the length of the message body // only, as shown in the previous examples. However, in some protocols, the // length field represents the length of the whole message, including the // message header. In such a case, we specify a non-zero // `LengthAdjustment`. Because the length value in this example message // // is always greater than the body length by `2`, we specify `-2` // as `LengthAdjustment` for compensation. // // LengthFieldOffset = 0 // LengthFieldLength = 2 // LengthAdjustment = -2 (= the length of the Length field) // InitialBytesToStrip = 0 // // BEFORE DECODE (14 bytes) AFTER DECODE (14 bytes) // +--------+----------------+ +--------+----------------+ // | Length | Actual Content |----->| Length | Actual Content | // | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" | // +--------+----------------+ +--------+----------------+ // // // // The following message is a simple variation of the first example. An extra // header value is prepended to the message. LengthAdjustment is zero // again because the decoder always takes the length of the prepended data into // account during frame length calculation. // // LengthFieldOffset = 2 (= the length of Header 1) // LengthFieldLength = 3 // LengthAdjustment = 0 // InitialBytesToStrip = 0 // // BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes) // +----------+----------+----------------+ +----------+----------+----------------+ // | Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content | // | 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" | // +----------+----------+----------------+ +----------+----------+----------------+ // // // // // This is an advanced example that shows the case where there is an extra // header between the length field and the message body. You have to specify a // positive `LengthAdjustment` so that the decoder counts the extra // header into the frame length calculation. // // LengthFieldOffset = 0 // LengthFieldLength = 3 // LengthAdjustment = 2 (= the length of Header 1) // InitialBytesToStrip = 0 // // BEFORE DECODE (17 bytes) AFTER DECODE (17 bytes) // +----------+----------+----------------+ +----------+----------+----------------+ // | Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content | // | 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" | // +----------+----------+----------------+ +----------+----------+----------------+ // // // // // This is a combination of all the examples above. There are the prepended // header before the length field and the extra header after the length field. // The prepended header affects the `LengthFieldOffset` and the extra // header affects the `LengthAdjustment`. We also specified a non-zero // `InitialBytesToStrip` to strip the length field and the prepended // header from the frame. If you don't want to strip the prepended header, you // could specify `0` for `initialBytesToSkip`. // // LengthFieldOffset = 1 (= the length of HDR1) // LengthFieldLength = 2 // LengthAdjustment = 1 (= the length of HDR2) // InitialBytesToStrip = 3 (= the length of HDR1 + LEN) // // BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) // +------+--------+------+----------------+ +------+----------------+ // | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | // | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | // +------+--------+------+----------------+ +------+----------------+ // // // // // Let's give another twist to the previous example. The only difference from // the previous example is that the length field represents the length of the // whole message instead of the message body, just like the third example. // We have to count the length of HDR1 and Length into `LengthAdjustment`. // Please note that we don't need to take the length of HDR2 into account // because the length field already includes the whole header length. // // LengthFieldOffset = 1 // LengthFieldLength = 2 // LengthAdjustment = -3 (= the length of HDR1 + LEN, negative) // InitialBytesToStrip = 3 // // BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) // +------+--------+------+----------------+ +------+----------------+ // | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | // | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | // +------+--------+------+----------------+ +------+----------------+ // << 中文含义 By Aceld >> // // FrameDecoder // 一个解码器,根据消息中长度字段的值动态地拆分接收到的二进制数据帧, // 当您解码具有表示消息正文或整个消息长度的整数头字段的二进制消息时,它特别有用。 // // ziface.LengthField 有许多配置参数,因此它可以解码任何具有长度字段的消息, // 这在专有的客户端-服务器协议中经常见到。 // // 以下是一些示例,它们将为您提供基本的想法,了解每个选项的作用。 // // 案例一. 在偏移量为0的位置使用2字节长度字段,不去掉消息头 // // 在这个例子中,长度字段的值是 `12 (0x0C)`,表示"HELLO, WORLD"的长度。 // 默认情况下,解码器会假设长度字段代表跟在长度字段后面的字节数。因此, // 可以使用简单的参数组合进行解码。 // // LengthFieldOffset = 0 // LengthFieldLength = 2 // LengthAdjustment = 0 // InitialBytesToStrip = 0 (= 不去掉消息头) // // 解码前(14个字节) 解码后(14个字节) // +--------+----------------+ +--------+----------------+ // | Length | Actual Content |----->| Length | Actual Content | // | 0x000C | "HELLO, WORLD" | | 0x000C | "HELLO, WORLD" | // +--------+----------------+ +--------+----------------+ // // 案例二. 位于偏移量0的2字节长度字段,去掉消息头 // // 由于我们可以通过调用{@link ByteBuf#readableBytes()}来获取内容的长度, // 因此您可能希望通过指定"InitialBytesToStrip"来去掉长度字段。在此示例中,我们指定了"2", // 与长度字段的长度相同,以去掉前两个字节。 // // LengthFieldOffset = 0 // LengthFieldLength = 2 // LengthAdjustment = 0 // InitialBytesToStrip = 2 (等于Length字段的长度) // // 解码前 (14 bytes) 解码后 (12 bytes) // +--------+----------------+ +----------------+ // | Length | Actual Content |----->| Actual Content | // | 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" | // +--------+----------------+ +----------------+ // // 案例三. 位于偏移量0处的2字节长度字段,不剥离头部,该长度字段表示整个消息的长度 // // 在大多数情况下,长度字段仅表示消息体的长度,就像前面的例子所示。然而,在一些协议中, // 长度字段表示整个消息的长度,包括消息头部。在这种情况下,我们需要指定一个非零的LengthAdjustment。 // 因为这个例子消息中长度值总是比消息体长度大2,所以我们将LengthAdjustment设置为-2进行补偿。 // // LengthFieldOffset = 0 // LengthFieldLength = 2 // LengthAdjustment = -2 (长度字段的长度) // InitialBytesToStrip = 0 // // 解码前 (14 bytes) 解码后 (14 bytes) // +--------+----------------+ +--------+----------------+ // | Length | Actual Content |----->| Length | Actual Content | // | 0x000E | "HELLO, WORLD" | | 0x000E | "HELLO, WORLD" | // +--------+----------------+ +--------+----------------+ // // 案例四. 5个字节的头部中包含3个字节的长度字段,不去除头部 // // 下面的消息是第一个示例的简单变体。在消息前面添加了额外的头部值。 // LengthAdjustment 再次为零,因为解码器始终考虑预置数据的长度进行帧长度计算。 // // LengthFieldOffset = 2(等于 Header 1 的长度) // LengthFieldLength = 3 // LengthAdjustment = 0 // InitialBytesToStrip = 0 // // 解码前 (17 bytes) 解码后(17 bytes) // +----------+----------+----------------+ +----------+----------+----------------+ // | Header 1 | Length | Actual Content |----->| Header 1 | Length | Actual Content | // | 0xCAFE | 0x00000C | "HELLO, WORLD" | | 0xCAFE | 0x00000C | "HELLO, WORLD" | // +----------+----------+----------------+ +----------+----------+----------------+ // // 案例五. 在 5 个字节的头部中有 3 个字节的长度字段,不剥离头部 // // 这是一个高级的例子,展示了在长度字段和消息体之间有额外头部的情况。 // 您需要指定一个正的 LengthAdjustment,以便解码器在帧长度计算中计算额外的头部。 // // LengthFieldOffset = 0 // LengthFieldLength = 3 // LengthAdjustment = 2 (即 Header 1 的长度) // InitialBytesToStrip = 0 // // 解码前 (17 bytes) 解码后 (17 bytes) // +----------+----------+----------------+ +----------+----------+----------------+ // | Length | Header 1 | Actual Content |----->| Length | Header 1 | Actual Content | // | 0x00000C | 0xCAFE | "HELLO, WORLD" | | 0x00000C | 0xCAFE | "HELLO, WORLD" | // +----------+----------+----------------+ +----------+----------+----------------+ // // // 案例六. 4字节头部,其中2字节长度字段位于偏移量1的位置,剥离第一个头部字段和长度字段 // // 这是以上所有示例的组合。在长度字段之前有预置的头部, // 而在长度字段之后有额外的头部。预置头部影响LengthFieldOffset, // 额外的头部影响LengthAdjustment。我们还指定了一个非零的 // InitialBytesToStrip,以从帧中剥离长度字段和预置头部。 // 如果您不想剥离预置头部,则可以将initialBytesToSkip指定为0。 // // LengthFieldOffset = 1(HDR1的长度) // LengthFieldLength = 2 // LengthAdjustment = 1(HDR2的长度) // InitialBytesToStrip = 3(HDR1 + LEN的长度) // // BEFORE DECODE (16 bytes) AFTER DECODE (13 bytes) // +------+--------+------+----------------+ +------+----------------+ // | HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content | // | 0xCA | 0x000C | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | // +------+--------+------+----------------+ +------+----------------+ // // 案例七. 2字节长度字段在4字节头部的偏移量为1的位置,去除第一个头字段和长度字段,长度字段表示整个消息的长度 // // 让我们对前面的示例进行一些变化。 // 与先前的示例唯一的区别在于,长度字段表示整个消息的长度,而不是消息正文,就像第三个示例一样。 // 我们必须将HDR1和Length的长度计入LengthAdjustment。 // 请注意,我们不需要考虑HDR2的长度,因为长度字段已经包含了整个头部的长度。 // // LengthFieldOffset = 1 // LengthFieldLength = 2 // LengthAdjustment = -3 (HDR1 + LEN的长度,为负数) // InitialBytesToStrip = 3 // // 在解码之前(16个字节) 解码之后(13个字节) // +------+--------+------+----------------+ +------+----------------+ // | HDR1 | Length | HDR2 | Actual Content |-----> | HDR2 | Actual Content | // | 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" | | 0xFE | "HELLO, WORLD" | // +------+--------+------+----------------+ +------+----------------+ // FrameDecoder is a decoder based on the LengthField pattern. type FrameDecoder struct { ziface.LengthField // Basic properties inherited from ILengthField LengthFieldEndOffset int // Offset of the end position of the length field (LengthFieldOffset+LengthFieldLength) (长度字段结束位置的偏移量) failFast bool // Fast failure (快速失败) discardingTooLongFrame bool // true indicates discard mode is enabled, false indicates normal working mode (true 表示开启丢弃模式,false 正常工作模式) tooLongFrameLength 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,则开启丢弃模式,此字段记录需要丢弃的数据长度) bytesToDiscard int64 // Records how many bytes still need to be discarded (记录还剩余多少字节需要丢弃) in []byte } func NewFrameDecoder(lf ziface.LengthField) ziface.IFrameDecoder { frameDecoder := new(FrameDecoder) if lf.Order == nil { frameDecoder.Order = binary.BigEndian } else { frameDecoder.Order = lf.Order } frameDecoder.MaxFrameLength = lf.MaxFrameLength frameDecoder.LengthFieldOffset = lf.LengthFieldOffset frameDecoder.LengthFieldLength = lf.LengthFieldLength frameDecoder.LengthAdjustment = lf.LengthAdjustment frameDecoder.InitialBytesToStrip = lf.InitialBytesToStrip //self frameDecoder.LengthFieldEndOffset = lf.LengthFieldOffset + lf.LengthFieldLength frameDecoder.in = make([]byte, 0) return frameDecoder } func NewFrameDecoderByParams(maxFrameLength uint64, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip int) ziface.IFrameDecoder { return NewFrameDecoder(ziface.LengthField{ MaxFrameLength: maxFrameLength, LengthFieldOffset: lengthFieldOffset, LengthFieldLength: lengthFieldLength, LengthAdjustment: lengthAdjustment, InitialBytesToStrip: initialBytesToStrip, Order: binary.BigEndian, }) } func (d *FrameDecoder) fail(frameLength int64) { //丢弃完成或未完成都抛异常 //if frameLength > 0 { // msg := fmt.Sprintf("Adjusted frame length exceeds %d : %d - discarded", this.MaxFrameLength, frameLength) // panic(msg) //} else { // msg := fmt.Sprintf("Adjusted frame length exceeds %d - discarded", this.MaxFrameLength) // panic(msg) //} } func (d *FrameDecoder) discardingTooLongFrameFunc(buffer *bytes.Buffer) { // Save the number of bytes still to be discarded // (保存还需丢弃多少字节) bytesToDiscard := d.bytesToDiscard // Get the number of bytes that can be discarded now, there may be a half package situation // (获取当前可以丢弃的字节数,有可能出现半包) localBytesToDiscard := math.Min(float64(bytesToDiscard), float64(buffer.Len())) // Discard (丢弃) buffer.Next(int(localBytesToDiscard)) // Update the number of bytes still to be discarded (更新还需丢弃的字节数) bytesToDiscard -= int64(localBytesToDiscard) d.bytesToDiscard = bytesToDiscard // Determine if fast failure is needed, go back to the logic above (是否需要快速失败,回到上面的逻辑) d.failIfNecessary(false) } func (d *FrameDecoder) getUnadjustedFrameLength(buf *bytes.Buffer, offset int, length int, order binary.ByteOrder) int64 { // Value of the length field (长度字段的值) var frameLength int64 arr := buf.Bytes() arr = arr[offset : offset+length] buffer := bytes.NewBuffer(arr) switch length { case 1: //byte var value uint8 binary.Read(buffer, order, &value) frameLength = int64(value) case 2: //short var value uint16 binary.Read(buffer, order, &value) frameLength = int64(value) case 3: // int occupies 32 bits, here take out the last 24 bits and return as int type // (int占32位,这里取出后24位,返回int类型) if order == binary.LittleEndian { n := uint(arr[0]) | uint(arr[1])<<8 | uint(arr[2])<<16 frameLength = int64(n) } else { n := uint(arr[2]) | uint(arr[1])<<8 | uint(arr[0])<<16 frameLength = int64(n) } case 4: //int var value uint32 binary.Read(buffer, order, &value) frameLength = int64(value) case 8: //long binary.Read(buffer, order, &frameLength) default: panic(fmt.Sprintf("unsupported LengthFieldLength: %d (expected: 1, 2, 3, 4, or 8)", d.LengthFieldLength)) } return frameLength } func (d *FrameDecoder) failOnNegativeLengthField(in *bytes.Buffer, frameLength int64, lengthFieldEndOffset int) { in.Next(lengthFieldEndOffset) panic(fmt.Sprintf("negative pre-adjustment length field: %d", frameLength)) } func (d *FrameDecoder) failIfNecessary(firstDetectionOfTooLongFrame bool) { if d.bytesToDiscard == 0 { // Indicates that the data to be discarded has been discarded (说明需要丢弃的数据已经丢弃完成) // Save the length of the discarded data packet (保存一下被丢弃的数据包长度) tooLongFrameLength := d.tooLongFrameLength d.tooLongFrameLength = 0 // Turn off discard mode (关闭丢弃模式) d.discardingTooLongFrame = false // failFast: Default is true (failFast:默认true) // firstDetectionOfTooLongFrame: Passed in as true (firstDetectionOfTooLongFrame:传入true) if !d.failFast || firstDetectionOfTooLongFrame { // Fast failure (快速失败) d.fail(tooLongFrameLength) } } else { // Indicates that the discard has not been completed yet (说明还未丢弃完成) if d.failFast && firstDetectionOfTooLongFrame { // Fast failure (快速失败) d.fail(d.tooLongFrameLength) } } } // exceededFrameLength // frameLength: Length of the data packet (frameLength:数据包的长度) func (d *FrameDecoder) exceededFrameLength(in *bytes.Buffer, frameLength int64) { // Packet length - readable bytes (两种情况) // 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 // (数据包总长度为100,可读的字节数为50,说明还剩余50个字节需要丢弃但还未接收到) // 2. Total length of the data packet is 100, readable bytes is 150, indicating that the buffer already contains the entire data packet // (数据包总长度为100,可读的字节数为150,说明缓冲区已经包含了整个数据包) discard := frameLength - int64(in.Len()) // Record the maximum length of the data packet (记录一下最大的数据包的长度) d.tooLongFrameLength = frameLength if discard < 0 { // Indicates the second case, directly discard the current data packet (说明是第2种情况,直接丢弃当前数据包) in.Next(int(frameLength)) } else { // Indicates the first case, some data is still pending reception (说明是第1种情况,还有部分数据未接收到) // Enable discard mode (开启丢弃模式) d.discardingTooLongFrame = true // Record how many bytes need to be discarded next time (记录下次还需丢弃多少字节) d.bytesToDiscard = discard // Discard all data in the buffer (丢弃缓冲区所有数据) in.Next(in.Len()) } // Update the status and determine if there is an error. (更新状态,判断是否有误) d.failIfNecessary(true) } func (d *FrameDecoder) failOnFrameLengthLessThanInitialBytesToStrip(in *bytes.Buffer, frameLength int64, initialBytesToStrip int) { in.Next(int(frameLength)) panic(fmt.Sprintf("Adjusted frame length (%d) is less than InitialBytesToStrip: %d", frameLength, initialBytesToStrip)) } func (d *FrameDecoder) decode(buf []byte) []byte { in := bytes.NewBuffer(buf) // Determine if it is in discard mode (判断是否为丢弃模式) if d.discardingTooLongFrame { d.discardingTooLongFrameFunc(in) } // Determine if the number of readable bytes in the buffer is less than the offset of the length field // (判断缓冲区中可读的字节数是否小于长度字段的偏移量) if in.Len() < d.LengthFieldEndOffset { // Indicates that the length field packets are incomplete, half package // (说明长度字段的包都还不完整,半包) return nil } // --> If execution reaches here, it means that the value of the length field can be parsed <-- // (执行到这,说明可以解析出长度字段的值了) // Calculate the offset of the length field // (计算出长度字段的开始偏移量) actualLengthFieldOffset := d.LengthFieldOffset // Get the value of the length field, excluding the adjustment value of lengthAdjustment // (获取长度字段的值,不包括lengthAdjustment的调整值) frameLength := d.getUnadjustedFrameLength(in, actualLengthFieldOffset, d.LengthFieldLength, d.Order) // If the data frame length is less than 0, it means it is an error data packet // (如果数据帧长度小于0,说明是个错误的数据包) if frameLength < 0 { // It will skip the number of bytes of this data packet and throw an exception // (内部会跳过这个数据包的字节数,并抛异常) d.failOnNegativeLengthField(in, frameLength, d.LengthFieldEndOffset) } // Apply the formula: Number of bytes after the length field = value of the length field + lengthAdjustment (应用公式:长度字段后的字节数=长度字段的值+长度调整值) // frameLength is the value of the length field, plus lengthAdjustment equals the number of bytes after the length field (lengthFieldEndOffset is lengthFieldOffset+lengthFieldLength) // (frameLength 是长度字段的值,加上长度调整值等于长度字段后的字节数,lengthFieldEndOffset 是长度字段的偏移量加上长度字段本身) // So the frameLength calculated in the end is the length of the entire data packet (那说明最后计算出的frameLength就是整个数据包的长度) frameLength += int64(d.LengthAdjustment) + int64(d.LengthFieldEndOffset) // Discard mode is turned on here (丢弃模式就是在这开启的) // If the data packet length is greater than the maximum length (如果数据包长度大于最大长度) if uint64(frameLength) > d.MaxFrameLength { // It has exceeded the maximum length of a single data frame, and the exceeded part is processed // (已经超过单次数据帧最大长度,对超过的部分进行处理) d.exceededFrameLength(in, frameLength) return nil } // --> If execution reaches here, it means normal mode <-- // (执行到这, 说明是正常模式) // Size of the data packet (数据包的大小) frameLengthInt := int(frameLength) // Determine if the number of readable bytes in the buffer is less than the size of the data packet (判断缓冲区可读字节数是否小于数据包的字节数) if in.Len() < frameLengthInt { // Half package, will parse again later (半包,等会再来解析) return nil } // --> If execution reaches here, it means that the buffer already contains the entire data packet <-- // (执行到这, 说明缓冲区的数据已经包含了数据包) // Whether the number of bytes to be skipped is greater than the length of the data packet (跳过的字节数是否大于数据包长度) if d.InitialBytesToStrip > frameLengthInt { // Will throw an exception if the length of the data packet is less than the number of bytes to be skipped (如果数据包长度小于跳过的字节数,将抛出异常) d.failOnFrameLengthLessThanInitialBytesToStrip(in, frameLength, d.InitialBytesToStrip) } // Skip the initialBytesToStrip bytes (跳过initialBytesToStrip个字节) in.Next(d.InitialBytesToStrip) // Decode (解码) // Get the real data length after skipping (获取跳过后的真实数据长度) actualFrameLength := frameLengthInt - d.InitialBytesToStrip // Extract the real data (提取真实的数据) buff := make([]byte, actualFrameLength) _, _ = in.Read(buff) return buff } func (d *FrameDecoder) Decode(buff []byte) [][]byte { d.in = append(d.in, buff...) resp := make([][]byte, 0) for { arr := d.decode(d.in) if arr != nil { // Indicates that a complete packet has been parsed // (证明已经解析出一个完整包) resp = append(resp, arr) _size := len(arr) + d.InitialBytesToStrip if _size > 0 { d.in = d.in[_size:] } } else { return resp } } } ================================================ FILE: zinterceptor/interceptor.go ================================================ /** * @author uuxia * @date 15:58 2023/3/10 * @description 通过拦截,处理数据,任务向下传递 **/ package zinterceptor // 暂时不用 /* // Interceptor 基于LengthField规则的拦截器 type Interceptor struct { frameDecoder ziface.IFrameDecoder } func NewInterceptor(maxFrameLength uint64, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip int) ziface.IInterceptor { return &Interceptor{ frameDecoder: NewFrameDecoderByParams(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip), } } func (l *Interceptor) Intercept(chain ziface.IChain) ziface.IcResp { req := chain.Request() if req == nil || l.frameDecoder == nil { goto END } switch req.(type) { case ziface.IRequest: iRequest := req.(ziface.IRequest) iMessage := iRequest.GetMessage() if iMessage == nil { break } data := iMessage.GetData() bytebuffers := l.frameDecoder.Decode(data) size := len(bytebuffers) if size == 0 { //半包,或者其他情况,任务就不要往下再传递了 return nil } for i := 0; i < size; i++ { buffer := bytebuffers[i] if buffer == nil { continue } bufferSize := len(buffer) iMessage.SetData(buffer) iMessage.SetDataLen(uint32(bufferSize)) if i < size-1 { chain.Proceed(chain.Request()) } } } END: return chain.Proceed(chain.Request()) } */ ================================================ FILE: zinx_app_demo/mmo_game/README-CN.md ================================================ # Zinx应用-MMO游戏案例 [English](README.md) | 简体中文 ## 〇. MMO Game Client 案例源代码(仅供学习使用) https://github.com/aceld/mmo_game_client ## 一、应用案例介绍 ​ “ 好了,以上Zinx的框架的一些核心功能我们已经完成了,那么接下来我们就要基于Zinx完成一个服务端的应用程序了,整理用一个游戏应用服务器作为Zinx的一个应用案例。” ​ 游戏场景是一款MMO大型多人在线游戏,带unity3d 客户端的服务器端demo,该demo实现了mmo游戏的基础模块aoi(基于兴趣范围的广播), 世界聊天等。 ![13-Zinx游戏-示例图.png](https://upload-images.jianshu.io/upload_images/11093205-593bb6246327e900.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) ## 二、服务器应用基础协议 | MsgID | Client | Server | 描述 | | ----- | ----------- | ----------- | ------------------------------------------------------------ | | 1 | - | SyncPid | 同步玩家本次登录的ID(用来标识玩家) | | 2 | Talk | - | 世界聊天 | | 3 | MovePackege | - | 移动 | | 200 | - | BroadCast | 广播消息(Tp 1 世界聊天 2 坐标(出生点同步) 3 动作 4 移动之后坐标信息更新) | | 201 | - | SyncPid | 广播消息 掉线/aoi消失在视野 | | 202 | - | SyncPlayers | 同步周围的人位置信息(包括自己) | ## 三、Zinx 开发文档 [ < Zinx Wiki : English > ](https://github.com/aceld/zinx/wiki) [ < Zinx 文档 : 简体中文> ](https://www.yuque.com/aceld/tsgooa/sbvzgczh3hqz8q3l) ## 四、Zinx 在线开发教程 ### 文字教程 | platform | Entry | | ---- | ---- | | | [Zinx Framework tutorial-Lightweight server based on Golang](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)| ||[《Golang轻量级并发服务器框架zinx》](https://www.yuque.com/aceld)| ### 视频教程 | platform | online video | | ---- | ---- | | | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.bilibili.com/video/av71067087)| | | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.douyin.com/video/6983301202939333891) | | | [![zinx-youtube](https://s2.ax1x.com/2019/10/14/KSurCR.jpg)](https://www.youtube.com/watch?v=U95iF-HMWsU&list=PL_GrAPKmuajzeNI8HBTi-k5NQO1g0rM-A)| --- ### 关于作者: 作者:`Aceld(刘丹冰)` 简书号:`IT无崖子` `mail`: [danbing.at@gmail.com](mailto:danbing.at@gmail.com) `github`: [https://github.com/aceld](https://github.com/aceld) `原创书籍gitbook`: [http://legacy.gitbook.com/@aceld](http://legacy.gitbook.com/@aceld) ### Zinx技术讨论社区 | platform | Entry | | ---- | ---- | | | https://discord.gg/xQ8Xxfyfcz| | | 加微信: `ace_ld` 或扫二维码,备注`zinx`即可。
| || **WeChat Public Account** | || **QQ Group** | ================================================ FILE: zinx_app_demo/mmo_game/README.md ================================================ # Zinx Application - MMO Game Case Study English | [简体中文](README-CN.md) ## 0. MMO Game Client Source Code https://github.com/aceld/mmo_game_client ## 1.Introduction to the Application Case "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." 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. ![mmo-game01](https://github.com/aceld/zinx/assets/7778936/b9576a95-d33f-4ffd-bf89-94a551af1977) ## 2.Server Application Basic Protocols | MsgID | Client | Server | 描述 | | ----- | ----------- | ----------- | ---------------------------------------------------------- | | 1 | - | SyncPid | Synchronize the player's login ID (used for player ID).| | 2 | Talk | - | World chat. | | 3 | MovePackege | - | Movement. | | 200 | - | BroadCast | Broadcast messages (Tp 1: world chat, 2: coordinates synchronization (spawn point sync), 3: action, 4: updated coordinates after movement). | | 201 | - | SyncPid | Broadcast messages for disconnect/aoi disappearance.| | 202 | - | SyncPlayers | Synchronize the position information of nearby players (including oneself). | ## 3. Zinx Documentation [ < Zinx Wiki : English > ](https://github.com/aceld/zinx/wiki) [ < Zinx 文档 : 简体中文> ](https://www.yuque.com/aceld/tsgooa/sbvzgczh3hqz8q3l) ## 4.Tutorial ### 4.1 Online Tutorial | platform | Entry | | ---- | ---- | | | [Zinx Framework tutorial-Lightweight server based on Golang](https://dev.to/aceld/1building-basic-services-with-zinx-framework-296e)| ||[《Golang轻量级并发服务器框架zinx》](https://www.yuque.com/aceld)| ### 4.2 Online Tutorial Video | platform | online video | | ---- | ---- | | | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.bilibili.com/video/av71067087)| | | [![zinx-BiliBili](https://s2.ax1x.com/2019/10/13/uv340S.jpg)](https://www.douyin.com/video/6983301202939333891) | | | [![zinx-youtube](https://s2.ax1x.com/2019/10/14/KSurCR.jpg)](https://www.youtube.com/watch?v=U95iF-HMWsU&list=PL_GrAPKmuajzeNI8HBTi-k5NQO1g0rM-A)| --- ### 关于作者: 作者:`Aceld(刘丹冰)` 简书号:`IT无崖子` `mail`: [danbing.at@gmail.com](mailto:danbing.at@gmail.com) `github`: [https://github.com/aceld](https://github.com/aceld) `原创书籍gitbook`: [http://legacy.gitbook.com/@aceld](http://legacy.gitbook.com/@aceld) ### Zinx技术讨论社区 | platform | Entry | | ---- | ---- | | | https://discord.gg/xQ8Xxfyfcz| | | 加微信: `ace_ld` 或扫二维码,备注`zinx`即可。
| || **WeChat Public Account** | || **QQ Group** | ================================================ FILE: zinx_app_demo/mmo_game/api/move.go ================================================ package api import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zinx_app_demo/mmo_game/core" "github.com/aceld/zinx/zinx_app_demo/mmo_game/pb" "github.com/aceld/zinx/znet" "github.com/golang/protobuf/proto" ) // MoveApi Player movement // MoveApi 玩家移动 type MoveApi struct { znet.BaseRouter } func (*MoveApi) Handle(request ziface.IRequest) { //1. MoveApi Player movement // (1. 将客户端传来的proto协议解码) msg := &pb.Position{} err := proto.Unmarshal(request.GetData(), msg) if err != nil { fmt.Println("Move: Position Unmarshal error ", err) return } // 2. Identify which player sent the current message, retrieve from the connection property pID // (2. 得知当前的消息是从哪个玩家传递来的,从连接属性pID中获取) pID, err := request.GetConnection().GetProperty("pID") if err != nil { fmt.Println("GetProperty pID error", err) request.GetConnection().Stop() return } //fmt.Printf("user pID = %d , move(%f,%f,%f,%f)\n", pID, msg.X, msg.Y, msg.Z, msg.V) // 3. Get the player object based on pID // (3. 根据pID得到player对象) player := core.WorldMgrObj.GetPlayerByPID(pID.(int32)) // 4. Have the player object initiate the broadcast of movement position information // (4. 让player对象发起移动位置信息广播) player.UpdatePos(msg.X, msg.Y, msg.Z, msg.V) } ================================================ FILE: zinx_app_demo/mmo_game/api/world_chat.go ================================================ package api import ( "fmt" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zinx_app_demo/mmo_game/core" "github.com/aceld/zinx/zinx_app_demo/mmo_game/pb" "github.com/aceld/zinx/znet" "github.com/golang/protobuf/proto" ) // WorldChatApi World chat route business // (WorldChatApi 世界聊天 路由业务) type WorldChatApi struct { znet.BaseRouter } func (*WorldChatApi) Handle(request ziface.IRequest) { // 1. Decode the incoming proto protocol from the client // (1. 将客户端传来的proto协议解码) msg := &pb.Talk{} err := proto.Unmarshal(request.GetData(), msg) if err != nil { fmt.Println("Talk Unmarshal error ", err) return } // 2. Identify which player sent the current message, retrieve from the connection property pID // (2. 得知当前的消息是从哪个玩家传递来的,从连接属性pID中获取) pID, err := request.GetConnection().GetProperty("pID") if err != nil { fmt.Println("GetProperty pID error", err) request.GetConnection().Stop() return } // 3. Get the player object based on pID // (3. 根据pID得到player对象) player := core.WorldMgrObj.GetPlayerByPID(pID.(int32)) // 4. Have the player object initiate the chat broadcast request // (4. 让player对象发起聊天广播请求) player.Talk(msg.Content) } ================================================ FILE: zinx_app_demo/mmo_game/client_AI_robot.go ================================================ //go:build robot // +build robot package main import ( "bytes" "encoding/binary" "fmt" "io" "math/rand" "net" "runtime" "sync" "time" "github.com/aceld/zinx/zinx_app_demo/mmo_game/pb" "github.com/golang/protobuf/proto" ) type Message struct { Len uint32 MsgID uint32 Data []byte } type TcpClient struct { conn net.Conn X float32 Y float32 Z float32 V float32 PID int32 isOnline chan bool } func (this *TcpClient) Unpack(headdata []byte) (head *Message, err error) { headBuf := bytes.NewReader(headdata) head = &Message{} // 读取Len if err = binary.Read(headBuf, binary.LittleEndian, &head.Len); err != nil { return nil, err } // 读取MsgID if err = binary.Read(headBuf, binary.LittleEndian, &head.MsgID); err != nil { return nil, err } // 封包太大 //if head.Len > MaxPacketSize { // return nil, packageTooBig //} return head, nil } func (this *TcpClient) Pack(msgID uint32, dataBytes []byte) (out []byte, err error) { outbuff := bytes.NewBuffer([]byte{}) // 写Len if err = binary.Write(outbuff, binary.LittleEndian, uint32(len(dataBytes))); err != nil { return } // 写MsgID if err = binary.Write(outbuff, binary.LittleEndian, msgID); err != nil { return } //all pkg data if err = binary.Write(outbuff, binary.LittleEndian, dataBytes); err != nil { return } out = outbuff.Bytes() return } func (this *TcpClient) SendMsg(msgID uint32, data proto.Message) { // 进行编码 binaryData, err := proto.Marshal(data) if err != nil { fmt.Println(fmt.Sprintf("marshaling error: %s", err)) return } sendData, err := this.Pack(msgID, binaryData) if err == nil { _, _ = this.conn.Write(sendData) } else { fmt.Println(err) } return } func (this *TcpClient) AIRobotAction() { //聊天或者移动 //随机获得动作 tp := rand.Intn(2) if tp == 0 { content := fmt.Sprintf("hello 我是player %d, 你是谁?", this.PID) msg := &pb.Talk{ Content: content, } this.SendMsg(2, msg) } else { //移动 x := this.X z := this.Z randPos := rand.Intn(2) if randPos == 0 { x -= float32(rand.Intn(10)) z -= float32(rand.Intn(10)) } else { x += float32(rand.Intn(10)) z += float32(rand.Intn(10)) } //纠正坐标位置 if x > 410 { x = 410 } else if x < 85 { x = 85 } if z > 400 { z = 400 } else if z < 75 { z = 75 } //移动方向角度 randV := rand.Intn(2) v := this.V if randV == 0 { v = 25 } else { v = 335 } //封装Position消息 msg := &pb.Position{ X: x, Y: this.Y, Z: z, V: v, } fmt.Println(fmt.Sprintf("player ID: %d. Walking...", this.PID)) //发送移动MsgID:3的指令 this.SendMsg(3, msg) } } /* 处理一个回执业务 */ func (this *TcpClient) DoMsg(msg *Message) { //处理消息 fmt.Println(fmt.Sprintf("msg ID :%d, data len: %d", msg.MsgID, msg.Len)) if msg.MsgID == 1 { //服务器回执给客户端 分配ID //解析proto syncpID := &pb.SyncPID{} _ = proto.Unmarshal(msg.Data, syncpID) //给当前客户端ID进行赋值 this.PID = syncpID.PID } else if msg.MsgID == 200 { //服务器回执客户端广播数据 //解析proto bdata := &pb.BroadCast{} _ = proto.Unmarshal(msg.Data, bdata) //初次玩家上线 广播位置消息 if bdata.Tp == 2 && bdata.PID == this.PID { //本人 //更新客户端坐标 this.X = bdata.GetP().X this.Y = bdata.GetP().Y this.Z = bdata.GetP().Z this.V = bdata.GetP().V fmt.Println(fmt.Sprintf("player ID: %d online.. at(%f,%f,%f,%f)", bdata.PID, this.X, this.Y, this.Z, this.V)) //玩家已经成功上线 this.isOnline <- true } else if bdata.Tp == 1 { fmt.Println(fmt.Sprintf("世界聊天,玩家%d说的话是: %s", bdata.PID, bdata.GetContent())) } } } func (this *TcpClient) Start() { go func() { for { //读取服务端发来的数据 ==》 SyncPID //1.读取8字节 //第一次读取,读取数据头 headData := make([]byte, 8) if _, err := io.ReadFull(this.conn, headData); err != nil { fmt.Println(err) return } pkgHead, err := this.Unpack(headData) if err != nil { return } //data if pkgHead.Len > 0 { pkgHead.Data = make([]byte, pkgHead.Len) if _, err := io.ReadFull(this.conn, pkgHead.Data); err != nil { return } } //处理服务器回执业务 this.DoMsg(pkgHead) } }() // 10s后,断开连接 for { select { case <-this.isOnline: go func() { for { this.AIRobotAction() time.Sleep(time.Second) } }() case <-time.After(time.Second * 10): _ = this.conn.Close() return } } } func NewTcpClient(ip string, port int) *TcpClient { addrStr := fmt.Sprintf("%s:%d", ip, port) conn, err := net.Dial("tcp", addrStr) if err != nil { fmt.Println("net.Dial err: ", err) panic(err) } client := &TcpClient{ conn: conn, PID: 0, X: 0, Y: 0, Z: 0, V: 0, isOnline: make(chan bool), } fmt.Println(fmt.Sprintf("conn: %+v. Connected to server...", conn)) return client } func main() { // 开启一个waitgroup,同时运行3个goroutine runtime.GOMAXPROCS(runtime.NumCPU()) var wg sync.WaitGroup wg.Add(3) go func() { defer wg.Done() for i := 0; i < 10; i++ { client := NewTcpClient("127.0.0.1", 8999) client.Start() } }() go func() { defer wg.Done() for i := 0; i < 10; i++ { client := NewTcpClient("127.0.0.1", 8999) client.Start() } }() go func() { defer wg.Done() for i := 0; i < 10; i++ { client := NewTcpClient("127.0.0.1", 8999) client.Start() } }() fmt.Println("AI robot start") wg.Wait() fmt.Println("AI robot exit") select {} } ================================================ FILE: zinx_app_demo/mmo_game/conf/zinx.json ================================================ { "Name":"Zinx Game", "Host":"0.0.0.0", "TcpPort":8999, "MaxConn":3000, "WorkerPoolSize":10, "LogDir": "./mmo_game_log", "LogFile":"mmo_game.log" } ================================================ FILE: zinx_app_demo/mmo_game/core/aoi.go ================================================ package core import "fmt" const ( AOI_MIN_X int = 85 AOI_MAX_X int = 410 AOI_CNTS_X int = 10 AOI_MIN_Y int = 75 AOI_MAX_Y int = 400 AOI_CNTS_Y int = 20 ) // AOIManager AOI management module(AOI管理模块) type AOIManager struct { MinX int // Left boundary coordinate of the area(区域左边界坐标) MaxX int // Right boundary coordinate of the area(区域右边界坐标) CntsX int // Number of grids in the x direction(x方向格子的数量) MinY int // Upper boundary coordinate of the area(区域上边界坐标) MaxY int // Lower boundary coordinate of the area(区域下边界坐标) CntsY int // Number of grids in the y direction(y方向的格子数量) grIDs map[int]*GrID // Which grids are present in the current area, key = grid ID, value = grid object(当前区域中都有哪些格子,key=格子ID, value=格子对象) } // NewAOIManager Initialize an AOI area(初始化一个AOI区域) func NewAOIManager(minX, maxX, cntsX, minY, maxY, cntsY int) *AOIManager { aoiMgr := &AOIManager{ MinX: minX, MaxX: maxX, CntsX: cntsX, MinY: minY, MaxY: maxY, CntsY: cntsY, grIDs: make(map[int]*GrID), } // Initialize all grids in the AOI region (给AOI初始化区域中所有的格子) for y := 0; y < cntsY; y++ { for x := 0; x < cntsX; x++ { // Calculate the grid ID // Grid number: ID = IDy *nx + IDx (obtain grid number using grid coordinates) // 计算格子ID // 格子编号:ID = IDy *nx + IDx (利用格子坐标得到格子编号) gID := y*cntsX + x // Initialize a grid in the AOI map, where the key is the current grid's ID // 初始化一个格子放在AOI中的map里,key是当前格子的ID aoiMgr.grIDs[gID] = NewGrID(gID, aoiMgr.MinX+x*aoiMgr.grIDWIDth(), aoiMgr.MinX+(x+1)*aoiMgr.grIDWIDth(), aoiMgr.MinY+y*aoiMgr.grIDLength(), aoiMgr.MinY+(y+1)*aoiMgr.grIDLength()) } } return aoiMgr } // grIDWIDth Get the width of each grid in the x-axis direction // (得到每个格子在x轴方向的宽度) func (m *AOIManager) grIDWIDth() int { return (m.MaxX - m.MinX) / m.CntsX } // grIDLength Get the length of each grid in the x-axis direction // (得到每个格子在x轴方向的长度) func (m *AOIManager) grIDLength() int { return (m.MaxY - m.MinY) / m.CntsY } // String Print information method // (打印信息方法) func (m *AOIManager) String() string { s := fmt.Sprintf("AOIManagr:\nminX:%d, maxX:%d, cntsX:%d, minY:%d, maxY:%d, cntsY:%d\n GrIDs in AOI Manager:\n", m.MinX, m.MaxX, m.CntsX, m.MinY, m.MaxY, m.CntsY) for _, grID := range m.grIDs { s += fmt.Sprintln(grID) } return s } // GetSurroundGrIDsByGID Get the surrounding nine grids information based on the grid's gID // 根据格子的gID得到当前周边的九宫格信息 func (m *AOIManager) GetSurroundGrIDsByGID(gID int) (grIDs []*GrID) { // Check if gID exists // 判断gID是否存在 if _, ok := m.grIDs[gID]; !ok { return } // Add the current gID to the nine grids // 将当前gID添加到九宫格中 grIDs = append(grIDs, m.grIDs[gID]) // Get the coordinates of the grid based on gID // 根据gID, 得到格子所在的坐标 x, y := gID%m.CntsX, gID/m.CntsX // Create a temporary array to store the coordinates of the surrounding grids // 新建一个临时存储周围格子的数组 surroundGID := make([]int, 0) // Create eight direction vectors: Upper left: (-1, -1), Left middle: (-1, 0), Upper right: (-1,1), // Middle upper: (0,-1), Middle lower: (0,1), Right upper: (1, -1), Right middle: (1, 0), Right lower: (1, 1), // respectively insert these eight direction vectors into the x and y component arrays in order // 新建8个方向向量: 左上: (-1, -1), 左中: (-1, 0), 左下: (-1,1), 中上: (0,-1), 中下: (0,1), 右上:(1, -1) // 右中: (1, 0), 右下: (1, 1), 分别将这8个方向的方向向量按顺序写入x, y的分量数组 dx := []int{-1, -1, -1, 0, 0, 1, 1, 1} dy := []int{-1, 0, 1, -1, 1, -1, 0, 1} // Get the relative coordinates of the surrounding points based on the eight direction vectors, // select the coordinates that do not go out of bounds, and convert the coordinates to gID // 根据8个方向向量, 得到周围点的相对坐标, 挑选出没有越界的坐标, 将坐标转换为gID for i := 0; i < 8; i++ { newX := x + dx[i] newY := y + dy[i] if newX >= 0 && newX < m.CntsX && newY >= 0 && newY < m.CntsY { surroundGID = append(surroundGID, newY*m.CntsX+newX) } } // Get grid information based on valid gID // 根据没有越界的gID, 得到格子信息 for _, gID := range surroundGID { grIDs = append(grIDs, m.grIDs[gID]) } return } // GetGIDByPos Get the corresponding grid ID by horizontal and vertical coordinates // 通过横纵坐标获取对应的格子ID func (m *AOIManager) GetGIDByPos(x, y float32) int { gx := (int(x) - m.MinX) / m.grIDWIDth() gy := (int(y) - m.MinY) / m.grIDLength() return gy*m.CntsX + gx } // GetPIDsByPos Get all PlayerIDs within the surrounding nine grids by horizontal and vertical coordinates // 通过横纵坐标得到周边九宫格内的全部PlayerIDs func (m *AOIManager) GetPIDsByPos(x, y float32) (playerIDs []int) { // Get which grid ID the current coordinates belong to // 根据横纵坐标得到当前坐标属于哪个格子ID gID := m.GetGIDByPos(x, y) // Get information about the surrounding nine grids based on the grid ID // 根据格子ID得到周边九宫格的信息 grIDs := m.GetSurroundGrIDsByGID(gID) for _, v := range grIDs { playerIDs = append(playerIDs, v.GetPlyerIDs()...) //fmt.Printf("===> grID ID : %d, pIDs : %v ====", v.GID, v.GetPlyerIDs()) } return } // GetPIDsByGID Get all PlayerIDs within the surrounding nine grids by horizontal and vertical coordinates // 通过GID获取当前格子的全部playerID func (m *AOIManager) GetPIDsByGID(gID int) (playerIDs []int) { playerIDs = m.grIDs[gID].GetPlyerIDs() return } // RemovePIDFromGrID Remove a PlayerID from a grid // 移除一个格子中的PlayerID func (m *AOIManager) RemovePIDFromGrID(pID, gID int) { m.grIDs[gID].Remove(pID) } // AddPIDToGrID Add a PlayerID to a grid // 添加一个PlayerID到一个格子中 func (m *AOIManager) AddPIDToGrID(pID, gID int) { m.grIDs[gID].Add(pID) } // AddToGrIDByPos Add a Player to a grid based on horizontal and vertical coordinates // 通过横纵坐标添加一个Player到一个格子中 func (m *AOIManager) AddToGrIDByPos(pID int, x, y float32) { gID := m.GetGIDByPos(x, y) grID := m.grIDs[gID] grID.Add(pID) } // RemoveFromGrIDByPos Remove a Player from the corresponding grid based on horizontal and vertical coordinates // 通过横纵坐标把一个Player从对应的格子中删除 func (m *AOIManager) RemoveFromGrIDByPos(pID int, x, y float32) { gID := m.GetGIDByPos(x, y) grID := m.grIDs[gID] grID.Remove(pID) } ================================================ FILE: zinx_app_demo/mmo_game/core/aoi_test.go ================================================ package core import ( "fmt" "testing" ) func TestNewAOIManager(t *testing.T) { aoiMgr := NewAOIManager(100, 300, 4, 200, 450, 5) fmt.Println(aoiMgr) } func TestAOIManagerSuroundGrIDsByGID(t *testing.T) { aoiMgr := NewAOIManager(0, 250, 5, 0, 250, 5) for k := range aoiMgr.grIDs { // Get the surrounding nine grids of the current grid // (得到当前格子周边的九宫格) grIDs := aoiMgr.GetSurroundGrIDsByGID(k) // Get all IDs of the surrounding nine grids // (得到九宫格所有的IDs) fmt.Println("gID : ", k, " grIDs len = ", len(grIDs)) gIDs := make([]int, 0, len(grIDs)) for _, grID := range grIDs { gIDs = append(gIDs, grID.GID) } fmt.Printf("grID ID: %d, surrounding grID IDs are %v\n", k, gIDs) } } ================================================ FILE: zinx_app_demo/mmo_game/core/grid.go ================================================ package core import ( "fmt" "sync" ) // GrID A grid class in a map 一个地图中的格子类 type GrID struct { GID int // Grid ID MinX int // Left boundary coordinate of the grid MaxX int // Right boundary coordinate of the grid MinY int // Upper boundary coordinate of the grid MaxY int // Lower boundary coordinate of the grid playerIDs map[int]bool // IDs of players or objects in the current grid pIDLock sync.RWMutex // Lock for protecting the playerIDs map } // NewGrID Initialize a grid func NewGrID(gID, minX, maxX, minY, maxY int) *GrID { return &GrID{ GID: gID, MinX: minX, MaxX: maxX, MinY: minY, MaxY: maxY, playerIDs: make(map[int]bool), } } // Add a player to the current grid func (g *GrID) Add(playerID int) { g.pIDLock.Lock() defer g.pIDLock.Unlock() g.playerIDs[playerID] = true } // Remove a player from the grid func (g *GrID) Remove(playerID int) { g.pIDLock.Lock() defer g.pIDLock.Unlock() delete(g.playerIDs, playerID) } // GetPlyerIDs Get all players in the current grid func (g *GrID) GetPlyerIDs() (playerIDs []int) { g.pIDLock.RLock() defer g.pIDLock.RUnlock() for k := range g.playerIDs { playerIDs = append(playerIDs, k) } return } // String Print information method func (g *GrID) String() string { return fmt.Sprintf("GrID ID: %d, minX:%d, maxX:%d, minY:%d, maxY:%d, playerIDs:%v", g.GID, g.MinX, g.MaxX, g.MinY, g.MaxY, g.playerIDs) } ================================================ FILE: zinx_app_demo/mmo_game/core/player.go ================================================ package core import ( "fmt" "math/rand" "sync" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zinx_app_demo/mmo_game/pb" "github.com/golang/protobuf/proto" ) // Player object type Player struct { PID int32 // Player ID Conn ziface.IConnection // Current player's connection X float32 // Planar x coordinate(平面x坐标) Y float32 // Height(高度) Z float32 // Planar y coordinate (Note: not Y)- 平面y坐标 (注意不是Y) V float32 // Rotation 0-360 degrees(旋转0-360度) } // Player ID Generator var PIDGen int32 = 1 // Counter for generating player IDs(用来生成玩家ID的计数器) var IDLock sync.Mutex // Mutex for protecting PIDGen(保护PIDGen的互斥机制) // NewPlayer Create a player object func NewPlayer(conn ziface.IConnection) *Player { IDLock.Lock() ID := PIDGen PIDGen++ IDLock.Unlock() p := &Player{ PID: ID, Conn: conn, X: float32(160 + rand.Intn(50)), // Randomly offset on the X-axis based on the point 160(随机在160坐标点 基于X轴偏移若干坐标) Y: 0, // Height is 0 Z: float32(134 + rand.Intn(50)), // Randomly offset on the Y-axis based on the point 134(随机在134坐标点 基于Y轴偏移若干坐标) V: 0, // Angle is 0, not yet implemented(角度为0,尚未实现) } return p } // SyncPID Inform the client about pID and synchronize the generated player ID to the client // (告知客户端pID,同步已经生成的玩家ID给客户端) func (p *Player) SyncPID() { // Assemble MsgID0 proto data // (组建MsgID0 proto数据) data := &pb.SyncPID{ PID: p.PID, } // Send data to the client // (发送数据给客户端) p.SendMsg(1, data) } // BroadCastStartPosition Broadcast the player's starting position // (广播玩家自己的出生地点) func (p *Player) BroadCastStartPosition() { // Assemble MsgID200 proto data // (组建MsgID200 proto数据) msg := &pb.BroadCast{ PID: p.PID, Tp: 2, //TP:2 represents broadcasting coordinates (广播坐标) Data: &pb.BroadCast_P{ P: &pb.Position{ X: p.X, Y: p.Y, Z: p.Z, V: p.V, }, }, } // Send data to the client // 发送数据给客户端 p.SendMsg(200, msg) } // SyncSurrounding Broadcast the player's position to the surrounding players in the same grid // 给当前玩家周边的(九宫格内)玩家广播自己的位置,让他们显示自己 func (p *Player) SyncSurrounding() { //1 Get pIDs of players in the surrounding nine grids based on the player's position // 根据自己的位置,获取周围九宫格内的玩家pID pIDs := WorldMgrObj.AoiMgr.GetPIDsByPos(p.X, p.Z) // 2 Get all player objects based on the pIDs // 根据pID得到所有玩家对象 players := make([]*Player, 0, len(pIDs)) // 3 Send MsgID:200 message to these players to display themselves in each other's views // 给这些玩家发送MsgID:200消息,让自己出现在对方视野中 for _, pID := range pIDs { players = append(players, WorldMgrObj.GetPlayerByPID(int32(pID))) } // 3.1 Assemble MsgID200 proto data // 组建MsgID200 proto数据 msg := &pb.BroadCast{ PID: p.PID, Tp: 2, //TP:2 represents broadcasting coordinates (广播坐标) Data: &pb.BroadCast_P{ P: &pb.Position{ X: p.X, Y: p.Y, Z: p.Z, V: p.V, }, }, } // 3.2 Send the 200 message to each player's client to display characters // 每个玩家分别给对应的客户端发送200消息,显示人物 for _, player := range players { player.SendMsg(200, msg) } // 4 Make surrounding players in the nine grids appear in the player's view // 让周围九宫格内的玩家出现在自己的视野中 // 4.1 Create Message SyncPlayers data // 制作Message SyncPlayers 数据 playersData := make([]*pb.Player, 0, len(players)) for _, player := range players { p := &pb.Player{ PID: player.PID, P: &pb.Position{ X: player.X, Y: player.Y, Z: player.Z, V: player.V, }, } playersData = append(playersData, p) } // 4.2 Encapsulate SyncPlayers protobuf data // 封装SyncPlayer protobuf数据 SyncPlayersMsg := &pb.SyncPlayers{ Ps: playersData[:], } // 4.3 Send all player data to the current player to display surrounding players // 给当前玩家发送需要显示周围的全部玩家数据 p.SendMsg(202, SyncPlayersMsg) } // Talk Broadcast player chat // 广播玩家聊天 func (p *Player) Talk(content string) { // 1. Assemble MsgID200 proto data msg := &pb.BroadCast{ PID: p.PID, Tp: 1, // TP: 1 represents chat broadcast (代表聊天广播) Data: &pb.BroadCast_Content{ Content: content, }, } // 2. Get all online players in the current world // 得到当前世界所有的在线玩家 players := WorldMgrObj.GetAllPlayers() // 3. Send MsgID:200 message to all players // 向所有的玩家发送MsgID:200消息 for _, player := range players { player.SendMsg(200, msg) } } // UpdatePos Broadcast player position update // (广播玩家位置移动) func (p *Player) UpdatePos(x float32, y float32, z float32, v float32) { // Trigger visibility change and addition business // Calculate the old grid gID // 触发消失视野和添加视野业务 // 计算旧格子gID oldGID := WorldMgrObj.AoiMgr.GetGIDByPos(p.X, p.Z) // Calculate the new grid gID // 计算新格子gID newGID := WorldMgrObj.AoiMgr.GetGIDByPos(x, z) // Update the player's position information // 更新玩家的位置信息 p.X = x p.Y = y p.Z = z p.V = v if oldGID != newGID { // Trigger grid switch // Remove pID from the old aoi grid // 触发gird切换 // 把pID从就的aoi格子中删除 WorldMgrObj.AoiMgr.RemovePIDFromGrID(int(p.PID), oldGID) // 把pID添加到新的aoi格子中去 // Add pID to the new aoi grid WorldMgrObj.AoiMgr.AddPIDToGrID(int(p.PID), newGID) _ = p.OnExchangeAoiGrID(oldGID, newGID) } // Assemble protobuf data, send position to surrounding players // 组装protobuf协议,发送位置给周围玩家 msg := &pb.BroadCast{ PID: p.PID, Tp: 4, //Tp:4 Coordinates information after movement(移动之后的坐标信息) Data: &pb.BroadCast_P{ P: &pb.Position{ X: p.X, Y: p.Y, Z: p.Z, V: p.V, }, }, } // Get all players around the current player // (获取当前玩家周边全部玩家) players := p.GetSurroundingPlayers() // Send MsgID:200 message to each player's client, updating position after movement // (向周边的每个玩家发送MsgID:200消息,移动位置更新消息) for _, player := range players { player.SendMsg(200, msg) } } func (p *Player) OnExchangeAoiGrID(oldGID, newGID int) error { // Get members in the old nine-grid from the old grid // (获取就的九宫格成员) oldGrIDs := WorldMgrObj.AoiMgr.GetSurroundGrIDsByGID(oldGID) // Create a hash table for the old nine-grid members to quickly search // 为旧的九宫格成员建立哈希表,用来快速查找 oldGrIDsMap := make(map[int]bool, len(oldGrIDs)) for _, grID := range oldGrIDs { oldGrIDsMap[grID.GID] = true } // Get members in the new nine-grid from the new grid // 获取新的九宫格成员 newGrIDs := WorldMgrObj.AoiMgr.GetSurroundGrIDsByGID(newGID) // Create a hash table for the new nine-grid members to quickly search // 为新的九宫格成员建立哈希表,用来快速查找 newGrIDsMap := make(map[int]bool, len(newGrIDs)) for _, grID := range newGrIDs { newGrIDsMap[grID.GID] = true } //------ > Handle visibility disappearance (处理视野消失) <------- offlineMsg := &pb.SyncPID{ PID: p.PID, } // Find the grid IDs that appear in the old nine-grid but not in the new nine-grid // (找到在旧的九宫格中出现,但是在新的九宫格中没有出现的格子) leavingGrIDs := make([]*GrID, 0) for _, grID := range oldGrIDs { if _, ok := newGrIDsMap[grID.GID]; !ok { leavingGrIDs = append(leavingGrIDs, grID) } } // Get all players in the disappearing grids // (获取需要消失的格子中的全部玩家) for _, grID := range leavingGrIDs { players := WorldMgrObj.GetPlayersByGID(grID.GID) for _, player := range players { // Make oneself disappear in the views of other players // 让自己在其他玩家的客户端中消失 player.SendMsg(201, offlineMsg) // Make other players' information disappear in one's own client // 将其他玩家信息 在自己的客户端中消失 anotherOfflineMsg := &pb.SyncPID{ PID: player.PID, } p.SendMsg(201, anotherOfflineMsg) time.Sleep(200 * time.Millisecond) } } // ------ > Handle visibility appearance(处理视野出现) <------- // Find the grid IDs that appear in the new nine-grid but not in the old nine-grid // 找到在新的九宫格内出现,但是没有在就的九宫格内出现的格子 enteringGrIDs := make([]*GrID, 0) for _, grID := range newGrIDs { if _, ok := oldGrIDsMap[grID.GID]; !ok { enteringGrIDs = append(enteringGrIDs, grID) } } onlineMsg := &pb.BroadCast{ PID: p.PID, Tp: 2, Data: &pb.BroadCast_P{ P: &pb.Position{ X: p.X, Y: p.Y, Z: p.Z, V: p.V, }, }, } // Get all players in the appearing grids // 获取需要显示格子的全部玩家 for _, grID := range enteringGrIDs { players := WorldMgrObj.GetPlayersByGID(grID.GID) for _, player := range players { // Make oneself appear in the views of other players // 让自己出现在其他人视野中 player.SendMsg(200, onlineMsg) // Make other players appear in one's own client // 让其他人出现在自己的视野中 anotherOnlineMsg := &pb.BroadCast{ PID: player.PID, Tp: 2, Data: &pb.BroadCast_P{ P: &pb.Position{ X: player.X, Y: player.Y, Z: player.Z, V: player.V, }, }, } time.Sleep(200 * time.Millisecond) p.SendMsg(200, anotherOnlineMsg) } } return nil } // Get information of surrounding players in the current player's AOI // 获得当前玩家的AOI周边玩家信息 func (p *Player) GetSurroundingPlayers() []*Player { // Get all pIDs in the current AOI area // 得到当前AOI区域的所有pID pIDs := WorldMgrObj.AoiMgr.GetPIDsByPos(p.X, p.Z) // Put all players corresponding to pIDs into the Player slice // 将所有pID对应的Player放到Player切片中 players := make([]*Player, 0, len(pIDs)) for _, pID := range pIDs { players = append(players, WorldMgrObj.GetPlayerByPID(int32(pID))) } return players } // Player logs off // 玩家下线 func (p *Player) LostConnection() { // 1 Get players in the surrounding AOI nine-grid // 获取周围AOI九宫格内的玩家 players := p.GetSurroundingPlayers() // 2 Assemble MsgID:201 message // 封装MsgID:201消息 msg := &pb.SyncPID{ PID: p.PID, } // 3 Send messages to surrounding players // 向周围玩家发送消息 for _, player := range players { player.SendMsg(201, msg) } // 4 Remove the current player from AOI in the world manager // 世界管理器将当前玩家从AOI中摘除 WorldMgrObj.AoiMgr.RemoveFromGrIDByPos(int(p.PID), p.X, p.Z) WorldMgrObj.RemovePlayerByPID(p.PID) } // SendMsg Send messages to the client, mainly serializing and sending the protobuf data of the pb Message // // (发送消息给客户端,主要是将pb的protobuf数据序列化之后发送) func (p *Player) SendMsg(msgID uint32, data proto.Message) { if p.Conn == nil { fmt.Println("connection in player is nil") return } // fmt.Printf("before Marshal data = %+v\n", data) // Serialize the proto Message structure // 将proto Message结构体序列化 msg, err := proto.Marshal(data) if err != nil { fmt.Println("marshal msg err: ", err) return } // fmt.Printf("after Marshal data = %+v\n", msg) // Call the Zinx framework's SendMsg to send the packet // 调用Zinx框架的SendMsg发包 if err := p.Conn.SendMsg(msgID, msg); err != nil { fmt.Println("Player SendMsg error !") return } return } ================================================ FILE: zinx_app_demo/mmo_game/core/world_manager.go ================================================ package core import ( "sync" ) // WorldManager The overall management module of the current game world 当前游戏世界的总管理模块 type WorldManager struct { AoiMgr *AOIManager // AOI planning manager for the current world map(当前世界地图的AOI规划管理器) Players map[int32]*Player // Collection of currently online players(当前在线的玩家集合) pLock sync.RWMutex // Mutual exclusion mechanism to protect Players(保护Players的互斥读写机制) } // Provide an external handle to the world management module // 提供一个对外的世界管理模块句柄 var WorldMgrObj *WorldManager // Provide an initialization method for WorldManager // 提供WorldManager 初始化方法 func init() { WorldMgrObj = &WorldManager{ Players: make(map[int32]*Player), AoiMgr: NewAOIManager(AOI_MIN_X, AOI_MAX_X, AOI_CNTS_X, AOI_MIN_Y, AOI_MAX_Y, AOI_CNTS_Y), } } // AddPlayer Provide the ability to add a player, adding the player to the player information table Players // (提供添加一个玩家的的功能,将玩家添加进玩家信息表Players) func (wm *WorldManager) AddPlayer(player *Player) { // Add the player to the world manager // 将player添加到 世界管理器中 wm.pLock.Lock() wm.Players[player.PID] = player wm.pLock.Unlock() // Add the player to the AOI network planning // 将player 添加到AOI网络规划中 wm.AoiMgr.AddToGrIDByPos(int(player.PID), player.X, player.Z) } // RemovePlayerByPID Remove a player from the player information table by player ID // 从玩家信息表中移除一个玩家 func (wm *WorldManager) RemovePlayerByPID(pID int32) { wm.pLock.Lock() delete(wm.Players, pID) wm.pLock.Unlock() } // GetPlayerByPID Get corresponding player information by player ID // 通过玩家ID 获取对应玩家信息 func (wm *WorldManager) GetPlayerByPID(pID int32) *Player { wm.pLock.RLock() defer wm.pLock.RUnlock() return wm.Players[pID] } // GetAllPlayers Get information of all players // 获取所有玩家的信息 func (wm *WorldManager) GetAllPlayers() []*Player { wm.pLock.RLock() defer wm.pLock.RUnlock() // Create a slice to return player collection // 创建返回的player集合切片 players := make([]*Player, 0) // Add to the slice // 添加切片 for _, v := range wm.Players { players = append(players, v) } return players } // GetPlayersByGID Get information of all players in a specific gID // 获取指定gID中的所有player信息 func (wm *WorldManager) GetPlayersByGID(gID int) []*Player { // Get all pIDs corresponding to the gID // 通过gID获取 对应 格子中的所有pID pIDs := wm.AoiMgr.grIDs[gID].GetPlyerIDs() // Get player objects corresponding to pIDs // 通过pID找到对应的player对象 players := make([]*Player, 0, len(pIDs)) wm.pLock.RLock() for _, pID := range pIDs { players = append(players, wm.Players[int32(pID)]) } wm.pLock.RUnlock() return players } ================================================ FILE: zinx_app_demo/mmo_game/pb/build.sh ================================================ #!/bin/bash protoc --go_out=. *.proto ================================================ FILE: zinx_app_demo/mmo_game/pb/msg.pb.go ================================================ // Code generated by protoc-gen-go. DO NOT EDIT. // source: msg.proto package pb import ( fmt "fmt" proto "github.com/golang/protobuf/proto" math "math" ) // Reference imports to suppress errors if they are not otherwise used. var _ = proto.Marshal var _ = fmt.Errorf var _ = math.Inf // This is a compile-time assertion to ensure that this generated file // is compatible with the proto package it is being compiled against. // A compilation error at this line likely means your copy of the // proto package needs to be updated. const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package // Synchronize client player ID // 同步客户端玩家ID type SyncPID struct { PID int32 `protobuf:"varint,1,opt,name=PID,proto3" json:"PID,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *SyncPID) Reset() { *m = SyncPID{} } func (m *SyncPID) String() string { return proto.CompactTextString(m) } func (*SyncPID) ProtoMessage() {} func (*SyncPID) Descriptor() ([]byte, []int) { return fileDescriptor_c06e4cca6c2cc899, []int{0} } func (m *SyncPID) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SyncPID.Unmarshal(m, b) } func (m *SyncPID) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_SyncPID.Marshal(b, m, deterministic) } func (m *SyncPID) XXX_Merge(src proto.Message) { xxx_messageInfo_SyncPID.Merge(m, src) } func (m *SyncPID) XXX_Size() int { return xxx_messageInfo_SyncPID.Size(m) } func (m *SyncPID) XXX_DiscardUnknown() { xxx_messageInfo_SyncPID.DiscardUnknown(m) } var xxx_messageInfo_SyncPID proto.InternalMessageInfo func (m *SyncPID) GetPID() int32 { if m != nil { return m.PID } return 0 } // Player position type Position struct { X float32 `protobuf:"fixed32,1,opt,name=X,proto3" json:"X,omitempty"` Y float32 `protobuf:"fixed32,2,opt,name=Y,proto3" json:"Y,omitempty"` Z float32 `protobuf:"fixed32,3,opt,name=Z,proto3" json:"Z,omitempty"` V float32 `protobuf:"fixed32,4,opt,name=V,proto3" json:"V,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Position) Reset() { *m = Position{} } func (m *Position) String() string { return proto.CompactTextString(m) } func (*Position) ProtoMessage() {} func (*Position) Descriptor() ([]byte, []int) { return fileDescriptor_c06e4cca6c2cc899, []int{1} } func (m *Position) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Position.Unmarshal(m, b) } func (m *Position) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Position.Marshal(b, m, deterministic) } func (m *Position) XXX_Merge(src proto.Message) { xxx_messageInfo_Position.Merge(m, src) } func (m *Position) XXX_Size() int { return xxx_messageInfo_Position.Size(m) } func (m *Position) XXX_DiscardUnknown() { xxx_messageInfo_Position.DiscardUnknown(m) } var xxx_messageInfo_Position proto.InternalMessageInfo func (m *Position) GetX() float32 { if m != nil { return m.X } return 0 } func (m *Position) GetY() float32 { if m != nil { return m.Y } return 0 } func (m *Position) GetZ() float32 { if m != nil { return m.Z } return 0 } func (m *Position) GetV() float32 { if m != nil { return m.V } return 0 } // Player broadcast data // 玩家广播数据 type BroadCast struct { PID int32 `protobuf:"varint,1,opt,name=PID,proto3" json:"PID,omitempty"` // 1 - World chat, 2 - Player position, 3 - Action, 4 - Update of coordinates after movement // 1-世界聊天 2-玩家位置 3 动作 4 移动之后坐标信息更新 Tp int32 `protobuf:"varint,2,opt,name=Tp,proto3" json:"Tp,omitempty"` // Types that are valid to be assigned to Data: // *BroadCast_Content // *BroadCast_P // *BroadCast_ActionData Data isBroadCast_Data `protobuf_oneof:"Data"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *BroadCast) Reset() { *m = BroadCast{} } func (m *BroadCast) String() string { return proto.CompactTextString(m) } func (*BroadCast) ProtoMessage() {} func (*BroadCast) Descriptor() ([]byte, []int) { return fileDescriptor_c06e4cca6c2cc899, []int{2} } func (m *BroadCast) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_BroadCast.Unmarshal(m, b) } func (m *BroadCast) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_BroadCast.Marshal(b, m, deterministic) } func (m *BroadCast) XXX_Merge(src proto.Message) { xxx_messageInfo_BroadCast.Merge(m, src) } func (m *BroadCast) XXX_Size() int { return xxx_messageInfo_BroadCast.Size(m) } func (m *BroadCast) XXX_DiscardUnknown() { xxx_messageInfo_BroadCast.DiscardUnknown(m) } var xxx_messageInfo_BroadCast proto.InternalMessageInfo func (m *BroadCast) GetPID() int32 { if m != nil { return m.PID } return 0 } func (m *BroadCast) GetTp() int32 { if m != nil { return m.Tp } return 0 } type isBroadCast_Data interface { isBroadCast_Data() } type BroadCast_Content struct { Content string `protobuf:"bytes,3,opt,name=Content,proto3,oneof"` } type BroadCast_P struct { P *Position `protobuf:"bytes,4,opt,name=P,proto3,oneof"` } type BroadCast_ActionData struct { ActionData int32 `protobuf:"varint,5,opt,name=ActionData,proto3,oneof"` } func (*BroadCast_Content) isBroadCast_Data() {} func (*BroadCast_P) isBroadCast_Data() {} func (*BroadCast_ActionData) isBroadCast_Data() {} func (m *BroadCast) GetData() isBroadCast_Data { if m != nil { return m.Data } return nil } func (m *BroadCast) GetContent() string { if x, ok := m.GetData().(*BroadCast_Content); ok { return x.Content } return "" } func (m *BroadCast) GetP() *Position { if x, ok := m.GetData().(*BroadCast_P); ok { return x.P } return nil } func (m *BroadCast) GetActionData() int32 { if x, ok := m.GetData().(*BroadCast_ActionData); ok { return x.ActionData } return 0 } // XXX_OneofWrappers is for the internal use of the proto package. func (*BroadCast) XXX_OneofWrappers() []interface{} { return []interface{}{ (*BroadCast_Content)(nil), (*BroadCast_P)(nil), (*BroadCast_ActionData)(nil), } } // Player chat data // 玩家聊天数据 type Talk struct { Content string `protobuf:"bytes,1,opt,name=Content,proto3" json:"Content,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Talk) Reset() { *m = Talk{} } func (m *Talk) String() string { return proto.CompactTextString(m) } func (*Talk) ProtoMessage() {} func (*Talk) Descriptor() ([]byte, []int) { return fileDescriptor_c06e4cca6c2cc899, []int{3} } func (m *Talk) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Talk.Unmarshal(m, b) } func (m *Talk) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Talk.Marshal(b, m, deterministic) } func (m *Talk) XXX_Merge(src proto.Message) { xxx_messageInfo_Talk.Merge(m, src) } func (m *Talk) XXX_Size() int { return xxx_messageInfo_Talk.Size(m) } func (m *Talk) XXX_DiscardUnknown() { xxx_messageInfo_Talk.DiscardUnknown(m) } var xxx_messageInfo_Talk proto.InternalMessageInfo func (m *Talk) GetContent() string { if m != nil { return m.Content } return "" } // Player information type Player struct { PID int32 `protobuf:"varint,1,opt,name=PID,proto3" json:"PID,omitempty"` P *Position `protobuf:"bytes,2,opt,name=P,proto3" json:"P,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *Player) Reset() { *m = Player{} } func (m *Player) String() string { return proto.CompactTextString(m) } func (*Player) ProtoMessage() {} func (*Player) Descriptor() ([]byte, []int) { return fileDescriptor_c06e4cca6c2cc899, []int{4} } func (m *Player) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_Player.Unmarshal(m, b) } func (m *Player) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_Player.Marshal(b, m, deterministic) } func (m *Player) XXX_Merge(src proto.Message) { xxx_messageInfo_Player.Merge(m, src) } func (m *Player) XXX_Size() int { return xxx_messageInfo_Player.Size(m) } func (m *Player) XXX_DiscardUnknown() { xxx_messageInfo_Player.DiscardUnknown(m) } var xxx_messageInfo_Player proto.InternalMessageInfo func (m *Player) GetPID() int32 { if m != nil { return m.PID } return 0 } func (m *Player) GetP() *Position { if m != nil { return m.P } return nil } // Synchronize player display data // 同步玩家显示数据 type SyncPlayers struct { Ps []*Player `protobuf:"bytes,1,rep,name=ps,proto3" json:"ps,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } func (m *SyncPlayers) Reset() { *m = SyncPlayers{} } func (m *SyncPlayers) String() string { return proto.CompactTextString(m) } func (*SyncPlayers) ProtoMessage() {} func (*SyncPlayers) Descriptor() ([]byte, []int) { return fileDescriptor_c06e4cca6c2cc899, []int{5} } func (m *SyncPlayers) XXX_Unmarshal(b []byte) error { return xxx_messageInfo_SyncPlayers.Unmarshal(m, b) } func (m *SyncPlayers) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { return xxx_messageInfo_SyncPlayers.Marshal(b, m, deterministic) } func (m *SyncPlayers) XXX_Merge(src proto.Message) { xxx_messageInfo_SyncPlayers.Merge(m, src) } func (m *SyncPlayers) XXX_Size() int { return xxx_messageInfo_SyncPlayers.Size(m) } func (m *SyncPlayers) XXX_DiscardUnknown() { xxx_messageInfo_SyncPlayers.DiscardUnknown(m) } var xxx_messageInfo_SyncPlayers proto.InternalMessageInfo func (m *SyncPlayers) GetPs() []*Player { if m != nil { return m.Ps } return nil } func init() { proto.RegisterType((*SyncPID)(nil), "pb.SyncPID") proto.RegisterType((*Position)(nil), "pb.Position") proto.RegisterType((*BroadCast)(nil), "pb.BroadCast") proto.RegisterType((*Talk)(nil), "pb.Talk") proto.RegisterType((*Player)(nil), "pb.Player") proto.RegisterType((*SyncPlayers)(nil), "pb.SyncPlayers") } func init() { proto.RegisterFile("msg.proto", fileDescriptor_c06e4cca6c2cc899) } var fileDescriptor_c06e4cca6c2cc899 = []byte{ // 278 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xbf, 0x4e, 0xc3, 0x30, 0x10, 0x87, 0x63, 0xe7, 0x4f, 0xc9, 0xa5, 0x42, 0xc8, 0x93, 0x15, 0x18, 0x22, 0x4f, 0x65, 0xc9, 0x50, 0x24, 0x76, 0xd2, 0x0c, 0xe9, 0x66, 0x99, 0xa8, 0x6a, 0xbb, 0x39, 0xa5, 0x42, 0x15, 0x25, 0xb6, 0x62, 0x2f, 0x7d, 0x0c, 0x5e, 0x83, 0xa7, 0x44, 0x76, 0x54, 0x40, 0x6a, 0x27, 0xfb, 0xbb, 0xb3, 0x7f, 0xf7, 0xc9, 0x86, 0xf4, 0xd3, 0xbc, 0x97, 0x7a, 0x50, 0x56, 0x11, 0xac, 0x3b, 0x76, 0x0f, 0x93, 0xd7, 0x53, 0xbf, 0xe3, 0xcb, 0x9a, 0xdc, 0x41, 0xc8, 0x97, 0x35, 0x45, 0x05, 0x9a, 0xc5, 0xc2, 0x6d, 0x59, 0x05, 0x37, 0x5c, 0x99, 0x83, 0x3d, 0xa8, 0x9e, 0x4c, 0x01, 0xad, 0x7d, 0x0f, 0x0b, 0xb4, 0x76, 0xb4, 0xa1, 0x78, 0xa4, 0x8d, 0xa3, 0x2d, 0x0d, 0x47, 0xda, 0x3a, 0x5a, 0xd1, 0x68, 0xa4, 0x15, 0xfb, 0x42, 0x90, 0x56, 0x83, 0x92, 0x6f, 0x0b, 0x69, 0xec, 0xe5, 0x0c, 0x72, 0x0b, 0xb8, 0xd5, 0x3e, 0x2a, 0x16, 0xb8, 0xd5, 0x24, 0x87, 0xc9, 0x42, 0xf5, 0x76, 0xdf, 0x5b, 0x9f, 0x98, 0x36, 0x81, 0x38, 0x17, 0xc8, 0x03, 0x20, 0xee, 0x93, 0xb3, 0xf9, 0xb4, 0xd4, 0x5d, 0x79, 0x96, 0x6b, 0x02, 0x81, 0x38, 0x29, 0x00, 0x5e, 0x76, 0x0e, 0x6b, 0x69, 0x25, 0x8d, 0x5d, 0x62, 0x13, 0x88, 0x7f, 0xb5, 0x2a, 0x81, 0xc8, 0xad, 0xac, 0x80, 0xa8, 0x95, 0xc7, 0x0f, 0x42, 0xff, 0x66, 0x39, 0xa3, 0xf4, 0x77, 0x12, 0x7b, 0x86, 0x84, 0x1f, 0xe5, 0x69, 0x3f, 0x5c, 0x31, 0xce, 0x9d, 0x05, 0xbe, 0xb4, 0x10, 0x88, 0xb3, 0x47, 0xc8, 0xfc, 0x73, 0xfa, 0xbb, 0x86, 0xe4, 0x80, 0xb5, 0xa1, 0xa8, 0x08, 0x67, 0xd9, 0x1c, 0xfc, 0x59, 0xdf, 0x10, 0x58, 0x9b, 0x2a, 0xfe, 0xc6, 0x98, 0x77, 0x5d, 0xe2, 0xff, 0xe2, 0xe9, 0x27, 0x00, 0x00, 0xff, 0xff, 0x31, 0xa4, 0x44, 0x79, 0x98, 0x01, 0x00, 0x00, } ================================================ FILE: zinx_app_demo/mmo_game/pb/msg.proto ================================================ syntax="proto3"; // Version of protobuf package pb; // Current package name option csharp_namespace="Pb"; // Option for C# (给C#提供的选项) // Synchronize client player ID // 同步客户端玩家ID message SyncPID{ int32 PID=1; } // Player position message Position{ float X=1; float Y=2; float Z=3; float V=4; } // Player broadcast data // 玩家广播数据 message BroadCast{ int32 PID=1; // 1 - World chat, 2 - Player position, 3 - Action, 4 - Update of coordinates after movement // 1-世界聊天 2-玩家位置 3 动作 4 移动之后坐标信息更新 int32 Tp=2; oneof Data { string Content=3; // Chat message(聊天的信息) Position P=4; // Player's position for broadcasting(广播用户的位置) int32 ActionData=5; } } // Player chat data // 玩家聊天数据 message Talk{ string Content=1; //聊天内容 } // Player information message Player{ int32 PID=1; Position P=2; } // Synchronize player display data // 同步玩家显示数据 message SyncPlayers{ repeated Player ps=1; } ================================================ FILE: zinx_app_demo/mmo_game/server.go ================================================ package main import ( "fmt" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zinx_app_demo/mmo_game/api" "github.com/aceld/zinx/zinx_app_demo/mmo_game/core" "github.com/aceld/zinx/znet" "github.com/aceld/zinx/zpack" ) // OnConnectionAdd is a hook function called when a client establishes a connection // 当客户端建立连接的时候的hook函数 func OnConnectionAdd(conn ziface.IConnection) { fmt.Println("=====> OnConnectionAdd is Called ...") // Create a new player // 创建一个玩家 player := core.NewPlayer(conn) // Synchronize the current player's ID to the client using MsgID:1 message // 同步当前的PlayerID给客户端, 走MsgID:1 消息 player.SyncPID() // Synchronize the initial coordinate information of the current player to the client using MsgID:200 message // 同步当前玩家的初始化坐标信息给客户端,走MsgID:200消息 player.BroadCastStartPosition() // Add the newly online player to the WorldManager // 将当前新上线玩家添加到worldManager中 core.WorldMgrObj.AddPlayer(player) // Bind the property "pID" to the connection // 将该连接绑定属性PID conn.SetProperty("pID", player.PID) // Synchronize online player information and display surrounding player information // 同步周边玩家上线信息,与现实周边玩家信息 player.SyncSurrounding() fmt.Println("=====> Player pIDID = ", player.PID, " arrived ====") } // OnConnectionLost Hook function called when a client disconnects // 当客户端断开连接的时候的hook函数 func OnConnectionLost(conn ziface.IConnection) { // Get the "pID" property of the current connection // 获取当前连接的PID属性 pID, _ := conn.GetProperty("pID") var playerID int32 if pID != nil { playerID = pID.(int32) } // Get the corresponding player object based on the player ID // 根据pID获取对应的玩家对象 player := core.WorldMgrObj.GetPlayerByPID(playerID) // Trigger the player's disconnection business logic // 触发玩家下线业务 if player != nil { player.LostConnection() } fmt.Println("====> Player ", playerID, " left =====") } func main() { // Create a server instance // 创建服务器句柄 s := znet.NewServer() // Register functions for client connection establishment and loss // 注册客户端连接建立和丢失函数 s.SetOnConnStart(OnConnectionAdd) s.SetOnConnStop(OnConnectionLost) // Register routers s.AddRouter(2, &api.WorldChatApi{}) s.AddRouter(3, &api.MoveApi{}) // Add LTV data format Decoder s.SetDecoder(zdecoder.NewLTV_Little_Decoder()) // Add LTV data format Pack packet Encoder s.SetPacket(zpack.NewDataPackLtv()) // Start the server s.Serve() } ================================================ FILE: zlog/default.go ================================================ package zlog import ( "context" "fmt" "github.com/aceld/zinx/ziface" ) var zLogInstance ziface.ILogger = new(zinxDefaultLog) type zinxDefaultLog struct{} func (log *zinxDefaultLog) InfoF(format string, v ...interface{}) { StdZinxLog.Infof(format, v...) } func (log *zinxDefaultLog) ErrorF(format string, v ...interface{}) { StdZinxLog.Errorf(format, v...) } func (log *zinxDefaultLog) DebugF(format string, v ...interface{}) { StdZinxLog.Debugf(format, v...) } func (log *zinxDefaultLog) InfoFX(ctx context.Context, format string, v ...interface{}) { fmt.Println(ctx) StdZinxLog.Infof(format, v...) } func (log *zinxDefaultLog) ErrorFX(ctx context.Context, format string, v ...interface{}) { fmt.Println(ctx) StdZinxLog.Errorf(format, v...) } func (log *zinxDefaultLog) DebugFX(ctx context.Context, format string, v ...interface{}) { fmt.Println(ctx) StdZinxLog.Debugf(format, v...) } func SetLogger(newlog ziface.ILogger) { zLogInstance = newlog } func Ins() ziface.ILogger { return zLogInstance } ================================================ FILE: zlog/logger_core.go ================================================ // Package zlog provides logging interfaces for zinx. // This includes: // // - stdzlog module, which provides global logging methods // - zlogger module, which defines logging protocols as object methods // // Current file description: // @Title zlogger.go // @Description Basic logging interface, including Debug, Fatal, etc. // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package zlog /* All methods and APIs of the log class. Add By Aceld(刘丹冰) 2019-4-23 */ import ( "bytes" "fmt" "os" "path/filepath" "runtime" "sync" "time" "github.com/aceld/zinx/zutils" ) const ( LOG_MAX_BUF = 1024 * 1024 ) // Log header information flag, using bitmap mode, users can choose which flag bits to print in the header // (日志头部信息标记位,采用bitmap方式,用户可以选择头部需要哪些标记位被打印) const ( BitDate = 1 << iota // Date flag bit 2019/01/23 (日期标记位) BitTime // Time flag bit 01:23:12 (时间标记位) BitMicroSeconds // Microsecond flag bit 01:23:12.111222 (微秒级标记位) BitLongFile // Complete file name /home/go/src/zinx/server.go (完整文件名称) BitShortFile // Last file name server.go (最后文件名) BitLevel // Current log level: 0(Debug), 1(Info), 2(Warn), 3(Error), 4(Panic), 5(Fatal) (当前日志级别) BitStdFlag = BitDate | BitTime // Standard log header format (标准头部日志格式) BitDefault = BitLevel | BitShortFile | BitStdFlag // Default log header format (默认日志头部格式) ) // Log Level const ( LogDebug = iota LogInfo LogWarn LogError LogPanic LogFatal ) // Log Level String var levels = []string{ "[DEBUG]", "[INFO]", "[WARN]", "[ERROR]", "[PANIC]", "[FATAL]", } type ZinxLoggerCore struct { // to ensure thread-safe when multiple goroutines read and write files to prevent mixed-up content, achieving concurrency safety // (确保多协程读写文件,防止文件内容混乱,做到协程安全) mu sync.Mutex // the prefix string for each line of the log, which has the log tag // (每行log日志的前缀字符串,拥有日志标记) prefix string // log tag bit (日志标记位) flag int // the output buffer (输出的缓冲区) buf bytes.Buffer // log isolation level // (日志隔离级别) isolationLevel int // call stack depth of the function that gets the log file name and code using runtime.Call // (获取日志文件名和代码上述的runtime.Call 的函数调用层数) calldDepth int fw *zutils.Writer onLogHook func([]byte) } /* NewZinxLog Create a new log out: The file io for standard output prefix: The prefix of the log flag: The flag of the log header information */ func NewZinxLog(prefix string, flag int) *ZinxLoggerCore { // 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 // (默认 debug打开, calledDepth深度为2,ZinxLogger对象调用日志打印方法最多调用两层到达output函数) zlog := &ZinxLoggerCore{prefix: prefix, flag: flag, isolationLevel: 0, calldDepth: 2} // 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) // (设置log对象 回收资源 析构方法(不设置也可以,go的Gc会自动回收,强迫症没办法)) runtime.SetFinalizer(zlog, CleanZinxLog) return zlog } // CleanZinxLog Recycle log resources func CleanZinxLog(log *ZinxLoggerCore) { log.closeFile() } func (log *ZinxLoggerCore) SetLogHook(f func([]byte)) { log.onLogHook = f } /* formatHeader generates the header information for a log entry. t: The current time. file: The file name of the source code invoking the log function. line: The line number of the source code invoking the log function. level: The log level of the current log entry. */ func (log *ZinxLoggerCore) formatHeader(t time.Time, file string, line int, level int) { var buf *bytes.Buffer = &log.buf // If the current prefix string is not empty, write the prefix first. if log.prefix != "" { buf.WriteByte('<') buf.WriteString(log.prefix) buf.WriteByte('>') } // If the time-related flags are set, add the time information to the log header. if log.flag&(BitDate|BitTime|BitMicroSeconds) != 0 { // Date flag is set if log.flag&BitDate != 0 { year, month, day := t.Date() itoa(buf, year, 4) buf.WriteByte('/') // "2019/" itoa(buf, int(month), 2) buf.WriteByte('/') // "2019/04/" itoa(buf, day, 2) buf.WriteByte(' ') // "2019/04/11 " } // Time flag is set if log.flag&(BitTime|BitMicroSeconds) != 0 { hour, min, sec := t.Clock() itoa(buf, hour, 2) buf.WriteByte(':') // "11:" itoa(buf, min, 2) buf.WriteByte(':') // "11:15:" itoa(buf, sec, 2) // "11:15:33" // Microsecond flag is set if log.flag&BitMicroSeconds != 0 { buf.WriteByte('.') itoa(buf, t.Nanosecond()/1e3, 6) // "11:15:33.123123 } buf.WriteByte(' ') } // Log level flag is set if log.flag&BitLevel != 0 { buf.WriteString(levels[level]) } // Short file name flag or long file name flag is set if log.flag&(BitShortFile|BitLongFile) != 0 { // Short file name flag is set if log.flag&BitShortFile != 0 { short := file for i := len(file) - 1; i > 0; i-- { if file[i] == '/' { // Get the file name after the last '/' character, e.g. "zinx.go" from "/home/go/src/zinx.go" short = file[i+1:] break } } file = short } buf.WriteString(file) buf.WriteByte(':') itoa(buf, line, -1) // line number buf.WriteString(": ") } } } // OutPut outputs log file, the original method func (log *ZinxLoggerCore) OutPut(level int, s string) error { now := time.Now() // get current time var file string // file name of the current caller of the log interface var line int // line number of the executed code log.mu.Lock() defer log.mu.Unlock() if log.flag&(BitShortFile|BitLongFile) != 0 { log.mu.Unlock() var ok bool // get the file name and line number of the current caller _, file, line, ok = runtime.Caller(log.calldDepth) if !ok { file = "unknown-file" line = 0 } log.mu.Lock() } // reset buffer log.buf.Reset() // write log header log.formatHeader(now, file, line, level) // write log content log.buf.WriteString(s) // add line break if len(s) > 0 && s[len(s)-1] != '\n' { log.buf.WriteByte('\n') } var err error if log.fw == nil { // if log file is not set, output to console _, _ = os.Stderr.Write(log.buf.Bytes()) } else { // write the filled buffer to IO output _, err = log.fw.Write(log.buf.Bytes()) } if log.onLogHook != nil { log.onLogHook(log.buf.Bytes()) } return err } func (log *ZinxLoggerCore) verifyLogIsolation(logLevel int) bool { return log.isolationLevel > logLevel } func (log *ZinxLoggerCore) Debugf(format string, v ...interface{}) { if log.verifyLogIsolation(LogDebug) { return } _ = log.OutPut(LogDebug, fmt.Sprintf(format, v...)) } func (log *ZinxLoggerCore) Debug(v ...interface{}) { if log.verifyLogIsolation(LogDebug) { return } _ = log.OutPut(LogDebug, fmt.Sprintln(v...)) } func (log *ZinxLoggerCore) Infof(format string, v ...interface{}) { if log.verifyLogIsolation(LogInfo) { return } _ = log.OutPut(LogInfo, fmt.Sprintf(format, v...)) } func (log *ZinxLoggerCore) Info(v ...interface{}) { if log.verifyLogIsolation(LogInfo) { return } _ = log.OutPut(LogInfo, fmt.Sprintln(v...)) } func (log *ZinxLoggerCore) Warnf(format string, v ...interface{}) { if log.verifyLogIsolation(LogWarn) { return } _ = log.OutPut(LogWarn, fmt.Sprintf(format, v...)) } func (log *ZinxLoggerCore) Warn(v ...interface{}) { if log.verifyLogIsolation(LogWarn) { return } _ = log.OutPut(LogWarn, fmt.Sprintln(v...)) } func (log *ZinxLoggerCore) Errorf(format string, v ...interface{}) { if log.verifyLogIsolation(LogError) { return } _ = log.OutPut(LogError, fmt.Sprintf(format, v...)) } func (log *ZinxLoggerCore) Error(v ...interface{}) { if log.verifyLogIsolation(LogError) { return } _ = log.OutPut(LogError, fmt.Sprintln(v...)) } func (log *ZinxLoggerCore) Fatalf(format string, v ...interface{}) { if log.verifyLogIsolation(LogFatal) { return } _ = log.OutPut(LogFatal, fmt.Sprintf(format, v...)) os.Exit(1) } func (log *ZinxLoggerCore) Fatal(v ...interface{}) { if log.verifyLogIsolation(LogFatal) { return } _ = log.OutPut(LogFatal, fmt.Sprintln(v...)) os.Exit(1) } func (log *ZinxLoggerCore) Panicf(format string, v ...interface{}) { if log.verifyLogIsolation(LogPanic) { return } s := fmt.Sprintf(format, v...) _ = log.OutPut(LogPanic, s) panic(s) } func (log *ZinxLoggerCore) Panic(v ...interface{}) { if log.verifyLogIsolation(LogPanic) { return } s := fmt.Sprintln(v...) _ = log.OutPut(LogPanic, s) panic(s) } func (log *ZinxLoggerCore) Stack(v ...interface{}) { s := fmt.Sprint(v...) s += "\n" buf := make([]byte, LOG_MAX_BUF) n := runtime.Stack(buf, true) //得到当前堆栈信息 s += string(buf[:n]) s += "\n" _ = log.OutPut(LogError, s) } // Flags gets the current log bitmap flags // (获取当前日志bitmap标记) func (log *ZinxLoggerCore) Flags() int { log.mu.Lock() defer log.mu.Unlock() return log.flag } // ResetFlags resets the log Flags bitmap flags // (重新设置日志Flags bitMap 标记位) func (log *ZinxLoggerCore) ResetFlags(flag int) { log.mu.Lock() defer log.mu.Unlock() log.flag = flag } // AddFlag adds a flag to the bitmap flags // (添加flag标记) func (log *ZinxLoggerCore) AddFlag(flag int) { log.mu.Lock() defer log.mu.Unlock() log.flag |= flag } // SetPrefix sets a custom prefix for the log // (设置日志的 用户自定义前缀字符串) func (log *ZinxLoggerCore) SetPrefix(prefix string) { log.mu.Lock() defer log.mu.Unlock() log.prefix = prefix } // SetLogFile sets the log file output // (设置日志文件输出) func (log *ZinxLoggerCore) SetLogFile(fileDir string, fileName string) { if log.fw != nil { log.fw.Close() } log.fw = zutils.New(filepath.Join(fileDir, fileName)) } // SetMaxAge 最大保留天数 func (log *ZinxLoggerCore) SetMaxAge(ma int) { if log.fw == nil { return } log.mu.Lock() defer log.mu.Unlock() log.fw.SetMaxAge(ma) } // SetMaxSize 单个日志最大容量 单位:字节 func (log *ZinxLoggerCore) SetMaxSize(ms int64) { if log.fw == nil { return } log.mu.Lock() defer log.mu.Unlock() log.fw.SetMaxSize(ms) } // SetCons 同时输出控制台 func (log *ZinxLoggerCore) SetCons(b bool) { if log.fw == nil { return } log.mu.Lock() defer log.mu.Unlock() log.fw.SetCons(b) } // Close the file associated with the log // (关闭日志绑定的文件) func (log *ZinxLoggerCore) closeFile() { if log.fw != nil { log.fw.Close() } } func (log *ZinxLoggerCore) SetLogLevel(logLevel int) { log.isolationLevel = logLevel } // Convert an integer to a fixed-length string, where the width of the string should be greater than 0 // Ensure that the buffer has sufficient capacity // (将一个整形转换成一个固定长度的字符串,字符串宽度应该是大于0的 // 要确保buffer是有容量空间的) func itoa(buf *bytes.Buffer, i int, wID int) { var u uint = uint(i) if u == 0 && wID <= 1 { buf.WriteByte('0') return } // Assemble decimal in reverse order. var b [32]byte bp := len(b) for ; u > 0 || wID > 0; u /= 10 { bp-- wID-- b[bp] = byte(u%10) + '0' } // avoID slicing b to avoID an allocation. for bp < len(b) { buf.WriteByte(b[bp]) bp++ } } ================================================ FILE: zlog/stdzlog.go ================================================ // @Title stdzlog.go // @Description Wraps zlogger log methods to provide global methods // @Author Aceld - Thu Mar 11 10:32:29 CST 2019 package zlog /* A global Log handle is provided by default for external use, which can be called directly through the API series. The global log object is StdZinxLog. Note: The methods in this file do not support customization and cannot replace the log recording mode. If you need a custom logger, please use the following methods: zlog.SetLogger(yourLogger) zlog.Ins().InfoF() and other methods. 全局默认提供一个Log对外句柄,可以直接使用API系列调用 全局日志对象 StdZinxLog 注意:本文件方法不支持自定义,无法替换日志记录模式,如果需要自定义Logger: 请使用如下方法: zlog.SetLogger(yourLogger) zlog.Ins().InfoF()等方法 */ // StdZinxLog creates a global log var StdZinxLog = NewZinxLog("", BitDefault) // Flags gets the flags of StdZinxLog func Flags() int { return StdZinxLog.Flags() } // ResetFlags sets the flags of StdZinxLog func ResetFlags(flag int) { StdZinxLog.ResetFlags(flag) } // AddFlag adds a flag to StdZinxLog func AddFlag(flag int) { StdZinxLog.AddFlag(flag) } // SetPrefix sets the log prefix of StdZinxLog func SetPrefix(prefix string) { StdZinxLog.SetPrefix(prefix) } // SetLogFile sets the log file of StdZinxLog func SetLogFile(fileDir string, fileName string) { StdZinxLog.SetLogFile(fileDir, fileName) } // SetMaxAge 最大保留天数 func SetMaxAge(ma int) { StdZinxLog.SetMaxAge(ma) } // SetMaxSize 单个日志最大容量 单位:字节 func SetMaxSize(ms int64) { StdZinxLog.SetMaxSize(ms) } // SetCons 同时输出控制台 func SetCons(b bool) { StdZinxLog.SetCons(b) } // SetLogLevel sets the log level of StdZinxLog func SetLogLevel(logLevel int) { StdZinxLog.SetLogLevel(logLevel) } func Debugf(format string, v ...interface{}) { StdZinxLog.Debugf(format, v...) } func Debug(v ...interface{}) { StdZinxLog.Debug(v...) } func Infof(format string, v ...interface{}) { StdZinxLog.Infof(format, v...) } func Info(v ...interface{}) { StdZinxLog.Info(v...) } func Warnf(format string, v ...interface{}) { StdZinxLog.Warnf(format, v...) } func Warn(v ...interface{}) { StdZinxLog.Warn(v...) } func Errorf(format string, v ...interface{}) { StdZinxLog.Errorf(format, v...) } func Error(v ...interface{}) { StdZinxLog.Error(v...) } func Fatalf(format string, v ...interface{}) { StdZinxLog.Fatalf(format, v...) } func Fatal(v ...interface{}) { StdZinxLog.Fatal(v...) } func Panicf(format string, v ...interface{}) { StdZinxLog.Panicf(format, v...) } func Panic(v ...interface{}) { StdZinxLog.Panic(v...) } func Stack(v ...interface{}) { StdZinxLog.Stack(v...) } func init() { // Since the StdZinxLog object wraps all output methods with an extra layer, the call depth is one more than a normal logger object // The call depth of a regular zinxLogger object is 2, and the call depth of StdZinxLog is 3 // (因为StdZinxLog对象 对所有输出方法做了一层包裹,所以在打印调用函数的时候,比正常的logger对象多一层调用 // 一般的zinxLogger对象 calldDepth=2, StdZinxLog的calldDepth=3) StdZinxLog.calldDepth = 3 } ================================================ FILE: zlog/zlog_test.go ================================================ package zlog_test import ( "testing" "github.com/aceld/zinx/zlog" ) func TestStdZLog(t *testing.T) { //测试 默认debug输出 zlog.Debug("zinx debug content1") zlog.Debug("zinx debug content2") zlog.Debugf(" zinx debug a = %d\n", 10) //设置log标记位,加上长文件名称 和 微秒 标记 zlog.ResetFlags(zlog.BitDate | zlog.BitLongFile | zlog.BitLevel) zlog.Info("zinx info content") //设置日志前缀,主要标记当前日志模块 zlog.SetPrefix("MODULE") zlog.Error("zinx error content") //添加标记位 zlog.AddFlag(zlog.BitShortFile | zlog.BitTime) zlog.Stack(" Zinx Stack! ") //设置日志写入文件 zlog.SetLogFile("./log", "testfile.log") zlog.Debug("===> zinx debug content ~~666") zlog.Debug("===> zinx debug content ~~888") zlog.Error("===> zinx Error!!!! ~~~555~~~") //调试隔离级别 zlog.Debug("=================================>") //1.debug zlog.SetLogLevel(zlog.LogInfo) zlog.Debug("===> 调试Debug:debug不应该出现") zlog.Info("===> 调试Debug:info应该出现") zlog.Warn("===> 调试Debug:warn应该出现") zlog.Error("===> 调试Debug:error应该出现") //2.info zlog.SetLogLevel(zlog.LogWarn) zlog.Debug("===> 调试Info:debug不应该出现") zlog.Info("===> 调试Info:info不应该出现") zlog.Warn("===> 调试Info:warn应该出现") zlog.Error("===> 调试Info:error应该出现") //3.warn zlog.SetLogLevel(zlog.LogError) zlog.Debug("===> 调试Warn:debug不应该出现") zlog.Info("===> 调试Warn:info不应该出现") zlog.Warn("===> 调试Warn:warn不应该出现") zlog.Error("===> 调试Warn:error应该出现") //4.error zlog.SetLogLevel(zlog.LogPanic) zlog.Debug("===> 调试Error:debug不应该出现") zlog.Info("===> 调试Error:info不应该出现") zlog.Warn("===> 调试Error:warn不应该出现") zlog.Error("===> 调试Error:error不应该出现") } func TestZLogger(t *testing.T) { } ================================================ FILE: znet/acceptdelay.go ================================================ package znet import ( "time" ) const ( maxDelay = 1 * time.Second ) var AcceptDelay *acceptDelay func init() { AcceptDelay = &acceptDelay{duration: 0} } type acceptDelay struct { duration time.Duration } func (d *acceptDelay) Delay() { d.Up() d.do() } func (d *acceptDelay) Reset() { d.duration = 0 } func (d *acceptDelay) Up() { if d.duration == 0 { d.duration = 5 * time.Millisecond return } d.duration = 2 * d.duration if d.duration > maxDelay { d.duration = maxDelay } } func (d *acceptDelay) do() { if d.duration > 0 { time.Sleep(d.duration) } } ================================================ FILE: znet/acceptdelay_test.go ================================================ package znet import ( "fmt" "os" "testing" "time" "github.com/stretchr/testify/assert" ) func setup() { fmt.Println("Test Begin") } func teardown() { AcceptDelay.Reset() fmt.Println("Test End") } func TestDelay(t *testing.T) { assert.Equal(t, time.Duration(0), AcceptDelay.duration) AcceptDelay.Up() assert.Equal(t, 5*time.Millisecond, AcceptDelay.duration) AcceptDelay.Reset() assert.Equal(t, time.Duration(0), AcceptDelay.duration) for i := 0; i < 600; i++ { AcceptDelay.Up() } assert.Equal(t, 1*time.Second, AcceptDelay.duration) } func TestMain(m *testing.M) { setup() code := m.Run() teardown() os.Exit(code) } ================================================ FILE: znet/callbacks.go ================================================ package znet // callbackNode represents a node in the callback linked list // Each node contains handler identifier, key, callback function and pointer to next node type callbackNode struct { handler any // Handler identifier, used to identify the source or type of callback key any // Unique identifier key for callback, used in combination with handler call func() // Actual callback function to be executed next *callbackNode // Pointer to next node, forming linked list structure } // callbacks is a singly linked list structure for managing multiple callback functions // Supports dynamic addition, removal and execution of callbacks type callbacks struct { first *callbackNode // Pointer to the first node of the linked list last *callbackNode // Pointer to the last node of the linked list, used for quick addition of new nodes } // Add adds a new callback function to the callback linked list // Parameters: // - handler: Handler identifier, can be any type // - key: Unique identifier key for callback, used in combination with handler // - callback: Callback function to be executed, ignored if nil // // Note: If a callback with the same handler and key already exists, it will be replaced func (t *callbacks) Add(handler, key any, callback func()) { // Prevent adding empty callback function if callback == nil { return } // Check if a callback with the same handler and key already exists for cb := t.first; cb != nil; cb = cb.next { if cb.handler == handler && cb.key == key { // Replace existing callback cb.call = callback return } } // Create new callback node newItem := &callbackNode{handler, key, callback, nil} if t.first == nil { // If linked list is empty, new node becomes the first node t.first = newItem } else { // Otherwise add new node to the end of linked list t.last.next = newItem } // Update pointer to last node t.last = newItem } // Remove removes the specified callback function from the callback linked list // Parameters: // - handler: Handler identifier of the callback to be removed // - key: Unique identifier key of the callback to be removed // // Note: If no matching callback is found, this method has no effect func (t *callbacks) Remove(handler, key any) { var prev *callbackNode // Traverse linked list to find the node to be removed for callback := t.first; callback != nil; prev, callback = callback, callback.next { // Found matching node if callback.handler == handler && callback.key == key { if t.first == callback { // If it's the first node, update first pointer t.first = callback.next } else if prev != nil { // If it's a middle node, update the next pointer of the previous node prev.next = callback.next } if t.last == callback { // If it's the last node, update last pointer t.last = prev } // Return immediately after finding and removing return } } } // Invoke executes all registered callback functions in the linked list // Executes each callback in the order they were added // Note: If a callback function is nil, it will be skipped // If a callback panics, it will be handled by the outer caller's panic recovery func (t *callbacks) Invoke() { // Traverse the entire linked list starting from the head node for callback := t.first; callback != nil; callback = callback.next { callback.call() } } // Len returns the number of callback functions in the linked list // Return value: Total number of currently registered callback functions func (t *callbacks) Len() int { var count int // Traverse linked list to count for callback := t.first; callback != nil; callback = callback.next { count++ } return count } ================================================ FILE: znet/callbacks_test.go ================================================ package znet import ( "testing" ) func TestCallback(t *testing.T) { // Test empty list cb := &callbacks{} if cb.Len() != 0 { t.Errorf("Expected count for empty list is 0, but got %d", cb.Len()) } // Ensure invoking on an empty registry is a no-op (no panic). cb.Invoke() // Test adding callback functions var count, expected, remove, totalCount int totalCount = 10 remove = 5 // Add multiple callback functions for i := 1; i < totalCount; i++ { expected = expected + i func(ii int) { cb.Add(ii, ii, func() { count = count + ii }) }(i) } // Verify count after adding expectedCallbacks := totalCount - 1 if cb.Len() != expectedCallbacks { t.Errorf("Expected callback count is %d, but got %d", expectedCallbacks, cb.Len()) } // Test adding nil callback cb.Add(remove, remove, nil) if cb.Len() != expectedCallbacks { t.Errorf("Expected count after adding nil callback is %d, but got %d", expectedCallbacks, cb.Len()) } // Replace an existing callback with a non-nil one; count should remain unchanged. cb.Add(remove, remove, func() { count += remove }) if cb.Len() != expectedCallbacks { t.Errorf("Expected count after replacing existing callback is %d, but got %d", expectedCallbacks, cb.Len()) } // Remove specified callback cb.Remove(remove, remove) // Try to remove non-existent callback cb.Remove(remove+1, remove+2) // Execute all callbacks cb.Invoke() // Verify execution result expectedSum := expected - remove if count != expectedSum { t.Errorf("Expected execution result is %d, but got %d", expectedSum, count) } // Test string type handler and key cb2 := &callbacks{} // Add callbacks cb2.Add("handler1", "key1", func() {}) cb2.Add("handler2", "key2", func() {}) cb2.Add("handler3", "key3", func() {}) if cb2.Len() != 3 { t.Errorf("Expected callback count is 3, but got %d", cb2.Len()) } // Remove middle callback cb2.Remove("handler2", "key2") if cb2.Len() != 2 { t.Errorf("Expected count after removing middle callback is 2, but got %d", cb2.Len()) } // Remove first callback cb2.Remove("handler1", "key1") if cb2.Len() != 1 { t.Errorf("Expected count after removing first callback is 1, but got %d", cb2.Len()) } // Remove last callback cb2.Remove("handler3", "key3") if cb2.Len() != 0 { t.Errorf("Expected count after removing last callback is 0, but got %d", cb2.Len()) } // Test removing non-existent callback cb2.Add("handler1", "key1", func() {}) cb2.Remove("handler2", "key2") // Try to remove non-existent callback // Should still have 1 callback if cb2.Len() != 1 { t.Errorf("Expected callback count is 1, but got %d", cb2.Len()) } } func TestCallbackInvokePanicPropagation(t *testing.T) { cb := &callbacks{} cb.Add("h", "k1", func() { panic("boom") }) // Test that panic is propagated (not swallowed by Invoke) defer func() { if r := recover(); r != nil { if r != "boom" { t.Errorf("Expected panic 'boom', got %v", r) } } else { t.Errorf("Expected panic to be propagated, but it was swallowed") } }() // This should panic and be caught by the defer above cb.Invoke() } ================================================ FILE: znet/chainbuilder.go ================================================ /** * @author uuxia * @date 15:57 2023/3/10 * @description 拦截器管理 **/ package znet import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zinterceptor" ) // chainBuilder is a builder for creating a chain of interceptors. // (责任链构造器) type chainBuilder struct { body []ziface.IInterceptor head, tail ziface.IInterceptor } // newChainBuilder creates a new instance of chainBuilder. func newChainBuilder() *chainBuilder { return &chainBuilder{ body: make([]ziface.IInterceptor, 0), } } // Head adds an interceptor to the head of the chain. func (ic *chainBuilder) Head(interceptor ziface.IInterceptor) { ic.head = interceptor } // Tail adds an interceptor to the tail of the chain. func (ic *chainBuilder) Tail(interceptor ziface.IInterceptor) { ic.tail = interceptor } // AddInterceptor adds an interceptor to the body of the chain. func (ic *chainBuilder) AddInterceptor(interceptor ziface.IInterceptor) { ic.body = append(ic.body, interceptor) } // Execute executes all the interceptors in the current chain in order. func (ic *chainBuilder) Execute(req ziface.IcReq) ziface.IcResp { // Put all the interceptors into the builder var interceptors []ziface.IInterceptor if ic.head != nil { interceptors = append(interceptors, ic.head) } if len(ic.body) > 0 { interceptors = append(interceptors, ic.body...) } if ic.tail != nil { interceptors = append(interceptors, ic.tail) } // Create a new interceptor chain and execute each interceptor chain := zinterceptor.NewChain(interceptors, 0, req) // Execute the chain return chain.Proceed(req) } ================================================ FILE: znet/client.go ================================================ package znet import ( "context" "crypto/tls" "fmt" "net" "net/http" "net/url" "sync" "time" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/zpack" "github.com/gorilla/websocket" ) type Client struct { sync.WaitGroup sync.Mutex started bool ctx context.Context cancel context.CancelFunc // Client Name 客户端的名称 Name string // IP of the target server to connect 目标连接服务器的IP Ip string // Port of the target server to connect 目标连接服务器的端口 Port int Url *url.URL // 扩展,连接时带上其他参数 // Custom headers for WebSocket connection WebSocket连接的自定义头信息 WsHeader http.Header // Client version tcp,websocket,客户端版本 tcp,websocket version string // Connection instance 连接实例 conn ziface.IConnection // Connection instance 连接实例的锁,保证可见性 connMux sync.Mutex // Hook function called on connection start 该client的连接创建时Hook函数 onConnStart func(conn ziface.IConnection) // Hook function called on connection stop 该client的连接断开时的Hook函数 onConnStop func(conn ziface.IConnection) // Data packet packer 数据报文封包方式 packet ziface.IDataPack // Asynchronous channel for capturing connection close status 异步捕获连接关闭状态 // exitChan chan struct{} // Message management module 消息管理模块 msgHandler ziface.IMsgHandle // Disassembly and assembly decoder for resolving sticky and broken packages //断粘包解码器 decoder ziface.IDecoder // Heartbeat checker 心跳检测器 hc ziface.IHeartbeatChecker // Use TLS 使用TLS useTLS bool // For websocket connections dialer *websocket.Dialer // Error channel errChan chan error } func NewClient(ip string, port int, opts ...ClientOption) ziface.IClient { c := &Client{ // Default name, can be modified using the WithNameClient Option // (默认名称,可以使用WithNameClient的Option修改) Name: "ZinxClientTcp", Ip: ip, Port: port, msgHandler: newCliMsgHandle(), packet: zpack.Factory().NewPack(ziface.ZinxDataPack), // Default to using Zinx's TLV packet format(默认使用zinx的TLV封包方式) decoder: zdecoder.NewTLVDecoder(), // Default to using Zinx's TLV decoder(默认使用zinx的TLV解码器) version: "tcp", errChan: make(chan error, 1), } // Apply Option settings (应用Option设置) for _, opt := range opts { opt(c) } return c } func NewWsClient(ip string, port int, opts ...ClientOption) ziface.IClient { c := &Client{ // Default name, can be modified using the WithNameClient Option // (默认名称,可以使用WithNameClient的Option修改) Name: "ZinxClientWs", Ip: ip, Port: port, msgHandler: newCliMsgHandle(), packet: zpack.Factory().NewPack(ziface.ZinxDataPack), // Default to using Zinx's TLV packet format(默认使用zinx的TLV封包方式) decoder: zdecoder.NewTLVDecoder(), // Default to using Zinx's TLV decoder(默认使用zinx的TLV解码器) version: "websocket", dialer: &websocket.Dialer{}, errChan: make(chan error, 1), } // Apply Option settings (应用Option设置) for _, opt := range opts { opt(c) } return c } func NewTLSClient(ip string, port int, opts ...ClientOption) ziface.IClient { c, _ := NewClient(ip, port, opts...).(*Client) c.useTLS = true return c } // notify error unblock func (c *Client) notifyErr(err error) { select { case c.errChan <- err: default: } } // Start starts the client, sends requests and establishes a connection. // (重新启动客户端,发送请求且建立连接) func (c *Client) Restart() { //try to stop and wait until client stoped c.Stop() //set started flag c.Lock() if c.started { // already started, just return c.Unlock() return } c.started = true c.ctx, c.cancel = context.WithCancel(context.Background()) c.Add(1) c.Unlock() zlog.Ins().InfoF("[START] Zinx Client dial RemoteAddr: %s:%d\n", c.Ip, c.Port) go func() { defer c.Done() // Create a raw socket and get net.Conn (创建原始Socket,得到net.Conn) var connect ziface.IConnection switch c.version { case "websocket": wsAddr := fmt.Sprintf("ws://%s:%d", c.Ip, c.Port) if c.Url != nil { wsAddr = c.Url.String() } // Create a raw socket and get net.Conn (创建原始Socket,得到net.Conn) wsConn, _, err := c.dialer.DialContext(c.ctx, wsAddr, c.WsHeader) if err != nil { // connection failed zlog.Ins().ErrorF("WsClient connect to server failed, err:%v", err) c.notifyErr(err) return } // Create Connection object connect = newWsClientConn(c, wsConn) default: var conn net.Conn var err error if c.useTLS { // TLS encryption config := &tls.Config{ // Skip certificate verification here because the CA certificate of the certificate issuer is not authenticated // (这里是跳过证书验证,因为证书签发机构的CA证书是不被认证的) InsecureSkipVerify: true, } d := &tls.Dialer{ Config: config, } //conn, err = tls.Dial("tcp", fmt.Sprintf("%v:%v", net.ParseIP(c.Ip), c.Port), config) conn, err = d.DialContext(c.ctx, "tcp", fmt.Sprintf("%v:%v", net.ParseIP(c.Ip), c.Port)) if err != nil { zlog.Ins().ErrorF("tls client connect to server failed, err:%v", err) c.notifyErr(err) return } } else { //conn, err = net.DialTCP("tcp", nil, addr) d := &net.Dialer{} conn, err = d.DialContext(c.ctx, "tcp", fmt.Sprintf("%v:%v", net.ParseIP(c.Ip), c.Port)) if err != nil { // connection failed zlog.Ins().ErrorF("client connect to server failed, err:%v", err) c.notifyErr(err) return } } // Create Connection object connect = newClientConn(c, conn) } // Set connection to the client c.setConn(connect) zlog.Ins().InfoF("[START] Zinx Client LocalAddr: %s, RemoteAddr: %s\n", connect.LocalAddr(), connect.RemoteAddr()) // HeartBeat detection if c.hc != nil { // Bind connection and heartbeat detector after connection is successfully established // (创建连接成功,绑定连接与心跳检测器) c.hc.BindConn(connect) } // Start connection go connect.Start() <-c.ctx.Done() zlog.Ins().InfoF("client exit.") }() } // Start starts the client, sends requests and establishes a connection. // (启动客户端,发送请求且建立连接) func (c *Client) Start() { // Add the decoder to the interceptor list (将解码器添加到拦截器) if c.decoder != nil { c.msgHandler.AddInterceptor(c.decoder) } c.Restart() } // StartHeartBeat starts heartbeat detection with a fixed time interval. // interval: the time interval between each heartbeat message. // (启动心跳检测, interval: 每次发送心跳的时间间隔) func (c *Client) StartHeartBeat(interval time.Duration) { checker := NewHeartbeatChecker(interval) // Add the heartbeat checker's route to the client's message handler. // (添加心跳检测的路由) c.AddRouter(checker.MsgID(), checker.Router()) // Bind the heartbeat checker to the client's connection. // (client绑定心跳检测器) c.hc = checker } // StartHeartBeatWithOption starts heartbeat detection with a custom callback function. // interval: the time interval between each heartbeat message. // option: a HeartBeatOption struct that contains the custom callback function and message // 启动心跳检测(自定义回调) func (c *Client) StartHeartBeatWithOption(interval time.Duration, option *ziface.HeartBeatOption) { // Create a new heartbeat checker with the given interval. checker := NewHeartbeatChecker(interval) // Set the heartbeat checker's callback function and message ID based on the HeartBeatOption struct. if option != nil { checker.SetHeartbeatMsgFunc(option.MakeMsg) checker.SetOnRemoteNotAlive(option.OnRemoteNotAlive) checker.BindRouter(option.HeartBeatMsgID, option.Router) } // Add the heartbeat checker's route to the client's message handler. c.AddRouter(checker.MsgID(), checker.Router()) // Bind the heartbeat checker to the client's connection. c.hc = checker } // 保证重复调用Stop不会导致panic func (c *Client) Stop() { c.Lock() defer c.Unlock() if !c.started { return } c.started = false con := c.Conn() if con != nil { zlog.Ins().InfoF("[STOP] Zinx Client LocalAddr: %s, RemoteAddr: %s\n", con.LocalAddr(), con.RemoteAddr()) con.Stop() } // c.exitChan <- struct{}{} // close(c.exitChan) // close(c.ErrChan) if c.cancel != nil { c.cancel() } c.Wait() } func (c *Client) AddRouter(msgID uint32, router ziface.IRouter) { c.msgHandler.AddRouter(msgID, router) } func (c *Client) Conn() ziface.IConnection { c.connMux.Lock() defer c.connMux.Unlock() return c.conn } func (c *Client) setConn(con ziface.IConnection) { c.connMux.Lock() defer c.connMux.Unlock() c.conn = con } func (c *Client) SetOnConnStart(hookFunc func(ziface.IConnection)) { c.onConnStart = hookFunc } func (c *Client) SetOnConnStop(hookFunc func(ziface.IConnection)) { c.onConnStop = hookFunc } func (c *Client) GetOnConnStart() func(ziface.IConnection) { return c.onConnStart } func (c *Client) GetOnConnStop() func(ziface.IConnection) { return c.onConnStop } func (c *Client) GetPacket() ziface.IDataPack { return c.packet } func (c *Client) SetPacket(packet ziface.IDataPack) { c.packet = packet } func (c *Client) GetMsgHandler() ziface.IMsgHandle { return c.msgHandler } func (c *Client) AddInterceptor(interceptor ziface.IInterceptor) { c.msgHandler.AddInterceptor(interceptor) } func (c *Client) SetDecoder(decoder ziface.IDecoder) { c.decoder = decoder } func (c *Client) GetLengthField() *ziface.LengthField { if c.decoder != nil { return c.decoder.GetLengthField() } return nil } func (c *Client) GetErrChan() <-chan error { return c.errChan } func (c *Client) SetName(name string) { c.Name = name } func (c *Client) GetName() string { return c.Name } func (c *Client) SetUrl(url *url.URL) { c.Url = url } func (c *Client) GetUrl() *url.URL { return c.Url } func (c *Client) SetWsHeader(header http.Header) { c.WsHeader = header } func (c *Client) GetWsHeader() http.Header { return c.WsHeader } ================================================ FILE: znet/connection.go ================================================ package znet import ( "bufio" "context" "encoding/hex" "errors" "net" "strconv" "sync" "sync/atomic" "time" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zinterceptor" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/zpack" "github.com/gorilla/websocket" ) // CallBackFunc defines the callback function type // (定义回调函数类型) type CallBackFunc func() // Connection TCP connection module // Used to handle the read and write business of TCP connections, one Connection corresponds to one connection // (用于处理Tcp连接的读写业务 一个连接对应一个Connection) type Connection struct { // // The socket TCP socket of the current connection(当前连接的socket TCP套接字) conn net.Conn // The buffer writer of the current connection(当前连接的写缓冲) bufWriter *bufio.Writer // The ID of the current connection, also known as SessionID, globally unique, used by server Connection // uint64 range: 0~18,446,744,073,709,551,615 // This is the maximum number of connID theoretically supported by the process // (当前连接的ID 也可以称作为SessionID,ID全局唯一 ,服务端Connection使用 // uint64 取值范围:0 ~ 18,446,744,073,709,551,615 // 这个是理论支持的进程connID的最大数量) connID uint64 // connection id for string // (字符串的连接id) connIdStr string // The workerid responsible for handling the link // 负责处理该连接的workerid workerID uint32 // The message management module that manages MsgID and the corresponding processing method // (消息管理MsgID和对应处理方法的消息管理模块) msgHandler ziface.IMsgHandle // Channel to notify that the connection has exited/stopped // (告知该连接已经退出/停止的channel) ctx context.Context cancel context.CancelFunc // Buffered channel used for message communication between the read and write goroutines // (有缓冲管道,用于读、写两个goroutine之间的消息通信) msgBuffChan chan []byte // Go StartWriter Flag // (开始初始化写协程标志) startWriterFlag int32 // Connection properties // (连接属性) property map[string]interface{} // Lock to protect the current property // (保护当前property的锁) propertyLock sync.Mutex // Which Connection Manager the current connection belongs to // (当前连接是属于哪个Connection Manager的) connManager ziface.IConnManager // Hook function when the current connection is created // (当前连接创建时Hook函数) onConnStart func(conn ziface.IConnection) // Hook function when the current connection is disconnected // (当前连接断开时的Hook函数) onConnStop func(conn ziface.IConnection) // Data packet packaging method // (数据报文封包方式) packet ziface.IDataPack // Last activity time // (最后一次活动时间) lastActivityTime time.Time // Framedecoder for solving fragmentation and packet sticking problems // (断粘包解码器) frameDecoder ziface.IFrameDecoder // Heartbeat checker // (心跳检测器) hc ziface.IHeartbeatChecker // Connection name, default to be the same as the name of the Server/Client that created the connection // (连接名称,默认与创建连接的Server/Client的Name一致) name string // Local address of the current connection // (当前连接的本地地址) localAddr string // Remote address of the current connection // (当前连接的远程地址) remoteAddr string // Close callback closeCallback callbacks // Close callback mutex closeCallbackMutex sync.RWMutex } // newServerConn :for Server, method to create a Server-side connection with Server-specific properties // (创建一个Server服务端特性的连接的方法) func newServerConn(server ziface.IServer, conn net.Conn, connID uint64) ziface.IConnection { // Initialize Conn properties c := &Connection{ conn: conn, bufWriter: bufio.NewWriterSize(conn, 16*1024), connID: connID, connIdStr: strconv.FormatUint(connID, 10), startWriterFlag: 0, msgBuffChan: nil, property: nil, name: server.ServerName(), localAddr: conn.LocalAddr().String(), remoteAddr: conn.RemoteAddr().String(), } lengthField := server.GetLengthField() if lengthField != nil { c.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField) } // Inherited properties from server (从server继承过来的属性) c.packet = server.GetPacket() c.onConnStart = server.GetOnConnStart() c.onConnStop = server.GetOnConnStop() c.msgHandler = server.GetMsgHandler() // Bind the current Connection with the Server's ConnManager // (将当前的Connection与Server的ConnManager绑定) c.connManager = server.GetConnMgr() // Add the newly created Conn to the connection manager // (将新创建的Conn添加到连接管理中) server.GetConnMgr().Add(c) return c } // newServerConn :for Server, method to create a Client-side connection with Client-specific properties // (创建一个Client服务端特性的连接的方法) func newClientConn(client ziface.IClient, conn net.Conn) ziface.IConnection { c := &Connection{ conn: conn, bufWriter: bufio.NewWriterSize(conn, 16*1024), connID: 0, // client ignore connIdStr: "", // client ignore startWriterFlag: 0, msgBuffChan: nil, property: nil, name: client.GetName(), localAddr: conn.LocalAddr().String(), remoteAddr: conn.RemoteAddr().String(), } lengthField := client.GetLengthField() if lengthField != nil { c.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField) } // Inherited properties from server (从client继承过来的属性) c.packet = client.GetPacket() c.onConnStart = client.GetOnConnStart() c.onConnStop = client.GetOnConnStop() c.msgHandler = client.GetMsgHandler() return c } // StartWriter is the goroutine that writes messages to the client // (写消息Goroutine, 用户将数据发送给客户端) func (c *Connection) StartWriter() { zlog.Ins().InfoF("Writer Goroutine is running") ticker := time.NewTicker(10 * time.Millisecond) defer func() { zlog.Ins().InfoF("%s [conn Writer exit!]", c.RemoteAddr().String()) ticker.Stop() c.Flush() }() for { select { case <-ticker.C: err := c.Flush() if err != nil { zlog.Ins().ErrorF("Flush Buff Data error: %v Conn Writer exit", err) return } case data, ok := <-c.msgBuffChan: if ok { if err := c.SendBuf(data); err != nil { zlog.Ins().ErrorF("Send Buff Data error:, %s Conn Writer exit", err) return } } else { zlog.Ins().ErrorF("msgBuffChan is Closed") return } case <-c.ctx.Done(): return } } } // StartReader is a goroutine that reads data from the client // (读消息Goroutine,用于从客户端中读取数据) func (c *Connection) StartReader() { zlog.Ins().InfoF("[Reader Goroutine is running]") defer zlog.Ins().InfoF("%s [conn Reader exit!]", c.RemoteAddr().String()) defer c.Stop() defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("connID=%d, panic err=%v", c.GetConnID(), err) } }() //Reduce buffer allocation times to improve efficiency // add by ray 2023-02-03 buffer := make([]byte, zconf.GlobalObject.IOReadBuffSize) for { select { case <-c.ctx.Done(): return default: // read data from the connection's IO into the memory buffer // (从conn的IO中读取数据到内存缓冲buffer中) n, err := c.conn.Read(buffer) if err != nil { zlog.Ins().ErrorF("read msg head [read datalen=%d], error = %s", n, err) return } zlog.Ins().DebugF("read buffer %s \n", hex.EncodeToString(buffer[0:n])) // If normal data is read from the peer, update the heartbeat detection Active state // (正常读取到对端数据,更新心跳检测Active状态) if n > 0 && c.hc != nil { c.updateActivity() } // Deal with the custom protocol fragmentation problem, added by uuxia 2023-03-21 // (处理自定义协议断粘包问题) if c.frameDecoder != nil { // Decode the 0-n bytes of data read // (为读取到的0-n个字节的数据进行解码) bufArrays := c.frameDecoder.Decode(buffer[0:n]) if bufArrays == nil { continue } for _, bytes := range bufArrays { // zlog.Ins().DebugF("read buffer %s \n", hex.EncodeToString(bytes)) msg := zpack.NewMessage(uint32(len(bytes)), bytes) // Get the current client's Request data // (得到当前客户端请求的Request数据) req := GetRequest(c, msg) c.msgHandler.Execute(req) } } else { msg := zpack.NewMessage(uint32(n), buffer[0:n]) // Get the current client's Request data // (得到当前客户端请求的Request数据) req := GetRequest(c, msg) c.msgHandler.Execute(req) } } } } // Start starts the connection and makes the current connection work. // (启动连接,让当前连接开始工作) func (c *Connection) Start() { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("Connection Start() error: %v", err) } }() c.ctx, c.cancel = context.WithCancel(context.Background()) // Execute the hook method for processing business logic when creating a connection // (按照用户传递进来的创建连接时需要处理的业务,执行钩子方法) c.callOnConnStart() // Start heartbeating detection if c.hc != nil { c.hc.Start() c.updateActivity() } // 占用workerid c.workerID = useWorker(c) // Start the Goroutine for reading data from the client // (开启用户从客户端读取数据流程的Goroutine) go c.StartReader() select { case <-c.ctx.Done(): c.finalizer() // 归还workerid freeWorker(c) return } } // Stop stops the connection and ends the current connection state. // (停止连接,结束当前连接状态) func (c *Connection) Stop() { c.cancel() } func (c *Connection) GetConnection() net.Conn { return c.conn } func (c *Connection) GetWsConn() *websocket.Conn { return nil } // Deprecated: use GetConnection instead func (c *Connection) GetTCPConnection() net.Conn { return c.conn } func (c *Connection) GetConnID() uint64 { return c.connID } func (c *Connection) GetConnIdStr() string { return c.connIdStr } func (c *Connection) GetWorkerID() uint32 { return c.workerID } func (c *Connection) RemoteAddr() net.Addr { return c.conn.RemoteAddr() } func (c *Connection) LocalAddr() net.Addr { return c.conn.LocalAddr() } func (c *Connection) Flush() error { if c.isClosed() == true { return errors.New("connection closed when flush data") } return c.bufWriter.Flush() } func (c *Connection) Send(data []byte) error { if c.isClosed() == true { return errors.New("connection closed when send msg") } _, err := c.conn.Write(data) if err != nil { zlog.Ins().ErrorF("SendMsg err data = %+v, err = %+v", data, err) return err } return nil } func (c *Connection) SendBuf(data []byte) error { if c.isClosed() == true { return errors.New("connection closed when send msg") } _, err := c.bufWriter.Write(data) if err != nil { zlog.Ins().ErrorF("SendMsg err data = %+v, err = %+v", data, err) return err } return nil } func (c *Connection) SendToQueue(data []byte, opts ...ziface.MsgSendOption) error { if c.msgBuffChan == nil && c.setStartWriterFlag() { c.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen) // Start a Goroutine to write data back to the client // This method only reads data from the MsgBuffChan without allocating memory or starting a Goroutine // (开启用于写回客户端数据流程的Goroutine // 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程) go c.StartWriter() } opt := ziface.MsgSendOptionObj{ Timeout: 5 * time.Millisecond, } for _, o := range opts { o(&opt) } idleTimeout := time.NewTimer(opt.Timeout) defer idleTimeout.Stop() if c.isClosed() == true { return errors.New("Connection closed when send buff msg") } if data == nil { zlog.Ins().ErrorF("Pack data is nil") return errors.New("Pack data is nil") } // Send timeout select { case <-c.ctx.Done(): // Close all channels associated with the connection close(c.msgBuffChan) return errors.New("connection closed when send buff msg") case <-idleTimeout.C: return errors.New("send buff msg timeout") case c.msgBuffChan <- data: return nil } } // SendMsg directly sends Message data to the remote TCP client. // (直接将Message数据发送数据给远程的TCP客户端) func (c *Connection) SendMsg(msgID uint32, data []byte) error { if c.isClosed() == true { return errors.New("connection closed when send msg") } // Pack data and send it msg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data)) if err != nil { zlog.Ins().ErrorF("Pack error msg ID = %d", msgID) return errors.New("Pack error msg ") } err = c.Send(msg) if err != nil { zlog.Ins().ErrorF("SendMsg err msg ID = %d, data = %+v, err = %+v", msgID, string(msg), err) return err } return nil } func (c *Connection) SendBuffMsg(msgID uint32, data []byte, opts ...ziface.MsgSendOption) error { msg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data)) if err != nil { zlog.Ins().ErrorF("Pack error msg ID = %d", msgID) return errors.New("Pack error msg ") } return c.SendToQueue(msg, opts...) } func (c *Connection) SetProperty(key string, value interface{}) { c.propertyLock.Lock() defer c.propertyLock.Unlock() if c.property == nil { c.property = make(map[string]interface{}) } c.property[key] = value } func (c *Connection) GetProperty(key string) (interface{}, error) { c.propertyLock.Lock() defer c.propertyLock.Unlock() if value, ok := c.property[key]; ok { return value, nil } return nil, errors.New("no property found") } func (c *Connection) RemoveProperty(key string) { c.propertyLock.Lock() defer c.propertyLock.Unlock() delete(c.property, key) } func (c *Connection) Context() context.Context { return c.ctx } func (c *Connection) finalizer() { // Call the callback function registered by the user when closing the connection if it exists // (如果用户注册了该连接的 关闭回调业务,那么在此刻应该显示调用) c.callOnConnStop() // Stop the heartbeat detector associated with the connection if c.hc != nil { c.hc.Stop() } // Close the socket connection _ = c.conn.Close() // Remove the connection from the connection manager if c.connManager != nil { c.connManager.Remove(c) } go func() { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("Conn finalizer panic: %v", err) } }() c.InvokeCloseCallbacks() }() zlog.Ins().InfoF("Conn Stop()...ConnID = %d", c.connID) } func (c *Connection) callOnConnStart() { if c.onConnStart != nil { zlog.Ins().InfoF("ZINX CallOnConnStart....") c.onConnStart(c) } } func (c *Connection) callOnConnStop() { if c.onConnStop != nil { zlog.Ins().InfoF("ZINX CallOnConnStop....") c.onConnStop(c) } } func (c *Connection) IsAlive() bool { if c.isClosed() { return false } // Check the last activity time of the connection. If it's beyond the heartbeat interval, // then the connection is considered dead. // (检查连接最后一次活动时间,如果超过心跳间隔,则认为连接已经死亡) return time.Now().Sub(c.lastActivityTime) < zconf.GlobalObject.HeartbeatMaxDuration() } func (c *Connection) updateActivity() { c.lastActivityTime = time.Now() } func (c *Connection) SetHeartBeat(checker ziface.IHeartbeatChecker) { c.hc = checker } func (c *Connection) LocalAddrString() string { return c.localAddr } func (c *Connection) RemoteAddrString() string { return c.remoteAddr } func (c *Connection) GetName() string { return c.name } func (c *Connection) GetMsgHandler() ziface.IMsgHandle { return c.msgHandler } func (c *Connection) isClosed() bool { return c.ctx == nil || c.ctx.Err() != nil } func (c *Connection) setStartWriterFlag() bool { return atomic.CompareAndSwapInt32(&c.startWriterFlag, 0, 1) } func (s *Connection) AddCloseCallback(handler, key interface{}, f func()) { if s.isClosed() { return } s.closeCallbackMutex.Lock() defer s.closeCallbackMutex.Unlock() s.closeCallback.Add(handler, key, f) } func (s *Connection) RemoveCloseCallback(handler, key interface{}) { if s.isClosed() { return } s.closeCallbackMutex.Lock() defer s.closeCallbackMutex.Unlock() s.closeCallback.Remove(handler, key) } func (s *Connection) InvokeCloseCallbacks() { s.closeCallbackMutex.RLock() defer s.closeCallbackMutex.RUnlock() s.closeCallback.Invoke() } ================================================ FILE: znet/connmanager.go ================================================ package znet import ( "errors" "strconv" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/zutils" ) type ConnManager struct { connections zutils.ShardLockMaps } func newConnManager() *ConnManager { return &ConnManager{ connections: zutils.NewShardLockMaps(), } } func (connMgr *ConnManager) Add(conn ziface.IConnection) { connMgr.connections.Set(conn.GetConnIdStr(), conn) // 将conn连接添加到ConnManager中 zlog.Ins().DebugF("connection add to ConnManager successfully: conn num = %d", connMgr.Len()) } func (connMgr *ConnManager) Remove(conn ziface.IConnection) { connMgr.connections.Remove(conn.GetConnIdStr()) // 删除连接信息 zlog.Ins().DebugF("connection Remove ConnID=%d successfully: conn num = %d", conn.GetConnID(), connMgr.Len()) } func (connMgr *ConnManager) Get(connID uint64) (ziface.IConnection, error) { strConnId := strconv.FormatUint(connID, 10) if conn, ok := connMgr.connections.Get(strConnId); ok { return conn.(ziface.IConnection), nil } return nil, errors.New("connection not found") } // Get2 It is recommended to use this method to obtain connection instances func (connMgr *ConnManager) Get2(strConnId string) (ziface.IConnection, error) { if conn, ok := connMgr.connections.Get(strConnId); ok { return conn.(ziface.IConnection), nil } return nil, errors.New("connection not found") } func (connMgr *ConnManager) Len() int { length := connMgr.connections.Count() return length } func (connMgr *ConnManager) ClearConn() { // Stop and delete all connection information for item := range connMgr.connections.IterBuffered() { val := item.Val if conn, ok := val.(ziface.IConnection); ok { // stop will eventually trigger the deletion of the connection, // no additional deletion is required conn.Stop() } } zlog.Ins().InfoF("Clear All Connections successfully: conn num = %d", connMgr.Len()) } func (connMgr *ConnManager) GetAllConnID() []uint64 { strConnIdList := connMgr.connections.Keys() ids := make([]uint64, 0, len(strConnIdList)) for _, strId := range strConnIdList { connId, err := strconv.ParseUint(strId, 10, 64) if err == nil { ids = append(ids, connId) } else { zlog.Ins().InfoF("GetAllConnID Id: %d, error: %v", connId, err) } } return ids } func (connMgr *ConnManager) GetAllConnIdStr() []string { return connMgr.connections.Keys() } func (connMgr *ConnManager) Range(cb func(uint64, ziface.IConnection, interface{}) error, args interface{}) (err error) { connMgr.connections.IterCb(func(key string, v interface{}) { conn, _ := v.(ziface.IConnection) connId, _ := strconv.ParseUint(key, 10, 64) err = cb(connId, conn, args) if err != nil { zlog.Ins().InfoF("Range key: %v, v: %v, error: %v", key, v, err) } }) return err } // Range2 It is recommended to use this method to 'Range' func (connMgr *ConnManager) Range2(cb func(string, ziface.IConnection, interface{}) error, args interface{}) (err error) { connMgr.connections.IterCb(func(key string, v interface{}) { conn, _ := v.(ziface.IConnection) err = cb(conn.GetConnIdStr(), conn, args) if err != nil { zlog.Ins().InfoF("Range2 key: %v, v: %v, error: %v", key, v, err) } }) return err } ================================================ FILE: znet/defaultrouterfunc.go ================================================ package znet import ( "bytes" "fmt" "path" "runtime" "strings" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" ) const ( // The number of stack frames to start tracing from // (开始追踪堆栈信息的层数) StackBegin = 3 // The number of stack frames to trace until the end // (追踪到最后的层数) StackEnd = 5 ) // This is used to store some default middlewares that can be used in RouterSlicesMode // (用来存放一些RouterSlicesMode下的路由可用的默认中间件) // RouterRecovery If you use the server obtained from initializing with NewDefaultRouterSlicesServer method, // this function will be included. It is used to catch any panics that occur during request handling // and attempt to record the context information. // (如果使用NewDefaultRouterSlicesServer方法初始化的获得的server将自带这个函数 // 作用是接收业务执行上产生的panic并且尝试记录现场信息) func RouterRecovery(request ziface.IRequest) { defer func() { if err := recover(); err != nil { panicInfo := getInfo(StackBegin) // Record the error zlog.Ins().ErrorF("MsgId:%d Handler panic: info:%s err:%v", request.GetMsgID(), panicInfo, err) //fmt.Printf("MsgId:%d Handler panic: info:%s err:%v", request.GetMsgID(), panicInfo, err) // Should return an error (应该回传一个错误的) //request.GetConnection().SendMsg() } }() request.RouterSlicesNext() } // RouterTime Simply accumulates the time taken by all the routing groups, but not enabled // (简单累计所有路由组的耗时,不启用) func RouterTime(request ziface.IRequest) { now := time.Now() request.RouterSlicesNext() duration := time.Since(now) fmt.Println(duration.String()) } func getInfo(ship int) (infoStr string) { panicInfo := new(bytes.Buffer) // It is also possible to not specify the end point layer as i := ship;; i++ and end the loop with if !ok, // but it will keep going until the bottom layer error information is reached. // (也可以不指定终点层数即i := ship;; i++ 通过if!ok 结束循环,但是会一直追到最底层报错信息) for i := ship; i <= StackEnd; i++ { pc, file, lineNo, ok := runtime.Caller(i) if !ok { break } funcname := runtime.FuncForPC(pc).Name() filename := path.Base(file) funcname = strings.Split(funcname, ".")[1] fmt.Fprintf(panicInfo, "funcname:%s filename:%s LineNo:%d\n", funcname, filename, lineNo) } return panicInfo.String() } ================================================ FILE: znet/heartbeat.go ================================================ package znet import ( "fmt" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" ) type HeartbeatChecker struct { interval time.Duration // Heartbeat detection interval(心跳检测时间间隔) quitChan chan bool // Quit signal(退出信号) makeMsg ziface.HeartBeatMsgFunc //User-defined heartbeat message processing method(用户自定义的心跳检测消息处理方法) onRemoteNotAlive ziface.OnRemoteNotAlive // User-defined method for handling remote connections that are not alive (用户自定义的远程连接不存活时的处理方法) msgID uint32 // Heartbeat message ID(心跳的消息ID) router ziface.IRouter // User-defined heartbeat message business processing router(用户自定义的心跳检测消息业务处理路由) routerSlices []ziface.RouterHandler //(用户自定义的心跳检测消息业务处理新路由) conn ziface.IConnection // Bound connection(绑定的连接) beatFunc ziface.HeartBeatFunc // // User-defined heartbeat sending function(用户自定义心跳发送函数) } /* Default callback routing business for receiving remote heartbeat messages (收到remote心跳消息的默认回调路由业务) */ type HeatBeatDefaultRouter struct { BaseRouter } func (r *HeatBeatDefaultRouter) Handle(req ziface.IRequest) { zlog.Ins().DebugF("Recv Heartbeat from %s, MsgID = %+v, Data = %s", req.GetConnection().RemoteAddr(), req.GetMsgID(), string(req.GetData())) } func HeatBeatDefaultHandle(req ziface.IRequest) { zlog.Ins().DebugF("Recv Heartbeat from %s, MsgID = %+v, Data = %s", req.GetConnection().RemoteAddr(), req.GetMsgID(), string(req.GetData())) } func makeDefaultMsg(conn ziface.IConnection) []byte { msg := fmt.Sprintf("heartbeat [%s->%s]", conn.LocalAddr(), conn.RemoteAddr()) return []byte(msg) } func notAliveDefaultFunc(conn ziface.IConnection) { zlog.Ins().InfoF("Remote connection %s is not alive, stop it", conn.RemoteAddr()) conn.Stop() } func NewHeartbeatChecker(interval time.Duration) ziface.IHeartbeatChecker { heartbeat := &HeartbeatChecker{ interval: interval, quitChan: make(chan bool), // Use default heartbeat message generation function and remote connection not alive handling method // (均使用默认的心跳消息生成函数和远程连接不存活时的处理方法) makeMsg: makeDefaultMsg, onRemoteNotAlive: notAliveDefaultFunc, msgID: ziface.HeartBeatDefaultMsgID, router: &HeatBeatDefaultRouter{}, routerSlices: []ziface.RouterHandler{HeatBeatDefaultHandle}, beatFunc: nil, } return heartbeat } func (h *HeartbeatChecker) SetOnRemoteNotAlive(f ziface.OnRemoteNotAlive) { if f != nil { h.onRemoteNotAlive = f } } func (h *HeartbeatChecker) SetHeartbeatMsgFunc(f ziface.HeartBeatMsgFunc) { if f != nil { h.makeMsg = f } } func (h *HeartbeatChecker) SetHeartbeatFunc(beatFunc ziface.HeartBeatFunc) { if beatFunc != nil { h.beatFunc = beatFunc } } func (h *HeartbeatChecker) BindRouter(msgID uint32, router ziface.IRouter) { if router != nil && msgID != ziface.HeartBeatDefaultMsgID { h.msgID = msgID h.router = router } } func (h *HeartbeatChecker) BindRouterSlices(msgID uint32, handlers ...ziface.RouterHandler) { if len(handlers) > 0 && msgID != ziface.HeartBeatDefaultMsgID { h.msgID = msgID h.routerSlices = append(h.routerSlices, handlers...) } } func (h *HeartbeatChecker) start() { ticker := time.NewTicker(h.interval) for { select { case <-ticker.C: h.check() case <-h.quitChan: ticker.Stop() return } } } func (h *HeartbeatChecker) Start() { go h.start() } func (h *HeartbeatChecker) Stop() { zlog.Ins().InfoF("heartbeat checker stop, connID=%+v", h.conn.GetConnID()) h.quitChan <- true } func (h *HeartbeatChecker) SendHeartBeatMsg() error { msg := h.makeMsg(h.conn) err := h.conn.SendMsg(h.msgID, msg) if err != nil { zlog.Ins().ErrorF("send heartbeat msg error: %v, msgId=%+v msg=%+v", err, h.msgID, msg) return err } return nil } func (h *HeartbeatChecker) check() (err error) { if h.conn == nil { return nil } if !h.conn.IsAlive() { h.onRemoteNotAlive(h.conn) } else { if h.beatFunc != nil { err = h.beatFunc(h.conn) } else { err = h.SendHeartBeatMsg() } } return err } func (h *HeartbeatChecker) BindConn(conn ziface.IConnection) { h.conn = conn conn.SetHeartBeat(h) } // Clone clones to a specified connection // (克隆到一个指定的连接上) func (h *HeartbeatChecker) Clone() ziface.IHeartbeatChecker { heartbeat := &HeartbeatChecker{ interval: h.interval, quitChan: make(chan bool), beatFunc: h.beatFunc, makeMsg: h.makeMsg, onRemoteNotAlive: h.onRemoteNotAlive, msgID: h.msgID, router: h.router, conn: nil, // The bound connection needs to be reassigned } // deep copy routerSlices heartbeat.routerSlices = append(heartbeat.routerSlices, h.routerSlices...) return heartbeat } func (h *HeartbeatChecker) MsgID() uint32 { return h.msgID } func (h *HeartbeatChecker) Router() ziface.IRouter { return h.router } func (h *HeartbeatChecker) RouterSlices() []ziface.RouterHandler { return h.routerSlices } ================================================ FILE: znet/kcp_connection.go ================================================ package znet import ( "context" "encoding/hex" "errors" "net" "strconv" "sync" "sync/atomic" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/zinterceptor" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/zpack" "github.com/gorilla/websocket" "github.com/xtaci/kcp-go" ) // Connection KCP connection module // Used to handle the read and write business of KCP connections, one Connection corresponds to one connection // (用于处理KCP连接的读写业务 一个连接对应一个Connection) type KcpConnection struct { // The socket KCP socket of the current connection(当前连接的socket KCP套接字) conn *kcp.UDPSession // The ID of the current connection, also known as SessionID, globally unique, used by server Connection // uint64 range: 0~18,446,744,073,709,551,615 // This is the maximum number of connID theoretically supported by the process // (当前连接的ID 也可以称作为SessionID,ID全局唯一 ,服务端Connection使用 // uint64 取值范围:0 ~ 18,446,744,073,709,551,615 // 这个是理论支持的进程connID的最大数量) connID uint64 // connection id for string // (字符串的连接id) connIdStr string // The workerid responsible for handling the link // 负责处理该连接的workerid workerID uint32 // The message management module that manages MsgID and the corresponding processing method // (消息管理MsgID和对应处理方法的消息管理模块) msgHandler ziface.IMsgHandle // Channel to notify that the connection has exited/stopped // (告知该连接已经退出/停止的channel) ctx context.Context cancel context.CancelFunc // Buffered channel used for message communication between the read and write goroutines // (有缓冲管道,用于读、写两个goroutine之间的消息通信) msgBuffChan chan []byte // Lock for user message reception and transmission // (用户收发消息的Lock) msgLock sync.RWMutex // Connection properties // (连接属性) property map[string]interface{} // Lock to protect the current property // (保护当前property的锁) propertyLock sync.Mutex // The current connection's close state // (当前连接的关闭状态) closed int32 // Which Connection Manager the current connection belongs to // (当前连接是属于哪个Connection Manager的) connManager ziface.IConnManager // Hook function when the current connection is created // (当前连接创建时Hook函数) onConnStart func(conn ziface.IConnection) // Hook function when the current connection is disconnected // (当前连接断开时的Hook函数) onConnStop func(conn ziface.IConnection) // Data packet packaging method // (数据报文封包方式) packet ziface.IDataPack // Last activity time // (最后一次活动时间) lastActivityTime time.Time // Framedecoder for solving fragmentation and packet sticking problems // (断粘包解码器) frameDecoder ziface.IFrameDecoder // Heartbeat checker // (心跳检测器) hc ziface.IHeartbeatChecker // Connection name, default to be the same as the name of the Server/Client that created the connection // (连接名称,默认与创建连接的Server/Client的Name一致) name string // Local address of the current connection // (当前连接的本地地址) localAddr string // Remote address of the current connection // (当前连接的远程地址) remoteAddr string // Close callback closeCallback callbacks // Close callback mutex closeCallbackMutex sync.RWMutex } // newKcpServerConn :for Server, method to create a Server-side connection with Server-specific properties // (创建一个Server服务端特性的连接的方法) func newKcpServerConn(server ziface.IServer, conn *kcp.UDPSession, connID uint64) ziface.IConnection { // Initialize Conn properties c := &KcpConnection{ conn: conn, connID: connID, connIdStr: strconv.FormatUint(connID, 10), msgBuffChan: nil, property: nil, name: server.ServerName(), localAddr: conn.LocalAddr().String(), remoteAddr: conn.RemoteAddr().String(), } lengthField := server.GetLengthField() if lengthField != nil { c.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField) } // Inherited properties from server (从server继承过来的属性) c.packet = server.GetPacket() c.onConnStart = server.GetOnConnStart() c.onConnStop = server.GetOnConnStop() c.msgHandler = server.GetMsgHandler() // Bind the current Connection with the Server's ConnManager // (将当前的Connection与Server的ConnManager绑定) c.connManager = server.GetConnMgr() // Add the newly created Conn to the connection manager // (将新创建的Conn添加到连接管理中) server.GetConnMgr().Add(c) return c } // newKcpServerConn :for Server, method to create a Client-side connection with Client-specific properties // (创建一个Client服务端特性的连接的方法) func newKcpClientConn(client ziface.IClient, conn *kcp.UDPSession) ziface.IConnection { c := &KcpConnection{ conn: conn, connID: 0, // client ignore connIdStr: "", // client ignore msgBuffChan: nil, property: nil, name: client.GetName(), localAddr: conn.LocalAddr().String(), remoteAddr: conn.RemoteAddr().String(), } lengthField := client.GetLengthField() if lengthField != nil { c.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField) } // Inherited properties from server (从client继承过来的属性) c.packet = client.GetPacket() c.onConnStart = client.GetOnConnStart() c.onConnStop = client.GetOnConnStop() c.msgHandler = client.GetMsgHandler() return c } // StartWriter is the goroutine that writes messages to the client // (写消息Goroutine, 用户将数据发送给客户端) func (c *KcpConnection) StartWriter() { zlog.Ins().DebugF("Writer Goroutine is running") defer zlog.Ins().DebugF("%s [conn Writer exit!]", c.RemoteAddr().String()) for { select { case data, ok := <-c.msgBuffChan: if ok { if err := c.Send(data); err != nil { zlog.Ins().ErrorF("Send Buff Data error:, %s Conn Writer exit", err) break } } else { zlog.Ins().ErrorF("msgBuffChan is Closed") break } case <-c.ctx.Done(): return } } } // StartReader is a goroutine that reads data from the client // (读消息Goroutine,用于从客户端中读取数据) func (c *KcpConnection) StartReader() { zlog.Ins().DebugF("[Reader Goroutine is running]") defer zlog.Ins().DebugF("%s [conn Reader exit!]", c.RemoteAddr().String()) defer c.Stop() defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("connID=%d, panic err=%v", c.GetConnID(), err) } }() for { select { case <-c.ctx.Done(): return default: // add by uuxia 2023-02-03 buffer := make([]byte, zconf.GlobalObject.IOReadBuffSize) // read data from the connection's IO into the memory buffer // (从conn的IO中读取数据到内存缓冲buffer中) n, err := c.conn.Read(buffer) if err != nil { zlog.Ins().ErrorF("read msg head [read datalen=%d], error = %s", n, err) return } zlog.Ins().DebugF("read buffer %s \n", hex.EncodeToString(buffer[0:n])) // If normal data is read from the peer, update the heartbeat detection Active state // (正常读取到对端数据,更新心跳检测Active状态) if n > 0 && c.hc != nil { c.updateActivity() } // Deal with the custom protocol fragmentation problem, added by uuxia 2023-03-21 // (处理自定义协议断粘包问题) if c.frameDecoder != nil { // Decode the 0-n bytes of data read // (为读取到的0-n个字节的数据进行解码) bufArrays := c.frameDecoder.Decode(buffer[0:n]) if bufArrays == nil { continue } for _, bytes := range bufArrays { // zlog.Ins().DebugF("read buffer %s \n", hex.EncodeToString(bytes)) msg := zpack.NewMessage(uint32(len(bytes)), bytes) // Get the current client's Request data // (得到当前客户端请求的Request数据) req := GetRequest(c, msg) c.msgHandler.Execute(req) } } else { msg := zpack.NewMessage(uint32(n), buffer[0:n]) // Get the current client's Request data // (得到当前客户端请求的Request数据) req := GetRequest(c, msg) c.msgHandler.Execute(req) } } } } // Start starts the connection and makes the current connection work. // (启动连接,让当前连接开始工作) func (c *KcpConnection) Start() { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("Connection Start() error: %v", err) } }() c.ctx, c.cancel = context.WithCancel(context.Background()) // Execute the hook method for processing business logic when creating a connection // (按照用户传递进来的创建连接时需要处理的业务,执行钩子方法) c.callOnConnStart() // Start heartbeating detection if c.hc != nil { c.hc.Start() c.updateActivity() } // 占用workerid c.workerID = useWorker(c) // Start the Goroutine for reading data from the client // (开启用户从客户端读取数据流程的Goroutine) go c.StartReader() select { case <-c.ctx.Done(): c.finalizer() // 归还workerid freeWorker(c) return } } // Stop stops the connection and ends the current connection state. // (停止连接,结束当前连接状态) func (c *KcpConnection) Stop() { c.cancel() } func (c *KcpConnection) GetConnection() net.Conn { return c.conn } func (c *KcpConnection) GetWsConn() *websocket.Conn { return nil } // Deprecated: use GetConnection instead func (c *KcpConnection) GetTCPConnection() net.Conn { return c.conn } func (c *KcpConnection) GetConnID() uint64 { return c.connID } func (c *KcpConnection) GetConnIdStr() string { return c.connIdStr } func (c *KcpConnection) GetWorkerID() uint32 { return c.workerID } func (c *KcpConnection) RemoteAddr() net.Addr { return c.conn.RemoteAddr() } func (c *KcpConnection) LocalAddr() net.Addr { return c.conn.LocalAddr() } func (c *KcpConnection) Send(data []byte) error { c.msgLock.RLock() defer c.msgLock.RUnlock() if c.isClosed() { return errors.New("connection closed when send msg") } _, err := c.conn.Write(data) if err != nil { zlog.Ins().ErrorF("SendMsg err data = %+v, err = %+v", data, err) return err } return nil } func (c *KcpConnection) SendToQueue(data []byte, opts ...ziface.MsgSendOption) error { c.msgLock.RLock() defer c.msgLock.RUnlock() if c.msgBuffChan == nil { c.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen) // Start a Goroutine to write data back to the client // This method only reads data from the MsgBuffChan without allocating memory or starting a Goroutine // (开启用于写回客户端数据流程的Goroutine // 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程) go c.StartWriter() } opt := ziface.MsgSendOptionObj{ Timeout: 5 * time.Millisecond, } for _, o := range opts { o(&opt) } idleTimeout := time.NewTimer(opt.Timeout) defer idleTimeout.Stop() if c.isClosed() { return errors.New("Connection closed when send buff msg") } if data == nil { zlog.Ins().ErrorF("Pack data is nil") return errors.New("Pack data is nil") } // Send timeout select { case <-idleTimeout.C: return errors.New("send buff msg timeout") case c.msgBuffChan <- data: return nil } } // SendMsg directly sends Message data to the remote KCP client. // (直接将Message数据发送数据给远程的KCP客户端) func (c *KcpConnection) SendMsg(msgID uint32, data []byte) error { if c.isClosed() { return errors.New("connection closed when send msg") } // Pack data and send it msg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data)) if err != nil { zlog.Ins().ErrorF("Pack error msg ID = %d", msgID) return errors.New("Pack error msg ") } err = c.Send(msg) if err != nil { zlog.Ins().ErrorF("SendMsg err msg ID = %d, data = %+v, err = %+v", msgID, string(msg), err) return err } return nil } func (c *KcpConnection) SendBuffMsg(msgID uint32, data []byte, opts ...ziface.MsgSendOption) error { if c.isClosed() { return errors.New("connection closed when send buff msg") } if c.msgBuffChan == nil { c.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen) // Start a Goroutine to write data back to the client // This method only reads data from the MsgBuffChan without allocating memory or starting a Goroutine // (开启用于写回客户端数据流程的Goroutine // 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程) go c.StartWriter() } opt := ziface.MsgSendOptionObj{ Timeout: 5 * time.Millisecond, } for _, o := range opts { o(&opt) } idleTimeout := time.NewTimer(opt.Timeout) defer idleTimeout.Stop() msg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data)) if err != nil { zlog.Ins().ErrorF("Pack error msg ID = %d", msgID) return errors.New("Pack error msg ") } // send timeout select { case <-idleTimeout.C: return errors.New("send buff msg timeout") case c.msgBuffChan <- msg: return nil } } func (c *KcpConnection) SetProperty(key string, value interface{}) { c.propertyLock.Lock() defer c.propertyLock.Unlock() if c.property == nil { c.property = make(map[string]interface{}) } c.property[key] = value } func (c *KcpConnection) GetProperty(key string) (interface{}, error) { c.propertyLock.Lock() defer c.propertyLock.Unlock() if value, ok := c.property[key]; ok { return value, nil } return nil, errors.New("no property found") } func (c *KcpConnection) RemoveProperty(key string) { c.propertyLock.Lock() defer c.propertyLock.Unlock() delete(c.property, key) } func (c *KcpConnection) Context() context.Context { return c.ctx } func (c *KcpConnection) finalizer() { // If the connection has already been closed if c.isClosed() == true { return } //set closed if !c.setClose() { return } // Call the callback function registered by the user when closing the connection if it exists //(如果用户注册了该连接的 关闭回调业务,那么在此刻应该显示调用) c.callOnConnStop() c.msgLock.Lock() defer c.msgLock.Unlock() // Stop the heartbeat detector associated with the connection if c.hc != nil { c.hc.Stop() } // Close the socket connection _ = c.conn.Close() // Remove the connection from the connection manager if c.connManager != nil { c.connManager.Remove(c) } // Close all channels associated with the connection if c.msgBuffChan != nil { close(c.msgBuffChan) } go func() { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("Conn finalizer panic: %v", err) } }() c.InvokeCloseCallbacks() }() zlog.Ins().DebugF("Conn Stop()...ConnID = %d", c.connID) } func (c *KcpConnection) callOnConnStart() { if c.onConnStart != nil { zlog.Ins().DebugF("ZINX CallOnConnStart....") c.onConnStart(c) } } func (c *KcpConnection) callOnConnStop() { if c.onConnStop != nil { zlog.Ins().DebugF("ZINX CallOnConnStop....") c.onConnStop(c) } } func (c *KcpConnection) IsAlive() bool { if c.isClosed() { return false } // Check the last activity time of the connection. If it's beyond the heartbeat interval, // then the connection is considered dead. // (检查连接最后一次活动时间,如果超过心跳间隔,则认为连接已经死亡) return time.Now().Sub(c.lastActivityTime) < zconf.GlobalObject.HeartbeatMaxDuration() } func (c *KcpConnection) updateActivity() { c.lastActivityTime = time.Now() } func (c *KcpConnection) SetHeartBeat(checker ziface.IHeartbeatChecker) { c.hc = checker } func (c *KcpConnection) LocalAddrString() string { return c.localAddr } func (c *KcpConnection) RemoteAddrString() string { return c.remoteAddr } func (c *KcpConnection) GetName() string { return c.name } func (c *KcpConnection) GetMsgHandler() ziface.IMsgHandle { return c.msgHandler } func (c *KcpConnection) isClosed() bool { return atomic.LoadInt32(&c.closed) != 0 } func (c *KcpConnection) setClose() bool { return atomic.CompareAndSwapInt32(&c.closed, 0, 1) } func (s *KcpConnection) AddCloseCallback(handler, key interface{}, f func()) { if s.isClosed() { return } s.closeCallbackMutex.Lock() defer s.closeCallbackMutex.Unlock() s.closeCallback.Add(handler, key, f) } func (s *KcpConnection) RemoveCloseCallback(handler, key interface{}) { if s.isClosed() { return } s.closeCallbackMutex.Lock() defer s.closeCallbackMutex.Unlock() s.closeCallback.Remove(handler, key) } // invokeCloseCallbacks 触发 close callback, 在独立协程完成 func (s *KcpConnection) InvokeCloseCallbacks() { s.closeCallbackMutex.RLock() defer s.closeCallbackMutex.RUnlock() s.closeCallback.Invoke() } // Implement other KCP specific methods here... // ... // ... // For example, to create a new KCP connection: // func newKCPConn(client ziface.IClient, conn net.Conn) ziface.IConnection { // c := &Connection{ // conn: conn, // connID: 0, // client ignore // isClosed: false, // msgBuffChan: nil, // property: nil, // name: client.GetName(), // localAddr: conn.LocalAddr().String(), // remoteAddr: conn.RemoteAddr().String(), // // ... Initialize other KCP specific properties ... // } // // Inherited properties from client // c.packet = client.GetPacket() // c.onConnStart = client.GetOnConnStart() // c.onConnStop = client.GetOnConnStop() // c.msgHandler = client.GetMsgHandler() // return c // } // And similarly for the Server-side KCP connection: // func newServerKCPConn(server ziface.IServer, conn net.Conn, connID uint64) ziface.IConnection { // c := &Connection{ // conn: conn, // connID: connID, // isClosed: false, // msgBuffChan: nil, // property: nil, // name: server.ServerName(), // localAddr: conn.LocalAddr().String(), // remoteAddr: conn.RemoteAddr().String(), // // ... Initialize other KCP specific properties ... // } // // Inherited properties from server // c.packet = server.GetPacket() // c.onConnStart = server.GetOnConnStart() // c.onConnStop = server.GetOnConnStop() // c.msgHandler = server.GetMsgHandler() // // ... Additional initialization specific to KCP ... // return c // } ================================================ FILE: znet/msghandler.go ================================================ package znet import ( "encoding/hex" "fmt" "sync" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" ) const ( // If the Worker goroutine pool is not started, a virtual WorkerID is assigned to the MsgHandler, which is 0, for metric counting // After starting the Worker goroutine pool, the ID of each worker is 0,1,2,3... // (如果不启动Worker协程池,则会给MsgHandler分配一个虚拟的WorkerID,这个workerID为0, 便于指标统计 // 启动了Worker协程池后,每个worker的ID为0,1,2,3...) WorkerIDWithoutWorkerPool int = 0 ) // MsgHandle is the module for handling message processing callbacks // (对消息的处理回调模块) type MsgHandle struct { // A map property that stores the processing methods for each MsgID // (存放每个MsgID 所对应的处理方法的map属性) Apis map[uint32]ziface.IRouter // The number of worker goroutines in the business work Worker pool // (业务工作Worker池的数量) WorkerPoolSize uint32 // A collection of idle workers, used for zconf.WorkerModeBind // 空闲worker集合,用于zconf.WorkerModeBind freeWorkers map[uint32]struct{} freeWorkerMu sync.Mutex // A message queue for workers to take tasks // (Worker负责取任务的消息队列) TaskQueue []chan ziface.IRequest // A collection of extra workers, used for zconf.WorkerModeDynamicBind // (池里的工作线程不够用的时候, 可临时额外分配workerID集合, 用于zconf.WorkerModeDynamicBind) extraFreeWorkers map[uint32]struct{} extraFreeWorkerMu sync.Mutex // Chain builder for the responsibility chain // (责任链构造器) builder *chainBuilder RouterSlices *RouterSlices } // newMsgHandle creates MsgHandle // zinxRole: IServer func newMsgHandle() *MsgHandle { var freeWorkers map[uint32]struct{} var extraFreeWorkers map[uint32]struct{} if zconf.GlobalObject.WorkerMode == zconf.WorkerModeBind { // Assign a workder to each link, avoid interactions when multiple links are processed by the same worker // MaxWorkerTaskLen can also be reduced, for example, 50 // 为每个连接分配一个workder,避免同一worker处理多个连接时的互相影响 // 同时可以减小MaxWorkerTaskLen,比如50,因为每个worker的负担减轻了 zconf.GlobalObject.WorkerPoolSize = uint32(zconf.GlobalObject.MaxConn) freeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.WorkerPoolSize) for i := uint32(0); i < zconf.GlobalObject.WorkerPoolSize; i++ { freeWorkers[i] = struct{}{} } } TaskQueueLen := zconf.GlobalObject.WorkerPoolSize if zconf.GlobalObject.WorkerMode == zconf.WorkerModeDynamicBind { zlog.Ins().DebugF("WorkerMode = %s", zconf.WorkerModeDynamicBind) freeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.WorkerPoolSize) for i := uint32(0); i < zconf.GlobalObject.WorkerPoolSize; i++ { freeWorkers[i] = struct{}{} } extraFreeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.MaxConn-int(zconf.GlobalObject.WorkerPoolSize)) for i := zconf.GlobalObject.WorkerPoolSize; i < uint32(zconf.GlobalObject.MaxConn); i++ { extraFreeWorkers[i] = struct{}{} } TaskQueueLen = uint32(zconf.GlobalObject.MaxConn) } handle := &MsgHandle{ Apis: make(map[uint32]ziface.IRouter), RouterSlices: NewRouterSlices(), freeWorkers: freeWorkers, builder: newChainBuilder(), // 可额外临时分配的workerID集合 extraFreeWorkers: extraFreeWorkers, } // server handle.WorkerPoolSize = zconf.GlobalObject.WorkerPoolSize // One worker corresponds to one queue (一个worker对应一个queue) handle.TaskQueue = make([]chan ziface.IRequest, TaskQueueLen) // 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 // (此处必须把 msghandler 添加到责任链中,并且是责任链最后一环,在msghandler中进行解码后由router做数据分发) handle.builder.Tail(handle) return handle } // newCliMsgHandle creates MsgHandle // zinxRole: IClient func newCliMsgHandle() *MsgHandle { var freeWorkers map[uint32]struct{} var extraFreeWorkers map[uint32]struct{} if zconf.GlobalObject.WorkerMode == zconf.WorkerModeBind { // Assign a workder to each link, avoid interactions when multiple links are processed by the same worker // MaxWorkerTaskLen can also be reduced, for example, 50 // 为每个连接分配一个workder,避免同一worker处理多个连接时的互相影响 // 同时可以减小MaxWorkerTaskLen,比如50,因为每个worker的负担减轻了 zconf.GlobalObject.WorkerPoolSize = uint32(zconf.GlobalObject.MaxConn) freeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.WorkerPoolSize) for i := uint32(0); i < zconf.GlobalObject.WorkerPoolSize; i++ { freeWorkers[i] = struct{}{} } } TaskQueueLen := zconf.GlobalObject.WorkerPoolSize if zconf.GlobalObject.WorkerMode == zconf.WorkerModeDynamicBind { zlog.Ins().DebugF("WorkerMode = %s", zconf.WorkerModeDynamicBind) freeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.WorkerPoolSize) for i := uint32(0); i < zconf.GlobalObject.WorkerPoolSize; i++ { freeWorkers[i] = struct{}{} } extraFreeWorkers = make(map[uint32]struct{}, zconf.GlobalObject.MaxConn-int(zconf.GlobalObject.WorkerPoolSize)) for i := zconf.GlobalObject.WorkerPoolSize; i < uint32(zconf.GlobalObject.MaxConn); i++ { extraFreeWorkers[i] = struct{}{} } TaskQueueLen = uint32(zconf.GlobalObject.MaxConn) } handle := &MsgHandle{ Apis: make(map[uint32]ziface.IRouter), RouterSlices: NewRouterSlices(), freeWorkers: freeWorkers, builder: newChainBuilder(), // 可额外临时分配的workerID集合 extraFreeWorkers: extraFreeWorkers, } // client: Set worker pool size to 0 to turn off the worker pool in the client (客户端将协程池关闭) handle.WorkerPoolSize = 0 // One worker corresponds to one queue (一个worker对应一个queue) handle.TaskQueue = make([]chan ziface.IRequest, TaskQueueLen) // 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 // (此处必须把 msghandler 添加到责任链中,并且是责任链最后一环,在msghandler中进行解码后由router做数据分发) handle.builder.Tail(handle) return handle } // Use worker ID // 占用workerID func useWorker(conn ziface.IConnection) uint32 { var workerId uint32 mh, _ := conn.GetMsgHandler().(*MsgHandle) if mh == nil { zlog.Ins().ErrorF("useWorker failed, mh is nil") return 0 } if zconf.GlobalObject.WorkerMode == zconf.WorkerModeBind { mh.freeWorkerMu.Lock() defer mh.freeWorkerMu.Unlock() for k := range mh.freeWorkers { delete(mh.freeWorkers, k) return k } } if zconf.GlobalObject.WorkerMode == zconf.WorkerModeDynamicBind { mh.freeWorkerMu.Lock() // try to get workerID from workerPool first // 首先尝试从工作线程池里获取一个空闲的workerID for workerID := range mh.freeWorkers { delete(mh.freeWorkers, workerID) mh.freeWorkerMu.Unlock() return workerID } mh.freeWorkerMu.Unlock() // 工作池的worker用完了,临时从extraFreeWorkers取一个额外的workerID, 并相应启动一个临时的worker mh.extraFreeWorkerMu.Lock() defer mh.extraFreeWorkerMu.Unlock() for workerID := range mh.extraFreeWorkers { zlog.Ins().DebugF("start extra worker, workerID=%d", workerID) mh.TaskQueue[workerID] = make(chan ziface.IRequest, zconf.GlobalObject.MaxWorkerTaskLen) go mh.StartOneWorker(int(workerID), mh.TaskQueue[workerID]) return workerID } } //Compatible with the situation where the client has no worker, and solve the situation divide 0 //(兼容client没有worker情况,解决除0的情况) if mh.WorkerPoolSize == 0 { workerId = 0 } else { // Assign the worker responsible for processing the current connection based on the ConnID // Using a round-robin average allocation rule to get the workerID that needs to process this connection // (根据ConnID来分配当前的连接应该由哪个worker负责处理 // 轮询的平均分配法则 // 得到需要处理此条连接的workerID) workerId = uint32(conn.GetConnID() % uint64(mh.WorkerPoolSize)) } return workerId } // Free worker ID // 释放workerid func freeWorker(conn ziface.IConnection) { mh, _ := conn.GetMsgHandler().(*MsgHandle) if mh == nil { zlog.Ins().ErrorF("useWorker failed, mh is nil") return } if zconf.GlobalObject.WorkerMode == zconf.WorkerModeBind { mh.freeWorkerMu.Lock() defer mh.freeWorkerMu.Unlock() mh.freeWorkers[conn.GetWorkerID()] = struct{}{} } if zconf.GlobalObject.WorkerMode == zconf.WorkerModeDynamicBind { workerID := conn.GetWorkerID() if workerID < mh.WorkerPoolSize { // 说明这个是工作线程池里的workerID,回收这个workerID, workerID对应的worker不需要销毁 mh.freeWorkerMu.Lock() mh.freeWorkers[workerID] = struct{}{} mh.freeWorkerMu.Unlock() } else { // 说明这个worker是一个临时的worker,需要销毁这个worker mh.StopOneWorker(int(workerID)) // 回收workerID, 放回额外workerID池里 mh.extraFreeWorkerMu.Lock() mh.extraFreeWorkers[workerID] = struct{}{} mh.extraFreeWorkerMu.Unlock() } } } // Data processing interceptor that is necessary by default in Zinx // (Zinx默认必经的数据处理拦截器) func (mh *MsgHandle) Intercept(chain ziface.IChain) ziface.IcResp { request := chain.Request() if request != nil { switch request.(type) { case ziface.IRequest: iRequest := request.(ziface.IRequest) if mh.WorkerPoolSize > 0 { // If the worker pool mechanism has been started, hand over the message to the worker for processing // (已经启动工作池机制,将消息交给Worker处理) mh.SendMsgToTaskQueue(iRequest) } else { // Execute the corresponding Handle method from the bound message and its corresponding processing method // (从绑定好的消息和对应的处理方法中执行对应的Handle方法) if !zconf.GlobalObject.RouterSlicesMode { go mh.doMsgHandler(iRequest, WorkerIDWithoutWorkerPool) } else if zconf.GlobalObject.RouterSlicesMode { go mh.doMsgHandlerSlices(iRequest, WorkerIDWithoutWorkerPool) } } } } return chain.Proceed(chain.Request()) } // SetHeadInterceptor sets the head interceptor of the responsibility chain, which is the first interceptor to be executed // (SetHeadInterceptor 设置责任链的头拦截器,也就是第一个要执行的拦截器) // will replace the default head interceptor func (mh *MsgHandle) SetHeadInterceptor(interceptor ziface.IInterceptor) { if mh.builder != nil { mh.builder.Head(interceptor) } } func (mh *MsgHandle) AddInterceptor(interceptor ziface.IInterceptor) { if mh.builder != nil { mh.builder.AddInterceptor(interceptor) } } // SendMsgToTaskQueue sends the message to the TaskQueue for processing by the worker // (将消息交给TaskQueue,由worker进行处理) func (mh *MsgHandle) SendMsgToTaskQueue(request ziface.IRequest) { workerID := request.GetConnection().GetWorkerID() // zlog.Ins().DebugF("Add ConnID=%d request msgID=%d to workerID=%d", request.GetConnection().GetConnID(), request.GetMsgID(), workerID) // Send the request message to the task queue mh.TaskQueue[workerID] <- request zlog.Ins().DebugF("SendMsgToTaskQueue-->%s", hex.EncodeToString(request.GetData())) } // doFuncHandler handles functional requests (执行函数式请求) func (mh *MsgHandle) doFuncHandler(request ziface.IFuncRequest, workerID int) { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("workerID: %d doFuncRequest panic: %v", workerID, err) } }() // Execute the functional request (执行函数式请求) request.CallFunc() } // doMsgHandler immediately handles messages in a non-blocking manner // (立即以非阻塞方式处理消息) func (mh *MsgHandle) doMsgHandler(request ziface.IRequest, workerID int) { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("workerID: %d doMsgHandler panic: %v", workerID, err) } }() msgId := request.GetMsgID() handler, ok := mh.Apis[msgId] if !ok { zlog.Ins().ErrorF("api msgID = %d is not FOUND!", request.GetMsgID()) return } // Bind the Request request to the corresponding Router relationship // (Request请求绑定Router对应关系) request.BindRouter(handler) // Execute the corresponding processing method request.Call() // 执行完成后回收 Request 对象回对象池 PutRequest(request) } func (mh *MsgHandle) Execute(request ziface.IRequest) { // Pass the message to the responsibility chain to handle it through interceptors layer by layer and pass it on layer by layer. // (将消息丢到责任链,通过责任链里拦截器层层处理层层传递) mh.builder.Execute(request) } // AddRouter adds specific processing logic for messages // (为消息添加具体的处理逻辑) func (mh *MsgHandle) AddRouter(msgID uint32, router ziface.IRouter) { // 1. Check whether the current API processing method bound to the msgID already exists // (判断当前msg绑定的API处理方法是否已经存在) if _, ok := mh.Apis[msgID]; ok { msgErr := fmt.Sprintf("repeated api , msgID = %+v\n", msgID) panic(msgErr) } // 2. Add the binding relationship between msg and API // (添加msg与api的绑定关系) mh.Apis[msgID] = router zlog.Ins().InfoF("Add Router msgID = %d", msgID) } // AddRouterSlices adds router handlers using slices // (切片路由添加) func (mh *MsgHandle) AddRouterSlices(msgId uint32, handler ...ziface.RouterHandler) ziface.IRouterSlices { mh.RouterSlices.AddHandler(msgId, handler...) return mh.RouterSlices } // Group routes into a group (路由分组) func (mh *MsgHandle) Group(start, end uint32, Handlers ...ziface.RouterHandler) ziface.IGroupRouterSlices { return NewGroup(start, end, mh.RouterSlices, Handlers...) } func (mh *MsgHandle) Use(Handlers ...ziface.RouterHandler) ziface.IRouterSlices { mh.RouterSlices.Use(Handlers...) return mh.RouterSlices } func (mh *MsgHandle) doMsgHandlerSlices(request ziface.IRequest, workerID int) { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("workerID: %d doMsgHandler panic: %v", workerID, err) } }() msgId := request.GetMsgID() handlers, ok := mh.RouterSlices.GetHandlers(msgId) if !ok { zlog.Ins().ErrorF("api msgID = %d is not FOUND!", request.GetMsgID()) return } request.BindRouterSlices(handlers) request.RouterSlicesNext() // 执行完成后回收 Request 对象回对象池 PutRequest(request) } func (mh *MsgHandle) StopOneWorker(workerID int) { zlog.Ins().DebugF("stop Worker ID = %d ", workerID) // Stop the worker by closing the corresponding taskQueue // (停止一个Worker,通过关闭对应的taskQueue) close(mh.TaskQueue[workerID]) } // StartOneWorker starts a worker workflow // (启动一个Worker工作流程) func (mh *MsgHandle) StartOneWorker(workerID int, taskQueue chan ziface.IRequest) { zlog.Ins().DebugF("Worker ID = %d is started.", workerID) // Continuously wait for messages in the queue // (不断地等待队列中的消息) for { select { // If there is a message, take out the Request from the queue and execute the bound business method // (有消息则取出队列的Request,并执行绑定的业务方法) case request, ok := <-taskQueue: if !ok { // DynamicBind Mode, destroy current worker by close the taskQueue // (DynamicBind模式下,临时创建的worker, 是通过关闭taskQueue 来销毁当前worker) zlog.Ins().ErrorF(" taskQueue is closed, Worker ID = %d quit", workerID) return } switch req := request.(type) { case ziface.IFuncRequest: // Internal function call request (内部函数调用request) mh.doFuncHandler(req, workerID) case ziface.IRequest: // Client message request if !zconf.GlobalObject.RouterSlicesMode { mh.doMsgHandler(req, workerID) } else if zconf.GlobalObject.RouterSlicesMode { mh.doMsgHandlerSlices(req, workerID) } } } } } // StartWorkerPool starts the worker pool func (mh *MsgHandle) StartWorkerPool() { // Iterate through the required number of workers and start them one by one // (遍历需要启动worker的数量,依此启动) for i := 0; i < int(mh.WorkerPoolSize); i++ { // A worker is started // Allocate space for the corresponding task queue for the current worker // (给当前worker对应的任务队列开辟空间) mh.TaskQueue[i] = make(chan ziface.IRequest, zconf.GlobalObject.MaxWorkerTaskLen) // Start the current worker, blocking and waiting for messages to be passed in the corresponding task queue // (启动当前Worker,阻塞的等待对应的任务队列是否有消息传递进来) go mh.StartOneWorker(i, mh.TaskQueue[i]) } } ================================================ FILE: znet/options.go ================================================ package znet import ( "net/http" "net/url" "github.com/aceld/zinx/ziface" ) // Options for Server // (Server的服务Option) type Option func(s *Server) // Implement custom data packet format by implementing the Packet interface, // otherwise use the default data packet format // (只要实现Packet 接口可自由实现数据包解析格式,如果没有则使用默认解析格式) func WithPacket(pack ziface.IDataPack) Option { return func(s *Server) { s.SetPacket(pack) } } // Options for Client type ClientOption func(c ziface.IClient) // Implement custom data packet format by implementing the Packet interface for client, // otherwise use the default data packet format func WithPacketClient(pack ziface.IDataPack) ClientOption { return func(c ziface.IClient) { c.SetPacket(pack) } } // Set client name func WithNameClient(name string) ClientOption { return func(c ziface.IClient) { c.SetName(name) } } func WithUrl(url *url.URL) ClientOption { return func(c ziface.IClient) { c.SetUrl(url) } } // Set custom headers for WebSocket connection func WithWsHeader(header http.Header) ClientOption { return func(c ziface.IClient) { c.SetWsHeader(header) } } ================================================ FILE: znet/request.go ================================================ package znet import ( "math" "sync" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zpack" ) const ( PRE_HANDLE ziface.HandleStep = iota // PreHandle for pre-processing HANDLE // Handle for processing POST_HANDLE // PostHandle for post-processing HANDLE_OVER ) var RequestPool = new(sync.Pool) func init() { RequestPool.New = func() interface{} { return allocateRequest() } } // Request 请求 type Request struct { ziface.BaseRequest conn ziface.IConnection // the connection which has been established with the client(已经和客户端建立好的连接) msg ziface.IMessage // the request data sent by the client(客户端请求的数据) router ziface.IRouter // the router that handles this request(请求处理的函数) steps ziface.HandleStep // used to control the execution of router functions(用来控制路由函数执行) stepLock sync.RWMutex // concurrency lock(并发互斥) needNext bool // whether to execute the next router function(是否需要执行下一个路由函数) icResp ziface.IcResp // response data returned by the interceptors (拦截器返回数据) handlers []ziface.RouterHandler // router function slice(路由函数切片) index int8 // router function slice index(路由函数切片索引) keys map[string]interface{} // keys 路由处理时可能会存取的上下文信息 } func (r *Request) GetResponse() ziface.IcResp { return r.icResp } func (r *Request) SetResponse(response ziface.IcResp) { r.icResp = response } func NewRequest(conn ziface.IConnection, msg ziface.IMessage) ziface.IRequest { req := new(Request) req.steps = PRE_HANDLE req.conn = conn req.msg = msg req.stepLock = sync.RWMutex{} req.needNext = true req.index = -1 return req } func GetRequest(conn ziface.IConnection, msg ziface.IMessage) ziface.IRequest { // 根据当前模式判断是否使用对象池 if zconf.GlobalObject.RequestPoolMode { // 从对象池中取得一个 Request 对象,如果池子中没有可用的 Request 对象则会调用 allocateRequest 函数构造一个新的对象分配 r := RequestPool.Get().(*Request) // 因为取出的 Request 对象可能是已存在也可能是新构造的,无论是哪种情况都应该初始化再返回使用 r.Reset(conn, msg) return r } return NewRequest(conn, msg) } func PutRequest(request ziface.IRequest) { // 判断是否开启了对象池模式 if zconf.GlobalObject.RequestPoolMode { RequestPool.Put(request) } } func allocateRequest() ziface.IRequest { req := new(Request) req.steps = PRE_HANDLE req.needNext = true req.index = -1 return req } func (r *Request) Reset(conn ziface.IConnection, msg ziface.IMessage) { r.steps = PRE_HANDLE r.conn = conn r.msg = msg r.needNext = true r.index = -1 r.keys = nil } // Copy 在执行路由函数的时候可能会出现需要再起一个协程的需求,但是 Request 对象由对象池管理后无法保证新协程中的 Request 参数一致 // 通过 Copy 方法复制一份 Request 对象保持创建协程时候的参数一致。但新开的协程不应该在对原始的执行过程有影响,所以不包含连接和路由对象。 // 但如果一定对连接信息有所需要可以在 Copy 后手动 set 一份参数在 Request 对象中 func (r *Request) Copy() ziface.IRequest { // 构造一个新的 Request 对象,复制部分原始对象的参数,但是复制的 Request 不应该再对原始连接操作,所以不含有连接参数 // 同理也不应该再执行路由方法,路由函数也不包含 newRequest := &Request{ conn: nil, router: nil, steps: r.steps, needNext: false, icResp: nil, handlers: nil, index: math.MaxInt8, } // 复制原本的上下文信息 newRequest.keys = make(map[string]interface{}) for k, v := range r.keys { newRequest.keys[k] = v } // 复制一份原本的 icResp copyResp := []ziface.IcResp{r.icResp} newIcResp := make([]ziface.IcResp, 0, 1) copy(newIcResp, copyResp) for _, v := range newIcResp { newRequest.icResp = v } // 复制一份原本的 msg 信息 newRequest.msg = zpack.NewMessageByMsgId(r.msg.GetMsgID(), r.msg.GetDataLen(), r.msg.GetRawData()) return newRequest } // Set 在 Request 中存放一个上下文,如果 keys 为空会实例化一个 func (r *Request) Set(key string, value interface{}) { r.stepLock.Lock() if r.keys == nil { r.keys = make(map[string]interface{}) } r.keys[key] = value r.stepLock.Unlock() } // Get 在 Request 中取出一个上下文信息 func (r *Request) Get(key string) (value interface{}, exists bool) { r.stepLock.RLock() value, exists = r.keys[key] r.stepLock.RUnlock() return } func (r *Request) GetMessage() ziface.IMessage { return r.msg } func (r *Request) GetConnection() ziface.IConnection { return r.conn } func (r *Request) GetData() []byte { return r.msg.GetData() } func (r *Request) GetMsgID() uint32 { return r.msg.GetMsgID() } func (r *Request) BindRouter(router ziface.IRouter) { r.router = router } func (r *Request) next() { if r.needNext == false { r.needNext = true return } r.stepLock.Lock() r.steps++ r.stepLock.Unlock() } func (r *Request) Goto(step ziface.HandleStep) { r.stepLock.Lock() r.steps = step r.needNext = false r.stepLock.Unlock() } func (r *Request) Call() { if r.router == nil { return } for r.steps < HANDLE_OVER { switch r.steps { case PRE_HANDLE: r.router.PreHandle(r) case HANDLE: r.router.Handle(r) case POST_HANDLE: r.router.PostHandle(r) } r.next() } r.steps = PRE_HANDLE } func (r *Request) Abort() { if zconf.GlobalObject.RouterSlicesMode { r.index = int8(len(r.handlers)) } else { r.stepLock.Lock() r.steps = HANDLE_OVER r.stepLock.Unlock() } } // BindRouterSlices New version func (r *Request) BindRouterSlices(handlers []ziface.RouterHandler) { r.handlers = handlers } func (r *Request) RouterSlicesNext() { r.index++ for r.index < int8(len(r.handlers)) { r.handlers[r.index](r) r.index++ } } ================================================ FILE: znet/request_func.go ================================================ package znet import "github.com/aceld/zinx/ziface" type RequestFunc struct { ziface.BaseRequest conn ziface.IConnection callFunc func() } func (rf *RequestFunc) GetConnection() ziface.IConnection { return rf.conn } func (rf *RequestFunc) CallFunc() { if rf.callFunc != nil { rf.callFunc() } } func NewFuncRequest(conn ziface.IConnection, callFunc func()) ziface.IRequest { req := new(RequestFunc) req.conn = conn req.callFunc = callFunc return req } ================================================ FILE: znet/router.go ================================================ package znet import ( "strconv" "sync" "github.com/aceld/zinx/ziface" ) // BaseRouter is used as the base class when implementing a router. // Depending on the needs, the methods of this base class can be overridden. // (实现router时,先嵌入这个基类,然后根据需要对这个基类的方法进行重写) type BaseRouter struct{} // Here, all of BaseRouter's methods are empty, because some routers may not want to have PreHandle or PostHandle. // Therefore, inheriting all routers from BaseRouter has the advantage that PreHandle and PostHandle do not need to be // implemented to instantiate a router. // (这里之所以BaseRouter的方法都为空, // 是因为有的Router不希望有PreHandle或PostHandle // 所以Router全部继承BaseRouter的好处是,不需要实现PreHandle和PostHandle也可以实例化) // PreHandle - func (br *BaseRouter) PreHandle(req ziface.IRequest) {} // Handle - func (br *BaseRouter) Handle(req ziface.IRequest) {} // PostHandle - func (br *BaseRouter) PostHandle(req ziface.IRequest) {} // New slice-based router // The new version of the router has basic logic that allows users to pass in varying numbers of router handlers. // The router will save all of these router handler functions and find them when a request comes in, then execute them using IRequest. // The router can set globally shared components using the Use method. // The router can be grouped using Group, and groups also have their own Use method for setting group-shared components. // (新切片集合式路由 // 新版本路由基本逻辑,用户可以传入不等数量的路由路由处理器 // 路由本体会讲这些路由处理器函数全部保存,在请求来的时候找到,并交由IRequest去执行 // 路由可以设置全局的共用组件通过Use方法 // 路由可以分组,通过Group,分组也有自己对应Use方法设置组共有组件) type RouterSlices struct { Apis map[uint32][]ziface.RouterHandler Handlers []ziface.RouterHandler sync.RWMutex } func NewRouterSlices() *RouterSlices { return &RouterSlices{ Apis: make(map[uint32][]ziface.RouterHandler, 10), Handlers: make([]ziface.RouterHandler, 0, 6), } } func (r *RouterSlices) Use(handles ...ziface.RouterHandler) { r.Handlers = append(r.Handlers, handles...) } func (r *RouterSlices) AddHandler(msgId uint32, Handlers ...ziface.RouterHandler) { // 1. Check if the API handler method bound to the current msg already exists if _, ok := r.Apis[msgId]; ok { panic("repeated api , msgId = " + strconv.Itoa(int(msgId))) } finalSize := len(r.Handlers) + len(Handlers) mergedHandlers := make([]ziface.RouterHandler, finalSize) copy(mergedHandlers, r.Handlers) copy(mergedHandlers[len(r.Handlers):], Handlers) r.Apis[msgId] = append(r.Apis[msgId], mergedHandlers...) } func (r *RouterSlices) GetHandlers(MsgId uint32) ([]ziface.RouterHandler, bool) { r.RLock() defer r.RUnlock() handlers, ok := r.Apis[MsgId] return handlers, ok } func (r *RouterSlices) Group(start, end uint32, Handlers ...ziface.RouterHandler) ziface.IGroupRouterSlices { return NewGroup(start, end, r, Handlers...) } type GroupRouter struct { start uint32 end uint32 Handlers []ziface.RouterHandler router ziface.IRouterSlices } func NewGroup(start, end uint32, router *RouterSlices, Handlers ...ziface.RouterHandler) *GroupRouter { g := &GroupRouter{ start: start, end: end, Handlers: make([]ziface.RouterHandler, 0, len(Handlers)), router: router, } g.Handlers = append(g.Handlers, Handlers...) return g } func (g *GroupRouter) Use(Handlers ...ziface.RouterHandler) { g.Handlers = append(g.Handlers, Handlers...) } func (g *GroupRouter) AddHandler(MsgId uint32, Handlers ...ziface.RouterHandler) { if MsgId < g.start || MsgId > g.end { panic("add router to group err in msgId:" + strconv.Itoa(int(MsgId))) } finalSize := len(g.Handlers) + len(Handlers) mergedHandlers := make([]ziface.RouterHandler, finalSize) copy(mergedHandlers, g.Handlers) copy(mergedHandlers[len(g.Handlers):], Handlers) g.router.AddHandler(MsgId, mergedHandlers...) } ================================================ FILE: znet/routerSilces_test.go ================================================ package znet import ( "fmt" "testing" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" ) func A1(request ziface.IRequest) { fmt.Println("我要写入一些上下文到 Request 中了") request.Set("Hey", "zinx!") request.Set("Age", 2) } func A2(request ziface.IRequest) { name, _ := request.Get("Hey") age, _ := request.Get("Age") fmt.Printf("我是练习时长%v年半的%v \n", age, name) //如果需要开新协程操作应该 copy cp := request.Copy() go A4(cp) request.Abort() } func A3(request ziface.IRequest) { fmt.Println("No! 不带我玩") } func A4(request ziface.IRequest) { // 需要新线程同时也需要上下文的情况 fmt.Println(request) } func TestRouterAdd(t *testing.T) { server := NewUserConfServer(&zconf.Config{RouterSlicesMode: true, TCPPort: 8999, Host: "127.0.0.1"}) server.AddRouterSlices(1, A1, A2, A3) server.Serve() } ================================================ FILE: znet/server.go ================================================ package znet import ( "crypto/rand" "crypto/tls" "errors" "fmt" "net" "net/http" "os" "os/signal" "sync/atomic" "syscall" "time" "github.com/gorilla/websocket" "github.com/aceld/zinx/logo" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/zdecoder" "github.com/aceld/zinx/zlog" "github.com/xtaci/kcp-go" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zpack" ) // Server interface implementation, defines a Server service class // (接口实现,定义一个Server服务类) type Server struct { // Name of the server (服务器的名称) Name string //tcp4 or other IPVersion string // IP version (e.g. "tcp4") - 服务绑定的IP地址 IP string // IP address the server is bound to (服务绑定的端口) Port int // 服务绑定的websocket 端口 (Websocket port the server is bound to) WsPort int // 服务绑定的websocket 路径 (Websocket path the server is bound to) WsPath string // 服务绑定的kcp 端口 (kcp port the server is bound to) KcpPort int // Current server's message handler module, used to bind MsgID to corresponding processing methods // (当前Server的消息管理模块,用来绑定MsgID和对应的处理方法) msgHandler ziface.IMsgHandle // Routing mode (路由模式) RouterSlicesMode bool // Request 对象池模式 RequestPoolMode bool // Current server's connection manager (当前Server的连接管理器) ConnMgr ziface.IConnManager // Hook function called when a new connection is established // (该Server的连接创建时Hook函数) onConnStart func(conn ziface.IConnection) // Hook function called when a connection is terminated // (该Server的连接断开时的Hook函数) onConnStop func(conn ziface.IConnection) // Data packet encapsulation method // (数据报文封包方式) packet ziface.IDataPack // Asynchronous capture of connection closing status // (异步捕获连接关闭状态) exitChan chan struct{} // Decoder for dealing with message fragmentation and reassembly // (断粘包解码器) decoder ziface.IDecoder // Heartbeat checker // (心跳检测器) hc ziface.IHeartbeatChecker // websocket upgrader *websocket.Upgrader // websocket connection authentication websocketAuth func(r *http.Request) error kcpConfig *KcpConfig // connection id cID uint64 } type KcpConfig struct { // changes ack flush option, set true to flush ack immediately, // (改变ack刷新选项,设置为true立即刷新ack) KcpACKNoDelay bool // toggles the stream mode on/off // (切换流模式开/关) KcpStreamMode bool // Whether nodelay mode is enabled, 0 is not enabled; 1 enabled. // (是否启用nodelay模式,0不启用;1启用) KcpNoDelay int // Protocol internal work interval, in milliseconds, such as 10 ms or 20 ms. // (协议内部工作的间隔,单位毫秒,比如10ms或者20ms) KcpInterval int // Fast retransmission mode, 0 represents off by default, 2 can be set (2 ACK spans will result in direct retransmission) // (快速重传模式,默认为0关闭,可以设置2(2次ACK跨越将会直接重传) KcpResend int // Whether to turn off flow control, 0 represents “Do not turn off” by default, 1 represents “Turn off”. // (是否关闭流控,默认是0代表不关闭,1代表关闭) KcpNc int // SND_BUF, this unit is the packet, default 32. // (SND_BUF发送缓冲区大小,单位是包,默认是32) KcpSendWindow int // RCV_BUF, this unit is the packet, default 32. // (RCV_BUF接收缓冲区大小,单位是包,默认是32) KcpRecvWindow int // FEC data shards, default 0. // (FEC数据分片,用于前向纠错比例配制) 默认是0 KcpFecDataShards int // FEC parity shards, default 0. // (FEC校验分片,用于前向纠错比例配制) 默认是0 KcpFecParityShards int } // newServerWithConfig creates a server handle based on config // (根据config创建一个服务器句柄) func newServerWithConfig(config *zconf.Config, ipVersion string, opts ...Option) ziface.IServer { logo.PrintLogo() s := &Server{ Name: config.Name, IPVersion: ipVersion, IP: config.Host, Port: config.TCPPort, WsPort: config.WsPort, WsPath: config.WsPath, KcpPort: config.KcpPort, msgHandler: newMsgHandle(), RouterSlicesMode: config.RouterSlicesMode, RequestPoolMode: config.RequestPoolMode, ConnMgr: newConnManager(), exitChan: nil, // Default to using Zinx's TLV data pack format // (默认使用zinx的TLV封包方式) packet: zpack.Factory().NewPack(ziface.ZinxDataPack), decoder: zdecoder.NewTLVDecoder(), // Default to using TLV decode (默认使用TLV的解码方式) upgrader: &websocket.Upgrader{ ReadBufferSize: int(config.IOReadBuffSize), CheckOrigin: func(r *http.Request) bool { return true }, }, kcpConfig: &KcpConfig{ KcpACKNoDelay: config.KcpACKNoDelay, KcpStreamMode: config.KcpStreamMode, KcpNoDelay: config.KcpNoDelay, KcpInterval: config.KcpInterval, KcpResend: config.KcpResend, KcpNc: config.KcpNc, KcpSendWindow: config.KcpSendWindow, KcpRecvWindow: config.KcpRecvWindow, KcpFecDataShards: config.KcpFecDataShards, KcpFecParityShards: config.KcpFecParityShards, }, } for _, opt := range opts { opt(s) } // Display current configuration information // (提示当前配置信息) config.Show() return s } // NewServer creates a server handle // (创建一个服务器句柄) func NewServer(opts ...Option) ziface.IServer { return newServerWithConfig(zconf.GlobalObject, "tcp", opts...) } // NewUserConfServer creates a server handle using user-defined configuration // (创建一个服务器句柄) func NewUserConfServer(config *zconf.Config, opts ...Option) ziface.IServer { // Refresh user configuration to global configuration variable // (刷新用户配置到全局配置变量) zconf.UserConfToGlobal(config) s := newServerWithConfig(zconf.GlobalObject, "tcp4", opts...) return s } // NewDefaultRouterSlicesServer creates a server handle with a default RouterRecovery processor. // (创建一个默认自带一个Recover处理器的服务器句柄) func NewDefaultRouterSlicesServer(opts ...Option) ziface.IServer { zconf.GlobalObject.RouterSlicesMode = true s := newServerWithConfig(zconf.GlobalObject, "tcp", opts...) s.Use(RouterRecovery) return s } // NewUserConfDefaultRouterSlicesServer creates a server handle with user-configured options and a default Recover handler. // If the user does not wish to use the Use method, they should use NewUserConfServer instead. // (创建一个用户配置的自带一个Recover处理器的服务器句柄,如果用户不希望Use这个方法,那么应该使用NewUserConfServer) func NewUserConfDefaultRouterSlicesServer(config *zconf.Config, opts ...Option) ziface.IServer { if !config.RouterSlicesMode { panic("RouterSlicesMode is false") } // Refresh user configuration to global configuration variable (刷新用户配置到全局配置变量) zconf.UserConfToGlobal(config) s := newServerWithConfig(zconf.GlobalObject, "tcp4", opts...) s.Use(RouterRecovery) return s } func (s *Server) StartConn(conn ziface.IConnection) { // HeartBeat check if s.hc != nil { // Clone a heart-beat checker from the server side heartBeatChecker := s.hc.Clone() // Bind current connection heartBeatChecker.BindConn(conn) } // Start processing business for the current connection conn.Start() } func (s *Server) ListenTcpConn() { zlog.Ins().InfoF("[START] TCP Server name: %s,listener at IP: %s, Port %d is starting", s.Name, s.IP, s.Port) // 1. Get a TCP address addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port)) if err != nil { zlog.Ins().ErrorF("[START] resolve tcp addr err: %v\n", err) return } // 2. Listen to the server address var listener net.Listener if zconf.GlobalObject.CertFile != "" && zconf.GlobalObject.PrivateKeyFile != "" { // Read certificate and private key crt, err := tls.LoadX509KeyPair(zconf.GlobalObject.CertFile, zconf.GlobalObject.PrivateKeyFile) if err != nil { panic(err) } // TLS connection tlsConfig := &tls.Config{} tlsConfig.Certificates = []tls.Certificate{crt} tlsConfig.Time = time.Now tlsConfig.Rand = rand.Reader listener, err = tls.Listen(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port), tlsConfig) if err != nil { panic(err) } } else { listener, err = net.ListenTCP(s.IPVersion, addr) if err != nil { panic(err) } } // 3. Start server network connection business go func() { for { // 3.1 Set the maximum connection control for the server. If it exceeds the maximum connection, wait. // (设置服务器最大连接控制,如果超过最大连接,则等待) if s.ConnMgr.Len() >= zconf.GlobalObject.MaxConn { zlog.Ins().InfoF("Exceeded the maxConnNum:%d, Wait:%d", zconf.GlobalObject.MaxConn, AcceptDelay.duration) AcceptDelay.Delay() continue } // 3.2 Block and wait for a client to establish a connection request. // (阻塞等待客户端建立连接请求) conn, err := listener.Accept() if err != nil { //Go 1.17+ if errors.Is(err, net.ErrClosed) { zlog.Ins().ErrorF("Listener closed") return } zlog.Ins().ErrorF("Accept err: %v", err) AcceptDelay.Delay() continue } AcceptDelay.Reset() // 3.4 Handle the business method for this new connection request. At this time, the handler and conn should be bound. // (处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的) newCid := atomic.AddUint64(&s.cID, 1) dealConn := newServerConn(s, conn, newCid) go s.StartConn(dealConn) } }() select { case <-s.exitChan: err := listener.Close() if err != nil { zlog.Ins().ErrorF("listener close err: %v", err) } } } func (s *Server) ListenWebsocketConn() { zlog.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) http.HandleFunc(s.WsPath, func(w http.ResponseWriter, r *http.Request) { // 1. Check if the server has reached the maximum allowed number of connections // (设置服务器最大连接控制,如果超过最大连接,则等待) if s.ConnMgr.Len() >= zconf.GlobalObject.MaxConn { zlog.Ins().InfoF("Exceeded the maxConnNum:%d, Wait:%d", zconf.GlobalObject.MaxConn, AcceptDelay.duration) AcceptDelay.Delay() return } // 2. If websocket authentication is required, set the authentication information // (如果需要 websocket 认证请设置认证信息) if s.websocketAuth != nil { err := s.websocketAuth(r) if err != nil { zlog.Ins().ErrorF(" websocket auth err:%v", err) w.WriteHeader(401) AcceptDelay.Delay() return } } // 3. Check if there is a subprotocol specified in the header // (判断 header 里面是有子协议) if len(r.Header.Get("Sec-Websocket-Protocol")) > 0 { s.upgrader.Subprotocols = websocket.Subprotocols(r) } // 4. Upgrade the connection to a websocket connection // (升级成 websocket 连接) conn, err := s.upgrader.Upgrade(w, r, nil) if err != nil { zlog.Ins().ErrorF("new websocket err:%v", err) w.WriteHeader(500) AcceptDelay.Delay() return } AcceptDelay.Reset() // 5. Handle the business logic of the new connection, which should already be bound to a handler and conn // 5. 处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn是绑定的 newCid := atomic.AddUint64(&s.cID, 1) wsConn := newWebsocketConn(s, conn, newCid, r) go s.StartConn(wsConn) }) if zconf.GlobalObject.CertFile != "" && zconf.GlobalObject.PrivateKeyFile != "" { err := http.ListenAndServeTLS(fmt.Sprintf("%s:%d", s.IP, s.WsPort), zconf.GlobalObject.CertFile, zconf.GlobalObject.PrivateKeyFile, nil) if err != nil { panic(err) } } else { err := http.ListenAndServe(fmt.Sprintf("%s:%d", s.IP, s.WsPort), nil) if err != nil { panic(err) } } } func (s *Server) ListenKcpConn() { // 1. Listen to the server address listener, err := kcp.ListenWithOptions(fmt.Sprintf("%s:%d", s.IP, s.KcpPort), nil, s.kcpConfig.KcpFecDataShards, s.kcpConfig.KcpFecParityShards) if err != nil { zlog.Ins().ErrorF("[START] resolve KCP addr err: %v\n", err) return } zlog.Ins().InfoF("[START] KCP server listening at IP: %s, Port %d, Addr %s", s.IP, s.KcpPort, listener.Addr().String()) // 2. Start server network connection business go func() { for { // 2.1 Set the maximum connection control for the server. If it exceeds the maximum connection, wait. // (设置服务器最大连接控制,如果超过最大连接,则等待) if s.ConnMgr.Len() >= zconf.GlobalObject.MaxConn { zlog.Ins().InfoF("Exceeded the maxConnNum:%d, Wait:%d", zconf.GlobalObject.MaxConn, AcceptDelay.duration) AcceptDelay.Delay() continue } // 2.2 Block and wait for a client to establish a connection request. // (阻塞等待客户端建立连接请求) conn, err := listener.Accept() if err != nil { zlog.Ins().ErrorF("Accept KCP err: %v", err) AcceptDelay.Delay() continue } AcceptDelay.Reset() // 3.4 Handle the business method for this new connection request. At this time, the handler and conn should be bound. // (处理该新连接请求的 业务 方法, 此时应该有 handler 和 conn 是绑定的) newCid := atomic.AddUint64(&s.cID, 1) kcpConn := conn.(*kcp.UDPSession) kcpConn.SetACKNoDelay(s.kcpConfig.KcpACKNoDelay) kcpConn.SetStreamMode(s.kcpConfig.KcpStreamMode) kcpConn.SetNoDelay(s.kcpConfig.KcpNoDelay, s.kcpConfig.KcpInterval, s.kcpConfig.KcpResend, s.kcpConfig.KcpNc) kcpConn.SetWindowSize(s.kcpConfig.KcpSendWindow, s.kcpConfig.KcpRecvWindow) dealConn := newKcpServerConn(s, kcpConn, newCid) go s.StartConn(dealConn) } }() select { case <-s.exitChan: err := listener.Close() if err != nil { zlog.Ins().ErrorF("KCP listener close err: %v", err) } } } // Start the network service // (开启网络服务) func (s *Server) Start() { s.exitChan = make(chan struct{}) // Add decoder to interceptors head // (将解码器添加到拦截器最前面) if s.decoder != nil { s.msgHandler.SetHeadInterceptor(s.decoder) } // Start worker pool mechanism // (启动worker工作池机制) s.msgHandler.StartWorkerPool() // Start a goroutine to handle server listener business // (开启一个go去做服务端Listener业务) switch zconf.GlobalObject.Mode { case zconf.ServerModeTcp: go s.ListenTcpConn() case zconf.ServerModeWebsocket: go s.ListenWebsocketConn() case zconf.ServerModeKcp: go s.ListenKcpConn() default: go s.ListenTcpConn() go s.ListenWebsocketConn() } } // Stop stops the server (停止服务) func (s *Server) Stop() { zlog.Ins().InfoF("[STOP] Zinx server , name %s", s.Name) // Clear other connection information or other information that needs to be cleaned up // (将其他需要清理的连接信息或者其他信息 也要一并停止或者清理) s.ConnMgr.ClearConn() s.exitChan <- struct{}{} close(s.exitChan) } // Serve runs the server (运行服务) func (s *Server) Serve() { s.Start() // Block, otherwise the listener's goroutine will exit when the main Go exits (阻塞,否则主Go退出, listenner的go将会退出) c := make(chan os.Signal, 1) // Listen for specified signals: ctrl+c or kill signal (监听指定信号 ctrl+c kill信号) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) sig := <-c zlog.Ins().InfoF("[SERVE] Zinx server , name %s, Serve Interrupt, signal = %v", s.Name, sig) } func (s *Server) AddRouter(msgID uint32, router ziface.IRouter) { if s.RouterSlicesMode { panic("Server RouterSlicesMode is true ") } s.msgHandler.AddRouter(msgID, router) } func (s *Server) AddRouterSlices(msgID uint32, router ...ziface.RouterHandler) ziface.IRouterSlices { if !s.RouterSlicesMode { panic("Server RouterSlicesMode is false ") } return s.msgHandler.AddRouterSlices(msgID, router...) } func (s *Server) Group(start, end uint32, Handlers ...ziface.RouterHandler) ziface.IGroupRouterSlices { if !s.RouterSlicesMode { panic("Server RouterSlicesMode is false") } return s.msgHandler.Group(start, end, Handlers...) } func (s *Server) Use(Handlers ...ziface.RouterHandler) ziface.IRouterSlices { if !s.RouterSlicesMode { panic("Server RouterSlicesMode is false") } return s.msgHandler.Use(Handlers...) } func (s *Server) GetConnMgr() ziface.IConnManager { return s.ConnMgr } func (s *Server) SetOnConnStart(hookFunc func(ziface.IConnection)) { s.onConnStart = hookFunc } func (s *Server) SetOnConnStop(hookFunc func(ziface.IConnection)) { s.onConnStop = hookFunc } func (s *Server) GetOnConnStart() func(ziface.IConnection) { return s.onConnStart } func (s *Server) GetOnConnStop() func(ziface.IConnection) { return s.onConnStop } func (s *Server) GetPacket() ziface.IDataPack { return s.packet } func (s *Server) SetPacket(packet ziface.IDataPack) { s.packet = packet } func (s *Server) GetMsgHandler() ziface.IMsgHandle { return s.msgHandler } // StartHeartBeat starts the heartbeat check. // interval is the time interval between each heartbeat. // (启动心跳检测 // interval 每次发送心跳的时间间隔) func (s *Server) StartHeartBeat(interval time.Duration) { checker := NewHeartbeatChecker(interval) // Add the heartbeat check router. (添加心跳检测的路由) //检测当前路由模式 if s.RouterSlicesMode { s.AddRouterSlices(checker.MsgID(), checker.RouterSlices()...) } else { s.AddRouter(checker.MsgID(), checker.Router()) } // Bind the heartbeat checker to the server. (server绑定心跳检测器) s.hc = checker } // StartHeartBeatWithOption starts the heartbeat detection with the given configuration. // interval is the time interval for sending heartbeat messages. // option is the configuration for heartbeat detection. // 启动心跳检测 // (option 心跳检测的配置) func (s *Server) StartHeartBeatWithOption(interval time.Duration, option *ziface.HeartBeatOption) { checker := NewHeartbeatChecker(interval) // Configure the heartbeat checker with the provided options if option != nil { checker.SetHeartbeatMsgFunc(option.MakeMsg) checker.SetOnRemoteNotAlive(option.OnRemoteNotAlive) //检测当前路由模式 if s.RouterSlicesMode { checker.BindRouterSlices(option.HeartBeatMsgID, option.RouterSlices...) } else { checker.BindRouter(option.HeartBeatMsgID, option.Router) } } // Add the heartbeat checker's router to the server's router (添加心跳检测的路由) //检测当前路由模式 if s.RouterSlicesMode { s.AddRouterSlices(checker.MsgID(), checker.RouterSlices()...) } else { s.AddRouter(checker.MsgID(), checker.Router()) } // Bind the server with the heartbeat checker (server绑定心跳检测器) s.hc = checker } func (s *Server) GetHeartBeat() ziface.IHeartbeatChecker { return s.hc } func (s *Server) SetDecoder(decoder ziface.IDecoder) { s.decoder = decoder } func (s *Server) GetLengthField() *ziface.LengthField { if s.decoder != nil { return s.decoder.GetLengthField() } return nil } func (s *Server) AddInterceptor(interceptor ziface.IInterceptor) { s.msgHandler.AddInterceptor(interceptor) } func (s *Server) SetWebsocketAuth(f func(r *http.Request) error) { s.websocketAuth = f } func (s *Server) ServerName() string { return s.Name } func init() {} ================================================ FILE: znet/server_test.go ================================================ package znet import ( "fmt" "io" "net" "sync" "testing" "time" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zpack" ) // run in terminal: // go test -v ./znet -run=TestServer /* ClientTest client */ func ClientTest(i uint32) { fmt.Println("Client Test ... start") //3秒之后发起测试请求,给服务端开启服务的机会 time.Sleep(3 * time.Second) conn, err := net.Dial("tcp", "127.0.0.1:8999") if err != nil { fmt.Println("client start err, exit!") return } for { dp := zpack.Factory().NewPack(ziface.ZinxDataPack) msg, _ := dp.Pack(zpack.NewMsgPackage(i, []byte("client test message"))) _, err := conn.Write(msg) if err != nil { fmt.Println("client write err: ", err) return } headData := make([]byte, dp.GetHeadLen()) _, err = io.ReadFull(conn, headData) if err != nil { fmt.Println("client read head err: ", err) return } msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("client unpack head err: ", err) return } if msgHead.GetDataLen() > 0 { msg := msgHead.(*zpack.Message) msg.Data = make([]byte, msg.GetDataLen()) _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("client unpack data err") return } fmt.Printf("==> Client receive Msg: ID = %d, len = %d , data = %s\n", msg.ID, msg.DataLen, msg.Data) } time.Sleep(time.Second) } } /* server */ type PingRouter struct { BaseRouter } // Test PreHandle func (this *PingRouter) PreHandle(request ziface.IRequest) { fmt.Println("Call Router PreHandle") err := request.GetConnection().SendMsg(1, []byte("before ping ....\n")) if err != nil { fmt.Println("preHandle SendMsg err: ", err) } } // Test Handle func (this *PingRouter) Handle(request ziface.IRequest) { fmt.Println("Call PingRouter Handle") //先读取客户端的数据,再回写ping...ping...ping fmt.Println("recv from client : msgID=", request.GetMsgID(), ", data=", string(request.GetData())) err := request.GetConnection().SendMsg(1, []byte("ping...ping...ping\n")) if err != nil { fmt.Println("Handle SendMsg err: ", err) } } // Test PostHandle func (this *PingRouter) PostHandle(request ziface.IRequest) { fmt.Println("Call Router PostHandle") err := request.GetConnection().SendMsg(1, []byte("After ping .....\n")) if err != nil { fmt.Println("Post SendMsg err: ", err) } } type HelloRouter struct { BaseRouter } func (this *HelloRouter) Handle(request ziface.IRequest) { fmt.Println("call helloRouter Handle") fmt.Printf("receive from client msgID=%d, data=%s\n", request.GetMsgID(), string(request.GetData())) err := request.GetConnection().SendMsg(2, []byte("hello zix hello Router")) if err != nil { fmt.Println(err) } } func DoConnectionBegin(conn ziface.IConnection) { fmt.Println("DoConnectionBegin is Called ... ") err := conn.SendMsg(2, []byte("DoConnection BEGIN...")) if err != nil { fmt.Println(err) } } func DoConnectionLost(conn ziface.IConnection) { fmt.Println("DoConnectionLost is Called ... ") } func TestServer(t *testing.T) { s := NewServer() s.SetOnConnStart(DoConnectionBegin) s.SetOnConnStop(DoConnectionLost) s.AddRouter(1, &PingRouter{}) s.AddRouter(2, &HelloRouter{}) go ClientTest(1) go ClientTest(2) go s.Serve() select { case <-time.After(time.Second * 10): return } } func TestServerDeadLock(t *testing.T) { s := NewServer() s.Start() time.Sleep(time.Second * 1) go func() { _, _ = net.Dial("tcp", "127.0.0.1:8999") }() time.Sleep(time.Second * 1) s.Stop() } type CloseConnectionBeforeSendMsgRouter struct { BaseRouter } type DemoPacket struct { zpack.DataPack } func (d *DemoPacket) Pack(msg ziface.IMessage) ([]byte, error) { time.Sleep(time.Second * 1) return d.DataPack.Pack(msg) } func (br *CloseConnectionBeforeSendMsgRouter) Handle(req ziface.IRequest) { connection := req.GetConnection() msg := "Zinx server response message for CloseConnectionBeforeSendMsgRouter" connection.Stop() _ = connection.SendMsg(1, []byte(msg)) fmt.Println("send: ", msg) } func TestCloseConnectionBeforeSendMsg(t *testing.T) { s := NewServer() s.AddRouter(1, &CloseConnectionBeforeSendMsgRouter{}) s.Start() time.Sleep(time.Second * 1) wg := sync.WaitGroup{} wg.Add(1) go func() { conn, _ := net.Dial("tcp", "127.0.0.1:8999") dp := zpack.Factory().NewPack(ziface.ZinxDataPack) msg := "Zinx client request message for CloseConnectionBeforeSendMsgRouter" pack, _ := dp.Pack(zpack.NewMsgPackage(1, []byte(msg))) _, _ = conn.Write(pack) fmt.Println("send: ", msg) buffer := make([]byte, 1024) readLen, _ := conn.Read(buffer) fmt.Println("received all data: ", string(buffer[dp.GetHeadLen():readLen])) wg.Done() }() wg.Wait() s.Stop() } ================================================ FILE: znet/ws_connection.go ================================================ package znet import ( "context" "encoding/hex" "errors" "net" "net/http" "strconv" "sync" "time" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zinterceptor" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/zpack" "github.com/gorilla/websocket" ) // WsConnectionHttpReqCtxKey http请求上下文key type WsConnectionHttpReqCtxKey struct{} // WsConnection is a module for handling the read and write operations of a WebSocket connection. // (Websocket连接模块, 用于处理 Websocket 连接的读写业务 一个连接对应一个Connection) type WsConnection struct { // conn is the current connection's WebSocket socket TCP socket. (当前连接的socket TCP套接字) conn *websocket.Conn // connID is the current connection's ID, which can also be referred to as SessionID, and is globally unique. // uint64 range: 018,446,744,073,709,551,615 // This is the maximum number of connIDs that the theory supports per process. // (当前连接的ID 也可以称作为SessionID,ID全局唯一 ,服务端Connection使用 // uint64 取值范围:0 ~ 18,446,744,073,709,551,615 // 这个是理论支持的进程connID的最大数量) connID uint64 // connection id for string // (字符串的连接id) connIdStr string // The workerid responsible for handling the link // 负责处理该连接的workerid workerID uint32 // msgHandler is the message management module for MsgID and the corresponding message handling method. // (消息管理MsgID和对应处理方法的消息管理模块) msgHandler ziface.IMsgHandle // ctx and cancel are used to notify that the connection has exited/stopped. // (告知该连接已经退出/停止的channel) ctx context.Context cancel context.CancelFunc // msgBuffChan is a buffered channel used for message communication between the read and write goroutines. // (有缓冲管道,用于读、写两个goroutine之间的消息通信) msgBuffChan chan []byte // msgLock is used for locking when users send and receive messages. // (用户收发消息的Lock) msgLock sync.Mutex // property is the connection attribute. (连接属性) property map[string]interface{} // propertyLock protects the current property lock. (保护当前property的锁) propertyLock sync.Mutex // isClosed is the current connection's closed state. (当前连接的关闭状态) isClosed bool // connManager is the Connection Manager to which the current connection belongs. (当前连接是属于哪个Connection Manager的) connManager ziface.IConnManager // onConnStart is the Hook function when the current connection is created. // (当前连接创建时Hook函数) onConnStart func(conn ziface.IConnection) // onConnStop is the Hook function when the current connection is disconnected. // (当前连接断开时的Hook函数) onConnStop func(conn ziface.IConnection) // packet is the data packet format. // (数据报文封包方式) packet ziface.IDataPack // lastActivityTime is the last time the connection was active. // (最后一次活动时间) lastActivityTime time.Time // frameDecoder is the decoder for splitting or splicing data packets. // (断粘包解码器) frameDecoder ziface.IFrameDecoder // hc is the Heartbeat Checker. (心跳检测器) hc ziface.IHeartbeatChecker // name is the name of the connection and is the same as the Name of the Server/Client that created the connection. // (连接名称,默认与创建连接的Server/Client的Name一致) name string // localAddr is the local address of the current connection. (当前连接的本地地址) localAddr string // remoteAddr is the remote address of the current connection. (当前连接的远程地址) remoteAddr string // Close callback closeCallback callbacks // Close callback mutex closeCallbackMutex sync.RWMutex } // newServerConn: for Server, a method to create a connection with Server characteristics // Note: The name has been changed from NewConnection // (newServerConn :for Server, 创建一个Server服务端特性的连接的方法 // Note: 名字由 NewConnection 更变) func newWebsocketConn(server ziface.IServer, conn *websocket.Conn, connID uint64, r *http.Request) ziface.IConnection { // Initialize Conn properties (初始化Conn属性) c := &WsConnection{ ctx: context.WithValue(context.Background(), WsConnectionHttpReqCtxKey{}, r.Context()), // websocketAuth可以在上下文中传递特殊的参数或信息;比如鉴权后,设置一些用户信息或房间id conn: conn, connID: connID, connIdStr: strconv.FormatUint(connID, 10), isClosed: false, msgBuffChan: nil, property: nil, name: server.ServerName(), localAddr: conn.LocalAddr().String(), remoteAddr: conn.RemoteAddr().String(), } lengthField := server.GetLengthField() if lengthField != nil { c.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField) } // Inherited attributes from server (从server继承过来的属性) c.packet = server.GetPacket() c.onConnStart = server.GetOnConnStart() c.onConnStop = server.GetOnConnStop() c.msgHandler = server.GetMsgHandler() // Bind the current Connection to the Server's ConnManager (将当前的Connection与Server的ConnManager绑定) c.connManager = server.GetConnMgr() // Add the newly created Conn to the connection management (将新创建的Conn添加到连接管理中) server.GetConnMgr().Add(c) return c } // newClientConn :for Client, creates a connection with Client-side features // (newClientConn :for Client, 创建一个Client服务端特性的连接的方法) func newWsClientConn(client ziface.IClient, conn *websocket.Conn) ziface.IConnection { c := &WsConnection{ conn: conn, connID: 0, // client ignore connIdStr: "", // client ignore isClosed: false, msgBuffChan: nil, property: nil, name: client.GetName(), localAddr: conn.LocalAddr().String(), remoteAddr: conn.RemoteAddr().String(), } lengthField := client.GetLengthField() if lengthField != nil { c.frameDecoder = zinterceptor.NewFrameDecoder(*lengthField) } // Inherit properties from client (从client继承过来的属性) c.packet = client.GetPacket() c.onConnStart = client.GetOnConnStart() c.onConnStop = client.GetOnConnStop() c.msgHandler = client.GetMsgHandler() return c } // StartWriter is a Goroutine that sends messages to the client // (StartWriter 写消息Goroutine, 用户将数据发送给客户端) func (c *WsConnection) StartWriter() { zlog.Ins().InfoF("Writer Goroutine is running") defer zlog.Ins().InfoF("%s [conn Writer exit!]", c.RemoteAddr().String()) for { select { case data, ok := <-c.msgBuffChan: if ok { if err := c.Send(data); err != nil { zlog.Ins().ErrorF("Send Buff Data error:, %s Conn Writer exit", err) break } } else { zlog.Ins().ErrorF("msgBuffChan is Closed") break } case <-c.ctx.Done(): return } } } // StartReader is a Goroutine that reads messages from the client. // (StartReader 读消息Goroutine,用于从客户端中读取数据) func (c *WsConnection) StartReader() { zlog.Ins().InfoF("[Reader Goroutine is running]") defer zlog.Ins().InfoF("%s [conn Reader exit!]", c.RemoteAddr().String()) defer c.Stop() // Create a pack-unpack object. (创建拆包解包的对象) for { select { case <-c.ctx.Done(): return default: // add by uuxia 2023-02-03 // Read data from the conn's IO to the memory buffer. // (从conn的IO中读取数据到内存缓冲buffer中) messageType, buffer, err := c.conn.ReadMessage() if err != nil { c.cancel() return } if messageType == websocket.PingMessage { c.updateActivity() continue } n := len(buffer) if err != nil { zlog.Ins().ErrorF("read msg head [read datalen=%d], error = %s", n, err.Error()) return } zlog.Ins().DebugF("read buffer %s \n", hex.EncodeToString(buffer[0:n])) // Update the Active status of heartbeat detection normally after reading data from the peer. // (正常读取到对端数据,更新心跳检测Active状态) if n > 0 && c.hc != nil { c.updateActivity() } // Handle custom protocol fragmentation and packet sticking issues add by uuxia 2023-03-21 // (处理自定义协议断粘包问题) if c.frameDecoder != nil { // Decode the 0-n bytes of data read. // (为读取到的0-n个字节的数据进行解码) bufArrays := c.frameDecoder.Decode(buffer) if bufArrays == nil { continue } for _, bytes := range bufArrays { zlog.Ins().DebugF("read buffer %s \n", hex.EncodeToString(bytes)) msg := zpack.NewMessage(uint32(len(bytes)), bytes) // Get the Request data requested by the current client. // (得到当前客户端请求的Request数据) req := GetRequest(c, msg) c.msgHandler.Execute(req) } } else { msg := zpack.NewMessage(uint32(n), buffer[0:n]) // Get the Request data requested by the current client. // (得到当前客户端请求的Request数据) req := GetRequest(c, msg) c.msgHandler.Execute(req) } } } } // Start starts the connection and makes it work. // (Start 启动连接,让当前连接开始工作) func (c *WsConnection) Start() { ctx := c.ctx if ctx == nil { ctx = context.Background() } c.ctx, c.cancel = context.WithCancel(ctx) // Execute the hook method according to the business needs of creating the connection passed in by the user. // (按照用户传递进来的创建连接时需要处理的业务,执行钩子方法) c.callOnConnStart() // Start the heartbeat check // (启动心跳检测) if c.hc != nil { c.hc.Start() c.updateActivity() } // 占用workerid c.workerID = useWorker(c) // Start the Goroutine for users to read data from the client. // (开启用户从客户端读取数据流程的Goroutine) go c.StartReader() select { case <-c.ctx.Done(): c.finalizer() // 归还workerid freeWorker(c) return } } // Stop stops the connection and ends its current state. // (停止连接,结束当前连接状态) func (c *WsConnection) Stop() { c.cancel() } func (c *WsConnection) GetConnection() net.Conn { return nil } func (c *WsConnection) GetWsConn() *websocket.Conn { return c.conn } // Deprecated: use GetConnection instead func (c *WsConnection) GetTCPConnection() net.Conn { return nil } func (c *WsConnection) GetConnID() uint64 { return c.connID } func (c *WsConnection) GetConnIdStr() string { return c.connIdStr } func (c *WsConnection) GetWorkerID() uint32 { return c.workerID } func (c *WsConnection) RemoteAddr() net.Addr { return c.conn.RemoteAddr() } func (c *WsConnection) LocalAddr() net.Addr { return c.conn.LocalAddr() } func (c *WsConnection) Send(data []byte) error { c.msgLock.Lock() defer c.msgLock.Unlock() if c.isClosed == true { return errors.New("WsConnection closed when send msg") } err := c.conn.WriteMessage(websocket.BinaryMessage, data) if err != nil { zlog.Ins().ErrorF("SendMsg err data = %+v, err = %+v", data, err) return err } return nil } func (c *WsConnection) SendToQueue(data []byte, opts ...ziface.MsgSendOption) error { c.msgLock.Lock() defer c.msgLock.Unlock() if c.msgBuffChan == nil { c.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen) // Start a goroutine for writing data back to the client, // which only reads data from MsgBuffChan and hasn't allocated memory or started the coroutine until SendBuffMsg is called // (开启用于写回客户端数据流程的Goroutine // 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程) go c.StartWriter() } opt := ziface.MsgSendOptionObj{ Timeout: 5 * time.Millisecond, } for _, o := range opts { o(&opt) } idleTimeout := time.NewTimer(opt.Timeout) defer idleTimeout.Stop() if c.isClosed == true { return errors.New("WsConnection closed when send buff msg") } if data == nil { zlog.Ins().ErrorF("Pack data is nil") return errors.New("Pack data is nil ") } select { case <-idleTimeout.C: return errors.New("send buff msg timeout") case c.msgBuffChan <- data: return nil } } // SendMsg directly sends the Message data to the remote TCP client. // (直接将Message数据发送数据给远程的TCP客户端) func (c *WsConnection) SendMsg(msgID uint32, data []byte) error { c.msgLock.Lock() defer c.msgLock.Unlock() if c.isClosed == true { return errors.New("WsConnection closed when send msg") } // Package data and send // (将data封包,并且发送) msg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data)) if err != nil { zlog.Ins().ErrorF("Pack error msg ID = %d", msgID) return errors.New("Pack error msg ") } // Write back to the client err = c.conn.WriteMessage(websocket.BinaryMessage, msg) if err != nil { zlog.Ins().ErrorF("SendMsg err msg ID = %d, data = %+v, err = %+v", msgID, string(msg), err) return err } return nil } // SendBuffMsg sends BuffMsg func (c *WsConnection) SendBuffMsg(msgID uint32, data []byte, opts ...ziface.MsgSendOption) error { c.msgLock.Lock() defer c.msgLock.Unlock() if c.msgBuffChan == nil { c.msgBuffChan = make(chan []byte, zconf.GlobalObject.MaxMsgChanLen) // Start the Goroutine for writing back to the client data stream // This method only reads data from MsgBuffChan, allocating memory and starting Goroutine without calling SendBuffMsg // (开启用于写回客户端数据流程的Goroutine // 此方法只读取MsgBuffChan中的数据没调用SendBuffMsg可以分配内存和启用协程) go c.StartWriter() } opt := ziface.MsgSendOptionObj{ Timeout: 5 * time.Millisecond, } for _, o := range opts { o(&opt) } idleTimeout := time.NewTimer(opt.Timeout) defer idleTimeout.Stop() if c.isClosed == true { return errors.New("WsConnection closed when send buff msg") } // Package data and send // (将data封包,并且发送) msg, err := c.packet.Pack(zpack.NewMsgPackage(msgID, data)) if err != nil { zlog.Ins().ErrorF("Pack error msg ID = %d", msgID) return errors.New("Pack error msg ") } // Send timeout select { case <-idleTimeout.C: return errors.New("send buff msg timeout") case c.msgBuffChan <- msg: return nil } } func (c *WsConnection) SetProperty(key string, value interface{}) { c.propertyLock.Lock() defer c.propertyLock.Unlock() if c.property == nil { c.property = make(map[string]interface{}) } c.property[key] = value } func (c *WsConnection) GetProperty(key string) (interface{}, error) { c.propertyLock.Lock() defer c.propertyLock.Unlock() if value, ok := c.property[key]; ok { return value, nil } return nil, errors.New("no property found") } func (c *WsConnection) RemoveProperty(key string) { c.propertyLock.Lock() defer c.propertyLock.Unlock() delete(c.property, key) } // Context returns the context for the connection, which can be used by user-defined goroutines to get the connection exit status. // (返回ctx,用于用户自定义的go程获取连接退出状态) func (c *WsConnection) Context() context.Context { return c.ctx } func (c *WsConnection) finalizer() { // If the user has registered a close callback for the connection, it should be called explicitly at this moment. // (如果用户注册了该连接的 关闭回调业务,那么在此刻应该显示调用) c.callOnConnStop() c.msgLock.Lock() defer c.msgLock.Unlock() // If the current connection is already closed. // (如果当前连接已经关闭) if c.isClosed == true { return } // Stop the heartbeat detector bound to the connection. // (关闭连接绑定的心跳检测器) if c.hc != nil { c.hc.Stop() } // Close the socket connection. // (关闭socket连接) _ = c.conn.Close() // Remove the connection from the connection manager. // (将连接从连接管理器中删除) if c.connManager != nil { c.connManager.Remove(c) } // Close all channels associated with this connection. // (关闭该连接全部管道) if c.msgBuffChan != nil { close(c.msgBuffChan) } // Set the flag to indicate that the connection is closed. (设置标志位) c.isClosed = true go func() { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("Conn finalizer panic: %v", err) } }() c.InvokeCloseCallbacks() }() zlog.Ins().InfoF("Conn Stop()...ConnID = %d", c.connID) } func (c *WsConnection) callOnConnStart() { if c.onConnStart != nil { zlog.Ins().InfoF("ZINX CallOnConnStart....") c.onConnStart(c) } } func (c *WsConnection) callOnConnStop() { if c.onConnStop != nil { zlog.Ins().InfoF("ZINX CallOnConnStop....") c.onConnStop(c) } } func (c *WsConnection) IsAlive() bool { if c.isClosed { return false } // Check the time duration since the last activity of the connection, if it exceeds the maximum heartbeat interval, // then the connection is considered dead // (检查连接最后一次活动时间,如果超过心跳间隔,则认为连接已经死亡) return time.Now().Sub(c.lastActivityTime) < zconf.GlobalObject.HeartbeatMaxDuration() } func (c *WsConnection) updateActivity() { c.lastActivityTime = time.Now() } func (c *WsConnection) SetHeartBeat(checker ziface.IHeartbeatChecker) { c.hc = checker } func (c *WsConnection) LocalAddrString() string { return c.localAddr } func (c *WsConnection) RemoteAddrString() string { return c.remoteAddr } func (c *WsConnection) GetName() string { return c.name } func (c *WsConnection) GetMsgHandler() ziface.IMsgHandle { return c.msgHandler } func (s *WsConnection) AddCloseCallback(handler, key interface{}, f func()) { if s.isClosed { return } s.closeCallbackMutex.Lock() defer s.closeCallbackMutex.Unlock() s.closeCallback.Add(handler, key, f) } func (s *WsConnection) RemoveCloseCallback(handler, key interface{}) { if s.isClosed { return } s.closeCallbackMutex.Lock() defer s.closeCallbackMutex.Unlock() s.closeCallback.Remove(handler, key) } func (s *WsConnection) InvokeCloseCallbacks() { s.closeCallbackMutex.RLock() defer s.closeCallbackMutex.RUnlock() s.closeCallback.Invoke() } ================================================ FILE: znotify/notify.go ================================================ package znotify import ( "errors" "fmt" "strconv" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/zutils" ) // ConnIDMap Establish a structure that maps user-defined IDs to connections // Map will have concurrent access issues, as well as looping through large amounts of data // Use the map structure of shard and lock storage to minimize lock granularity and lock holding time // (建立一个用户自定义ID和连接映射的结构 // map会存在并发问题,大量数据循环读取问题 // 使用分片加锁的map结构存储,尽量减少锁的粒度和锁的持有时间) type notify struct { connIdMap zutils.ShardLockMaps } func NewZNotify() ziface.Inotify { return ¬ify{ connIdMap: zutils.NewShardLockMaps(), } } func (n *notify) genConnStrId(connID uint64) string { strConnId := strconv.FormatUint(connID, 10) return strConnId } func (n *notify) ConnNums() int { return n.connIdMap.Count() } func (n *notify) HasIdConn(Id uint64) bool { strId := n.genConnStrId(Id) return n.connIdMap.Has(strId) } func (n *notify) SetNotifyID(Id uint64, conn ziface.IConnection) { strId := n.genConnStrId(Id) n.connIdMap.Set(strId, conn) } func (n *notify) GetNotifyByID(Id uint64) (ziface.IConnection, error) { strId := n.genConnStrId(Id) Conn, ok := n.connIdMap.Get(strId) if !ok { return nil, errors.New(" Not Find UserId") } return Conn.(ziface.IConnection), nil } func (n *notify) DelNotifyByID(Id uint64) { strId := n.genConnStrId(Id) n.connIdMap.Remove(strId) } func (n *notify) NotifyToConnByID(Id uint64, MsgId uint32, data []byte) error { Conn, err := n.GetNotifyByID(Id) if err != nil { return err } err = Conn.SendMsg(MsgId, data) if err != nil { fmt.Printf("Notify to %d err:%s \n", Id, err) return err } return nil } func (n *notify) NotifyAll(MsgId uint32, data []byte) error { n.connIdMap.IterCb(func(key string, v interface{}) { conn, _ := v.(ziface.IConnection) err := conn.SendMsg(MsgId, data) if err != nil { zlog.Ins().ErrorF("Notify to %s err:%s \n", key, err) } }) return nil } func (n *notify) NotifyBuffToConnByID(Id uint64, MsgId uint32, data []byte) error { Conn, err := n.GetNotifyByID(Id) if err != nil { return err } err = Conn.SendBuffMsg(MsgId, data) if err != nil { zlog.Ins().ErrorF("Notify to %d err:%s \n", Id, err) return err } return nil } func (n *notify) NotifyBuffAll(MsgId uint32, data []byte) error { n.connIdMap.IterCb(func(key string, v interface{}) { conn, _ := v.(ziface.IConnection) err := conn.SendBuffMsg(MsgId, data) if err != nil { zlog.Ins().ErrorF("Notify to %s err:%s \n", key, err) } }) return nil } ================================================ FILE: znotify/notify_test.go ================================================ package znotify import ( "fmt" "net" "strconv" "testing" "time" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/znet" "github.com/aceld/zinx/zpack" ) var nt = NewZNotify() type router struct { znet.BaseRouter } func (r *router) Handle(req ziface.IRequest) { id, _ := strconv.Atoi(string(req.GetData())) nt.SetNotifyID(uint64(id), req.GetConnection()) } func Server() { s := znet.NewUserConfServer(&zconf.Config{ Host: "127.0.0.1", TCPPort: 9991, Name: "NtTest", Version: "1", MaxConn: 10000, MaxPacketSize: 4096, WorkerPoolSize: 10, MaxWorkerTaskLen: 10, MaxMsgChanLen: 10, }) s.AddRouter(1, &router{}) s.Serve() } func Clinet() { //conf.ConfigInit() for i := 0; i < 9000; i++ { go func(i int) { conn, err := net.Dial("tcp", "127.0.0.1:9991") if err != nil { fmt.Println("net dial err:", err) return } defer conn.Close() //连接调用write方法写入数据 id := strconv.Itoa(i) dp := zpack.NewDataPack() msg, err := dp.Pack(zpack.NewMsgPackage(1, []byte(id))) if err != nil { return } _, err = conn.Write(msg) if err != nil { return } select {} }(i) } } func ClientJoin() { t := time.NewTicker(50 * time.Millisecond) i := 10000 for { select { case <-t.C: go func(i int) { conn, err := net.Dial("tcp", "127.0.0.1:9991") if err != nil { fmt.Println("net dial err:", err) return } defer conn.Close() id := strconv.Itoa(i) dp := zpack.NewDataPack() msg, err := dp.Pack(zpack.NewMsgPackage(1, []byte(id))) if err != nil { return } _, err = conn.Write(msg) if err != nil { return } select {} }(i) i++ } } } func TestAA(t *testing.T) { time.AfterFunc(5*time.Second, func() { }) time.Sleep(6 * time.Second) nt.ConnNums() } func BenchmarkNotify(b *testing.B) { fmt.Println("Begin BenchmarkNotify") time.Sleep(60 * time.Second) b.ResetTimer() for i := 0; i < b.N; i++ { nt.NotifyAll(1, []byte("雪下的是盐")) } nt.ConnNums() } func init() { go Server() go Clinet() go ClientJoin() } ================================================ FILE: zpack/datapack_ltv_littleendian.go ================================================ package zpack import ( "bytes" "encoding/binary" "errors" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" ) // DataPackLtv // LTV little-endian data packing and unpacking used by Zinx in its early days, compatible with previous applications // (Zinx早期使用的LTV 小端方式,兼容之前的应用) type DataPackLtv struct{} // NewDataPackLtv initializes a packing and unpacking instance // (封包拆包实例初始化方法) func NewDataPackLtv() ziface.IDataPack { return &DataPackLtv{} } // GetHeadLen returns the length of the message header // (获取包头长度方法) func (dp *DataPackLtv) GetHeadLen() uint32 { //ID uint32(4 bytes) + DataLen uint32(4 bytes) return defaultHeaderLen } // Pack packs the message (compresses the data) // (封包方法,压缩数据) func (dp *DataPackLtv) Pack(msg ziface.IMessage) ([]byte, error) { // Create a buffer to store the bytes // (创建一个存放bytes字节的缓冲) dataBuff := bytes.NewBuffer([]byte{}) // Write the data length if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetDataLen()); err != nil { return nil, err } // Write the message ID if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetMsgID()); err != nil { return nil, err } // Write the data if err := binary.Write(dataBuff, binary.LittleEndian, msg.GetData()); err != nil { return nil, err } return dataBuff.Bytes(), nil } // Unpack unpacks the message (decompresses the data) // (拆包方法,解压数据) func (dp *DataPackLtv) Unpack(binaryData []byte) (ziface.IMessage, error) { // Create an ioReader for the input binary data dataBuff := bytes.NewReader(binaryData) // Only unpack the header information to obtain the data length and message ID // (只解压head的信息,得到dataLen和msgID) msg := &Message{} // Read the data length if err := binary.Read(dataBuff, binary.LittleEndian, &msg.DataLen); err != nil { return nil, err } // Read the message ID if err := binary.Read(dataBuff, binary.LittleEndian, &msg.ID); err != nil { return nil, err } // Check whether the data length exceeds the maximum allowed packet size // (判断dataLen的长度是否超出我们允许的最大包长度) if zconf.GlobalObject.MaxPacketSize > 0 && msg.GetDataLen() > zconf.GlobalObject.MaxPacketSize { return nil, errors.New("too large msg data received") } // Only the header data needs to be unpacked, and then another data read is performed from the connection based on the header length // (这里只需要把head的数据拆包出来就可以了,然后再通过head的长度,再从conn读取一次数据) return msg, nil } ================================================ FILE: zpack/datapack_tlv_bigendian.go ================================================ package zpack import ( "bytes" "encoding/binary" "errors" "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/ziface" ) var defaultHeaderLen uint32 = 8 type DataPack struct{} // NewDataPack initializes a packing and unpacking instance // (封包拆包实例初始化方法) func NewDataPack() ziface.IDataPack { return &DataPack{} } // GetHeadLen returns the length of the message header // (获取包头长度方法) func (dp *DataPack) GetHeadLen() uint32 { //ID uint32(4 bytes) + DataLen uint32(4 bytes) return defaultHeaderLen } // Pack packs the message (compresses the data) // (封包方法,压缩数据) func (dp *DataPack) Pack(msg ziface.IMessage) ([]byte, error) { // Create a buffer to store the bytes // (创建一个存放bytes字节的缓冲) dataBuff := bytes.NewBuffer([]byte{}) // Write the message ID if err := binary.Write(dataBuff, binary.BigEndian, msg.GetMsgID()); err != nil { return nil, err } // Write the data length if err := binary.Write(dataBuff, binary.BigEndian, msg.GetDataLen()); err != nil { return nil, err } // Write the data if err := binary.Write(dataBuff, binary.BigEndian, msg.GetData()); err != nil { return nil, err } return dataBuff.Bytes(), nil } // Unpack unpacks the message (decompresses the data) // (拆包方法,解压数据) func (dp *DataPack) Unpack(binaryData []byte) (ziface.IMessage, error) { // Create an ioReader for the input binary data dataBuff := bytes.NewReader(binaryData) // Only unpack the header information to obtain the data length and message ID // (只解压head的信息,得到dataLen和msgID) msg := &Message{} // Read the data length if err := binary.Read(dataBuff, binary.BigEndian, &msg.ID); err != nil { return nil, err } // Read the message ID if err := binary.Read(dataBuff, binary.BigEndian, &msg.DataLen); err != nil { return nil, err } // Check whether the data length exceeds the maximum allowed packet size // (判断dataLen的长度是否超出我们允许的最大包长度) if zconf.GlobalObject.MaxPacketSize > 0 && msg.GetDataLen() > zconf.GlobalObject.MaxPacketSize { return nil, errors.New("too large msg data received") } // Only the header data needs to be unpacked, and then another data read is performed from the connection based on the header length // (这里只需要把head的数据拆包出来就可以了,然后再通过head的长度,再从conn读取一次数据) return msg, nil } ================================================ FILE: zpack/datapack_tlv_bigendian_test.go ================================================ package zpack import ( "fmt" "io" "net" "testing" "time" "github.com/aceld/zinx/ziface" ) // run in terminal: // go test -v ./znet -run=TestDataPack // This function is responsible for testing the functionality of data packet splitting and packaging. func TestDataPack(t *testing.T) { // Create a TCP server socket. listener, err := net.Listen("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("server listen err:", err) return } // Create a server goroutine, responsible for reading and parsing the data from the client goroutine that may contain sticky packets. go func() { for { conn, err := listener.Accept() if err != nil { fmt.Println("server accept err:", err) } // Handle client requests go func(conn net.Conn) { // Create a packet splitting and packaging object dp. dp := Factory().NewPack(ziface.ZinxDataPack) for { // 1. Read the head part of the stream first. headData := make([]byte, dp.GetHeadLen()) _, err := io.ReadFull(conn, headData) // ReadFull will fill msg until it's full if err != nil { fmt.Println("read head error") } // Unpack the headData byte stream into msg. msgHead, err := dp.Unpack(headData) if err != nil { fmt.Println("server unpack err:", err) return } if msgHead.GetDataLen() > 0 { // msg has data, read data again. msg := msgHead.(*Message) msg.Data = make([]byte, msg.GetDataLen()) // Read the byte stream from io based on dataLen. _, err := io.ReadFull(conn, msg.Data) if err != nil { fmt.Println("server unpack data err:", err) return } fmt.Println("==> Recv Msg: ID=", msg.ID, ", len=", msg.DataLen, ", data=", string(msg.Data)) } } }(conn) } }() // Client goroutine, responsible for simulating data containing sticky packets and sending it to the server. go func() { conn, err := net.Dial("tcp", "127.0.0.1:7777") if err != nil { fmt.Println("client dial err:", err) return } // Create a packet splitting and packaging object dp. dp := Factory().NewPack(ziface.ZinxDataPack) // Package msg1. msg1 := &Message{ ID: 0, DataLen: 5, Data: []byte{'h', 'e', 'l', 'l', 'o'}, } sendData1, err := dp.Pack(msg1) if err != nil { fmt.Println("client pack msg1 err:", err) return } // Package msg2. msg2 := &Message{ ID: 1, DataLen: 7, Data: []byte{'w', 'o', 'r', 'l', 'd', '!', '!'}, } sendData2, err := dp.Pack(msg2) if err != nil { fmt.Println("client temp msg2 err:", err) return } // Concatenate sendData1 and sendData2 to create a sticky packet. sendData1 = append(sendData1, sendData2...) // Write data to the server. conn.Write(sendData1) }() // Block the client. select { case <-time.After(time.Second): return } } ================================================ FILE: zpack/message.go ================================================ package zpack // Message structure for messages type Message struct { DataLen uint32 // Length of the message ID uint32 // ID of the message Data []byte // Content of the message rawData []byte // Raw data of the message } func NewMsgPackage(ID uint32, data []byte) *Message { return &Message{ ID: ID, DataLen: uint32(len(data)), Data: data, rawData: data, } } func NewMessage(len uint32, data []byte) *Message { return &Message{ DataLen: len, Data: data, rawData: data, } } func NewMessageByMsgId(id uint32, len uint32, data []byte) *Message { return &Message{ ID: id, DataLen: len, Data: data, rawData: data, } } func (msg *Message) Init(ID uint32, data []byte) { msg.ID = ID msg.Data = data msg.rawData = data msg.DataLen = uint32(len(data)) } func (msg *Message) GetDataLen() uint32 { return msg.DataLen } func (msg *Message) GetMsgID() uint32 { return msg.ID } func (msg *Message) GetData() []byte { return msg.Data } func (msg *Message) GetRawData() []byte { return msg.rawData } func (msg *Message) SetDataLen(len uint32) { msg.DataLen = len } func (msg *Message) SetMsgID(msgID uint32) { msg.ID = msgID } func (msg *Message) SetData(data []byte) { msg.Data = data } ================================================ FILE: zpack/packfactory.go ================================================ package zpack import ( "sync" "github.com/aceld/zinx/ziface" ) var pack_once sync.Once type pack_factory struct{} var factoryInstance *pack_factory /* Factory Generates different packaging and unpackaging methods, singleton (生成不同封包解包的方式,单例) */ func Factory() *pack_factory { pack_once.Do(func() { factoryInstance = new(pack_factory) }) return factoryInstance } // NewPack creates a concrete packaging and unpackaging object // (NewPack 创建一个具体的拆包解包对象) func (f *pack_factory) NewPack(kind string) ziface.IDataPack { var dataPack ziface.IDataPack switch kind { // Zinx standard default packaging and unpackaging method // (Zinx 标准默认封包拆包方式) case ziface.ZinxDataPack: dataPack = NewDataPack() case ziface.ZinxDataPackOld: dataPack = NewDataPackLtv() // case for custom packaging and unpackaging methods // (case 自定义封包拆包方式case) default: dataPack = NewDataPack() } return dataPack } ================================================ FILE: ztimer/delayfunc.go ================================================ package ztimer /** * @Author: Aceld * @Date: 2019/4/30 11:57 * @Mail: danbing.at@gmail.com */ import ( "fmt" "reflect" "github.com/aceld/zinx/zlog" ) /* 定义一个延迟调用函数 延迟调用函数就是 时间定时器超时的时候,触发的事先注册好的 回调函数 */ // DelayFunc 延迟调用函数对象 type DelayFunc struct { f func(...interface{}) //f : 延迟函数调用原型 args []interface{} //args: 延迟调用函数传递的形参 } // NewDelayFunc 创建一个延迟调用函数 func NewDelayFunc(f func(v ...interface{}), args []interface{}) *DelayFunc { return &DelayFunc{ f: f, args: args, } } // String 打印当前延迟函数的信息,用于日志记录 func (df *DelayFunc) String() string { return fmt.Sprintf("{DelayFun:%s, args:%v}", reflect.TypeOf(df.f).Name(), df.args) } // Call 执行延迟函数---如果执行失败,抛出异常 func (df *DelayFunc) Call() { defer func() { if err := recover(); err != nil { zlog.Ins().ErrorF("%s Call err: %v", df.String(), err) } }() //调用定时器超时函数 df.f(df.args...) } ================================================ FILE: ztimer/delayfunc_test.go ================================================ /** * @Author: Aceld * @Date: 2019/4/30 15:17 * @Mail: danbing.at@gmail.com * * 针对 delayFunc.go 做单元测试,主要测试延迟函数结构体是否正常使用 */ package ztimer import ( "fmt" "testing" ) func SayHello(message ...interface{}) { fmt.Println(message[0].(string), " ", message[1].(string)) } func TestDelayfunc(t *testing.T) { df := NewDelayFunc(SayHello, []interface{}{"hello", "zinx!"}) fmt.Println("df.String() = ", df.String()) df.Call() } ================================================ FILE: ztimer/timer.go ================================================ package ztimer /** * @Author: Aceld * @Date: 2019/4/30 17:42 * @Mail: danbing.at@gmail.com */ import ( "time" ) const ( //HourName 小时 HourName = "HOUR" //HourInterval 小时间隔ms为精度 HourInterval = 60 * 60 * 1e3 //HourScales 12小时制 HourScales = 12 //MinuteName 分钟 MinuteName = "MINUTE" //MinuteInterval 每分钟时间间隔 MinuteInterval = 60 * 1e3 //MinuteScales 60分钟 MinuteScales = 60 //SecondName 秒 SecondName = "SECOND" //SecondInterval 秒的间隔 SecondInterval = 1e3 //SecondScales 60秒 SecondScales = 60 //TimersMaxCap //每个时间轮刻度挂载定时器的最大个数 TimersMaxCap = 2048 ) /* 注意: 有关时间的几个换算 time.Second(秒) = time.Millisecond * 1e3 time.Millisecond(毫秒) = time.Microsecond * 1e3 time.Microsecond(微秒) = time.Nanosecond * 1e3 time.Now().UnixNano() ==> time.Nanosecond (纳秒) */ // Timer 定时器实现 type Timer struct { //延迟调用函数 delayFunc *DelayFunc //调用时间(unix 时间, 单位ms) unixts int64 } // UnixMilli 返回1970-1-1至今经历的毫秒数 func UnixMilli() int64 { return time.Now().UnixNano() / 1e6 } // NewTimerAt 创建一个定时器,在指定的时间触发 定时器方法 df: DelayFunc类型的延迟调用函数类型;unixNano: unix计算机从1970-1-1至今经历的纳秒数 func NewTimerAt(df *DelayFunc, unixNano int64) *Timer { return &Timer{ delayFunc: df, unixts: unixNano / 1e6, //将纳秒转换成对应的毫秒 ms ,定时器以ms为最小精度 } } // NewTimerAfter 创建一个定时器,在当前时间延迟duration之后触发 定时器方法 func NewTimerAfter(df *DelayFunc, duration time.Duration) *Timer { return NewTimerAt(df, time.Now().UnixNano()+int64(duration)) } // Run 启动定时器,用一个go承载 func (t *Timer) Run() { go func() { now := UnixMilli() //设置的定时器是否在当前时间之后 if t.unixts > now { //睡眠,直至时间超时,已微秒为单位进行睡眠 time.Sleep(time.Duration(t.unixts-now) * time.Millisecond) } //调用事先注册好的超时延迟方法 t.delayFunc.Call() }() } ================================================ FILE: ztimer/timer_test.go ================================================ /** * @Author: Aceld * @Date: 2019/5/5 10:14 * @Mail: danbing.at@gmail.com * * 针对timer.go做单元测试,主要测试定时器相关接口 依赖模块delayFunc.go */ package ztimer import ( "fmt" "testing" "time" ) // 定义一个超时函数 func myFunc(v ...interface{}) { fmt.Printf("No.%d function calld. delay %d second(s)\n", v[0].(int), v[1].(int)) } func TestTimer(t *testing.T) { for i := 0; i < 5; i++ { go func(i int) { NewTimerAfter(NewDelayFunc(myFunc, []interface{}{i, 2 * i}), time.Duration(2*i)*time.Second).Run() }(i) } //主进程等待其他go,由于Run()方法是用一个新的go承载延迟方法,这里不能用waitGroup time.Sleep(1 * time.Minute) } ================================================ FILE: ztimer/timerscheduler.go ================================================ package ztimer /** * @Author: Aceld * @Date: 2019/5/8 17:43 * @Mail: danbing.at@gmail.com * * 时间轮调度器 * 依赖模块,delayfunc.go timer.go timewheel.go */ import ( "math" "sync" "time" "github.com/aceld/zinx/zlog" ) const ( //MaxChanBuff 默认缓冲触发函数队列大小 MaxChanBuff = 2048 //MaxTimeDelay 默认最大误差时间 MaxTimeDelay = 100 ) // TimerScheduler 计时器调度器 type TimerScheduler struct { //当前调度器的最高级时间轮 tw *TimeWheel //定时器编号累加器 IDGen uint32 //已经触发定时器的channel triggerChan chan *DelayFunc //互斥锁 sync.RWMutex } // NewTimerScheduler 返回一个定时器调度器 ,主要创建分层定时器,并做关联,并依次启动 func NewTimerScheduler() *TimerScheduler { //创建秒级时间轮 secondTw := NewTimeWheel(SecondName, SecondInterval, SecondScales, TimersMaxCap) //创建分钟级时间轮 minuteTw := NewTimeWheel(MinuteName, MinuteInterval, MinuteScales, TimersMaxCap) //创建小时级时间轮 hourTw := NewTimeWheel(HourName, HourInterval, HourScales, TimersMaxCap) //将分层时间轮做关联 hourTw.AddTimeWheel(minuteTw) minuteTw.AddTimeWheel(secondTw) //时间轮运行 secondTw.Run() minuteTw.Run() hourTw.Run() return &TimerScheduler{ tw: hourTw, triggerChan: make(chan *DelayFunc, MaxChanBuff), } } // CreateTimerAt 创建一个定点Timer 并将Timer添加到分层时间轮中, 返回Timer的tID func (ts *TimerScheduler) CreateTimerAt(df *DelayFunc, unixNano int64) (uint32, error) { ts.Lock() defer ts.Unlock() ts.IDGen++ return ts.IDGen, ts.tw.AddTimer(ts.IDGen, NewTimerAt(df, unixNano)) } // CreateTimerAfter 创建一个延迟Timer 并将Timer添加到分层时间轮中, 返回Timer的tID func (ts *TimerScheduler) CreateTimerAfter(df *DelayFunc, duration time.Duration) (uint32, error) { ts.Lock() defer ts.Unlock() ts.IDGen++ return ts.IDGen, ts.tw.AddTimer(ts.IDGen, NewTimerAfter(df, duration)) } // CancelTimer 删除timer func (ts *TimerScheduler) CancelTimer(tID uint32) { ts.Lock() defer ts.Unlock() tw := ts.tw for tw != nil { tw.RemoveTimer(tID) tw = tw.nextTimeWheel } } // GetTriggerChan 获取计时结束的延迟执行函数通道 func (ts *TimerScheduler) GetTriggerChan() chan *DelayFunc { return ts.triggerChan } // Start 非阻塞的方式启动timerSchedule func (ts *TimerScheduler) Start() { go func() { for { //当前时间 now := UnixMilli() //获取最近MaxTimeDelay 毫秒的超时定时器集合 timerList := ts.tw.GetTimerWithIn(MaxTimeDelay * time.Millisecond) for _, timer := range timerList { if math.Abs(float64(now-timer.unixts)) > MaxTimeDelay { //已经超时的定时器,报警 zlog.Error("want call at ", timer.unixts, "; real call at", now, "; delay ", now-timer.unixts) } ts.triggerChan <- timer.delayFunc } time.Sleep(MaxTimeDelay / 2 * time.Millisecond) } }() } // NewAutoExecTimerScheduler 时间轮定时器 自动调度 func NewAutoExecTimerScheduler() *TimerScheduler { //创建一个调度器 autoExecScheduler := NewTimerScheduler() //启动调度器 autoExecScheduler.Start() //永久从调度器中获取超时 触发的函数 并执行 go func() { delayFuncChan := autoExecScheduler.GetTriggerChan() for df := range delayFuncChan { go df.Call() } }() return autoExecScheduler } ================================================ FILE: ztimer/timerscheduler_test.go ================================================ package ztimer /** * @Author: Aceld(刘丹冰) * @Date: 2019/5/9 10:14 * @Mail: danbing.at@gmail.com * * 时间轮定时器调度器单元测试 */ import ( "fmt" "log" "testing" "time" "github.com/aceld/zinx/zlog" ) // 触发函数 func foo(args ...interface{}) { fmt.Printf("I am No. %d function, delay %d ms\n", args[0].(int), args[1].(int)) } // 手动创建调度器运转时间轮 func TestNewTimerScheduler(t *testing.T) { timerScheduler := NewTimerScheduler() timerScheduler.Start() //在scheduler中添加timer for i := 1; i < 2000; i++ { f := NewDelayFunc(foo, []interface{}{i, i * 3}) tID, err := timerScheduler.CreateTimerAfter(f, time.Duration(3*i)*time.Millisecond) if err != nil { zlog.Error("create timer error", tID, err) break } } //执行调度器触发函数 go func() { delayFuncChan := timerScheduler.GetTriggerChan() for df := range delayFuncChan { df.Call() } }() //阻塞等待 select {} } // 采用自动调度器运转时间轮 func TestNewAutoExecTimerScheduler(t *testing.T) { autoTS := NewAutoExecTimerScheduler() //给调度器添加Timer for i := 0; i < 2000; i++ { f := NewDelayFunc(foo, []interface{}{i, i * 3}) tID, err := autoTS.CreateTimerAfter(f, time.Duration(3*i)*time.Millisecond) if err != nil { zlog.Error("create timer error", tID, err) break } } //阻塞等待 select {} } // 测试取消一个定时器 func TestCancelTimerScheduler(t *testing.T) { Scheduler := NewAutoExecTimerScheduler() f1 := NewDelayFunc(foo, []interface{}{3, 3}) f2 := NewDelayFunc(foo, []interface{}{5, 5}) timerID1, err := Scheduler.CreateTimerAfter(f1, time.Duration(3)*time.Second) if nil != err { t.Log("Scheduler.CreateTimerAfter(f1, time.Duration(3)*time.Second)", "err:", err) } timerID2, err := Scheduler.CreateTimerAfter(f2, time.Duration(5)*time.Second) if nil != err { t.Log("Scheduler.CreateTimerAfter(f1, time.Duration(3)*time.Second)", "err:", err) } log.Printf("timerID1=%d ,timerID2=%d\n", timerID1, timerID2) Scheduler.CancelTimer(timerID1) //删除timerID1 //阻塞等待 select {} } ================================================ FILE: ztimer/timewheel.go ================================================ package ztimer /** * @Author: Aceld * @Date: 2019/4/30 11:57 * @Mail: danbing.at@gmail.com */ import ( "errors" "fmt" "sync" "time" "github.com/aceld/zinx/zlog" ) /* tips: 一个网络服务程序时需要管理大量客户端连接的, 其中每个客户端连接都需要管理它的 timeout 时间。 通常连接的超时管理一般设置为30~60秒不等,并不需要太精确的时间控制。 另外由于服务端管理着多达数万到数十万不等的连接数, 因此我们没法为每个连接使用一个Timer,那样太消耗资源不现实。 用时间轮的方式来管理和维护大量的timer调度,会解决上面的问题。 */ // TimeWheel 时间轮 type TimeWheel struct { //TimeWheel的名称 name string //刻度的时间间隔,单位ms interval int64 //每个时间轮上的刻度数 scales int //当前时间指针的指向 curIndex int //每个刻度所存放的timer定时器的最大容量 maxCap int //当前时间轮上的所有timer timerQueue map[int]map[uint32]*Timer //map[int] VALUE 其中int表示当前时间轮的刻度, // map[int] map[uint32] *Timer, uint32表示Timer的ID号 //下一层时间轮 nextTimeWheel *TimeWheel //互斥锁(继承RWMutex的 RWLock,UnLock 等方法) sync.RWMutex } // NewTimeWheel 创建一个时间轮 func NewTimeWheel(name string, interval int64, scales int, maxCap int) *TimeWheel { // name:时间轮的名称 // interval:每个刻度之间的duration时间间隔 // scales:当前时间轮的轮盘一共多少个刻度(如我们正常的时钟就是12个刻度) // maxCap: 每个刻度所最大保存的Timer定时器个数 tw := &TimeWheel{ name: name, interval: interval, scales: scales, maxCap: maxCap, timerQueue: make(map[int]map[uint32]*Timer, scales), } //初始化map for i := 0; i < scales; i++ { tw.timerQueue[i] = make(map[uint32]*Timer, maxCap) } zlog.Ins().InfoF("Init timerWhell name = %s is Done!", tw.name) return tw } /* 将一个timer定时器加入到分层时间轮中 tID: 每个定时器timer的唯一标识 t: 当前被加入时间轮的定时器 forceNext: 是否强制的将定时器添加到下一层时间轮 我们采用的算法是: 如果当前timer的超时时间间隔 大于一个刻度,那么进行hash计算 找到对应的刻度上添加 如果当前的timer的超时时间间隔 小于一个刻度 : 如果没有下一轮时间轮 */ func (tw *TimeWheel) addTimer(tID uint32, t *Timer, forceNext bool) error { defer func() error { if err := recover(); err != nil { errstr := fmt.Sprintf("addTimer function err : %s", err) zlog.Ins().ErrorF("addTimer function err : %s", err) return errors.New(errstr) } return nil }() //得到当前的超时时间间隔(ms)毫秒为单位 delayInterval := t.unixts - UnixMilli() //如果当前的超时时间 大于一个刻度的时间间隔 if delayInterval >= tw.interval { //得到需要跨越几个刻度 dn := delayInterval / tw.interval //在对应的刻度上的定时器Timer集合map加入当前定时器(由于是环形,所以要求余) tw.timerQueue[(tw.curIndex+int(dn))%tw.scales][tID] = t return nil } //如果当前的超时时间,小于一个刻度的时间间隔,并且当前时间轮没有下一层,经度最小的时间轮 if delayInterval < tw.interval && tw.nextTimeWheel == nil { if forceNext == true { //如果设置为强制移至下一个刻度,那么将定时器移至下一个刻度 //这种情况,主要是时间轮自动轮转的情况 //因为这是底层时间轮,该定时器在转动的时候,如果没有被调度者取走的话,该定时器将不会再被发现 //因为时间轮刻度已经过去,如果不强制把该定时器Timer移至下时刻,就永远不会被取走并触发调用 //所以这里强制将timer移至下个刻度的集合中,等待调用者在下次轮转之前取走该定时器 tw.timerQueue[(tw.curIndex+1)%tw.scales][tID] = t } else { //如果手动添加定时器,那么直接将timer添加到对应底层时间轮的当前刻度集合中 tw.timerQueue[tw.curIndex][tID] = t } return nil } //如果当前的超时时间,小于一个刻度的时间间隔,并且有下一层时间轮 if delayInterval < tw.interval { return tw.nextTimeWheel.AddTimer(tID, t) } return nil } // AddTimer 添加一个timer到一个时间轮中(非时间轮自转情况) func (tw *TimeWheel) AddTimer(tID uint32, t *Timer) error { tw.Lock() defer tw.Unlock() return tw.addTimer(tID, t, false) } // RemoveTimer 删除一个定时器,根据定时器的ID func (tw *TimeWheel) RemoveTimer(tID uint32) { tw.Lock() defer tw.Unlock() for i := 0; i < tw.scales; i++ { if _, ok := tw.timerQueue[i][tID]; ok { delete(tw.timerQueue[i], tID) } } } // AddTimeWheel 给一个时间轮添加下层时间轮 比如给小时时间轮添加分钟时间轮,给分钟时间轮添加秒时间轮 func (tw *TimeWheel) AddTimeWheel(next *TimeWheel) { tw.nextTimeWheel = next zlog.Ins().InfoF("Add timerWhell[%s]'s next [%s] is succ!", tw.name, next.name) } /* 启动时间轮 */ func (tw *TimeWheel) run() { for { //时间轮每间隔interval一刻度时间,触发转动一次 time.Sleep(time.Duration(tw.interval) * time.Millisecond) tw.Lock() //取出挂载在当前刻度的全部定时器 curTimers := tw.timerQueue[tw.curIndex] //当前定时器要重新添加 所给当前刻度再重新开辟一个map Timer容器 tw.timerQueue[tw.curIndex] = make(map[uint32]*Timer, tw.maxCap) for tID, timer := range curTimers { //这里属于时间轮自动转动,forceNext设置为true tw.addTimer(tID, timer, true) } //取出下一个刻度 挂载的全部定时器 进行重新添加 (为了安全起见,待考慮) nextTimers := tw.timerQueue[(tw.curIndex+1)%tw.scales] tw.timerQueue[(tw.curIndex+1)%tw.scales] = make(map[uint32]*Timer, tw.maxCap) for tID, timer := range nextTimers { tw.addTimer(tID, timer, true) } //当前刻度指针 走一格 tw.curIndex = (tw.curIndex + 1) % tw.scales tw.Unlock() } } // Run 非阻塞的方式让时间轮转起来 func (tw *TimeWheel) Run() { go tw.run() zlog.Ins().InfoF("timerwheel name = %s is running...", tw.name) } // GetTimerWithIn 获取定时器在一段时间间隔内的Timer func (tw *TimeWheel) GetTimerWithIn(duration time.Duration) map[uint32]*Timer { //最终触发定时器的一定是挂载最底层时间轮上的定时器 //1 找到最底层时间轮 leaftw := tw for leaftw.nextTimeWheel != nil { leaftw = leaftw.nextTimeWheel } leaftw.Lock() defer leaftw.Unlock() //返回的Timer集合 timerList := make(map[uint32]*Timer) now := UnixMilli() //取出当前时间轮刻度内全部Timer for tID, timer := range leaftw.timerQueue[leaftw.curIndex] { if timer.unixts-now < int64(duration/1e6) { //当前定时器已经超时 timerList[tID] = timer //定时器已经超时被取走,从当前时间轮上 摘除该定时器 delete(leaftw.timerQueue[leaftw.curIndex], tID) } } return timerList } ================================================ FILE: ztimer/timewheel_test.go ================================================ /** * @Author: Aceld * @Date: 2019/5/7 18:00 * @Mail: danbing.at@gmail.com * * 针对 timer_wheel.go 时间轮api 做单元测试, 主要测试时间轮运转功能 * 依赖模块 delayFunc.go timer.go */ package ztimer import ( "fmt" "testing" "time" ) func TestTimerWheel(t *testing.T) { //创建秒级时间轮 secondTw := NewTimeWheel(SecondName, SecondInterval, SecondScales, TimersMaxCap) //创建分钟级时间轮 minuteTw := NewTimeWheel(MinuteName, MinuteInterval, MinuteScales, TimersMaxCap) //创建小时级时间轮 hourTw := NewTimeWheel(HourName, HourInterval, HourScales, TimersMaxCap) // 将分层时间轮做关联 hourTw.AddTimeWheel(minuteTw) minuteTw.AddTimeWheel(secondTw) fmt.Println("init timewheels done!") //===== > 以上为初始化分层时间轮 <==== //给时间轮添加定时器 timer1 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{1, 10}), 10*time.Second) _ = hourTw.AddTimer(1, timer1) fmt.Println("add timer 1 done!") //给时间轮添加定时器 timer2 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{2, 20}), 20*time.Second) _ = hourTw.AddTimer(2, timer2) fmt.Println("add timer 2 done!") //给时间轮添加定时器 timer3 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{3, 30}), 30*time.Second) _ = hourTw.AddTimer(3, timer3) fmt.Println("add timer 3 done!") //给时间轮添加定时器 timer4 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{4, 40}), 40*time.Second) _ = hourTw.AddTimer(4, timer4) fmt.Println("add timer 4 done!") //给时间轮添加定时器 timer5 := NewTimerAfter(NewDelayFunc(myFunc, []interface{}{5, 50}), 50*time.Second) _ = hourTw.AddTimer(5, timer5) fmt.Println("add timer 5 done!") //时间轮运行 secondTw.Run() minuteTw.Run() hourTw.Run() fmt.Println("timewheels are run!") go func() { n := 0.0 for { fmt.Println("tick...", n) //取出近1ms的超时定时器有哪些 timers := hourTw.GetTimerWithIn(1000 * time.Millisecond) for _, timer := range timers { //调用定时器方法 timer.delayFunc.Call() } time.Sleep(500 * time.Millisecond) n += 0.5 } }() //主进程等待其他go,由于Run()方法是用一个新的go承载延迟方法,这里不能用waitGroup time.Sleep(10 * time.Minute) } ================================================ FILE: zutils/hash.go ================================================ package zutils const ( Prime = 16777619 HashVal = 2166136261 ) type IHash interface { Sum(string) uint32 } type Fnv32Hash struct{} func DefaultHash() IHash { return &Fnv32Hash{} } // fnv32 algorithm func (f *Fnv32Hash) Sum(key string) uint32 { hashVal := uint32(HashVal) prime := uint32(Prime) keyLength := len(key) for i := 0; i < keyLength; i++ { hashVal *= prime hashVal ^= uint32(key[i]) } return hashVal } ================================================ FILE: zutils/shard_lock_map.go ================================================ // Package zutils provides utility functions and data structures for the Zinx framework. // This package includes a high-performance sharded concurrent map implementation. package zutils import ( "encoding/json" "sync" ) // DefaultShardCount is the default number of shards for the concurrent map. // A higher number reduces lock contention but increases memory overhead. var DefaultShardCount = 32 // ShardLockMaps is a thread-safe map of type string:Anything. // To avoid lock bottlenecks, this map is divided into several shards. // Each shard has its own read-write mutex, allowing concurrent access // to different shards without blocking. type ShardLockMaps struct { shards []*SingleShardMap hash IHash shardCount int } // SingleShardMap is a thread-safe string to anything map. // It represents a single shard within the ShardLockMaps. type SingleShardMap struct { items map[string]interface{} sync.RWMutex } // createShardLockMaps Creates a new concurrent map. func createShardLockMaps(hash IHash, shardCount int) ShardLockMaps { slm := ShardLockMaps{ shards: make([]*SingleShardMap, shardCount), hash: hash, shardCount: shardCount, } for i := 0; i < shardCount; i++ { slm.shards[i] = &SingleShardMap{items: make(map[string]interface{})} } return slm } // NewShardLockMaps creates a new ShardLockMaps with default shard count. // Example usage: // // m := NewShardLockMaps() // m.Set("key", "value") // if val, ok := m.Get("key"); ok { // fmt.Println(val) // } func NewShardLockMaps() ShardLockMaps { return createShardLockMaps(DefaultHash(), DefaultShardCount) } // NewShardLockMapsWithCount creates a new ShardLockMaps with custom shard count. // Use this when you need to tune performance based on your workload. // More shards = less lock contention but more memory overhead. func NewShardLockMapsWithCount(shardCount int) ShardLockMaps { return createShardLockMaps(DefaultHash(), shardCount) } // NewWithCustomHash creates a new ShardLockMaps with custom hash function. // Use this when you need a different hash distribution strategy. func NewWithCustomHash(hash IHash) ShardLockMaps { return createShardLockMaps(hash, DefaultShardCount) } // NewWithCustomHashAndCount creates a new ShardLockMaps with custom hash function and shard count. // This provides maximum flexibility for performance tuning. func NewWithCustomHashAndCount(hash IHash, shardCount int) ShardLockMaps { return createShardLockMaps(hash, shardCount) } // GetShard returns shard under given key func (slm ShardLockMaps) GetShard(key string) *SingleShardMap { return slm.shards[slm.hash.Sum(key)%uint32(slm.shardCount)] } // Count returns the number of elements within the map. func (slm ShardLockMaps) Count() int { count := 0 for i := 0; i < slm.shardCount; i++ { shard := slm.shards[i] shard.RLock() count += len(shard.items) shard.RUnlock() } return count } // Get retrieves an element from map under given key. func (slm ShardLockMaps) Get(key string) (interface{}, bool) { shard := slm.GetShard(key) shard.RLock() val, ok := shard.items[key] shard.RUnlock() return val, ok } // Set Sets the given value under the specified key. func (slm ShardLockMaps) Set(key string, value interface{}) { shard := slm.GetShard(key) shard.Lock() shard.items[key] = value shard.Unlock() } // SetNX Sets the given value under the specified key if no value was associated with it. func (slm ShardLockMaps) SetNX(key string, value interface{}) bool { shard := slm.GetShard(key) shard.Lock() _, ok := shard.items[key] if !ok { shard.items[key] = value } shard.Unlock() return !ok } // MSet Sets the given value under the specified key. func (slm ShardLockMaps) MSet(data map[string]interface{}) { for key, value := range data { shard := slm.GetShard(key) shard.Lock() shard.items[key] = value shard.Unlock() } } // Has Looks up an item under specified key func (slm ShardLockMaps) Has(key string) bool { shard := slm.GetShard(key) shard.RLock() _, ok := shard.items[key] shard.RUnlock() return ok } // Remove removes an element from the map. func (slm ShardLockMaps) Remove(key string) { shard := slm.GetShard(key) shard.Lock() delete(shard.items, key) shard.Unlock() } // RemoveCb is a callback executed in a map.RemoveCb() call, while Lock is held // If returns true, the element will be removed from the map type RemoveCb func(key string, v interface{}, exists bool) bool // RemoveCb locks the shard containing the key, retrieves its current value and calls the callback with those params // If callback returns true and element exists, it will remove it from the map // Returns the value returned by the callback (even if element was not present in the map) func (slm ShardLockMaps) RemoveCb(key string, cb RemoveCb) bool { shard := slm.GetShard(key) shard.Lock() v, ok := shard.items[key] remove := cb(key, v, ok) if remove && ok { delete(shard.items, key) } shard.Unlock() return remove } // Pop removes an element from the map and returns it func (slm ShardLockMaps) Pop(key string) (v interface{}, exists bool) { shard := slm.GetShard(key) shard.Lock() v, exists = shard.items[key] delete(shard.items, key) shard.Unlock() return v, exists } // GetOrSet gets the value for the given key, or sets it if it doesn't exist. // Returns the value and whether it was set (true) or already existed (false). func (slm ShardLockMaps) GetOrSet(key string, value interface{}) (interface{}, bool) { if v, ok := slm.Get(key); ok { return v, false } return slm.doSetWithLockCheck(key, value) } // GetOrSetFunc gets the value for the given key, or sets it using the provided function if it doesn't exist. // The function f is called outside the lock to avoid deadlocks. // Returns the value and whether it was set (true) or already existed (false). func (slm ShardLockMaps) GetOrSetFunc(key string, f func(key string) interface{}) (interface{}, bool) { if v, ok := slm.Get(key); ok { return v, false } return slm.doSetWithLockCheck(key, f(key)) } // GetOrSetFuncLock gets the value for the given key, or sets it using the provided function if it doesn't exist. // The function f is called inside the lock for atomic operations. // WARNING: Do not perform operations on the container within f to avoid deadlocks. // Returns the value and whether it was set (true) or already existed (false). func (slm ShardLockMaps) GetOrSetFuncLock(key string, f func(key string) interface{}) (interface{}, bool) { if v, ok := slm.Get(key); ok { return v, false } return slm.doSetWithLockCheckWithFunc(key, f) } // doSetWithLockCheck performs a set operation with lock checking func (slm ShardLockMaps) doSetWithLockCheck(key string, val interface{}) (interface{}, bool) { shard := slm.GetShard(key) shard.Lock() defer shard.Unlock() if got, ok := shard.items[key]; ok { return got, false } shard.items[key] = val return val, true } // doSetWithLockCheckWithFunc performs a set operation with function execution inside lock func (slm ShardLockMaps) doSetWithLockCheckWithFunc(key string, f func(key string) interface{}) (interface{}, bool) { shard := slm.GetShard(key) shard.Lock() defer shard.Unlock() if got, ok := shard.items[key]; ok { return got, false } val := f(key) shard.items[key] = val return val, true } // Clear removes all items from map. func (slm ShardLockMaps) Clear() { for item := range slm.IterBuffered() { slm.Remove(item.Key) } } // LockFuncWithKey executes a function with write lock on the shard containing the key. // WARNING: Do not perform operations on the container within f to avoid deadlocks. func (slm ShardLockMaps) LockFuncWithKey(key string, f func(shardData map[string]interface{})) { shard := slm.GetShard(key) shard.Lock() defer shard.Unlock() f(shard.items) } // RLockFuncWithKey executes a function with read lock on the shard containing the key. // WARNING: Do not perform write operations on the container within f to avoid deadlocks. func (slm ShardLockMaps) RLockFuncWithKey(key string, f func(shardData map[string]interface{})) { shard := slm.GetShard(key) shard.RLock() defer shard.RUnlock() f(shard.items) } // LockFunc executes a function with write lock on all shards. // WARNING: Do not perform operations on the container within f to avoid deadlocks. func (slm ShardLockMaps) LockFunc(f func(shardData map[string]interface{})) { for _, shard := range slm.shards { shard.Lock() f(shard.items) shard.Unlock() } } // RLockFunc executes a function with read lock on all shards. // WARNING: Do not perform write operations on the container within f to avoid deadlocks. func (slm ShardLockMaps) RLockFunc(f func(shardData map[string]interface{})) { for _, shard := range slm.shards { shard.RLock() f(shard.items) shard.RUnlock() } } // ClearWithFuncLock clears all items with a callback function executed under lock. // WARNING: Do not perform operations on the container within onClear to avoid deadlocks. func (slm ShardLockMaps) ClearWithFuncLock(onClear func(key string, val interface{})) { for _, shard := range slm.shards { shard.Lock() for key, val := range shard.items { onClear(key, val) } shard.items = make(map[string]interface{}) shard.Unlock() } } // IsEmpty checks if map is empty. func (slm ShardLockMaps) IsEmpty() bool { return slm.Count() == 0 } // MGet retrieves multiple elements from the map. func (slm ShardLockMaps) MGet(keys ...string) map[string]interface{} { data := make(map[string]interface{}) for _, key := range keys { if val, ok := slm.Get(key); ok { data[key] = val } } return data } // GetAll returns a copy of all items in the map. func (slm ShardLockMaps) GetAll() map[string]interface{} { data := make(map[string]interface{}) for _, shard := range slm.shards { shard.RLock() for key, val := range shard.items { data[key] = val } shard.RUnlock() } return data } // Tuple Used by the IterBuffered functions to wrap two variables together over a channel, type Tuple struct { Key string Val interface{} } // Returns a array of channels that contains elements in each shard, // which likely takes a snapshot of `slm`. // It returns once the size of each buffered channel is determined, // before all the channels are populated using goroutines. func snapshot(slm ShardLockMaps) (chanList []chan Tuple) { chanList = make([]chan Tuple, slm.shardCount) wg := sync.WaitGroup{} wg.Add(slm.shardCount) for index, shard := range slm.shards { go func(index int, shard *SingleShardMap) { shard.RLock() chanList[index] = make(chan Tuple, len(shard.items)) wg.Done() for key, val := range shard.items { chanList[index] <- Tuple{key, val} } shard.RUnlock() close(chanList[index]) }(index, shard) } wg.Wait() return chanList } // fanIn reads elements from channels `chanList` into channel `out` func fanIn(chanList []chan Tuple, out chan Tuple) { wg := sync.WaitGroup{} wg.Add(len(chanList)) for _, ch := range chanList { go func(ch chan Tuple) { for t := range ch { out <- t } wg.Done() }(ch) } wg.Wait() close(out) } // IterBuffered returns a buffered iterator which could be used in a for range loop. func (slm ShardLockMaps) IterBuffered() <-chan Tuple { chanList := snapshot(slm) total := 0 for _, c := range chanList { total += cap(c) } ch := make(chan Tuple, total) go fanIn(chanList, ch) return ch } // Items returns all items as map[string]interface{} func (slm ShardLockMaps) Items() map[string]interface{} { tmp := make(map[string]interface{}) for item := range slm.IterBuffered() { tmp[item.Key] = item.Val } return tmp } // Keys returns all keys as []string func (slm ShardLockMaps) Keys() []string { count := slm.Count() ch := make(chan string, count) go func() { wg := sync.WaitGroup{} wg.Add(slm.shardCount) for _, shard := range slm.shards { go func(shard *SingleShardMap) { shard.RLock() for key := range shard.items { ch <- key } shard.RUnlock() wg.Done() }(shard) } wg.Wait() close(ch) }() keys := make([]string, 0, count) for k := range ch { keys = append(keys, k) } return keys } // IterCb Iterator callback,called for every key,value found in maps. // RLock is held for all calls for a given shard // therefore callback sess consistent view of a shard, // but not across the shards type IterCb func(key string, v interface{}) // IterCb Callback based iterator, cheapest way to read // all elements in a map. func (slm ShardLockMaps) IterCb(fn IterCb) { for idx := range slm.shards { shard := (slm.shards)[idx] shard.RLock() for key, value := range shard.items { fn(key, value) } shard.RUnlock() } } // MarshalJSON Reviles ConcurrentMap "private" variables to json marshal. func (slm ShardLockMaps) MarshalJSON() ([]byte, error) { tmp := make(map[string]interface{}) for item := range slm.IterBuffered() { tmp[item.Key] = item.Val } return json.Marshal(tmp) } // UnmarshalJSON Reverse process of Marshal. func (slm ShardLockMaps) UnmarshalJSON(b []byte) (err error) { tmp := make(map[string]interface{}) if err := json.Unmarshal(b, &tmp); err != nil { return err } for key, val := range tmp { slm.Set(key, val) } return nil } ================================================ FILE: zutils/shard_lock_map_bench_test.go ================================================ package zutils import ( "strconv" "sync" "testing" ) func BenchmarkItems(b *testing.B) { slm := NewShardLockMaps() for i := 0; i < 10000; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } for i := 0; i < b.N; i++ { slm.Items() } } func BenchmarkKeys(b *testing.B) { slm := NewShardLockMaps() for i := 0; i < 10000; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } for i := 0; i < b.N; i++ { slm.Keys() } } func BenchmarkMarshalJson(b *testing.B) { slm := NewShardLockMaps() for i := 0; i < 10000; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } for i := 0; i < b.N; i++ { _, err := slm.MarshalJSON() if err != nil { b.FailNow() } } } func BenchmarkStrconv(b *testing.B) { for i := 0; i < b.N; i++ { strconv.Itoa(i) } } func BenchmarkSingleInsertAbsent(b *testing.B) { slm := NewShardLockMaps() b.ResetTimer() for i := 0; i < b.N; i++ { slm.Set(strconv.Itoa(i), "value") } } func BenchmarkSingleInsertAbsentSyncMap(b *testing.B) { var m sync.Map b.ResetTimer() for i := 0; i < b.N; i++ { m.Store(strconv.Itoa(i), "value") } } func BenchmarkSingleInsertPresent(b *testing.B) { slm := NewShardLockMaps() slm.Set("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { slm.Set("key", "value") } } func BenchmarkSingleInsertPresentSyncMap(b *testing.B) { var m sync.Map m.Store("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { m.Store("key", "value") } } func BenchmarkMultiInsertDifferentSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, b.N) _, set := GetSetSyncMap(&m, finished) b.ResetTimer() for i := 0; i < b.N; i++ { go set(strconv.Itoa(i), "value") } for i := 0; i < b.N; i++ { <-finished } } func BenchmarkMultiInsertDifferent_1_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(1) benchmarkMultiInsertDifferentWithMap(slm, b) } func BenchmarkMultiInsertDifferent_16_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(16) benchmarkMultiInsertDifferentWithMap(slm, b) } func BenchmarkMultiInsertDifferent_32_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(32) benchmarkMultiInsertDifferentWithMap(slm, b) } func BenchmarkMultiInsertDifferent_256_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(256) benchmarkMultiInsertDifferentWithMap(slm, b) } func BenchmarkMultiInsertSame(b *testing.B) { slm := NewShardLockMaps() finished := make(chan struct{}, b.N) _, set := GetSet(slm, finished) slm.Set("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { go set("key", "value") } for i := 0; i < b.N; i++ { <-finished } } func BenchmarkMultiInsertSameSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, b.N) _, set := GetSetSyncMap(&m, finished) m.Store("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { go set("key", "value") } for i := 0; i < b.N; i++ { <-finished } } func BenchmarkMultiGetSame(b *testing.B) { slm := NewShardLockMaps() finished := make(chan struct{}, b.N) get, _ := GetSet(slm, finished) slm.Set("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { go get("key", "value") } for i := 0; i < b.N; i++ { <-finished } } func BenchmarkMultiGetSameSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, b.N) get, _ := GetSetSyncMap(&m, finished) m.Store("key", "value") b.ResetTimer() for i := 0; i < b.N; i++ { go get("key", "value") } for i := 0; i < b.N; i++ { <-finished } } func BenchmarkMultiGetSetDifferentSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, 2*b.N) get, set := GetSetSyncMap(&m, finished) m.Store("-1", "value") b.ResetTimer() for i := 0; i < b.N; i++ { go set(strconv.Itoa(i-1), "value") go get(strconv.Itoa(i), "value") } for i := 0; i < 2*b.N; i++ { <-finished } } func BenchmarkMultiGetSetDifferent_1_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(1) benchmarkMultiGetSetDifferentWithMap(slm, b) } func BenchmarkMultiGetSetDifferent_16_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(16) benchmarkMultiGetSetDifferentWithMap(slm, b) } func BenchmarkMultiGetSetDifferent_32_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(32) benchmarkMultiGetSetDifferentWithMap(slm, b) } func BenchmarkMultiGetSetDifferent_256_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(256) benchmarkMultiGetSetDifferentWithMap(slm, b) } func BenchmarkMultiGetSetBlockSyncMap(b *testing.B) { var m sync.Map finished := make(chan struct{}, 2*b.N) get, set := GetSetSyncMap(&m, finished) for i := 0; i < b.N; i++ { m.Store(strconv.Itoa(i%100), "value") } b.ResetTimer() for i := 0; i < b.N; i++ { go set(strconv.Itoa(i%100), "value") go get(strconv.Itoa(i%100), "value") } for i := 0; i < 2*b.N; i++ { <-finished } } func BenchmarkMultiGetSetBlock_1_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(1) benchmarkMultiGetSetBlockWithMap(slm, b) } func BenchmarkMultiGetSetBlock_16_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(16) benchmarkMultiGetSetBlockWithMap(slm, b) } func BenchmarkMultiGetSetBlock_32_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(32) benchmarkMultiGetSetBlockWithMap(slm, b) } func BenchmarkMultiGetSetBlock_256_Shard(b *testing.B) { slm := NewShardLockMapsWithCount(256) benchmarkMultiGetSetBlockWithMap(slm, b) } func benchmarkMultiInsertDifferent(b *testing.B) { slm := NewShardLockMaps() finished := make(chan struct{}, b.N) _, set := GetSet(slm, finished) b.ResetTimer() for i := 0; i < b.N; i++ { go set(strconv.Itoa(i), "value") } for i := 0; i < b.N; i++ { <-finished } } func benchmarkMultiGetSetDifferent(b *testing.B) { slm := NewShardLockMaps() finished := make(chan struct{}, 2*b.N) get, set := GetSet(slm, finished) slm.Set("-1", "value") b.ResetTimer() for i := 0; i < b.N; i++ { go set(strconv.Itoa(i-1), "value") go get(strconv.Itoa(i), "value") } for i := 0; i < 2*b.N; i++ { <-finished } } func benchmarkMultiGetSetBlock(b *testing.B) { slm := NewShardLockMaps() finished := make(chan struct{}, 2*b.N) get, set := GetSet(slm, finished) for i := 0; i < b.N; i++ { slm.Set(strconv.Itoa(i%100), "value") } b.ResetTimer() for i := 0; i < b.N; i++ { go set(strconv.Itoa(i%100), "value") go get(strconv.Itoa(i%100), "value") } for i := 0; i < 2*b.N; i++ { <-finished } } func GetSet(slm ShardLockMaps, finished chan struct{}) (set func(key, value string), get func(key, value string)) { return func(key, value string) { for i := 0; i < 10; i++ { slm.Get(key) } finished <- struct{}{} }, func(key, value string) { for i := 0; i < 10; i++ { slm.Set(key, value) } finished <- struct{}{} } } func GetSetSyncMap(m *sync.Map, finished chan struct{}) (get func(key, value string), set func(key, value string)) { get = func(key, value string) { for i := 0; i < 10; i++ { m.Load(key) } finished <- struct{}{} } set = func(key, value string) { for i := 0; i < 10; i++ { m.Store(key, value) } finished <- struct{}{} } return } func benchmarkMultiInsertDifferentWithMap(slm ShardLockMaps, b *testing.B) { finished := make(chan struct{}, b.N) _, set := GetSet(slm, finished) for i := 0; i < b.N; i++ { set(strconv.Itoa(i), strconv.Itoa(i)) } for i := 0; i < b.N; i++ { <-finished } } func benchmarkMultiGetSetDifferentWithMap(slm ShardLockMaps, b *testing.B) { finished := make(chan struct{}, 2*b.N) get, set := GetSet(slm, finished) for i := 0; i < b.N; i++ { go get(strconv.Itoa(i), "value") go set(strconv.Itoa(i), "value") } for i := 0; i < 2*b.N; i++ { <-finished } } func benchmarkMultiGetSetBlockWithMap(slm ShardLockMaps, b *testing.B) { finished := make(chan struct{}, 2*b.N) get, set := GetSet(slm, finished) for i := 0; i < 100; i++ { set(strconv.Itoa(i), "value") } b.ResetTimer() for i := 0; i < b.N; i++ { go set(strconv.Itoa(i%100), "value") go get(strconv.Itoa(i%100), "value") } for i := 0; i < 2*b.N; i++ { <-finished } } ================================================ FILE: zutils/shard_lock_map_test.go ================================================ package zutils import ( "fmt" "hash/fnv" "sort" "strconv" "testing" ) type TestUser struct { name string } func TestCreatMap(t *testing.T) { slm := NewShardLockMaps() if slm.shards == nil { t.Error("shardLockMaps is null.") } if slm.Count() != 0 { t.Error("new shardLockMaps should be empty.") } } func TestSet(t *testing.T) { slm := NewShardLockMaps() slm.Set("user", "14March") mData := make(map[string]interface{}) mData["aaa"] = 111 mData["bbb"] = "222" slm.MSet(mData) slm.SetNX("user", "14March") bo := TestUser{"bo"} slm.SetNX("bo", bo) if slm.Count() != 4 { t.Error("shardLockMaps should contain exactly one elements.") } } func TestGet(t *testing.T) { slm := NewShardLockMaps() val, ok := slm.Get("user") if ok == true { t.Error("ok should be false when item is missing from map.") } if val != nil { t.Error("Missing values should return as null.") } tony := TestUser{"tony"} slm.Set("tony", tony) tmp, ok := slm.Get("tony") if ok == false { t.Error("ok should be true for item stored within the map.") } tony, ok = tmp.(TestUser) if !ok { t.Error("expecting an element, not null.") } if tony.name != "tony" { t.Error("item was modified.") } } func TestHas(t *testing.T) { slm := NewShardLockMaps() if slm.Has("Money") == true { t.Error("element shouldn't exists") } slm.Set("user", "14March") if slm.Has("user") == false { t.Error("element exists, user Has to return True.") } } func TestCount(t *testing.T) { slm := NewShardLockMaps() for i := 0; i < 100; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } if slm.Count() != 100 { t.Error("Expecting 100 element within map.") } } func TestRemove(t *testing.T) { slm := NewShardLockMaps() david := TestUser{"David"} slm.Set("david", david) slm.Remove("david") if slm.Count() != 0 { t.Error("Expecting count to be zero once item was removed.") } temp, ok := slm.Get("david") if ok != false { t.Error("Expecting ok to be false for missing items.") } if temp != nil { t.Error("Expecting item to be nil after its removal.") } slm.Remove("user") isEmpty := slm.IsEmpty() if !isEmpty { t.Error("map should be empty.") } } func TestRemoveCb(t *testing.T) { slm := NewShardLockMaps() tony := TestUser{"tony"} slm.Set("tony", tony) david := TestUser{"david"} slm.Set("david", david) var ( mapKey string mapVal interface{} wasFound bool ) cb := func(key string, val interface{}, exists bool) bool { mapKey = key mapVal = val wasFound = exists if user, ok := val.(TestUser); ok { return user.name == "tony" } return false } result := slm.RemoveCb("tony", cb) if !result { t.Errorf("Result was not true") } if mapKey != "tony" { t.Error("Wrong key was provided to the callback") } if mapVal != tony { t.Errorf("Wrong value was provided to the value") } if !wasFound { t.Errorf("Key was not found") } if slm.Has("tony") { t.Errorf("Key was not removed") } result = slm.RemoveCb("david", cb) if result { t.Errorf("Result was true") } if mapKey != "david" { t.Error("Wrong key was provided to the callback") } if mapVal != david { t.Errorf("Wrong value was provided to the value") } if !wasFound { t.Errorf("Key was not found") } if !slm.Has("david") { t.Errorf("Key was removed") } result = slm.RemoveCb("danny", cb) if result { t.Errorf("Result was true") } if mapKey != "danny" { t.Error("Wrong key was provided to the callback") } if mapVal != nil { t.Errorf("Wrong value was provided to the value") } if wasFound { t.Errorf("Key was found") } if slm.Has("danny") { t.Errorf("Key was created") } } func TestPop(t *testing.T) { slm := NewShardLockMaps() _, exists := slm.Pop("user") if exists { t.Error("user should be not exists.") } slm.Set("user", "14March") val, exists := slm.Pop("user") if exists { t.Logf("user should be exists %v.", val) } } func TestBufferedIterator(t *testing.T) { slm := NewShardLockMaps() for i := 0; i < 100; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } counter := 0 for item := range slm.IterBuffered() { val := item.Val if val == nil { t.Error("Expecting an object.") } counter++ } if counter != 100 { t.Error("We should have counted 100 elements.") } } func TestClear(t *testing.T) { slm := NewShardLockMaps() for i := 0; i < 100; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } slm.Clear() if slm.Count() != 0 { t.Error("should have 0 elements.") } } func TestIterCb(t *testing.T) { slm := NewShardLockMaps() for i := 0; i < 100; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } counter := 0 slm.IterCb(func(key string, v interface{}) { _, ok := v.(TestUser) if !ok { t.Error("Expecting an user object") } counter++ }) if counter != 100 { t.Error("We should have counted 100 elements.") } } func TestItems(t *testing.T) { slm := NewShardLockMaps() for i := 0; i < 100; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } items := slm.Items() if len(items) != 100 { t.Error("We should have counted 100 elements.") } } func TestJsonMarshal(t *testing.T) { expected := "{\"a\":1,\"b\":2}" slm := NewShardLockMapsWithCount(2) slm.Set("a", 1) slm.Set("b", 2) j, err := slm.MarshalJSON() if err != nil { t.Error(err) } if string(j) != expected { t.Error("json", string(j), "differ from expected", expected) return } } func TestUnmarshalJSON(t *testing.T) { slm := NewShardLockMaps() bytes := []byte("{\"ccc\":1,\"ddd\":2}") err := slm.UnmarshalJSON(bytes) if err != nil { t.Error(err) } if slm.Count() != 2 { t.Error("Expecting count to be 2 once item was removed.") } for _, shard := range slm.shards { fmt.Printf("%+v \n", shard.items) } } func TestKeys(t *testing.T) { slm := NewShardLockMaps() for i := 0; i < 100; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } keys := slm.Keys() if len(keys) != 100 { t.Error("We should have counted 100 elements.") } } func TestFnv32(t *testing.T) { key := []byte("ABC") hasher := fnv.New32() _, err := hasher.Write(key) if err != nil { t.Errorf("%v", err) } if (&Fnv32Hash{}).Sum(string(key)) != hasher.Sum32() { t.Errorf("Bundled fnv32 produced %d, expected result from hash/fnv32 is %d", (&Fnv32Hash{}).Sum(string(key)), hasher.Sum32()) } } func TestKeysWhenRemoving(t *testing.T) { slm := NewShardLockMaps() Total := 100 for i := 0; i < Total; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } Num := 10 for i := 0; i < Num; i++ { go func(c *ShardLockMaps, n int) { c.Remove(strconv.Itoa(n)) }(&slm, i) } keys := slm.Keys() for _, k := range keys { if k == "" { t.Error("Empty keys returned") } } } func TestUnDrainedIterBuffered(t *testing.T) { slm := NewShardLockMaps() Total := 100 for i := 0; i < Total; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } counter := 0 // Iterate over elements. ch := slm.IterBuffered() for item := range ch { val := item.Val if val == nil { t.Error("Expecting an object.") } counter++ if counter == 42 { break } } for i := Total; i < 2*Total; i++ { slm.Set(strconv.Itoa(i), TestUser{strconv.Itoa(i)}) } for item := range ch { val := item.Val if val == nil { t.Error("Expecting an object.") } counter++ } if counter != 100 { t.Error("We should have been right where we stopped") } counter = 0 for item := range slm.IterBuffered() { val := item.Val if val == nil { t.Error("Expecting an object.") } counter++ } if counter != 200 { t.Error("We should have counted 200 elements.") } } func TestConcurrent(t *testing.T) { slm := NewShardLockMaps() ch := make(chan int) const iterations = 1000 var a [iterations]int // Using go routines insert 1000 ints into our map. go func() { for i := 0; i < iterations/2; i++ { // Add item to map. slm.Set(strconv.Itoa(i), i) // Retrieve item from map. val, _ := slm.Get(strconv.Itoa(i)) // Write to channel inserted value. ch <- val.(int) } // Call go routine with current index. }() go func() { for i := iterations / 2; i < iterations; i++ { // Add item to map. slm.Set(strconv.Itoa(i), i) // Retrieve item from map. val, _ := slm.Get(strconv.Itoa(i)) // Write to channel inserted value. ch <- val.(int) } // Call go routine with current index. }() // Wait for all go routines to finish. counter := 0 for elem := range ch { a[counter] = elem counter++ if counter == iterations { break } } // Sorts array, will make is simpler to verify all inserted values we're returned. sort.Ints(a[0:iterations]) // Make sure map contains 1000 elements. if slm.Count() != iterations { t.Error("Expecting 1000 elements.") } // Make sure all inserted values we're fetched from map. for i := 0; i < iterations; i++ { if i != a[i] { t.Error("missing value", i) } } } // TestGetOrSet tests the GetOrSet method func TestGetOrSet(t *testing.T) { slm := NewShardLockMaps() // Test setting a new value value, isSet := slm.GetOrSet("key1", "value1") if !isSet { t.Error("Expected isSet to be true for new key") } if value != "value1" { t.Error("Expected value to be 'value1'") } // Test getting existing value value, isSet = slm.GetOrSet("key1", "value2") if isSet { t.Error("Expected isSet to be false for existing key") } if value != "value1" { t.Error("Expected value to be 'value1'") } } // TestGetOrSetFunc tests the GetOrSetFunc method func TestGetOrSetFunc(t *testing.T) { slm := NewShardLockMaps() // Test setting a new value with function value, isSet := slm.GetOrSetFunc("key1", func(key string) interface{} { return "generated_" + key }) if !isSet { t.Error("Expected isSet to be true for new key") } if value != "generated_key1" { t.Error("Expected value to be 'generated_key1'") } // Test getting existing value value, isSet = slm.GetOrSetFunc("key1", func(key string) interface{} { return "should_not_be_called" }) if isSet { t.Error("Expected isSet to be false for existing key") } if value != "generated_key1" { t.Error("Expected value to be 'generated_key1'") } } // TestGetOrSetFuncLock tests the GetOrSetFuncLock method func TestGetOrSetFuncLock(t *testing.T) { slm := NewShardLockMaps() // Test setting a new value with function in lock value, isSet := slm.GetOrSetFuncLock("key1", func(key string) interface{} { return "locked_" + key }) if !isSet { t.Error("Expected isSet to be true for new key") } if value != "locked_key1" { t.Error("Expected value to be 'locked_key1'") } // Test getting existing value value, isSet = slm.GetOrSetFuncLock("key1", func(key string) interface{} { return "should_not_be_called" }) if isSet { t.Error("Expected isSet to be false for existing key") } if value != "locked_key1" { t.Error("Expected value to be 'locked_key1'") } } // TestMGet tests the MGet method func TestMGet(t *testing.T) { slm := NewShardLockMaps() slm.Set("key1", "value1") slm.Set("key2", "value2") slm.Set("key3", "value3") // Test getting multiple existing keys values := slm.MGet("key1", "key2", "key3") if len(values) != 3 { t.Error("Expected 3 values") } if values["key1"] != "value1" || values["key2"] != "value2" || values["key3"] != "value3" { t.Error("Expected correct values") } // Test getting mix of existing and non-existing keys values = slm.MGet("key1", "nonexistent", "key2") if len(values) != 2 { t.Error("Expected 2 values for mix of existing and non-existing keys") } if values["key1"] != "value1" || values["key2"] != "value2" { t.Error("Expected correct values for existing keys") } } // TestGetAll tests the GetAll method func TestGetAll(t *testing.T) { slm := NewShardLockMaps() slm.Set("key1", "value1") slm.Set("key2", "value2") all := slm.GetAll() if len(all) != 2 { t.Error("Expected 2 items in GetAll") } if all["key1"] != "value1" || all["key2"] != "value2" { t.Error("Expected correct values in GetAll") } } // TestLockFuncWithKey tests the LockFuncWithKey method func TestLockFuncWithKey(t *testing.T) { slm := NewShardLockMaps() slm.Set("key1", "value1") slm.Set("key2", "value2") // Test modifying a specific key's shard slm.LockFuncWithKey("key1", func(data map[string]interface{}) { if val, ok := data["key1"]; ok { data["key1"] = val.(string) + "_modified" } }) value, ok := slm.Get("key1") if !ok || value != "value1_modified" { t.Error("Expected key1 to be modified") } } // TestRLockFuncWithKey tests the RLockFuncWithKey method func TestRLockFuncWithKey(t *testing.T) { slm := NewShardLockMaps() slm.Set("key1", "value1") slm.Set("key2", "value2") // Test reading from a specific key's shard var found bool slm.RLockFuncWithKey("key1", func(data map[string]interface{}) { if _, ok := data["key1"]; ok { found = true } }) if !found { t.Error("Expected to find key1 in shard") } } // TestLockFunc tests the LockFunc method func TestLockFunc(t *testing.T) { slm := NewShardLockMaps() slm.Set("key1", "value1") slm.Set("key2", "value2") // Test modifying all shards slm.LockFunc(func(data map[string]interface{}) { for key, val := range data { if str, ok := val.(string); ok { data[key] = str + "_modified" } } }) value1, _ := slm.Get("key1") value2, _ := slm.Get("key2") if value1 != "value1_modified" || value2 != "value2_modified" { t.Error("Expected all values to be modified") } } // TestRLockFunc tests the RLockFunc method func TestRLockFunc(t *testing.T) { slm := NewShardLockMaps() slm.Set("key1", "value1") slm.Set("key2", "value2") // Test reading from all shards var count int slm.RLockFunc(func(data map[string]interface{}) { count += len(data) }) if count != 2 { t.Error("Expected to find 2 items across all shards") } } // TestClearWithFuncLock tests the ClearWithFuncLock method func TestClearWithFuncLock(t *testing.T) { slm := NewShardLockMaps() slm.Set("key1", "value1") slm.Set("key2", "value2") var clearedKeys []string slm.ClearWithFuncLock(func(key string, val interface{}) { clearedKeys = append(clearedKeys, key) }) if len(clearedKeys) != 2 { t.Error("Expected 2 keys to be cleared") } if slm.Count() != 0 { t.Error("Expected map to be empty after ClearWithFuncLock") } } ================================================ FILE: zutils/snowflake_uuid.go ================================================ package zutils import ( "fmt" "sync" "time" ) /* workerID 的大小为 10 位,因此可以生成的最大 workerID 数量为 2^10 = 1024。 sequenceBits 的大小为 12 位,因此对于每个工作器,可以在同一毫秒内生成的最大序列号数为 2^12 = 4096。 因此,在同一毫秒内,每个工作者最多可以生成 4096 个分布式 ID。 由于时间戳使用的是毫秒级别的时间戳,因此每个工作者在一秒钟内最多可以生成 4096 * 1000 = 4,096,000 个分布式 ID。 总体上,如果 workerID 保持唯一,这个算法可以生成多达 1024 x 4,096,000 = 4,194,304,000 个分布式 ID。 */ const ( workerBits uint8 = 10 maxWorker int64 = -1 ^ (-1 << workerBits) sequenceBits uint8 = 12 sequenceMask int64 = -1 ^ (-1 << sequenceBits) workerShift uint8 = sequenceBits timestampShift uint8 = sequenceBits + workerBits ) type IDWorker struct { sequence int64 lastTimestamp int64 workerId int64 mutex sync.Mutex } func NewIDWorker(workerId int64) (*IDWorker, error) { if workerId < 0 || workerId > maxWorker { return nil, fmt.Errorf("worker ID can't be greater than %d or less than 0", maxWorker) } return &IDWorker{ workerId: workerId, lastTimestamp: -1, sequence: 0, }, nil } func (w *IDWorker) NextID() (int64, error) { w.mutex.Lock() defer w.mutex.Unlock() timestamp := time.Now().UnixNano() / 1000000 if timestamp < w.lastTimestamp { return 0, fmt.Errorf("clock moved backwards") } if timestamp == w.lastTimestamp { w.sequence = (w.sequence + 1) & sequenceMask if w.sequence == 0 { timestamp = w.nextMillisecond(timestamp) } } else { w.sequence = 0 } w.lastTimestamp = timestamp return (timestamp << timestampShift) | (w.workerId << workerShift) | w.sequence, nil } func (w *IDWorker) nextMillisecond(currentTimestamp int64) int64 { for currentTimestamp <= w.lastTimestamp { currentTimestamp = time.Now().UnixNano() / 1000000 } return currentTimestamp } ================================================ FILE: zutils/snowflake_uuid_test.go ================================================ package zutils import ( "fmt" "testing" ) func TestSnowFlakeUUID(t *testing.T) { worker, err := NewIDWorker(1) if err != nil { fmt.Println(err) return } id, err := worker.NextID() if err != nil { fmt.Println(err) return } fmt.Println("ID:", id) } ================================================ FILE: zutils/witer.go ================================================ package zutils import ( "archive/zip" "bufio" "bytes" "fmt" "io" "io/fs" "os" "path/filepath" "strings" "sync" "time" ) const ( sizeMiB = 1024 * 1024 defMaxAge = 31 defMaxSize = 64 //MiB ) var _ io.WriteCloser = (*Writer)(nil) type Writer struct { maxAge int // 最大保留天数 maxSize int64 // 单个日志最大容量 默认 64MB size int64 // 累计大小 fpath string // 文件目录 完整路径 fpath=fdir+fname+fsuffix fdir string // fname string // 文件名 fsuffix string // 文件后缀名 默认 .log zipsuffix string // 文件后缀名 默认 .log created time.Time // 文件创建日期 creates []byte // 文件创建日期 cons bool // 标准输出 默认 false file *os.File bw *bufio.Writer mu sync.Mutex } func New(path string) *Writer { w := &Writer{ fpath: path, //dir1/dir2/app.log mu: sync.Mutex{}, } w.fdir = filepath.Dir(w.fpath) //dir1/dir2 w.fsuffix = filepath.Ext(w.fpath) //.log w.fname = strings.TrimSuffix(filepath.Base(w.fpath), w.fsuffix) //app if w.fsuffix == "" { w.fsuffix = ".log" } if w.zipsuffix == "" { w.zipsuffix = ".zip" } w.maxSize = sizeMiB * defMaxSize w.maxAge = defMaxAge os.MkdirAll(filepath.Dir(w.fpath), 0755) go w.daemon() return w } func (w *Writer) daemon() { for range time.NewTicker(time.Second * 5).C { w.flush() } } // SetMaxAge 最大保留天数 func (w *Writer) SetMaxAge(ma int) { w.mu.Lock() w.maxAge = ma w.mu.Unlock() } // SetMaxSize 单个日志最大容量 func (w *Writer) SetMaxSize(ms int64) { if ms < 1 { return } w.mu.Lock() w.maxSize = ms w.mu.Unlock() } // SetCons 同时输出控制台 func (w *Writer) SetCons(b bool) { w.mu.Lock() w.cons = b w.mu.Unlock() } func (w *Writer) Write(p []byte) (n int, err error) { w.mu.Lock() defer w.mu.Unlock() if w.cons { os.Stderr.Write(p) } if w.file == nil { if err := w.rotate(); err != nil { os.Stderr.Write(p) return 0, err } } t := time.Now() b := t.AppendFormat(nil, time.RFC3339) // 按天切割 if !bytes.Equal(w.creates[:10], b[:10]) { //2023-04-05 go w.delete() // 每天检测一次旧文件 if err := w.rotate(); err != nil { return 0, err } } // 按大小切割,需要考虑bw缓冲区中未写入的数据 if w.size+int64(len(p))+int64(w.bw.Buffered()) >= w.maxSize { if err := w.rotate(); err != nil { return 0, err } } // n, err = w.file.Write(p) n, err = w.bw.Write(p) w.size += int64(n) if err != nil { return n, err } return } // rotate 切割文件 func (w *Writer) rotate() error { now := time.Now() if w.file != nil { w.bw.Flush() w.file.Sync() w.file.Close() // 保存 fbak := w.fname + w.time2name(w.created) fbakname := fbak + w.fsuffix err := os.Rename(w.fpath, filepath.Join(w.fdir, fbakname)) if err == nil { err1 := ZipToFile(filepath.Join(w.fdir, fbak+".zip"), filepath.Join(w.fdir, fbakname)) if err1 == nil { os.Remove(filepath.Join(w.fdir, fbakname)) } else { fmt.Println(err1) } } w.size = 0 } finfo, err := os.Stat(w.fpath) w.created = now if err == nil { w.size = finfo.Size() w.created = finfo.ModTime() } w.creates = w.created.AppendFormat(nil, time.RFC3339) fout, err := os.OpenFile(w.fpath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0666) if err != nil { return err } w.file = fout w.bw = bufio.NewWriter(w.file) return nil } // 删除旧日志 func (w *Writer) delete() { if w.maxAge <= 0 { return } dir := filepath.Dir(w.fpath) fakeNow := time.Now().AddDate(0, 0, -w.maxAge) dirs, err := os.ReadDir(dir) if err != nil { return } for _, path := range dirs { name := path.Name() if path.IsDir() { continue } t, err := w.name2time(name) // 只删除满足格式的文件 if err == nil && t.Before(fakeNow) { os.Remove(filepath.Join(dir, name)) } } } func (w *Writer) name2time(name string) (time.Time, error) { name = strings.TrimPrefix(name, filepath.Base(w.fname)) name = strings.TrimSuffix(name, w.zipsuffix) // 改为微秒级别的文件后缀,避免1s内大量写入造成多次rotate,而覆盖丢失之前的日志文件 return time.Parse(".2006-01-02-150405.000000", name) } func (w *Writer) time2name(t time.Time) string { return t.Format(".2006-01-02-150405.000000") } func (w *Writer) Close() error { w.flush() return w.close() } // close closes the file if it is open. func (w *Writer) close() error { w.mu.Lock() defer w.mu.Unlock() if w.file == nil { return nil } w.file.Sync() err := w.file.Close() w.file = nil return err } func (w *Writer) flush() error { w.mu.Lock() defer w.mu.Unlock() if w.bw == nil { return nil } return w.bw.Flush() } // ZipToFile 压缩至文件 // @params dst string 压缩文件目标路径 // @params src string 待压缩源文件/目录路径 // @return error 错误信息 func ZipToFile(dst, src string) error { // 创建一个ZIP文件 fw, err := os.Create(filepath.Clean(dst)) if err != nil { return err } defer fw.Close() // 执行压缩 return Zip(fw, src) } // Zip 压缩文件或目录 // @params dst io.Writer 压缩文件可写流 // @params src string 待压缩源文件/目录路径 func Zip(dst io.Writer, src string) error { // 强转一下路径 src = filepath.Clean(src) // 提取最后一个文件或目录的名称 baseFile := filepath.Base(src) // 判断src是否存在 _, err := os.Stat(src) if err != nil { return err } // 通文件流句柄创建一个ZIP压缩包 zw := zip.NewWriter(dst) // 延迟关闭这个压缩包 defer zw.Close() // 通过filepath封装的Walk来递归处理源路径到压缩文件中 return filepath.Walk(src, func(path string, info fs.FileInfo, err error) error { // 是否存在异常 if err != nil { return err } // 通过原始文件头信息,创建zip文件头信息 zfh, err := zip.FileInfoHeader(info) if err != nil { return err } // 赋值默认的压缩方法,否则不压缩 zfh.Method = zip.Deflate // 移除绝对路径 tmpPath := path index := strings.Index(tmpPath, baseFile) if index > -1 { tmpPath = tmpPath[index:] } // 替换文件名,并且去除前后 "\" 或 "/" tmpPath = strings.Trim(tmpPath, string(filepath.Separator)) // 替换一下分隔符,zip不支持 "\\" zfh.Name = strings.ReplaceAll(tmpPath, "\\", "/") // 目录需要拼上一个 "/" ,否则会出现一个和目录一样的文件在压缩包中 if info.IsDir() { zfh.Name += "/" } // 写入文件头信息,并返回一个ZIP文件写入句柄 zfw, err := zw.CreateHeader(zfh) if err != nil { return err } // 仅在他是标准文件时进行文件内容写入 if zfh.Mode().IsRegular() { // 打开要压缩的文件 sfr, err := os.Open(path) if err != nil { return err } defer sfr.Close() // 将srcFileReader拷贝到zipFilWrite中 _, err = io.Copy(zfw, sfr) if err != nil { return err } } // 搞定 return nil }) }