Go WebSocket 服务端

由于在动森候机厅中,需要对用户进行实时推送(包括 QQ 推送、HTML5 推送)

虽然做轮询也可以实现,但是相对来说不够优雅,因此希望使用 WebSocket 主动进行推送

WebSocket

WebSocket 是一个基于 HTTP 的长连接协议。通信双端需要首先建立 HTTP 连接(需要有 TCP 三次握手过程),接着需要有标准的 HTTP 头部,接着在头部中会有 Upgrade 字段来告诉通信对端,虽然我们之前是 HTTP,但是现在是 WebSocket 了。剩下在使用上就和 TCP 等类似了

如果有多个 HTTP 请求要发送,如果换用 WebSocket 将会省去多次 TCP 握手的时间。但是现在实际上在 HTTP2 下以及浏览器本身的优化,大部分情况下都会对 HTTP 进行复用,并不需要在自己的项目中考虑那么多。

但是对于服务端向客户端主动推送数据,这个还是很有必要性的

Go 的 WebSocket 库

虽然不需要考虑复杂的 WebSocket 操作,但是考虑到毕竟是有东西急着用,所以还是没有自己造轮子。
在候机厅应用里,使用的是github.com/gorilla/websocket

在这里,首先要设置 HTTP 升级成 WebSocket 中要协商的内容,大部分保持默认即可,但是要考虑同源策略,不检查请求的来源

var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool { return true },
} 

在正常 HTTP 请求中,对 request 进行升级,即可得到 WebSocket 的连接对象

ws, err := upgrader.Upgrade(context.GetResponse(), context.GetRequest(), nil)

为了方便起见,与 HTTP 类似,这里也将 WebSocket 封装成了自己的对象(本来是为了让所有接口都能无修改迁移到 WebSocket 中使用,不过想了想目前没有必要)

获得连接对象后,需要考虑将读写分离,一个线程只负责读数据,一个线程只负责写数据。
一般来说,原本的进程死循环读数据即可,遇到报错就关掉;单独开一个线程使用通道来将要写入的数据发送出去

以及,由于该模块本身没有心跳包,用来告诉所有中间件,这个连接没有超时,所以需要自己再设置一个心跳包(单独开一个线程每 10 秒发送一个ping包即可)

最后就是让 Nginx 能正确处理 Upgrade

map $http_upgrade $connection_upgrade{
    default upgrade;
    '' close;
}
server {
    // ......
    location ^~/api/ {
        proxy_pass http://127.0.0.1:50000;
        proxy_set_header nginx true;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}