GWS (Go WebSocket) is a very simple, fast, reliable and feature-rich WebSocket implementation written in Go. It is
designed to be used in highly-concurrent environments, and it is suitable for
building API, PROXY, GAME, Live Video, MESSAGE, etc. It supports both server and client side with a simple API
which mean you can easily write a server or client by yourself.
GWS developed base on Event-Driven model. every connection has a goroutine to handle the event, and the event is able to be processed in a non-blocking way.
-
Simplicity and Ease of Use
- User-Friendly API: Straightforward and easy-to-understand API, making server and client setup hassle-free.
- Code Efficiency: Minimizes the amount of code needed to implement complex WebSocket solutions.
-
High-Performance
- Zero Allocs IO: Built-in multi-level memory pool to minimize dynamic memory allocation during reads and writes.
- Optimized for Speed: Designed for rapid data transmission and reception, ideal for time-sensitive applications.
-
Reliability and Stability
- Event-Driven Architecture: Ensures stable performance even in highly concurrent environments.
- Robust Error Handling: Advanced mechanisms to manage and mitigate errors, ensuring continuous operation.
GOMAXPROCS=4, Connection=1000, CompressEnabled=false
gorilla and nhooyr not using streaming api
goos: linux
goarch: amd64
pkg: github.com/lxzan/gws
cpu: AMD Ryzen 5 PRO 4650G with Radeon Graphics
BenchmarkConn_WriteMessage/compress_disabled-8 7252513 165.4 ns/op 0 B/op 0 allocs/op
BenchmarkConn_WriteMessage/compress_enabled-8 97394 10391 ns/op 349 B/op 0 allocs/op
BenchmarkConn_ReadMessage/compress_disabled-8 7812108 152.3 ns/op 16 B/op 0 allocs/op
BenchmarkConn_ReadMessage/compress_enabled-8 368712 3248 ns/op 108 B/op 0 allocs/op
PASS- Introduction
- Why GWS
- Benchmark
- Index
- Feature
- Attention
- Install
- Event
- Quick Start
- Best Practice
- More Examples
- Autobahn Test
- Communication
- Acknowledgments
- Event API
- Broadcast
- Dial via Proxy
- Zero Allocs Read / Write
- Concurrent & Asynchronous Non-Blocking Write
- Passed WebSocket Autobahn-Testsuite
- The errors returned by the gws.Conn export methods are ignorable, and are handled internally.
- Transferring large files with gws tends to block the connection.
- If HTTP Server is reused, it is recommended to enable goroutine, as blocking will prevent the context from being GC.
go get -v github.com/lxzan/gws@latesttype Event interface {
OnOpen(socket *Conn) // the connection is established
OnClose(socket *Conn, err error) // received a close frame or I/O error occurs
OnPing(socket *Conn, payload []byte) // receive a ping frame
OnPong(socket *Conn, payload []byte) // receive a pong frame
OnMessage(socket *Conn, message *Message) // receive a text/binary frame
}Very, very, very simple example.
The example let you know how to use the gws package without any other dependencies.
package main
import "github.com/lxzan/gws"
func main() {
gws.NewServer(&gws.BuiltinEventHandler{}, nil).Run(":6666")
}package main
import (
"github.com/lxzan/gws"
"net/http"
"time"
)
const (
PingInterval = 5 * time.Second
PingWait = 10 * time.Second
)
func main() {
upgrader := gws.NewUpgrader(&Handler{}, &gws.ServerOption{
ReadAsyncEnabled: true, // Parallel message processing
CompressEnabled: true, // Enable compression
Recovery: gws.Recovery, // Exception recovery
})
http.HandleFunc("/connect", func(writer http.ResponseWriter, request *http.Request) {
socket, err := upgrader.Upgrade(writer, request)
if err != nil {
return
}
go func() {
socket.ReadLoop() // Blocking prevents the context from being GC.
}()
})
http.ListenAndServe(":6666", nil)
}
type Handler struct{}
func (c *Handler) OnOpen(socket *gws.Conn) {
_ = socket.SetDeadline(time.Now().Add(PingInterval + PingWait))
}
func (c *Handler) OnClose(socket *gws.Conn, err error) {}
func (c *Handler) OnPing(socket *gws.Conn, payload []byte) {
_ = socket.SetDeadline(time.Now().Add(PingInterval + PingWait))
_ = socket.WritePong(nil)
}
func (c *Handler) OnPong(socket *gws.Conn, payload []byte) {}
func (c *Handler) OnMessage(socket *gws.Conn, message *gws.Message) {
defer message.Close()
socket.WriteMessage(message.Opcode, message.Bytes())
}- server
package main
import (
"log"
"github.com/lxzan/gws"
kcp "github.com/xtaci/kcp-go"
)
func main() {
listener, err := kcp.Listen(":6666")
if err != nil {
log.Println(err.Error())
return
}
app := gws.NewServer(&gws.BuiltinEventHandler{}, nil)
app.RunListener(listener)
}- client
package main
import (
"github.com/lxzan/gws"
kcp "github.com/xtaci/kcp-go"
"log"
)
func main() {
conn, err := kcp.Dial("127.0.0.1:6666")
if err != nil {
log.Println(err.Error())
return
}
app, _, err := gws.NewClientFromConn(&gws.BuiltinEventHandler{}, nil, conn)
if err != nil {
log.Println(err.Error())
return
}
app.ReadLoop()
}Dial via proxy, using socks5 protocol.
package main
import (
"crypto/tls"
"github.com/lxzan/gws"
"golang.org/x/net/proxy"
"log"
)
func main() {
socket, _, err := gws.NewClient(new(gws.BuiltinEventHandler), &gws.ClientOption{
Addr: "wss://example.com/connect",
TlsConfig: &tls.Config{InsecureSkipVerify: true},
NewDialer: func() (gws.Dialer, error) {
return proxy.SOCKS5("tcp", "127.0.0.1:1080", nil, nil)
},
})
if err != nil {
log.Println(err.Error())
return
}
socket.ReadLoop()
}Create a Broadcaster instance, call the Broadcast method in a loop to send messages to each client, and close the broadcaster to reclaim memory. The message is compressed only once.
func Broadcast(conns []*gws.Conn, opcode gws.Opcode, payload []byte) {
var b = gws.NewBroadcaster(opcode, payload)
defer b.Close()
for _, item := range conns {
_ = b.Broadcast(item)
}
}cd examples/autobahn
mkdir reports
docker run -it --rm \
-v ${PWD}/config:/config \
-v ${PWD}/reports:/reports \
crossbario/autobahn-testsuite \
wstest -m fuzzingclient -s /config/fuzzingclient.json微信需要先添加好友, 然后拉人入群, 请注明来意.
The following project had particular influence on gws's design.