赞
踩
最近和团队一起完成了软件工程的项目任务,实现一个在线聊天网页。我参与了后端的部分工作,但是具体到关键的websocket部分,我并没有了解得很详细、具体。所以我会花一些时间从我们自己的项目中总结一下websocket,作为今后的学习和复习资料。
可以把websocket看作是HTTP协议为了支持长连接所做的一个大补丁。
这两种模式都有一个共同的缺点,就是除了真正的数据部分之外,服务端和客户端还需要交换大量的HTTP header,信息交换效率很低
可以实现实时信息传递,同时websocket还可以绕过大多数防火墙的限制。使http协议变成真正的长连接,全双工协议。
websocket协议报文的头部比较轻量化,可以减少数据传输量,因为一旦websocket建立连接,双方的身份已经确定,所以不再需要像http一样发送额外的身份确认字段。
websocket之所以和http不同,很大的一个原因是相当于它把socket通信在应用层进行了一遍封装操作
websocket通信协议实现的是基于浏览器的原生socket,这样原先只有在c/s模式下的大量开发模式都可以搬到web上来了,基本就是通过浏览器的支持在web上实现了与服务器端的socket通信。
WebSocket没有试图在HTTP之上模拟server推送,而是直接在TCP之上定义了帧协议,因此WebSocket能够支持双向的通信。
websocket协议本质上也是使用系统socket,它是把socket引入了http通信,也就是不使用80端口进行http通信。它的目的是建立全双工的连接,可以用来解决服务器客户端保持长连接的问题。
他们都是基于TCP的协议,并且都是应用层协议。websocket的建立需要使用一次http请求,该请求中需要有一些特殊的字段要求服务器升级协议。可以理解为webscoket借用了http协议完成了一部份握手
对于要求升级为websocket的请求,如果服务器支持websocket协议,那么服务器会发送101的HTTP响应。如果不支持,服务器会优雅得忽略掉该报文
该报文也是websocket协议握手的关键,报文结构如下所示
重点就在于Upgrade和Connection这两个字段。
主要区别在于两者的持久性和生命周期。
websocket是一个持久化的协议;
对于一个http请求来说,一次请求只有一个Request和Response(HTTP1.1中可以使用长连接)。而websocket协议中,对于一个tcp连接,服务端和客户端都可以发送任意数量的报文,知道该连接中断。
学习github.com/gorilla/websocket开源仓库记录。参考/examples/chat案例学习
websocket虽然属于应用层协议,但是由于其已经基于TCP建立了发送方和接收方的连接,所以在后续的报文传输中,不再需要利用报文传输双方的身份信息。
具体的报文格式可以参考博客
该案例具体代码在github.com/gorilla/websocket/examples/chat中。
服务端维护两种对象,Hub和Client。Hub是单例对象,Client与每一个用户一一对应。Client需要在Hub中进行注册,同时需要将用户发送过来的消息转发给Hub,Hub发送给Client的消息,Client需要转发给对应的用户。
简而言之,就是所有的Client统一由Hub调控,每一个Client负责与一个用户进行通信,同时将通信情况反馈给Hub。
需要注意的一点是:Hub和Client通信通过Channel进行,而Client和用户通信通过Websocket connection进行
在main.go中首先会对默认路由发送默认的资源html文件,然后浏览器会申请升级协议。此时就需要调用下面的serveWs中的Upgrade函数进行升级协议,升级协议成功返回的状态码为101 Switching protocal。
// serveWs handles websocket requests from the peer. func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) { // 标准库提供的升级协议函数,直接使用即可 conn, err := upgrader.Upgrade(w, r, nil) if err != nil { log.Println(err) return } client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)} client.hub.register <- client // Allow collection of memory referenced by the caller by doing all work in // new goroutines. go client.writePump() go client.readPump() }
func (h *Hub) run() { for { select { case client := <-h.register: h.clients[client] = true case client := <-h.unregister: if _, ok := h.clients[client]; ok { delete(h.clients, client) close(client.send) } case message := <-h.broadcast: for client := range h.clients { select { case client.send <- message: default: close(client.send) delete(h.clients, client) } } } } }
该函数为单独开启的一个gorutine,用于维护Client的注册、注销、以及广播消息。
通过client.send <- message,将从某一个Client中收到的消息广播到其他的每一个Client中。
Client主要负责两件事情,一件事情就是从websocket连接中获取消息,并将其发送给Hub;另外一件事情就是从Hub中获取信息,然后通过websocket连接发送给对应的用户。
前一件事情由readPump函数进行;后一件事情由writePump函数进行。
// readPump pumps messages from the websocket connection to the hub. // // The application runs readPump in a per-connection goroutine. The application // ensures that there is at most one reader on a connection by executing all // reads from this goroutine. func (c *Client) readPump() { defer func() { c.hub.unregister <- c c.conn.Close() }() c.conn.SetReadLimit(maxMessageSize) c.conn.SetReadDeadline(time.Now().Add(pongWait)) c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil }) for { _, message, err := c.conn.ReadMessage() if err != nil { if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) { log.Printf("error: %v", err) } break } message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1)) c.hub.broadcast <- message } } // writePump pumps messages from the hub to the websocket connection. // // A goroutine running writePump is started for each connection. The // application ensures that there is at most one writer to a connection by // executing all writes from this goroutine. func (c *Client) writePump() { ticker := time.NewTicker(pingPeriod) defer func() { ticker.Stop() c.conn.Close() }() for { select { case message, ok := <-c.send: c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if !ok { // The hub closed the channel. c.conn.WriteMessage(websocket.CloseMessage, []byte{}) return } w, err := c.conn.NextWriter(websocket.TextMessage) if err != nil { return } w.Write(message) // Add queued chat messages to the current websocket message. n := len(c.send) for i := 0; i < n; i++ { w.Write(newline) w.Write(<-c.send) } if err := w.Close(); err != nil { return } case <-ticker.C: c.conn.SetWriteDeadline(time.Now().Add(writeWait)) if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil { return } } } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。