golang websocket连接
1.路由信息
// 应答客户端告知升级连接为websocket
beego.Router("/ws", &controllers.WebsocketController{}, "get:WsHandler")
2.Controller实现
package controllers
import (
"errors"
"fmt"
"net/http"
"sync"
"time"
"github.com/astaxie/beego"
"github.com/gorilla/websocket"
)
type WebsocketController struct {
beego.Controller
}
var wsUpgrader = websocket.Upgrader{
ReadBufferSize: 4096,
WriteBufferSize: 4096,
EnableCompression: true,
HandshakeTimeout: 5 * time.Second,
// CheckOrigin: 处理跨域问题,线上环境慎用
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// 客户端读写消息
type wsMessage struct {
messageType int
data []byte
}
// 客户端连接
type wsConnection struct {
wsSocket *websocket.Conn // 底层websocket
inChan chan *wsMessage // 读队列
outChan chan *wsMessage // 写队列
mutex sync.Mutex // Mutex互斥锁,避免重复关闭管道
isClosed bool
closeChan chan byte // 关闭通知
}
func (wsConn *wsConnection) wsReadLoop() {
for {
// 读一个message
msgType, data, err := wsConn.wsSocket.ReadMessage()
if err != nil {
goto error
}
req := &wsMessage{}
if string(data) == "test" {
req = &wsMessage{
msgType,
[]byte("Hi, this is a test websocket"),
}
} else {
req = &wsMessage{
msgType,
data,
}
}
// 放入请求队列
select {
case wsConn.inChan <- req:
case <-wsConn.closeChan:
goto closed
}
}
error:
wsConn.wsClose()
closed:
fmt.Println("websocket is closed.")
}
func (wsConn *wsConnection) wsWriteLoop() {
for {
select {
// 取一个应答
case msg := <-wsConn.outChan:
// 写给websocket
if err := wsConn.wsSocket.WriteMessage(msg.messageType, msg.data); err != nil {
goto error
}
case <-wsConn.closeChan:
goto closed
}
}
error:
wsConn.wsClose()
closed:
fmt.Println("websocket is closed.")
}
func (wsConn *wsConnection) procLoop() {
// 启动一个gouroutine发送心跳
go func() {
for {
time.Sleep(3 * time.Second)
if err := wsConn.wsWrite(websocket.TextMessage, []byte("heartbeat from server")); err != nil {
fmt.Println("heartbeat fail")
wsConn.wsClose()
break
}
}
}()
for {
msg, err := wsConn.wsRead()
if err != nil {
fmt.Println("read fail")
break
}
fmt.Println(string(msg.data))
err = wsConn.wsWrite(msg.messageType, msg.data)
if err != nil {
fmt.Println("write fail")
break
}
}
}
func (w *WebsocketController) WsHandler() {
wsSocket, err := wsUpgrader.Upgrade(w.Ctx.ResponseWriter, w.Ctx.Request, nil)
if err != nil {
return
}
wsConn := &wsConnection{
wsSocket: wsSocket,
inChan: make(chan *wsMessage, 1000),
outChan: make(chan *wsMessage, 1000),
closeChan: make(chan byte),
isClosed: false,
}
// 处理器
go wsConn.procLoop()
// 读协程
go wsConn.wsReadLoop()
// 写协程
go wsConn.wsWriteLoop()
}
func (wsConn *wsConnection) wsWrite(messageType int, data []byte) error {
select {
case wsConn.outChan <- &wsMessage{messageType, data}:
case <-wsConn.closeChan:
return errors.New("websocket closed")
}
return nil
}
func (wsConn *wsConnection) wsRead() (*wsMessage, error) {
select {
case msg := <-wsConn.inChan:
return msg, nil
case <-wsConn.closeChan:
}
return nil, errors.New("websocket closed")
}
func (wsConn *wsConnection) wsClose() {
wsConn.wsSocket.Close()
wsConn.mutex.Lock()
defer wsConn.mutex.Unlock()
if !wsConn.isClosed {
wsConn.isClosed = true
close(wsConn.closeChan)
}
}
【励志篇】:
古之成大事掌大学问者,不惟有超世之才,亦必有坚韧不拔之志。