func (ws *WebSocket) Send(msg []byte) error {
	ws.header[1] = 0b1000_0000
	size := len(msg)
	fieldLen := 0
	switch {
	case size <= 125:
		ws.header[1] |= byte(size)
	case size < 65536:
		ws.header[1] |= 126
		fieldLen = 2
	default:
		ws.header[1] |= 127
		fieldLen = 8
	}
	var i int
	for i = 0; i < fieldLen; i++ {
		digit := (fieldLen - i - 1) * 8
		ws.header[i+2] = byte((size >> digit) & 0xff)
	}
	copy(ws.header[i+2:], ws.mask)
	_, err := ws.conn.Write(ws.header[:i+6])
	if err != nil {
		return ws.checkClose(err)
	}
	for i := range msg {
		msg[i] = msg[i] ^ ws.mask[i%4]
	}
	_, err = ws.conn.Write(msg)
	return ws.checkClose(err)
}
Send a message to browser.
Because we use zero-copy design, it will modify the content of the msg.
It won't allocate new memory.
package gcd
import (
	"bufio"
	"context"
	"crypto/tls"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
)
type Dialer interface {
	DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
type WebSocket struct {
	Dialer Dialer
	close  func()
	conn   net.Conn
	r      *bufio.Reader
	header [18]byte  
	mask   []byte
}
func (ws *WebSocket) Connect(ctx context.Context, wsURL string, header http.Header) error {
	if ws.conn != nil {
		panic("duplicated connection: " + wsURL)
	}
	ctx, cancel := context.WithCancel(ctx)
	ws.close = cancel
	u, err := url.Parse(wsURL)
	if err != nil {
		return err
	}
	ws.initDialer(u)
	conn, err := ws.Dialer.DialContext(ctx, "tcp", u.Host)
	if err != nil {
		return err
	}
	go func() {
		<-ctx.Done()
		_ = conn.Close()
	}()
	ws.initConstants()
	ws.conn = conn
	ws.r = bufio.NewReader(conn)
	return ws.handshake(ctx, u, header)
}
func (ws *WebSocket) initDialer(u *url.URL) {
	if ws.Dialer != nil {
		return
	}
	if u.Scheme == "wss" {
		ws.Dialer = &tlsDialer{}
		if u.Port() == "" {
			u.Host += ":443"
		}
	} else {
		ws.Dialer = &net.Dialer{}
	}
}
func (ws *WebSocket) initConstants() {
	ws.header = [18]byte{0b1000_0001}
	ws.mask = []byte{0, 1, 2, 3}
}