Skip to content

hoululu945/gws

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

607 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

中文

GWS

logo

Simple, Fast, Reliable WebSocket Server & Client

awesome codecov go-test go-reportcard license go-version

Introduction

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.

Why GWS

  • 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.

Benchmark

IOPS (Echo Server)

GOMAXPROCS=4, Connection=1000, CompressEnabled=false

performance

gorilla and nhooyr not using streaming api

GoBench

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

Index

Feature

  • Event API
  • Broadcast
  • Dial via Proxy
  • Zero Allocs Read / Write
  • Concurrent & Asynchronous Non-Blocking Write
  • Passed WebSocket Autobahn-Testsuite

Attention

  • 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.

Install

go get -v github.com/lxzan/gws@latest

Event

type 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
}

Quick Start

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")
}

Best Practice

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())
}

More Examples

KCP

  • 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()
}

Proxy

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()
}

Broadcast

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)
    }
}

Autobahn Test

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

Communication

微信需要先添加好友, 然后拉人入群, 请注明来意.

WeChat      QQ

Acknowledgments

The following project had particular influence on gws's design.

About

simple, fast, reliable websocket server & client, supports running over tcp/kcp/unix domain socket. keywords: ws, proxy, chat, go, golang...

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Go 99.1%
  • Other 0.9%