{"id":13413584,"url":"https://github.com/DarthPestilane/easytcp","last_synced_at":"2025-03-14T19:32:39.820Z","repository":{"id":37805144,"uuid":"361704313","full_name":"DarthPestilane/easytcp","owner":"DarthPestilane","description":":sparkles: :rocket: EasyTCP is a light-weight TCP framework written in Go (Golang), built with message router. EasyTCP helps you build a TCP server easily fast and less painful.","archived":false,"fork":false,"pushed_at":"2024-03-19T06:06:07.000Z","size":523,"stargazers_count":813,"open_issues_count":2,"forks_count":89,"subscribers_count":8,"default_branch":"master","last_synced_at":"2024-11-24T19:50:03.558Z","etag":null,"topics":["go","golang","middleware","router","tcp","tcp-server"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/DarthPestilane.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["DarthPestilane"]}},"created_at":"2021-04-26T10:11:59.000Z","updated_at":"2024-11-13T08:07:16.000Z","dependencies_parsed_at":"2024-06-18T15:36:24.185Z","dependency_job_id":"a31b369a-69b3-496a-82b2-00cc400a1256","html_url":"https://github.com/DarthPestilane/easytcp","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DarthPestilane%2Feasytcp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DarthPestilane%2Feasytcp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DarthPestilane%2Feasytcp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/DarthPestilane%2Feasytcp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/DarthPestilane","download_url":"https://codeload.github.com/DarthPestilane/easytcp/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243635555,"owners_count":20322961,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["go","golang","middleware","router","tcp","tcp-server"],"created_at":"2024-07-30T20:01:43.818Z","updated_at":"2025-03-14T19:32:39.487Z","avatar_url":"https://github.com/DarthPestilane.png","language":"Go","funding_links":["https://github.com/sponsors/DarthPestilane"],"categories":["开源类库","Networking","Go","Open source library","网络","Relational Databases"],"sub_categories":["网络","Transliteration","The Internet","音译","Uncategorized"],"readme":"# EasyTCP\n\n[![gh-action](https://github.com/DarthPestilane/easytcp/actions/workflows/test.yml/badge.svg)](https://github.com/DarthPestilane/easytcp/actions/workflows/test.yml)\n[![Go Report](https://goreportcard.com/badge/github.com/darthPestilane/easytcp)](https://goreportcard.com/report/github.com/darthPestilane/easytcp)\n[![codecov](https://codecov.io/gh/DarthPestilane/easytcp/branch/master/graph/badge.svg?token=002KJ5IV4Z)](https://codecov.io/gh/DarthPestilane/easytcp)\n[![Go Reference](https://pkg.go.dev/badge/github.com/DarthPestilane/easytcp.svg)](https://pkg.go.dev/github.com/DarthPestilane/easytcp)\n[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go#networking)\n\n```\n$ ./start\n\n[EASYTCP] Message-Route Table:\n+------------+-----------------------+---------------------------------\n| Message ID |     Route Handler     |           Middlewares          |\n+------------+-----------------------+---------------------------------\n|       1000 | path/to/handler.Func1 |  /path/to/middleware.Func1(g)  |\n|            |                       |  /path/to/middleware.Func2     |\n+------------+-----------------------+---------------------------------\n|       1002 | path/to/handler.Func2 |  /path/to/middleware.Func1(g)  |\n|            |                       |  /path/to/middleware.Func2     |\n+------------+-----------------------+---------------------------------\n[EASYTCP] Serving at: tcp://[::]:10001\n```\n\n## Introduction\n\n`EasyTCP` is a light-weight and less painful TCP server framework written in Go (Golang) based on the standard `net` package.\n\n✨ Features:\n\n- Non-invasive design\n- Pipelined middlewares for route handler\n- Customizable message packer and codec, and logger\n- Handy functions to handle request data and send response\n- Common hooks\n\n`EasyTCP` helps you build a TCP server easily and fast.\n\nThis package has been tested on the latest Linux, Macos and Windows.\n\n## Install\n\nUse the below Go command to install EasyTCP.\n\n```sh\n$ go get -u github.com/DarthPestilane/easytcp\n```\n\nNote: EasyTCP uses **Go Modules** to manage dependencies.\n\n## Quick start\n\n```go\npackage main\n\nimport (\n    \"fmt\"\n    \"github.com/DarthPestilane/easytcp\"\n)\n\nfunc main() {\n    // Create a new server with options.\n    s := easytcp.NewServer(\u0026easytcp.ServerOption{\n        Packer: easytcp.NewDefaultPacker(), // use default packer\n        Codec:  nil,                        // don't use codec\n    })\n\n    // Register a route with message's ID.\n    // The `DefaultPacker` treats id as int,\n    // so when we add routes or return response, we should use int.\n    s.AddRoute(1001, func(c easytcp.Context) {\n        // acquire request\n        req := c.Request()\n\n        // do things...\n        fmt.Printf(\"[server] request received | id: %d; size: %d; data: %s\\n\", req.ID(), len(req.Data()), req.Data())\n\n        // set response\n        c.SetResponseMessage(easytcp.NewMessage(1002, []byte(\"copy that\")))\n    })\n\n    // Set custom logger (optional).\n    easytcp.SetLogger(lg)\n\n    // Add global middlewares (optional).\n    s.Use(recoverMiddleware)\n\n    // Set hooks (optional).\n    s.OnSessionCreate = func(session easytcp.Session) {...}\n    s.OnSessionClose = func(session easytcp.Session) {...}\n\n    // Set not-found route handler (optional).\n    s.NotFoundHandler(handler)\n\n    // Listen and serve.\n    if err := s.Run(\":5896\"); err != nil \u0026\u0026 err != server.ErrServerStopped {\n        fmt.Println(\"serve error: \", err.Error())\n    }\n}\n```\n\n### If we setup with the codec\n\n```go\n// Create a new server with options.\ns := easytcp.NewServer(\u0026easytcp.ServerOption{\n    Packer: easytcp.NewDefaultPacker(), // use default packer\n    Codec:  \u0026easytcp.JsonCodec{},       // use JsonCodec\n})\n\n// Register a route with message's ID.\n// The `DefaultPacker` treats id as int,\n// so when we add routes or return response, we should use int.\ns.AddRoute(1001, func(c easytcp.Context) {\n    // decode request data and bind to `reqData`\n    var reqData map[string]interface{}\n    if err := c.Bind(\u0026reqData); err != nil {\n        // handle err\n    }\n\n    // do things...\n    respId := 1002\n    respData := map[string]interface{}{\n        \"success\": true,\n        \"feeling\": \"Great!\",\n    }\n\n    // encode response data and set to `c`\n    if err := c.SetResponse(respId, respData); err != nil {\n        // handle err\n    }\n})\n```\n\nAbove is the server side example. There are client and more detailed examples including:\n\n- [broadcasting](./internal/examples/tcp/broadcast)\n- [custom packet](./internal/examples/tcp/custom_packet)\n- [communicating with protobuf](./internal/examples/tcp/proto_packet)\n\nin [examples/tcp](./internal/examples/tcp).\n\n## Benchmark\n\n```sh\ngo test -bench=. -run=none -benchmem -benchtime=250000x\ngoos: darwin\ngoarch: amd64\npkg: github.com/DarthPestilane/easytcp\ncpu: Intel(R) Core(TM) i5-8279U CPU @ 2.40GHz\nBenchmark_NoHandler-8                     250000              4277 ns/op              83 B/op          2 allocs/op\nBenchmark_OneHandler-8                    250000              4033 ns/op              81 B/op          2 allocs/op\nBenchmark_DefaultPacker_Pack-8            250000                38.00 ns/op           16 B/op          1 allocs/op\nBenchmark_DefaultPacker_Unpack-8          250000               105.8 ns/op            96 B/op          3 allocs/op\n```\n\n*since easytcp is built on the top of golang `net` library, the benchmark of networks does not make much sense.*\n\n## Architecture\n\n```\naccepting connection:\n\n+------------+    +-------------------+    +----------------+\n|            |    |                   |    |                |\n|            |    |                   |    |                |\n| tcp server |---\u003e| accept connection |---\u003e| create session |\n|            |    |                   |    |                |\n|            |    |                   |    |                |\n+------------+    +-------------------+    +----------------+\n\nin session:\n\n+------------------+    +-----------------------+    +----------------------------------+\n| read connection  |---\u003e| unpack packet payload |---\u003e|                                  |\n+------------------+    +-----------------------+    |                                  |\n                                                     | router (middlewares and handler) |\n+------------------+    +-----------------------+    |                                  |\n| write connection |\u003c---| pack packet payload   |\u003c---|                                  |\n+------------------+    +-----------------------+    +----------------------------------+\n\nin route handler:\n\n+----------------------------+    +------------+\n| codec decode request data  |---\u003e|            |\n+----------------------------+    |            |\n                                  | user logic |\n+----------------------------+    |            |\n| codec encode response data |\u003c---|            |\n+----------------------------+    +------------+\n```\n\n## Conception\n\n### Routing\n\nEasyTCP considers every message has a `ID` segment to distinguish one another.\nA message will be routed according to its id, to the handler through middlewares.\n\n```\nrequest flow:\n\n+----------+    +--------------+    +--------------+    +---------+\n| request  |---\u003e|              |---\u003e|              |---\u003e|         |\n+----------+    |              |    |              |    |         |\n                | middleware 1 |    | middleware 2 |    | handler |\n+----------+    |              |    |              |    |         |\n| response |\u003c---|              |\u003c---|              |\u003c---|         |\n+----------+    +--------------+    +--------------+    +---------+\n```\n\n#### Register a route\n\n```go\ns.AddRoute(reqID, func(c easytcp.Context) {\n    // acquire request\n    req := c.Request()\n\n    // do things...\n    fmt.Printf(\"[server] request received | id: %d; size: %d; data: %s\\n\", req.ID(), len(req.Data()), req.Data())\n\n    // set response\n    c.SetResponseMessage(easytcp.NewMessage(respID, []byte(\"copy that\")))\n})\n```\n\n#### Using middleware\n\n```go\n// register global middlewares.\n// global middlewares are prior than per-route middlewares, they will be invoked first\ns.Use(recoverMiddleware, logMiddleware, ...)\n\n// register middlewares for one route\ns.AddRoute(reqID, handler, middleware1, middleware2)\n\n// a middleware looks like:\nvar exampleMiddleware easytcp.MiddlewareFunc = func(next easytcp.HandlerFunc) easytcp.HandlerFunc {\n    return func(c easytcp.Context) {\n        // do things before...\n        next(c)\n        // do things after...\n    }\n}\n```\n\n### Packer\n\nA packer is to pack and unpack packets' payload. We can set the Packer when creating the server.\n\n```go\ns := easytcp.NewServer(\u0026easytcp.ServerOption{\n    Packer: new(MyPacker), // this is optional, the default one is DefaultPacker\n})\n```\n\nWe can set our own Packer or EasyTCP uses [`DefaultPacker`](./packer.go).\n\nThe `DefaultPacker` considers packet's payload as a `Size(4)|ID(4)|Data(n)` format. **`Size` only represents the length of `Data` instead of the whole payload length**\n\nThis may not covery some particular cases, but fortunately, we can create our own Packer.\n\n```go\n// CustomPacker is a custom packer, implements Packer interafce.\n// Treats Packet format as `size(2)id(2)data(n)`\ntype CustomPacker struct{}\n\nfunc (p *CustomPacker) bytesOrder() binary.ByteOrder {\n    return binary.BigEndian\n}\n\nfunc (p *CustomPacker) Pack(msg *easytcp.Message) ([]byte, error) {\n    size := len(msg.Data()) // only the size of data.\n    buffer := make([]byte, 2+2+size)\n    p.bytesOrder().PutUint16(buffer[:2], uint16(size))\n    p.bytesOrder().PutUint16(buffer[2:4], msg.ID().(uint16))\n    copy(buffer[4:], msg.Data())\n    return buffer, nil\n}\n\nfunc (p *CustomPacker) Unpack(reader io.Reader) (*easytcp.Message, error) {\n    headerBuffer := make([]byte, 2+2)\n    if _, err := io.ReadFull(reader, headerBuffer); err != nil {\n        return nil, fmt.Errorf(\"read size and id err: %s\", err)\n    }\n    size := p.bytesOrder().Uint16(headerBuffer[:2])\n    id := p.bytesOrder().Uint16(headerBuffer[2:])\n\n    data := make([]byte, size)\n    if _, err := io.ReadFull(reader, data); err != nil {\n        return nil, fmt.Errorf(\"read data err: %s\", err)\n    }\n\n    // since msg.ID is type of uint16, we need to use uint16 as well when adding routes.\n    // eg: server.AddRoute(uint16(123), ...)\n    msg := easytcp.NewMessage(id, data)\n    msg.Set(\"theWholeLength\", 2+2+size) // we can set our custom kv data here.\n    // c.Request().Get(\"theWholeLength\")  // and get them in route handler.\n    return msg, nil\n}\n```\n\nAnd see more custom packers:\n- [custom_packet](./examples/tcp/custom_packet/common/packer.go)\n- [proto_packet](./examples/tcp/proto_packet/common/packer.go)\n\n### Codec\n\nA Codec is to encode and decode message data. The Codec is optional, EasyTCP won't encode or decode message data if the Codec is not set.\n\nWe can set Codec when creating the server.\n\n```go\ns := easytcp.NewServer(\u0026easytcp.ServerOption{\n    Codec: \u0026easytcp.JsonCodec{}, // this is optional. The JsonCodec is a built-in codec\n})\n```\n\nSince we set the codec, we may want to decode the request data in route handler.\n\n```go\ns.AddRoute(reqID, func(c easytcp.Context) {\n    var reqData map[string]interface{}\n    if err := c.Bind(\u0026reqData); err != nil { // here we decode message data and bind to reqData\n        // handle error...\n    }\n    req := c.Request()\n    fmt.Printf(\"[server] request received | id: %d; size: %d; data-decoded: %+v\\n\", req.ID(), len(req.Data()), reqData())\n    respData := map[string]string{\"key\": \"value\"}\n    if err := c.SetResponse(respID, respData); err != nil {\n        // handle error...\n    }\n})\n```\n\nCodec's encoding will be invoked before message packed,\nand decoding should be invoked in the route handler which is after message unpacked.\n\n#### JSON Codec\n\n`JsonCodec` is an EasyTCP's built-in codec, which uses `encoding/json` as the default implementation.\nCan be changed by build from other tags.\n\n[jsoniter](https://github.com/json-iterator/go) :\n\n```sh\ngo build -tags=jsoniter .\n```\n\n#### Protobuf Codec\n\n`ProtobufCodec` is an EasyTCP's built-in codec, which uses `google.golang.org/protobuf` as the implementation.\n\n#### Msgpack Codec\n\n`MsgpackCodec` is an EasyTCP's built-in codec, which uses `github.com/vmihailenco/msgpack` as the implementation.\n\n## Contribute\n\nCheck out a new branch for the job, and make sure github action passed.\n\nUse issues for everything\n\n- For a small change, just send a PR.\n- For bigger changes open an issue for discussion before sending a PR.\n- PR should have:\n  - Test case\n  - Documentation\n  - Example (If it makes sense)\n- You can also contribute by:\n  - Reporting issues\n  - Suggesting new features or enhancements\n  - Improve/fix documentation\n\n## Stargazers over time\n\n[![Stargazers over time](https://starchart.cc/DarthPestilane/easytcp.svg)](https://starchart.cc/DarthPestilane/easytcp)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDarthPestilane%2Feasytcp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FDarthPestilane%2Feasytcp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FDarthPestilane%2Feasytcp/lists"}