前言
Gin 框架默认不支持 websocket,可以使用 github.com/gorilla/websocket
实现。
Github:https://github.com/gorilla/websocket
安装
1 |
go get -u github.com/gorilla/websocket |
使用
GO 服务端
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 |
package main import ( "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "net/http" ) // websocket服务端 var wsUpgrader = websocket.Upgrader{ // 解决跨域问题 CheckOrigin: func(r *http.Request) bool { return true }, } func WsServer(c *gin.Context) { // 升级get请求为webSocket协议 ws, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { fmt.Printf("Upgrade失败:%v \n",err.Error()) return } defer ws.Close() for { // 读取ws中的数据 mt, message, err := ws.ReadMessage() if err != nil { fmt.Printf("接收数据失败:%v \n",err.Error()) break } fmt.Printf("接收的数据:%v \n",message) // 写入ws数据 err = ws.WriteMessage(mt, message) if err != nil { fmt.Printf("回复数据失败:%v \n",err.Error()) break } } } func main() { bindAddress := "localhost:81" r := gin.Default() //监听 get请求 /test路径 r.GET("/wsServer", WsServer) r.Run(bindAddress) } |
js客户端
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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
<!DOCTYPE html> <html lang="zh"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/> </head> <body> <table> <tr><td valign="top" width="50%"> <p> 点击 “Open”创建到服务器的连接, "Send"发送消息到服务器, "Close"关闭连接。 您可以更改消息并多次发送。 <p> <form> <button id="open">Open</button> <button id="close">Close</button> <p><input id="input" type="text" value="Hello world!"> <button id="send">Send</button> </form> </td><td valign="top" width="50%"> <div id="output" style="max-height: 70vh;overflow-y: scroll;"></div> </td></tr> </table> </body> <script> window.addEventListener("load", function(evt) { var output = document.getElementById("output"); var input = document.getElementById("input"); var ws; var print = function(message) { var d = document.createElement("div"); d.textContent = message; output.appendChild(d); output.scroll(0, output.scrollHeight); }; document.getElementById("open").onclick = function(evt) { if (ws) { return false; } ws = new WebSocket("ws://localhost:81/api/wsServer"); // 连接打开时触发 ws.onopen = function(evt) { print("OPEN"); } // 连接关闭时触发 ws.onclose = function(evt) { print("CLOSE"); ws = null; } // 接收到消息时触发 ws.onmessage = function(evt) { print("RESPONSE: " + evt.data); } // 连接错误时触发 ws.onerror = function(evt) { print("ERROR: " + evt.data); } return false; }; document.getElementById("send").onclick = function(evt) { if (!ws) { return false; } print("SEND: " + input.value); // 发送 ws.send(input.value); return false; }; document.getElementById("close").onclick = function(evt) { if (!ws) { return false; } // 关闭 ws.close(); return false; }; }); </script> </html> |
GO 客户端
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 64 65 66 67 68 69 70 71 72 73 74 75 76 |
package main import ( "flag" "log" "net/url" "os" "os/signal" "time" "github.com/gorilla/websocket" ) var addr = flag.String("addr", "localhost:8080", "http service address") func main() { flag.Parse() log.SetFlags(0) interrupt := make(chan os.Signal, 1) signal.Notify(interrupt, os.Interrupt) u := url.URL{Scheme: "ws", Host: *addr, Path: "/echo"} log.Printf("connecting to %s", u.String()) c, _, err := websocket.DefaultDialer.Dial(u.String(), nil) if err != nil { log.Fatal("dial:", err) } defer c.Close() done := make(chan struct{}) go func() { defer close(done) for { _, message, err := c.ReadMessage() if err != nil { log.Println("read:", err) return } log.Printf("recv: %s", message) } }() ticker := time.NewTicker(time.Second) defer ticker.Stop() for { select { case <-done: return case t := <-ticker.C: err := c.WriteMessage(websocket.TextMessage, []byte(t.String())) if err != nil { log.Println("write:", err) return } case <-interrupt: log.Println("interrupt") // Cleanly close the connection by sending a close message and then // waiting (with timeout) for the server to close the connection. err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")) if err != nil { log.Println("write close:", err) return } select { case <-done: case <-time.After(time.Second): } return } } } |
websocket
Upgrader
Upgrader
用于升级 http 请求,把 http 请求升级为长连接的 WebSocket。结构如下:
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 |
type Upgrader struct { // 指定升级 websocket 握手完成的超时时间 HandshakeTimeout time.Duration // 指定 io 操作的缓存大小,如果不指定就会自动分配。 ReadBufferSize, WriteBufferSize int // 写数据操作的缓存池,如果没有设置值,write buffers 将会分配到链接生命周期里。 WriteBufferPool BufferPool //按顺序指定服务支持的协议,如值存在,则服务会从第一个开始匹配客户端的协议。 Subprotocols []string // 指定 http 的错误响应函数,如果没有设置 Error 则,会生成 http.Error 的错误响应。 Error func(w http.ResponseWriter, r *http.Request, status int, reason error) // 请求检查函数,用于统一的链接检查,以防止跨站点请求伪造。如果不检查,就设置一个返回值为true的函数。 // 如果请求Origin标头可以接受,CheckOrigin将返回true。 如果CheckOrigin为nil,则使用安全默认值:如果Origin请求头存在且原始主机不等于请求主机头,则返回false CheckOrigin func(r *http.Request) bool // EnableCompression 指定服务器是否应尝试协商每个邮件压缩(RFC 7692)。 // 将此值设置为true并不能保证将支持压缩。 // 目前仅支持“无上下文接管”模式 EnableCompression bool } |
func (*Upgrader) Upgrade
Upgrade
函数将 http 升级到 WebSocket 协议。定义如下:
1 2 3 4 5 |
// responseHeader包含在对客户端升级请求的响应中。 // 使用responseHeader指定cookie(Set-Cookie)和应用程序协商的子协议(Sec-WebSocket-Protocol)。 // 如果升级失败,则升级将使用HTTP错误响应回复客户端 // 返回一个 Conn 指针,拿到他后,可使用 Conn 读写数据与客户端通信。 func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header) (*Conn, error) |
使用实例
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 |
type WsServer struct { ...... // 定义一个 upgrade 类型用于升级 http 为 websocket upgrade *websocket.Upgrader } func NewWsServer() *WsServer { ws.upgrade = &websocket.Upgrader{ ReadBufferSize: 4096,//指定读缓存区大小 WriteBufferSize: 1024,// 指定写缓存区大小 // 检测请求来源 CheckOrigin: func(r *http.Request) bool { if r.Method != "GET" { fmt.Println("method is not GET") return false } if r.URL.Path != "/ws" { fmt.Println("path error") return false } return true }, } return ws } func (self *WsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...... // 收到 http 请求后 升级 协议 conn, err := self.upgrade.Upgrade(w, r, nil) if err != nil { fmt.Println("websocket error:", err) return } fmt.Println("client connect :", conn.RemoteAddr()) go self.connHandle(conn) } |