go中使用websocket

作者: 李佶澳   转载请保留:原文地址   更新时间:2018/03/07 19:30:01

说明

websocket协议提供了一种在客户端与浏览器之间建立双向连接的方法:RFC6455: The WebSocket Protocol

golang.org/x/net/websocket是一个golang的websocket库。

websocket协议了解

websocket直接建立在tcp协议上,它与http协议唯一的关系是,连接建立请求使用的是http协议。

用途

在开发web应用时候,client与server之间有时候需要双向通信。Http协议是被动响应的,以前浏览器端只能通过轮询的 方式获得server端的状态变化。这种方式,有三个坏处:

1. server端需要承受大量的tcp连接
2. client的每次请求都带有http头,额外开销大
3. client端需要维护、追踪轮询的情况

websocket是一个新的web协议,在一个tcp连接中完成双向通信。

协议格式

websocket协议由handshake和data transfer两部分组成。

client发送的handshake格式如下:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13

server回应的handshake格式如下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat

Closing Handshake

close通过发送control frame完成。

安全模型

Websocket使用origin model,依据origin判断某个网页是否具有访问websocket的权限。

如果是非浏览器客户端,origin model不起作用,client可以使用任意的origin。

URI

websocket的uri格式如下:

ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]

ws://默认的端口是80,wss://默认的端口是443。

Data Framing

websocket中使用frame传输数据,格式如下:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+

Status Codes

 |Status Code | Meaning         | Contact       | Reference |
-+------------+-----------------+---------------+-----------|
 | 1000       | Normal Closure  | [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1001       | Going Away      | [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1002       | Protocol error  | [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1003       | Unsupported Data| [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1004       | ---Reserved---- | [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1005       | No Status Rcvd  | [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1006       | Abnormal Closure| [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1007       | Invalid frame   | [email protected] | RFC 6455  |
 |            | payload data    |               |           |
-+------------+-----------------+---------------+-----------|
 | 1008       | Policy Violation| [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1009       | Message Too Big | [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1010       | Mandatory Ext.  | [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|
 | 1011       | Internal Server | [email protected] | RFC 6455  |
 |            | Error           |               |           |
-+------------+-----------------+---------------+-----------|
 | 1015       | TLS handshake   | [email protected] | RFC 6455  |
-+------------+-----------------+---------------+-----------|

websocket的server端

建立websocket连接之后,server直接通过Read和Write方法接收、发送数据。

package main

import (
	"golang.org/x/net/websocket"
	"io"
	"net/http"
)

func EchoServer(ws *websocket.Conn) {
	io.Copy(ws, ws)
}

func main() {
	http.Handle("/echo", websocket.Handler(EchoServer))
	err := http.ListenAndServe(":12345", nil)
	if err != nil {
		panic("ListenAndServer: " + err.Error())
	}
}

可以看到,server可以直接读取ws中的数据,以及向ws写入回应数据。

使用这种方式创建的websocket,无法用wscat连接。

gorilla/websocket中的例子,可以用wscat连接。

websocket的client端

建立websocket连接之后,client直接通过Read和Write方法接收、发送数据。

package main

import (
	"fmt"
	"golang.org/x/net/websocket"
	"log"
)

func main() {
	origin := "http://localhost/"
	url := "ws://localhost:12345/echo"
	ws, err := websocket.Dial(url, "", origin)
	if err != nil {
		log.Fatal(err)
	}
	
	if _, err := ws.Write([]byte("hello world!")); err != nil {
		log.Fatal(err)
	}
	
	var msg = make([]byte, 512)
	var n int
	if n, err = ws.Read(msg); err != nil {
		log.Fatal(err)
	}
	fmt.Printf("Received: %s.\n", msg[:n])
}

参考

  1. RFC6455: The WebSocket Protocol
  2. golang.org/x/net/websocket

本文原创首发于网站:www.lijiaocn.com

QQ交流群

区块链实践互助QQ群:576555864

Kubernetes实践互助QQ群:947371129

Prometheus实践互助QQ群:952461804

Kong/Envoy实践互助QQ群:952503851

Ansible实践互助QQ群:955105412

Copyright @2011-2019 All rights reserved. 转载请添加原文连接,合作请加微信lijiaocn或者发送邮件: [email protected],备注网站合作 友情链接: lijiaocn github.com