WebSocket实践(一)

简介

WebSocket解决了使用HTTP协议所带来的不能连续接收server端状态变化的问题(如聊天场景),具体区别见下面这张图

img_http_websocket.png

主要区别:

  • HTTP : request - response单向通信,短链接 ; WebSocket : Bi-directional messages双向通信,长链接
  • Websocket在一个链接内能让server主动推送数据给client

API

参考 https://developer.mozilla.org/en-US/docs/Web/API/WebSocket

统一使用Typescript的写法

Methods

  • WebSocket(url:string, protocols?:string|string[])

    主要的WebSocket interface , 用new 关键字生成对象

    1
    let ws = new WebSocket("ws://localhost:3000","my-protocol")

    protocol参数通过request中的Sec-WebSocket-Protocol字段传给server, server可以根据protocol决定应该返回的response

  • send(data:any)

    通过websocket发送data给server,在js中可以使用任何类型的数据

  • close(code?:number, reason?:string)

    client关闭websocket通信,code使用CloseEvent的标准,同时可以将自定义的信息写在reason中。

Attributes

省略了bufferAmountextensions

  • readyState: number (readonly)

    表示client上Websocket链接状态,有4个值:CONNECTING(0), OPEN(1), CLOSING(2),CLOSED(3)。参考https://developer.mozilla.org/en-US/docs/Web/API/WebSocket#Ready_state_constants

  • url: string(readonly)

    WebSocket函数的url参数

  • binaryType: string

    表示传递的数据类型 ,blobarraybuffer

  • onopen: (this: WebSocket, ev: Event) => any

    标准的EventListener,当readyState变为OPEN时调用,第二个参数为通用Event

    1
    2
    3
    // 两种写法
    ws.onopen = event => console.log(event)
    ws.addEventListener('open', event => console.log(event))
  • onmessage: (this: WebSocket, ev: MessageEvent) => any

    标准的EventListener,当接收server发来的消息时调用,第二个参数为MessageEvent ,写法与onopen类似(onerroronclose不再赘述)

  • onerror: (this: WebSocket, ev: Event) => any

    标准的EventListener,当在通信过程中发生error时调用,第二个参数为通用Event

  • onclose: (this: WebSocket, ev: CloseEvent) => any

    标准的EventListener,当readyState变为CLOSED时调用,第二个参数为CloseEvent

  • protocol

    WebSocket函数中的protocols中的某一个特定protocol

使用

简单是实现一个基于WebSocket通信的Client - Server例子,在线代码

有部分websocket库并不支持在client上直接使用W3C WebSocket API,如WebSocket-node , Socket-io等,这里server使用Go语言的实现 gorilla/websocket

Client

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const url = 'ws://localhost:1234'
let ws = null
// 新建一个WebSocket对象
ws = new WebSocket(url,'test_protocol')
ws.onopen = () => {
console.log('Client connection opened')
// 发送 "Hellow Websocket"
ws.send('Hello Websocket')
}
ws.onmessage = (event) => {
console.log('Received message : '+ event.data)
}
ws.onclose = () => {
console.log('Close ws')
}
ws.onerror = (event) => {
console.log('Error : ', event)
ws.close()
}

Server

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
package main
import (
"net/http"
"time"
"github.com/gorilla/websocket"
logging "github.com/op/go-logging"
)
var upgrader *websocket.Upgrader
var logger *logging.Logger
func init() {
logger = logging.MustGetLogger("main")
logging.SetLevel(logging.INFO, "main")
upgrader = &websocket.Upgrader{
// 设置握手超时
HandshakeTimeout: 5 * time.Second,
// 必须含有与client对应的protocol,否则报1006错误
Subprotocols: []string{"test_protocol"},
// 设置为不检查Requst Header中的 Origin
CheckOrigin: func(r *http.Request) bool {
return true
},
}
}
func websocketHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
logger.Errorf("Websocket upgrade err : %s", err.Error())
return
}
defer func(conn *websocket.Conn) {
err := conn.Close()
if err != nil {
logger.Errorf("Error closing websocket connection : %s", err.Error())
}
logger.Info("Connection closed")
}(conn)
for {
messageType, message, err := conn.ReadMessage()
if err != nil {
logger.Errorf("Error reading messages from client : %s", err.Error())
break
}
logger.Infof("Received message : %s", string(message[:]))
err = conn.WriteMessage(messageType, []byte("Websocket server"))
if err != nil {
logger.Errorf("Error writing messages : %s", err.Error())
break
}
logger.Info("Send message : Websocket server")
if string(message[:]) == "close ws" {
break
}
}
}
func main() {
http.HandleFunc("/", websocketHandler)
logger.Fatal(http.ListenAndServe(":1234", nil))
}

运行结果

Client:

img_client_result.jpg

Server:

img_server_result.jpg