{"id":13413612,"url":"https://github.com/lxzan/gws","last_synced_at":"2025-05-14T10:05:09.662Z","repository":{"id":59604319,"uuid":"534446803","full_name":"lxzan/gws","owner":"lxzan","description":"simple, fast, reliable websocket server \u0026 client, supports running over tcp/kcp/unix domain socket. keywords: ws, proxy, chat, go, golang...","archived":false,"fork":false,"pushed_at":"2025-05-13T06:55:28.000Z","size":16172,"stargazers_count":1542,"open_issues_count":0,"forks_count":97,"subscribers_count":15,"default_branch":"main","last_synced_at":"2025-05-13T07:36:15.420Z","etag":null,"topics":["context-takeover","go-websocket","go-ws","kcp","permessage-deflate","rfc7692","websocket","ws"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/lxzan/gws","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/lxzan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-09-09T00:51:50.000Z","updated_at":"2025-05-13T07:00:25.000Z","dependencies_parsed_at":"2023-12-03T07:23:38.349Z","dependency_job_id":"f1dae21c-5d90-4847-be62-2397d1f2fbb9","html_url":"https://github.com/lxzan/gws","commit_stats":{"total_commits":271,"total_committers":3,"mean_commits":90.33333333333333,"dds":0.3210332103321033,"last_synced_commit":"264af19a909460cbbbf4a02d8df4a9bfd10237f4"},"previous_names":[],"tags_count":74,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lxzan%2Fgws","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lxzan%2Fgws/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lxzan%2Fgws/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lxzan%2Fgws/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lxzan","download_url":"https://codeload.github.com/lxzan/gws/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254119469,"owners_count":22017951,"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":["context-takeover","go-websocket","go-ws","kcp","permessage-deflate","rfc7692","websocket","ws"],"created_at":"2024-07-30T20:01:44.499Z","updated_at":"2025-05-14T10:05:09.605Z","avatar_url":"https://github.com/lxzan.png","language":"Go","funding_links":[],"categories":["Networking","Go","网络","Tools per Language"],"sub_categories":["Transliteration","音译","Go"],"readme":"[中文](README_CN.md)\n\n\u003cdiv align=\"center\"\u003e\n\t\u003ch1\u003eGWS\u003c/h1\u003e\n\t\u003cimg src=\"assets/logo.png\" alt=\"logo\" width=\"300px\"\u003e\n\u003c/div\u003e\n\n\u003ch3 align=\"center\"\u003eSimple, Fast, Reliable WebSocket Server \u0026 Client\u003c/h3\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![awesome](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#networking)\n[![codecov](https://codecov.io/gh/lxzan/gws/graph/badge.svg?token=DJU7YXWN05)](https://codecov.io/gh/lxzan/gws)\n[![Go Test](https://github.com/lxzan/gws/actions/workflows/go.yml/badge.svg?branch=main)](https://github.com/lxzan/gws/actions/workflows/go.yml)\n[![go-reportcard](https://goreportcard.com/badge/github.com/lxzan/gws)](https://goreportcard.com/report/github.com/lxzan/gws)\n[![HelloGithub](https://api.hellogithub.com/v1/widgets/recommend.svg?rid=268cee8eb54b4a7189d38fb12f165177\u0026claim_uid=TeObZoJ8pgUvBWf\u0026theme=small)](https://hellogithub.com/repository/268cee8eb54b4a7189d38fb12f165177)\n[![license](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)\n[![go-version](https://img.shields.io/badge/go-%3E%3D1.18-30dff3?style=flat-square\u0026logo=go)](https://github.com/lxzan/gws)\n\n\u003c/div\u003e\n\n### Introduction\n\nGWS (Go WebSocket) is a very simple, fast, reliable and feature-rich WebSocket implementation written in Go. It is\ndesigned to be used in highly-concurrent environments, and it is suitable for\nbuilding `API`, `Proxy`, `Game`, `Live Video`, `Message`, etc. It supports both server and client side with a simple API\nwhich mean you can easily write a server or client by yourself.\n\nGWS developed base on Event-Driven model. every connection has a goroutine to handle the event, and the event is able\nto be processed in a non-blocking way.\n\n### Why GWS\n\n- \u003cfont size=3\u003eSimplicity and Ease of Use\u003c/font\u003e\n\n    - **User-Friendly**: Simple and clear `WebSocket` Event API design makes server-client interaction easy.\n    - **Code Efficiency**: Minimizes the amount of code needed to implement complex WebSocket solutions.\n\n- \u003cfont size=3\u003eHigh-Performance\u003c/font\u003e\n\n    - **High IOPS Low Latency**: Designed for rapid data transmission and reception, ideal for time-sensitive\n      applications.\n    - **Low Memory Usage**: Highly optimized memory multiplexing system to minimize memory usage and reduce your cost of\n      ownership.\n\n- \u003cfont size=3\u003eReliability and Stability\u003c/font\u003e\n    - **Robust Error Handling**: Advanced mechanisms to manage and mitigate errors, ensuring continuous operation.\n    - **Well-Developed Test Cases**: Passed all `Autobahn` test cases, fully compliant with `RFC 7692`. Unit test\n      coverage is almost 100%, covering all conditional branches.\n\n### Benchmark\n\n#### IOPS (Echo Server)\n\nGOMAXPROCS=4, Connection=1000, CompressEnabled=false\n\n![performance](assets/performance-compress-disabled.png)\n\n#### GoBench\n\n```go\ngo test -benchmem -run=^$ -bench . github.com/lxzan/gws\ngoos: linux\ngoarch: amd64\npkg: github.com/lxzan/gws\ncpu: AMD Ryzen 5 PRO 4650G with Radeon Graphics\nBenchmarkConn_WriteMessage/compress_disabled-12                  5263632               232.3 ns/op            24 B/op          1 allocs/op\nBenchmarkConn_WriteMessage/compress_enabled-12                     99663             11265 ns/op             386 B/op          1 allocs/op\nBenchmarkConn_ReadMessage/compress_disabled-12                   7809654               152.4 ns/op             8 B/op          0 allocs/op\nBenchmarkConn_ReadMessage/compress_enabled-12                     326257              3133 ns/op              81 B/op          1 allocs/op \nPASS\nok      github.com/lxzan/gws    17.231s\n```\n\n### Index\n\n- [Introduction](#introduction)\n- [Why GWS](#why-gws)\n- [Benchmark](#benchmark)\n\t- [IOPS (Echo Server)](#iops-echo-server)\n\t- [GoBench](#gobench)\n- [Index](#index)\n- [Feature](#feature)\n- [Attention](#attention)\n- [Install](#install)\n- [Event](#event)\n- [Quick Start](#quick-start)\n- [Best Practice](#best-practice)\n- [More Examples](#more-examples)\n\t- [KCP](#kcp)\n\t- [Proxy](#proxy)\n\t- [Broadcast](#broadcast)\n\t- [WriteWithTimeout](#writewithtimeout)\n\t- [Pub / Sub](#pub--sub)\n- [Autobahn Test](#autobahn-test)\n- [Communication](#communication)\n- [Buy me a coffee](#buy-me-a-coffee)\n- [Acknowledgments](#acknowledgments)\n\n### Feature\n\n- [x] Event API\n- [x] Broadcast\n- [x] Dial via Proxy\n- [x] Context-Takeover \n- [x] Concurrent \u0026 Asynchronous Non-Blocking Write\n- [x] Segmented Writing of Large Files\n- [x] Passed Autobahn Test Cases [Server](https://lxzan.github.io/gws/reports/servers/) / [Client](https://lxzan.github.io/gws/reports/clients/)\n\n### Attention\n\n- The errors returned by the gws.Conn export methods are ignorable, and are handled internally.\n- Transferring large files with gws tends to block the connection.\n- If HTTP Server is reused, it is recommended to enable goroutine, as blocking will prevent the context from being GC.\n\n### Install\n\n```bash\ngo get -v github.com/lxzan/gws@latest\n```\n\n### Event\n\n```go\ntype Event interface {\n    OnOpen(socket *Conn)                        // connection is established\n    OnClose(socket *Conn, err error)            // received a close frame or input/output error occurs\n    OnPing(socket *Conn, payload []byte)        // received a ping frame\n    OnPong(socket *Conn, payload []byte)        // received a pong frame\n    OnMessage(socket *Conn, message *Message)   // received a text/binary frame\n}\n```\n\n### Quick Start\n\n```go\npackage main\n\nimport \"github.com/lxzan/gws\"\n\nfunc main() {\n\tgws.NewServer(\u0026gws.BuiltinEventHandler{}, nil).Run(\":6666\")\n}\n```\n\n### Best Practice\n\n```go\npackage main\n\nimport (\n\t\"github.com/lxzan/gws\"\n\t\"net/http\"\n\t\"time\"\n)\n\nconst (\n\tPingInterval = 5 * time.Second\n\tPingWait     = 10 * time.Second\n)\n\nfunc main() {\n\tupgrader := gws.NewUpgrader(\u0026Handler{}, \u0026gws.ServerOption{\n\t\tParallelEnabled:  true,                                 // Parallel message processing\n\t\tRecovery:          gws.Recovery,                         // Exception recovery\n\t\tPermessageDeflate: gws.PermessageDeflate{Enabled: true}, // Enable compression\n\t})\n\thttp.HandleFunc(\"/connect\", func(writer http.ResponseWriter, request *http.Request) {\n\t\tsocket, err := upgrader.Upgrade(writer, request)\n\t\tif err != nil {\n\t\t\treturn\n\t\t}\n\t\tgo func() {\n\t\t\tsocket.ReadLoop() // Blocking prevents the context from being GC.\n\t\t}()\n\t})\n\thttp.ListenAndServe(\":6666\", nil)\n}\n\ntype Handler struct{}\n\nfunc (c *Handler) OnOpen(socket *gws.Conn) {\n\t_ = socket.SetDeadline(time.Now().Add(PingInterval + PingWait))\n}\n\nfunc (c *Handler) OnClose(socket *gws.Conn, err error) {}\n\nfunc (c *Handler) OnPing(socket *gws.Conn, payload []byte) {\n\t_ = socket.SetDeadline(time.Now().Add(PingInterval + PingWait))\n\t_ = socket.WritePong(nil)\n}\n\nfunc (c *Handler) OnPong(socket *gws.Conn, payload []byte) {}\n\nfunc (c *Handler) OnMessage(socket *gws.Conn, message *gws.Message) {\n\tdefer message.Close()\n\tsocket.WriteMessage(message.Opcode, message.Bytes())\n}\n\n```\n\n### More Examples\n\n#### KCP\n\n- server\n\n```go\npackage main\n\nimport (\n\t\"log\"\n\t\"github.com/lxzan/gws\"\n\tkcp \"github.com/xtaci/kcp-go\"\n)\n\nfunc main() {\n\tlistener, err := kcp.Listen(\":6666\")\n\tif err != nil {\n\t\tlog.Println(err.Error())\n\t\treturn\n\t}\n\tapp := gws.NewServer(\u0026gws.BuiltinEventHandler{}, nil)\n\tapp.RunListener(listener)\n}\n```\n\n- client\n\n```go\npackage main\n\nimport (\n\t\"github.com/lxzan/gws\"\n\tkcp \"github.com/xtaci/kcp-go\"\n\t\"log\"\n)\n\nfunc main() {\n\tconn, err := kcp.Dial(\"127.0.0.1:6666\")\n\tif err != nil {\n\t\tlog.Println(err.Error())\n\t\treturn\n\t}\n\tapp, _, err := gws.NewClientFromConn(\u0026gws.BuiltinEventHandler{}, nil, conn)\n\tif err != nil {\n\t\tlog.Println(err.Error())\n\t\treturn\n\t}\n\tapp.ReadLoop()\n}\n\n```\n\n#### Proxy\n\nDial via proxy, using socks5 protocol.\n\n```go\npackage main\n\nimport (\n\t\"crypto/tls\"\n\t\"github.com/lxzan/gws\"\n\t\"golang.org/x/net/proxy\"\n\t\"log\"\n)\n\nfunc main() {\n\tsocket, _, err := gws.NewClient(new(gws.BuiltinEventHandler), \u0026gws.ClientOption{\n\t\tAddr:      \"wss://example.com/connect\",\n\t\tTlsConfig: \u0026tls.Config{InsecureSkipVerify: true},\n\t\tNewDialer: func() (gws.Dialer, error) {\n\t\t\treturn proxy.SOCKS5(\"tcp\", \"127.0.0.1:1080\", nil, nil)\n\t\t},\n\t\tPermessageDeflate: gws.PermessageDeflate{\n\t\t\tEnabled:               true,\n\t\t\tServerContextTakeover: true,\n\t\t\tClientContextTakeover: true,\n\t\t},\n\t})\n\tif err != nil {\n\t\tlog.Println(err.Error())\n\t\treturn\n\t}\n\tsocket.ReadLoop()\n}\n\n```\n\n#### Broadcast\n\nCreate a Broadcaster instance, call the Broadcast method in a loop to send messages to each client, and close the\nbroadcaster to reclaim memory. The message is compressed only once.\n\n```go\nfunc Broadcast(conns []*gws.Conn, opcode gws.Opcode, payload []byte) {\n    var b = gws.NewBroadcaster(opcode, payload)\n    defer b.Close()\n    for _, item := range conns {\n        _ = b.Broadcast(item)\n    }\n}\n```\n\n#### WriteWithTimeout\n\n`SetDeadline` covers most of the scenarios, but if you want to control the timeout for each write, you need to\nencapsulate the `WriteWithTimeout` function, the creation and destruction of the `timer` will incur some overhead.\n\n```go\nfunc WriteWithTimeout(socket *gws.Conn, p []byte, timeout time.Duration) error {\n\tvar sig = atomic.Uint32{}\n\tvar timer = time.AfterFunc(timeout, func() {\n\t\tif sig.CompareAndSwap(0, 1) {\n\t\t\tsocket.WriteClose(1000, []byte(\"write timeout\"))\n\t\t}\n\t})\n\tvar err = socket.WriteMessage(gws.OpcodeText, p)\n\tif sig.CompareAndSwap(0, 1) {\n\t\ttimer.Stop()\n\t}\n\treturn err\n}\n```\n\n#### Pub / Sub\n\nUse the event_emitter package to implement the publish-subscribe model. Wrap `gws.Conn` in a structure and implement the\nGetSubscriberID method to get the subscription ID, which must be unique. The subscription ID is used to identify the\nsubscriber, who can only receive messages on the subject of his subscription.\n\nThis example is useful for building chat rooms or push messages using gws. This means that a user can subscribe to one\nor more topics via websocket, and when a message is posted to that topic, all subscribers will receive the message.\n\n```go\npackage main\n\nimport (\n    \"github.com/lxzan/event_emitter\"\n    \"github.com/lxzan/gws\"\n)\n\ntype Subscriber gws.Conn\n\nfunc NewSubscriber(conn *gws.Conn) *Subscriber { return (*Subscriber)(conn) }\n\nfunc (c *Subscriber) GetSubscriberID() int64 {\n    userId, _ := c.GetMetadata().Load(\"userId\")\n    return userId.(int64)\n}\n\nfunc (c *Subscriber) GetMetadata() event_emitter.Metadata { return c.Conn().Session() }\n\nfunc (c *Subscriber) Conn() *gws.Conn { return (*gws.Conn)(c) }\n\nfunc Subscribe(em *event_emitter.EventEmitter[int64, *Subscriber], s *Subscriber, topic string) {\n    em.Subscribe(s, topic, func(msg any) {\n        _ = msg.(*gws.Broadcaster).Broadcast(s.Conn())\n    })\n}\n\nfunc Publish(em *event_emitter.EventEmitter[int64, *Subscriber], topic string, msg []byte) {\n    var broadcaster = gws.NewBroadcaster(gws.OpcodeText, msg)\n    defer broadcaster.Close()\n    em.Publish(topic, broadcaster)\n}\n```\n\n### Autobahn Test\n\n```bash\ncd examples/autobahn\nmkdir reports\ndocker run -it --rm \\\n    -v ${PWD}/config:/config \\\n    -v ${PWD}/reports:/reports \\\n    crossbario/autobahn-testsuite \\\n    wstest -m fuzzingclient -s /config/fuzzingclient.json\n```\n\n### Communication\n\n\u003e 微信需要先添加好友再拉群, 请注明来自 GitHub\n\n\u003cdiv\u003e\n\u003cimg src=\"assets/wechat.png\" alt=\"WeChat\" width=\"300\" height=\"300\" style=\"display: inline-block;\"/\u003e\n\u003cspan\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u003c/span\u003e\n\u003cimg src=\"assets/qq.jpg\" alt=\"QQ\" width=\"300\" height=\"300\" style=\"display: inline-block\"/\u003e\n\u003c/div\u003e\n\n### Buy me a coffee\n\n\u003cimg src=\"assets/alipay.jpg\" alt=\"WeChat\" width=\"300\" style=\"display: inline-block;\"/\u003e\n\n### Acknowledgments\n\nThe following project had particular influence on gws's design.\n\n- [crossbario/autobahn-testsuite](https://github.com/crossbario/autobahn-testsuite)\n- [klauspost/compress](https://github.com/klauspost/compress)\n- [lesismal/nbio](https://github.com/lesismal/nbio)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flxzan%2Fgws","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flxzan%2Fgws","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flxzan%2Fgws/lists"}