websocket协议提供了一种在客户端与浏览器之间建立双向连接的方法:RFC6455: The WebSocket Protocol。
golang.org/x/net/websocket是一个golang的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
close通过发送control frame完成。
Websocket使用origin model
,依据origin
判断某个网页是否具有访问websocket的权限。
如果是非浏览器客户端,origin model不起作用,client可以使用任意的origin。
websocket的uri格式如下:
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
ws://
默认的端口是80,wss://
默认的端口是443。
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 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直接通过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直接通过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])
}