WebSocket 协议解读
WebSocket和http协议的关联:
- 都是应用层协议,都基于TCP 传输协议。
- 跟HTTP 有良好的兼容性,WebSocket 和HTTP 的默认端口都是80,wss 和https 的默认端口都是443。
- WebSocket在握手阶段采用http发送数据。
Websocket 和HTTP 协议的差异:
- HTTP 是半双工,而WebSocket 通过多路复用实现了全双工。
- HTTP 只能由Client 主动发起数据请求,而WebSocket 还可以由Server 主动向Client 推送数据。在需要及时刷新的场景中,HTTP 只能靠Client 高频地轮询,浪费严重。
- HTTP 是短连接(也可以实现长连接, HTTP1.1 的连接默认使用长连接),每次数据请求都得经过三次握手重新建立连接,而WebSocket 是长连接。
- HTTP 长连接中每次请求都要带上header ,而WebSocket 在传输数据阶段不需要带header 。
WebSocket 握手协议:
Request Header
Sec-Websocket-Version:13
Upgrade:websocket
Connection:Upgrade
Sec-Websocket-Key:duR0pUQxNgBJsRQKj2Jxsw==
Response Header
Upgrade:websocket
Connection:Upgrade
Sec-Websocket-Accept:a1y2oy1zvgHsVyHMx+hZ1AYrEHI=
Upgrade:websocket 和Connection :Upgrade 指明使用WebSocket协议。Sec-WebSocket-Version:指定Websocket协议版本。Sec-WebSocket-Key:一个Base64 encode 的值,是浏览器随机生成的。- 服务端收到
Sec-WebSocket-Key后拼接上一个固定的GUID ,进行一次SHA-1 摘要,再转成Base64编码,得到Sec-WebSocket-Accept返回给客户端。 - 客户端对本地的
Sec-WebSocket-Key执行同样的操作跟服务端返回的结果进行对比,如果不一致会返回错误关闭连接。如此操作是为了把WebSocket header跟HTTP header区分开。
WebSocket 消息类型
TextMessage:文本消息BinaryMessage:二进制消息CloseMessage:关闭帧,接收方收到该消息就关闭连接PingMessage和PongMessage是保持心跳的帧,发送方接收方是PingMessage ,接收方发送方是PongMessage,目前浏览器没有相关api 发送ping 给服务器,只能由服务器发ping 给浏览器,浏览器返回pong 消息。
WebSocket CS架构实现
- 首先需要安装gorilla的websocket包。
go get github.com/gorilla/websocket
Upgrader:-
Upgrader指定了用于将HTTP连接升级为WebSocket连接的参数。
-
同时调用Upgrader的方法是安全的。
-
type Upgrader struct {
HandshakeTimeout time.Duration //握手超时时间
// ReadBufferSize和WriteBufferSize指定I/O缓冲区的大小,单位为字节。
// 如果缓冲区大小为零,那么就使用HTTP服务器分配的缓冲区。
// I/O缓冲区的大小并不限制可以发送或接收的信息的大小。或接收的消息的大小。
ReadBufferSize, WriteBufferSize int
...
}
upgrade := &websocket.Upgrader{
HandshakeTimeout: 5 * time.Second,
ReadBufferSize: 2048,
WriteBufferSize: 1024,
}
- 将http升级到WebSocket协议。
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*websocket.Conn, error)
- Client 通过调用带有background context 的DialContext ,创建一个新的Client 端连接。
func (*websocket.Dialer) Dial(urlStr string, requestHeader http.Header) (*websocket.Conn, *http.Response, error)
- Server 端基于connection 进行read 和write 。
type (
Request struct {
A int
B int
}
Response struct {
Sum int
}
)
func home(w http.ResponseWriter, r *http.Request) {
upgrade := &websocket.Upgrader{
HandshakeTimeout: 5 * time.Second, //握手超时时间
ReadBufferSize: 2048, //读缓冲大小
WriteBufferSize: 1024,
}
conn, err := upgrade.Upgrade(w, r, nil) //将http协议升级到websocket协议
if err != nil {
fmt.Printf("upgrade http to websocket error: %v\n", err)
return
}
defer conn.Close()
for { //长连接
conn.SetReadDeadline(time.Now().Add(20 * time.Second))
var request socket.Request
if err := conn.ReadJSON(&request); err != nil {
//判断是不是超时
if netError, ok := err.(net.Error); ok { //如果ok==true,说明类型断言成功
if netError.Timeout() {
fmt.Printf("read message timeout, remote %s\n", conn.RemoteAddr().String())
return
}
}
//忽略websocket.CloseGoingAway/websocket.CloseNormalClosure这2种closeErr,如果是其他closeErr就打一条错误日志
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseNormalClosure) {
fmt.Printf("read message from %s error %v\n", conn.RemoteAddr().String(), err)
}
return //只要ReadMessage发生错误,就关闭这条连接
} else {
response := socket.Response{Sum: request.A + request.B}
if err = conn.WriteJSON(&response); err != nil {
fmt.Printf("write response failed: %v", err)
} else {
fmt.Printf("write response %d\n", response.Sum)
}
}
}
}
func main() {
http.HandleFunc("/", home)
if err := http.ListenAndServe("127.0.0.1:5657", nil); err != nil {
fmt.Println(err)
}
}