最近打算做一款类似腾讯<<脑力达人>>的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 websocketimport ("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) boolOnOpen(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.Connrw               *bufio.ReadWriterhandler          WsHandlerclientTerminated boolserverTerminated boolmaskOutgoing     bool
}type Option struct {Handler      WsHandler // 处理器, default WsDefaultHandlerMaskOutgoing 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 WebSocketif 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 WsHandlervar maskOutgoing boolif opt == nil {h = WsDefaultHandler{true}maskOutgoing = false} else {h = opt.HandlermaskOutgoing = opt.MaskOutgoing}challenge, err := acceptConnection(r, h)if err != nil {var code intif 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 = connws.rw = rwws.handler = hws.maskOutgoing = maskOutgoingif _, 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 lengthbuf := make([]byte, 0, len(data)+14)var finBit, maskBit byteif 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 != 0reservedBits := header&0x70 != 0frameOpcode := header & 0xfframeOpcodeIsControl := frameOpcode&0x8 != 0if reservedBits {// client is using as-yet-undefined extensionserr = ErrReservedBitsreturn}maskFrame := payload&0x80 != 0payloadlen := uint64(payload & 0x7f)if frameOpcodeIsControl && payloadlen >= 126 {err = ErrFrameOverloadreturn}if frameOpcodeIsControl && !final {err = ErrFrameFragmentedreturn}//解析frame长度var frameLength uint64if 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 == 127err = 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: //textcase 0x2: //binarycase 0x8: // closevar code uint16var reason []byteif frameLength >= 2 {code = binary.BigEndian.Uint16(message[:2])}if frameLength > 2 {reason = message[2:]}message = nilws.clientTerminated = truews.Close(0, nil)ws.handler.OnClose(ws, code, reason)case 0x9: //pingmessage = nilws.SendPong(nil)case 0xA:ws.handler.OnPong(ws, message)message = nildefault: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()}}}

给golang增加websocket模块相关推荐

  1. 增加无线模块传输距离的方法

    随着无线数据传输的发展,无线模块的应用也越来越广泛.无线模块广泛地应用于车辆监控.遥控.遥测.小型无线网络.无线抄表.门禁系统.小区传呼.工业数据采集系统.无线标签.身份识别.非接触RF智能卡.小型无 ...

  2. php ldap 模块,不重新编译为PHP增加LDAP模块的支持

    不重新编译为PHP增加LDAP模块的支持 2018-11-28 安装步骤 1.进入到php安装源码目录 root@vm-199:~/lnmp0.9# cd php-5.3.28root@vm-199: ...

  3. 增加spyder模块代码提示功能和spyder 代码自动补齐设置方式

    增加spyder模块代码提示功能 找到/home/pc314/anaconda3/envs/tensorflow/lib/python3.5/site-packages/spyder/utils/in ...

  4. php rewrite模块安装,Linux_Linux下Apache安装/增加mod_rewrite模块,如果你的服务器apache还没有安 - phpStudy...

    Linux下Apache安装/增加mod_rewrite模块 如果你的服务器apache还没有安装,那很简单,在编译apache时将mod_rewrite模块编译进去就可以,相关文档可以在http:/ ...

  5. 使用golang的http模块构建redis读写查api

    前沿: 这两天试着用golang做一些高性能的api,不想把压力到聚合在平台的接口上.平台因为要做很多耗时间的操作,uwsgi下会出现少许错误,找了一圈不知道如何解决该问题. 暂时先绕道而行,先拿简单 ...

  6. python websocket模块_python websocket学习使用

    前言 今天看了一些资料,记录一下心得. websocket是html5引入的一个新特性,传统的web应用是通过http协议来提供支持,如果要实时同步传输数据,需要轮询,效率低下 websocket是类 ...

  7. golang gorilla websocket例子

    WebSocket协议是基于TCP的一种新的网络协议.它实现了浏览器与服务器全双工(full-duplex)通信--允许服务器主动发送信息给客户端. WebSocket通信协议于2011年被IETF定 ...

  8. VB封装的WebSocket模块,拿来即用

    一共就下面的两个模块,调用只使用到mWSProtocol模块,所有调用函数功能简单介绍一下: 建立连接后就开始握手,服务端用Handshake()验证,如果是客户端自己发送握手封包 接收数据,先用An ...

  9. Golang Iris Websocket 跨域问题

    问题描述 在尝试使用iris中的websocket搭建一个实时通讯聊天的demo时,出现一个Upgrade Error错误,一个跨域问题,网上说要修改golang中的websocket upgrade ...

最新文章

  1. 数组内容转qstring_用Qstring给char[]数组赋值(转)
  2. live555 源码分析:RTSPServer 组件结构
  3. 可见光能量范围_JACS:游书力团队通过可见光促进的吲哚衍生物分子内去芳构化合成环丁烷稠合的四环吲哚螺环...
  4. Win32ASM学习[5]: 数据对齐相关的伪指令(ALIGN、EVEN、ORG)
  5. HTTP服务器的本质:tinyhttpd源码分析及拓展
  6. python 调用shell 不阻塞_遇到问题---python调用shell脚本时subprocess.check_call不阻塞
  7. 95-18-015-配置-AbstractBootstrapConfig
  8. 自定义 Web 服务器控件
  9. 【汇总目录】嵌入式系统技术
  10. Ubuntu系统lamp环境下安装wordpress、zencert和mangento程序网站
  11. Python编程 介绍(入门)
  12. 【Axure原型分享】短视频APP原型模板
  13. 【转载】教你怎么将centos7打造成桌面系统
  14. linux(三剑客之sed) sed字符串替换命令详解
  15. Tomcat中使用cookie
  16. 柚鸥ASO优化:aso搜索优化怎么做
  17. Unity-之-物体旋转-跳跃-2021.5.13
  18. 2020年11月最新互联网大厂面试经验分享【网易、阿里、腾讯、京东、百度、爱奇艺、字节、小米、美团、搜狐、58】
  19. 软件项目估算永远不准怎么办?钱少时间紧未必是坏事
  20. C/C++存储区域划分(栈区,堆区,全局区,代码区)

热门文章

  1. python炫酷烟花表白程序_python炫酷烟花表白源代码
  2. urv中保研碰撞测试结果_中保研碰撞测试结果出炉,奔驰GLC在安全性方面获优秀评价...
  3. 一文吃透接口调用神器RestTemplate
  4. 我的LED控制卡(硬件仿制,程序自己开发)
  5. Django:各页面优化,以及实现个人信息修改
  6. K8S!之Pod概念与网络通讯方式详解!
  7. 仿作苏宁易购主页(前端学习记录)
  8. 班章管家可靠安全月入5k怎样理财?怎样合理财脱节月光族?
  9. 0805_python和网络编程
  10. ctx.fillText水平居中问题