gws

package module
v1.5.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 5, 2023 License: MIT Imports: 22 Imported by: 64

README

gws

event-driven go websocket server

Build Status MIT licensed Go Version codecov Go Report Card

Highlight
  • Single dependency
  • IO multiplexing support, concurrent message processing and asynchronous non-blocking message writing
  • High IOPS and low latency, low CPU usage
  • Support fast parsing WebSocket protocol directly from TCP, faster handshake, 30% lower memory usage
  • Fully passes the WebSocket autobahn-testsuite
Install
go get -v github.com/lxzan/gws@latest
Event
type Event interface {
	OnOpen(socket *Conn)
	OnError(socket *Conn, err error)
	OnClose(socket *Conn, code uint16, reason []byte)
	OnPing(socket *Conn, payload []byte)
	OnPong(socket *Conn, payload []byte)
	OnMessage(socket *Conn, message *Message)
}
Quick Start
package main

import "github.com/lxzan/gws"

func main() {
	gws.NewServer(new(gws.BuiltinEventHandler), nil).Run(":6666")
}
Best Practice
package main

import (
	"github.com/lxzan/gws"
	"time"
)

const PingInterval = 10 * time.Second

func main() {
	options := &gws.ServerOption{ReadAsyncEnabled: true, ReadAsyncGoLimit: 4}
	gws.NewServer(new(Handler), options).Run(":6666") 
}

type Handler struct{}

func (c *Handler) OnOpen(socket *gws.Conn) { _ = socket.SetDeadline(time.Now().Add(3 * PingInterval)) }

func (c *Handler) DeleteSession(socket *gws.Conn) {}

func (c *Handler) OnError(socket *gws.Conn, err error) { c.DeleteSession(socket) }

func (c *Handler) OnClose(socket *gws.Conn, code uint16, reason []byte) { c.DeleteSession(socket) }

func (c *Handler) OnPing(socket *gws.Conn, payload []byte) {
	_ = socket.SetDeadline(time.Now().Add(3 * PingInterval))
	_ = socket.WritePong(nil)
}

func (c *Handler) OnPong(socket *gws.Conn, payload []byte) {}

func (c *Handler) OnMessage(socket *gws.Conn, message *gws.Message) {}
Usage
Upgrade from HTTP
package main

import (
	"github.com/lxzan/gws"
	"log"
	"net/http"
)

func main() {
	upgrader := gws.NewUpgrader(new(gws.BuiltinEventHandler), &gws.ServerOption{
		CheckOrigin: func(r *http.Request, session gws.SessionStorage) bool {
			session.Store("username", r.URL.Query().Get("username"))
			return true
		},
	})

	http.HandleFunc("/connect", func(writer http.ResponseWriter, request *http.Request) {
		socket, err := upgrader.Upgrade(writer, request)
		if err != nil {
			log.Printf(err.Error())
			return
		}
		socket.ReadLoop()
	})

	if err := http.ListenAndServe(":6666", nil); err != nil {
		log.Fatalf("%v", err)
	}
}
Unix Domain Socket
  • server
package main

import (
	"github.com/lxzan/gws"
	"log"
	"net"
)

func main() {
	listener, err := net.Listen("unix", "/run/gws.sock")
	if err != nil {
		log.Println(err.Error())
		return
	}
	var app = gws.NewServer(new(gws.BuiltinEventHandler), nil)
	if err := app.RunListener(listener); err != nil {
		log.Println(err.Error())
	}
}
  • client
package main

import (
	"fmt"
	"github.com/lxzan/gws"
	"log"
)

func main() {
	socket, _, err := gws.NewClient(new(gws.BuiltinEventHandler), &gws.ClientOption{
		Addr: "unix://localhost/run/gws.sock",
	})
	if err != nil {
		log.Println(err.Error())
		return
	}
	socket.ReadLoop()
}
Broadcast
func Broadcast(conns []*gws.Conn, opcode gws.Opcode, payload []byte) {
	for _, item := range conns {
		_ = item.WriteAsync(opcode, payload)
	}
}
Write JSON
socket.WriteAny(gws.JsonCodec, gws.OpcodeText, data)
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
Benchmark
  • Machine: Ubuntu 20.04LTS VM (4C8T)

  • IOPS

// ${message_num} depends on the maximum load capacity of each package
tcpkali -c 1000 --connect-rate 500 -r ${message_num} -T 300s -f assets/1K.txt --ws 127.0.0.1:${port}/connect

iops

  • Latency
tcpkali -c 1000 --connect-rate 500 -r 100 -T 300s -f assets/1K.txt --ws 127.0.0.1:${port}/connect

latency

  • CPU
 PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
9898 caster    20   0  721172  39648   7404 S 259.5   1.0  78:44.15 gorilla-linux-a
9871 caster    20   0  721212  41788   7188 S 161.5   1.0  51:39.43 gws-linux-amd64
Communication
QQ
Acknowledgments

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

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	JsonCodec = new(jsonCodec)
)

Functions

This section is empty.

Types

type BuiltinEventHandler added in v1.3.0

type BuiltinEventHandler struct{}

func (BuiltinEventHandler) OnClose added in v1.3.0

func (b BuiltinEventHandler) OnClose(socket *Conn, code uint16, reason []byte)

func (BuiltinEventHandler) OnError added in v1.3.0

func (b BuiltinEventHandler) OnError(socket *Conn, err error)

func (BuiltinEventHandler) OnMessage added in v1.3.0

func (b BuiltinEventHandler) OnMessage(socket *Conn, message *Message)

func (BuiltinEventHandler) OnOpen added in v1.3.0

func (b BuiltinEventHandler) OnOpen(socket *Conn)

func (BuiltinEventHandler) OnPing added in v1.3.0

func (b BuiltinEventHandler) OnPing(socket *Conn, payload []byte)

func (BuiltinEventHandler) OnPong added in v1.3.0

func (b BuiltinEventHandler) OnPong(socket *Conn, payload []byte)

type ClientOption added in v1.4.2

type ClientOption struct {
	// 写缓冲区的大小, v1.4.5版本此参数被废弃
	// Deprecated: Size of the write buffer, v1.4.5 version of this parameter is deprecated
	WriteBufferSize     int
	ReadAsyncEnabled    bool
	ReadAsyncGoLimit    int
	ReadAsyncCap        int
	ReadMaxPayloadSize  int
	ReadBufferSize      int
	WriteAsyncCap       int
	WriteMaxPayloadSize int
	CompressEnabled     bool
	CompressLevel       int
	CompressThreshold   int
	CheckUtf8Enabled    bool

	// 连接地址, 例如 wss://example.com/connect
	// service address, eg: wss://example.com/connect
	Addr string
	// 额外的请求头
	// extra request header
	RequestHeader http.Header
	// dial timeout
	// 连接超时时间
	DialTimeout time.Duration
	// TLS设置
	// tls config
	TlsConfig *tls.Config
}

type Codec added in v1.5.0

type Codec interface {
	NewEncoder(io.Writer) Encoder
}

type ConcurrentMap added in v1.2.5

type ConcurrentMap[K comparable, V any] struct {
	// contains filtered or unexported fields
}

ConcurrentMap used to store websocket connections in the IM server 用来存储IM等服务的连接

func NewConcurrentMap added in v1.2.5

func NewConcurrentMap[K comparable, V any](segments uint64) *ConcurrentMap[K, V]

func (*ConcurrentMap[K, V]) Delete added in v1.2.5

func (c *ConcurrentMap[K, V]) Delete(key K)

func (*ConcurrentMap[K, V]) Len added in v1.2.5

func (c *ConcurrentMap[K, V]) Len() int

func (*ConcurrentMap[K, V]) Load added in v1.2.5

func (c *ConcurrentMap[K, V]) Load(key K) (value V, exist bool)

func (*ConcurrentMap[K, V]) Range added in v1.2.5

func (c *ConcurrentMap[K, V]) Range(f func(key K, value V) bool)

Range calls f sequentially for each key and value present in the map. If f returns false, range stops the iteration.

func (*ConcurrentMap[K, V]) Store added in v1.2.5

func (c *ConcurrentMap[K, V]) Store(key K, value V)

type Config added in v1.2.0

type Config struct {
	// 是否开启异步读, 开启的话会并行调用OnMessage
	// Whether to enable asynchronous reading, if enabled OnMessage will be called in parallel
	ReadAsyncEnabled bool

	// 异步读的最大并行协程数量
	// Maximum number of parallel concurrent processes for asynchronous reads
	ReadAsyncGoLimit int

	// 异步读的容量限制, 容量溢出将会返回错误
	// Capacity limit for asynchronous reads, overflow will return an error
	ReadAsyncCap int

	// 最大读取的消息内容长度
	// Maximum read message content length
	ReadMaxPayloadSize int

	// 读缓冲区的大小
	// Size of the read buffer
	ReadBufferSize int

	// 异步写的容量限制, 容量溢出将会返回错误
	// Capacity limit for asynchronous writes, overflow will return an error
	WriteAsyncCap int

	// 最大写入的消息内容长度
	// Maximum length of written message content
	WriteMaxPayloadSize int

	// 写缓冲区的大小, v1.4.5版本此参数被废弃
	// Deprecated: Size of the write buffer, v1.4.5 version of this parameter is deprecated
	WriteBufferSize int

	// 是否开启数据压缩
	// Whether to turn on data compression
	CompressEnabled bool

	// 压缩级别
	// Compress level
	CompressLevel int

	// 压缩阈值, 低于阈值的消息不会被压缩
	// Compression threshold, messages below the threshold will not be compressed
	CompressThreshold int

	// 是否检查文本utf8编码, 关闭性能会好点
	// Whether to check the text utf8 encoding, turn off the performance will be better
	CheckUtf8Enabled bool
}

type Conn

type Conn struct {
	// store session information
	SessionStorage SessionStorage
	// contains filtered or unexported fields
}

func NewClient added in v1.4.2

func NewClient(handler Event, option *ClientOption) (client *Conn, resp *http.Response, e error)

NewClient 创建WebSocket客户端; 支持ws, wss, unix三种协议 Create WebSocket client, support ws, wss, unix three protocols

func (*Conn) Listen added in v1.1.2

func (c *Conn) Listen()

Listen 监听websocket消息 Deprecated: Listen will be deprecated in future versions, please use ReadLoop instead.

func (*Conn) LocalAddr added in v1.0.1

func (c *Conn) LocalAddr() net.Addr

func (*Conn) NetConn added in v1.2.10

func (c *Conn) NetConn() net.Conn

NetConn get tcp/tls/... conn

func (*Conn) ReadLoop added in v1.4.8

func (c *Conn) ReadLoop()

ReadLoop start a read message loop 启动一个读消息的死循环

func (*Conn) RemoteAddr added in v1.0.1

func (c *Conn) RemoteAddr() net.Addr

func (*Conn) SetDeadline

func (c *Conn) SetDeadline(t time.Time) error

SetDeadline sets deadline

func (*Conn) SetReadDeadline added in v1.1.2

func (c *Conn) SetReadDeadline(t time.Time) error

SetReadDeadline sets read deadline

func (*Conn) SetWriteDeadline added in v1.1.2

func (c *Conn) SetWriteDeadline(t time.Time) error

SetWriteDeadline sets write deadline

func (*Conn) WriteAny added in v1.5.0

func (c *Conn) WriteAny(codec Codec, opcode Opcode, v interface{}) error

WriteAny 以特定编码写入数据 使用此方法时, CheckUtf8Enabled=false且CompressThreshold选项无效 Write data in a specific encoding When using this method, CheckUtf8Enabled=false and CompressThreshold option is disabled

func (*Conn) WriteAsync added in v1.3.0

func (c *Conn) WriteAsync(opcode Opcode, payload []byte) error

WriteAsync 异步非阻塞地写入消息 Write messages asynchronously and non-blockingly

func (*Conn) WriteClose

func (c *Conn) WriteClose(code uint16, reason []byte)

WriteClose proactively close the connection code: https://developer.mozilla.org/zh-CN/docs/Web/API/CloseEvent#status_codes 通过emitError发送关闭帧, 将连接状态置为关闭, 用于服务端主动断开连接 没有特殊原因的话, 建议code=0, reason=nil

func (*Conn) WriteMessage added in v1.1.0

func (c *Conn) WriteMessage(opcode Opcode, payload []byte) error

WriteMessage 发送消息

func (*Conn) WritePing

func (c *Conn) WritePing(payload []byte) error

WritePing write ping frame

func (*Conn) WritePong

func (c *Conn) WritePong(payload []byte) error

WritePong write pong frame

func (*Conn) WriteString added in v1.2.10

func (c *Conn) WriteString(s string) error

WriteString write text frame force convert string to []byte

type Encoder added in v1.5.0

type Encoder interface {
	Encode(v interface{}) error
}

type Event added in v1.1.2

type Event interface {
	// 建立连接事件
	OnOpen(socket *Conn)

	// 错误事件
	// IO错误, 协议错误, 压缩解压错误...
	OnError(socket *Conn, err error)

	// 关闭事件
	// 接收到了另一端发送的关闭帧
	OnClose(socket *Conn, code uint16, reason []byte)

	// 心跳探测事件
	OnPing(socket *Conn, payload []byte)

	// 心跳响应事件
	OnPong(socket *Conn, payload []byte)

	// 消息事件
	// 如果开启了ReadAsyncEnabled, 会并行调用OnMessage
	OnMessage(socket *Conn, message *Message)
}

WebSocket Event one of onclose and onerror will be called once during the connection's lifetime. 在连接的生命周期中,onclose和onerror中的一个有且只有一次被调用.

type Message

type Message struct {
	Opcode Opcode        // 帧状态码
	Data   *bytes.Buffer // 数据缓冲
}

func (*Message) Bytes

func (c *Message) Bytes() []byte

func (*Message) Close

func (c *Message) Close()

Close recycle buffer

func (*Message) Read added in v1.1.0

func (c *Message) Read(p []byte) (n int, err error)

type Opcode

type Opcode uint8
const (
	OpcodeContinuation    Opcode = 0x0
	OpcodeText            Opcode = 0x1
	OpcodeBinary          Opcode = 0x2
	OpcodeCloseConnection Opcode = 0x8
	OpcodePing            Opcode = 0x9
	OpcodePong            Opcode = 0xA
)

func (Opcode) IsDataFrame added in v1.1.2

func (c Opcode) IsDataFrame() bool

type Server added in v1.4.7

type Server struct {

	// OnConnect 建立连接事件, 用于处理限流, 熔断和安全问题; 返回错误将会断开连接.
	// Creates connection events for current limit, fuse and security issues; returning an error will disconnect.
	OnConnect func(conn net.Conn) error

	// OnError 接收握手过程中产生的错误回调
	// Receive error callbacks generated during the handshake
	OnError func(conn net.Conn, err error)
	// contains filtered or unexported fields
}

func NewServer added in v1.4.7

func NewServer(eventHandler Event, option *ServerOption) *Server

NewServer 创建websocket服务器 create a websocket server

func (*Server) Run added in v1.4.7

func (c *Server) Run(addr string) error

Run runs ws server addr: Address of the listener

func (*Server) RunListener added in v1.4.9

func (c *Server) RunListener(listener net.Listener) error

func (*Server) RunTLS added in v1.4.7

func (c *Server) RunTLS(addr string, certFile, keyFile string) error

RunTLS runs wss server addr: Address of the listener config: tls config

type ServerOption added in v1.4.0

type ServerOption struct {
	// 写缓冲区的大小, v1.4.5版本此参数被废弃
	// Deprecated: Size of the write buffer, v1.4.5 version of this parameter is deprecated
	WriteBufferSize     int
	ReadAsyncEnabled    bool
	ReadAsyncGoLimit    int
	ReadAsyncCap        int
	ReadMaxPayloadSize  int
	ReadBufferSize      int
	WriteAsyncCap       int
	WriteMaxPayloadSize int
	CompressEnabled     bool
	CompressLevel       int
	CompressThreshold   int
	CheckUtf8Enabled    bool

	// WebSocket子协议, 一般不需要设置
	// WebSocket subprotocol, usually no need to set
	Subprotocols []string

	// 连接握手时添加的额外的响应头, 如果客户端不支持就不要传
	// https://www.rfc-editor.org/rfc/rfc6455.html#section-1.3
	// attention: client may not support custom response header, use nil instead
	ResponseHeader http.Header

	// 检查请求来源
	// Check the origin of the request
	CheckOrigin func(r *http.Request, session SessionStorage) bool
}

type SessionStorage added in v1.2.3

type SessionStorage interface {
	Load(key string) (value interface{}, exist bool)
	Delete(key string)
	Store(key string, value interface{})
	Range(f func(key string, value interface{}) bool)
}

SessionStorage because sync.Map is not easy to debug, so I implemented my own map. if you don't like it, use sync.Map instead.

type Upgrader

type Upgrader struct {
	// contains filtered or unexported fields
}

func NewUpgrader added in v1.2.11

func NewUpgrader(eventHandler Event, option *ServerOption) *Upgrader

func (*Upgrader) Accept added in v1.2.11

func (c *Upgrader) Accept(w http.ResponseWriter, r *http.Request) (*Conn, error)

Accept http upgrade to websocket protocol Deprecated: Accept will be deprecated in future versions, please use Upgrade instead.

func (*Upgrader) Upgrade

func (c *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request) (*Conn, error)

Upgrade http upgrade to websocket protocol

Directories

Path Synopsis
examples
autobahn/client command
autobahn/server command
chatroom command
client command
echo command
wss command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL