最近打算做一款类似腾讯<<脑力达人>>的h5游戏,之前打算用skynet来做,所以给skynet增加了websocket模块,
https://github.com/Skycrab/skynet_websocket。刚好最近在学习golang,考虑之下打算用golang来实现,说不定过段时间
还能整个golang游戏服务器。之前我一直认为Python是我的真爱,但现在真心喜欢golang,也许这也是弥补我静态语言
的缺失吧,虽然C++/C还算熟悉,但没有工程经验,始终觉得缺少点什么。我相信golang以后会在服务器领域有一席之地,
现在研究也算投资吧,等golang越来越成熟,gc越来越高效,会有很多转投golang的怀抱。
我始终相信,一门语言一种文化。当我写Python时,我很少会考虑效率,想的更多的是简洁与优雅实现; 但当我写golang时,
时不时会左右比较,在int32与int64之间徘徊,估算本次大概需要多少byte进行内存预分配。。。。在Python中即使你考虑了,
大多也是徒劳,语言本身很多没有提供。语言的文化,让我痴迷。
算上前一篇写的定时器(http://blog.csdn.net/yueguanghaidao/article/details/46290539)和本篇的websocket,还差不少东西才能组成游戏服务器,慢慢填坑吧。
有人说,golang的websocket很多,何必造*,但自己写的后期好优化,更新方便,造*是快速学习的途径,如果时间
允许,多多造*,会在中途收获很多。
github地址:https://github.com/Skycrab/code/tree/master/Go/websocket
首先看看如何使用:
package websocket
import (
"fmt"
"net/http"
"testing"
)
type MyHandler struct {
}
func (wd MyHandler) CheckOrigin(origin, host string) bool {
return true
}
func (wd MyHandler) OnOpen(ws *Websocket) {
fmt.Println("OnOpen")
ws.SendText([]byte("hello world from server"))
}
func (wd MyHandler) OnMessage(ws *Websocket, message []byte) {
fmt.Println("OnMessage:", string(message), len(message))
}
func (wd MyHandler) OnClose(ws *Websocket, code uint16, reason []byte) {
fmt.Println("OnClose", code, string(reason))
}
func (wd MyHandler) OnPong(ws *Websocket, data []byte) {
fmt.Println("OnPong:", string(data))
}
func TestWebsocket(t *testing.T) {
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
fmt.Println("...")
var opt = Option{MyHandler{}, false}
ws, err := New(w, r, &opt)
if err != nil {
t.Fatal(err.Error())
}
ws.Start()
})
fmt.Println("server start")
http.ListenAndServe(":8001", nil)
}
使用方法和之前的类似,都是像tornado websocket执行方式。
MyHandler实现了WsHandler接口,如果你并不关注所有事件,可以继承WsDefaultHandler,WsDefaultHandler为所有的事件
提供了默认实现。
通过Option实现了默认参数功能,第二个参数代表是否mask发送的数据,客户端是需要的,服务端不需要,所以默认为false。
由于暂时没有websocket client的需求,所以没有提供,需要时再添加吧。
对比一下golang和lua的实现,代码行数并没有增加多少,golang是400行,lua是340行,不得不说golang编码效率的确
赶得上动态语言。在编写golang和lua实现时,我明显感觉到静态语言具有很大优势,lua出错提示不给力,这也是动态语言的
痛处吧。好消息是Python3.5提供了类型检查,我觉得的确是一大利器。
在这里把代码贴一下,方便查看。
package websocketimport ( "bufio" "bytes" "crypto/sha1" "encoding/base64" "encoding/binary" "errors" "fmt" "io" "net" "net/http" "strings")var ( ErrUpgrade = errors.New("Can \"Upgrade\" only to \"WebSocket\"") ErrConnection = errors.New("\"Connection\" must be \"Upgrade\"") ErrCrossOrigin = errors.New("Cross origin websockets not allowed") ErrSecVersion = errors.New("HTTP/1.1 Upgrade Required\r\nSec-WebSocket-Version: 13\r\n\r\n") ErrSecKey = errors.New("\"Sec-WebSocket-Key\" must not be nil") ErrHijacker = errors.New("Not implement http.Hijacker"))var ( ErrReservedBits = errors.New("Reserved_bits show using undefined extensions") ErrFrameOverload = errors.New("Control frame payload overload") ErrFrameFragmented = errors.New("Control frame must not be fragmented") ErrInvalidOpcode = errors.New("Invalid frame opcode"))var ( crlf = []byte("\r\n") challengeKey = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))//referer https://github.com/Skycrab/skynet_websocket/blob/master/websocket.luatype WsHandler interface { CheckOrigin(origin, host string) bool OnOpen(ws *Websocket) OnMessage(ws *Websocket, message []byte) OnClose(ws *Websocket, code uint16, reason []byte) OnPong(ws *Websocket, data []byte)}type WsDefaultHandler struct { checkOriginOr bool // 是否校验origin, default true}func (wd WsDefaultHandler) CheckOrigin(origin, host string) bool { return true}func (wd WsDefaultHandler) OnOpen(ws *Websocket) {}func (wd WsDefaultHandler) OnMessage(ws *Websocket, message []byte) {}func (wd WsDefaultHandler) OnClose(ws *Websocket, code uint16, reason []byte) {}func (wd WsDefaultHandler) OnPong(ws *Websocket, data []byte) {}type Websocket struct { conn net.Conn rw *bufio.ReadWriter handler WsHandler clientTerminated bool serverTerminated bool maskOutgoing bool}type Option struct { Handler WsHandler // 处理器, default WsDefaultHandler MaskOutgoing bool //发送frame是否mask, default false}func challengeResponse(key, protocol string) []byte { sha := sha1.New() sha.Write([]byte(key)) sha.Write(challengeKey) accept := base64.StdEncoding.EncodeToString(sha.Sum(nil)) buf := bytes.NewBufferString("HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ") buf.WriteString(accept) buf.Write(crlf) if protocol != "" { buf.WriteString("Sec-WebSocket-Protocol: ") buf.WriteString(protocol) buf.Write(crlf) } buf.Write(crlf) return buf.Bytes()}func acceptConnection(r *http.Request, h WsHandler) (challenge []byte, err error) { //Upgrade header should be present and should be equal to WebSocket if strings.ToLower(r.Header.Get("Upgrade")) != "websocket" { return nil, ErrUpgrade } //Connection header should be upgrade. Some proxy servers/load balancers // might mess with it. if !strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade") { return nil, ErrConnection } // Handle WebSocket Origin naming convention differences // The difference between version 8 and 13 is that in 8 the // client sends a "Sec-Websocket-Origin" header and in 13 it's // simply "Origin". if r.Header.Get("Sec-Websocket-Version") != "13" { return nil, ErrSecVersion } origin := r.Header.Get("Origin") if origin == "" { origin = r.Header.Get("Sec-Websocket-Origin") } if origin != "" && !h.CheckOrigin(origin, r.Header.Get("Host")) { return nil, ErrCrossOrigin } key := r.Header.Get("Sec-Websocket-Key") if key == "" { return nil, ErrSecKey } protocol := r.Header.Get("Sec-Websocket-Protocol") if protocol != "" { idx := strings.IndexByte(protocol, ',') if idx != -1 { protocol = protocol[:idx] } } return challengeResponse(key, protocol), nil}func websocketMask(mask []byte, data []byte) { for i := range data { data[i] ^= mask[i%4] }}func New(w http.ResponseWriter, r *http.Request, opt *Option) (*Websocket, error) { var h WsHandler var maskOutgoing bool if opt == nil { h = WsDefaultHandler{true} maskOutgoing = false } else { h = opt.Handler maskOutgoing = opt.MaskOutgoing } challenge, err := acceptConnection(r, h) if err != nil { var code int if err == ErrCrossOrigin { code = 403 } else { code = 400 } w.WriteHeader(code) w.Write([]byte(err.Error())) return nil, err } hj, ok := w.(http.Hijacker) if !ok { return nil, ErrHijacker } conn, rw, err := hj.Hijack() ws := new(Websocket) ws.conn = conn ws.rw = rw ws.handler = h ws.maskOutgoing = maskOutgoing if _, err := ws.conn.Write(challenge); err != nil { ws.conn.Close() return nil, err } ws.handler.OnOpen(ws) return ws, nil}func (ws *Websocket) read(buf []byte) error { _, err := io.ReadFull(ws.rw, buf) return err}func (ws *Websocket) SendFrame(fin bool, opcode byte, data []byte) error { //max frame header may 14 length buf := make([]byte, 0, len(data)+14) var finBit, maskBit byte if fin { finBit = 0x80 } else { finBit = 0 } buf = append(buf, finBit|opcode) length := len(data) if ws.maskOutgoing { maskBit = 0x80 } else { maskBit = 0 } if length < 126 { buf = append(buf, byte(length)|maskBit) } else if length < 0xFFFF { buf = append(buf, 126|maskBit, 0, 0) binary.BigEndian.PutUint16(buf[len(buf)-2:], uint16(length)) } else { buf = append(buf, 127|maskBit, 0, 0, 0, 0, 0, 0, 0, 0) binary.BigEndian.PutUint64(buf[len(buf)-8:], uint64(length)) } if ws.maskOutgoing { } buf = append(buf, data...) ws.rw.Write(buf) return ws.rw.Flush()}func (ws *Websocket) SendText(data []byte) error { return ws.SendFrame(true, 0x1, data)}func (ws *Websocket) SendBinary(data []byte) error { return ws.SendFrame(true, 0x2, data)}func (ws *Websocket) SendPing(data []byte) error { return ws.SendFrame(true, 0x9, data)}func (ws *Websocket) SendPong(data []byte) error { return ws.SendFrame(true, 0xA, data)}func (ws *Websocket) Close(code uint16, reason []byte) { if !ws.serverTerminated { data := make([]byte, 0, len(reason)+2) if code == 0 && reason != nil { code = 1000 } if code != 0 { data = append(data, 0, 0) binary.BigEndian.PutUint16(data, code) } if reason != nil { data = append(data, reason...) } ws.SendFrame(true, 0x8, data) ws.serverTerminated = true } if ws.clientTerminated { ws.conn.Close() }}func (ws *Websocket) RecvFrame() (final bool, message []byte, err error) { //text 数据报文 buf := make([]byte, 8, 8) err = ws.read(buf[:2]) if err != nil { return } header, payload := buf[0], buf[1] final = header&0x80 != 0 reservedBits := header&0x70 != 0 frameOpcode := header & 0xf frameOpcodeIsControl := frameOpcode&0x8 != 0 if reservedBits { // client is using as-yet-undefined extensions err = ErrReservedBits return } maskFrame := payload&0x80 != 0 payloadlen := uint64(payload & 0x7f) if frameOpcodeIsControl && payloadlen >= 126 { err = ErrFrameOverload return } if frameOpcodeIsControl && !final { err = ErrFrameFragmented return } //解析frame长度 var frameLength uint64 if payloadlen < 126 { frameLength = payloadlen } else if payloadlen == 126 { err = ws.read(buf[:2]) if err != nil { return } frameLength = uint64(binary.BigEndian.Uint16(buf[:2])) } else { //payloadlen == 127 err = ws.read(buf[:8]) if err != nil { return } frameLength = binary.BigEndian.Uint64(buf[:8]) } frameMask := make([]byte, 4, 4) if maskFrame { err = ws.read(frameMask) if err != nil { return } } // fmt.Println("final_frame:", final, "frame_opcode:", frameOpcode, "mask_frame:", maskFrame, "frame_length:", frameLength) message = make([]byte, frameLength, frameLength) if frameLength > 0 { err = ws.read(message) if err != nil { return } } if maskFrame && frameLength > 0 { websocketMask(frameMask, message) } if !final { return } else { switch frameOpcode { case 0x1: //text case 0x2: //binary case 0x8: // close var code uint16 var reason []byte if frameLength >= 2 { code = binary.BigEndian.Uint16(message[:2]) } if frameLength > 2 { reason = message[2:] } message = nil ws.clientTerminated = true ws.Close(0, nil) ws.handler.OnClose(ws, code, reason) case 0x9: //ping message = nil ws.SendPong(nil) case 0xA: ws.handler.OnPong(ws, message) message = nil default: err = ErrInvalidOpcode } return }}func (ws *Websocket) Recv() ([]byte, error) { data := make([]byte, 0, 8) for { final, message, err := ws.RecvFrame() if final { data = append(data, message...) break } else { data = append(data, message...) } if err != nil { return data, err } } if len(data) > 0 { ws.handler.OnMessage(ws, data) } return data, nil}func (ws *Websocket) Start() { for { _, err := ws.Recv() if err != nil { ws.conn.Close() } }}