go的websocket实现(附代码)
websocket分为握手和数据传输阶段,即进行了HTTP握手 + 双工的TCP连接
握手阶段
握手阶段就是普通的HTTP
客户端发送消息:
1 2 3 4 5 6 7 | GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Version: 13 |
服务端返回消息:
1 2 3 4 | HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= |
这里的Sec-WebSocket-Accept的计算方法是:
1 | base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11)) |
如果这个Sec-WebSocket-Accept计算错误浏览器会提示:
Sec-WebSocket-Accept dismatch
如果返回成功,Websocket就会回调onopen事件
数据传输
websocket的数据传输使用的协议是:
参数的具体说明:
FIN:1位,用来表明这是一个消息的最后的消息片断,当然第一个消息片断也可能是最后的一个消息片断;
RSV1, RSV2, RSV3: 分别都是1位,如果双方之间没有约定自定义协议,那么这几位的值都必须为0,否则必须断掉WebSocket连接;
Opcode:4位操作码,定义有效负载数据,如果收到了一个未知的操作码,连接也必须断掉,以下是定义的操作码:
* %x0 表示连续消息片断
* %x1 表示文本消息片断
* %x2 表未二进制消息片断
* %x3-7 为将来的非控制消息片断保留的操作码
* %x8 表示连接关闭
* %x9 表示心跳检查的ping
* %xA 表示心跳检查的pong
* %xB-F 为将来的控制消息片断的保留操作码
Mask:1位,定义传输的数据是否有加掩码,如果设置为1,掩码键必须放在masking-key区域,客户端发送给服务端的所有消息,此位的值都是1;
Payload length: 传输数据的长度,以字节的形式表示:7位、7+16位、或者7+64位。
Masking-key:0或4个字节,客户端发送给服务端的数据,都是通过内嵌的一个32位值作为掩码的;掩码键只有在掩码位设置为1的时候存在。
Payload data: (x+y)位,负载数据为扩展数据及应用数据长度之和。
Extension data:x位,如果客户端与服务端之间没有特殊约定,那么扩展数据的长度始终为0,任何的扩展都必须指定扩展数据的长度,或者长度的计算方式,以及在握手时如何确定正确的握手方式。如果存在扩展数据,则扩展数据就会包括在负载数据的长度之内。
Application data:y位,任意的应用数据,放在扩展数据之后,应用数据的长度=负载数据的长度-扩展数据的长度。
实例
具体使用go的实现例子:
客户端:
html:
1 2 3 4 5 6 7 8 9 |
<script></script>
<input> <input> <input>
<script></script> |
js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | var socket;
$("#connect").click(function(event){ socket = new WebSocket("ws://127.0.0.1:8000");
socket.onopen = function(){ alert("Socket has been opened"); }
socket.onmessage = function(msg){ alert(msg.data); }
socket.onclose = function() { alert("Socket has been closed"); } });
$("#send").click(function(event){ socket.send("send from client"); });
$("#close").click(function(event){ socket.close(); }) |
服务端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 | package main
import( "net" "log" "strings" "crypto/sha1" "io" "encoding/base64" "errors" )
func main() { ln, err := net.Listen("tcp", ":8000") if err != nil { log.Panic(err) }
for { conn, err := ln.Accept() if err != nil { log.Println("Accept err:", err) } for { handleConnection(conn) } } }
func handleConnection(conn net.Conn) { content := make([]byte, 1024) _, err := conn.Read(content) log.Println(string(content)) if err != nil { log.Println(err) }
isHttp := false // 先暂时这么判断 if string(content[0:3]) == "GET" { isHttp = true; } log.Println("isHttp:", isHttp) if isHttp { headers := parseHandshake(string(content)) log.Println("headers", headers) secWebsocketKey := headers["Sec-WebSocket-Key"]
// NOTE:这里省略其他的验证 guid := "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
// 计算Sec-WebSocket-Accept h := sha1.New() log.Println("accept raw:", secWebsocketKey + guid)
io.WriteString(h, secWebsocketKey + guid) accept := make([]byte, 28) base64.StdEncoding.Encode(accept, h.Sum(nil)) log.Println(string(accept))
response := "HTTP/1.1 101 Switching Protocols\r\n" response = response + "Sec-WebSocket-Accept: " + string(accept) + "\r\n" response = response + "Connection: Upgrade\r\n" response = response + "Upgrade: websocket\r\n\r\n"
log.Println("response:", response) if lenth, err := conn.Write([]byte(response)); err != nil { log.Println(err) } else { log.Println("send len:", lenth) }
wssocket := NewWsSocket(conn) for { data, err := wssocket.ReadIframe() if err != nil { log.Println("readIframe err:" , err) } log.Println("read data:", string(data)) err = wssocket.SendIframe([]byte("good")) if err != nil { log.Println("sendIframe err:" , err) } log.Println("send data") }
} else { log.Println(string(content)) // 直接读取 } }
type WsSocket struct { MaskingKey []byte Conn net.Conn }
func NewWsSocket(conn net.Conn) *WsSocket { return &WsSocket{Conn: conn} }
func (this *WsSocket)SendIframe(data []byte) error { // 这里只处理data长度= 125 { return errors.New("send iframe data error") }
lenth := len(data) maskedData := make([]byte, lenth) for i := 0; i > 7 RSV1 := opcodeByte[0] >> 6 & 1 RSV2 := opcodeByte[0] >> 5 & 1 RSV3 := opcodeByte[0] >> 4 & 1 OPCODE := opcodeByte[0] & 15 log.Println(RSV1,RSV2,RSV3,OPCODE)
payloadLenByte := make([]byte, 1) this.Conn.Read(payloadLenByte) payloadLen := int(payloadLenByte[0] & 0x7F) mask := payloadLenByte[0] >> 7
if payloadLen == 127 { extendedByte := make([]byte, 8) this.Conn.Read(extendedByte) }
maskingByte := make([]byte, 4) if mask == 1 { this.Conn.Read(maskingByte) this.MaskingKey = maskingByte }
payloadDataByte := make([]byte, payloadLen) this.Conn.Read(payloadDataByte) log.Println("data:", payloadDataByte)
dataByte := make([]byte, payloadLen) for i := 0; i = 0 { words := strings.Split(line, ":") if len(words) == 2 { headers[strings.Trim(words[0]," ")] = strings.Trim(words[1], " ") } } } return headers } |
更多go语言知识请关注go语言教程栏目。
关注公众号:拾黑(shiheibook)了解更多
友情链接:
下软件就上简单下载站:https://www.jdsec.com/
四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/