Golang websocket.Conn的再封装

孟南知 · · 608 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

我们知道我们可以直接用websocket.Conn进行ws通讯,可是为什么要对他进行二次封装,主要有两个原因。

1,不方便分模块调用

2,websocket.Conn中的很多方法是非线程安全的

线程安全的意思是当多线程调用同一模块时,相对安全的不会出错。

js对ws的简单封装

首先我们稍微优化一下测试前端。

如果不对WebSocket对象封装的话,我们无法手动控制连接的状态。

<!doctype html>
<html>
<head>
  <meta charset="utf-8"/>
  <title>WebSocket</title>
</head>
<body>
<button type="button" onclick="connnectWS()">建立连接</button>
<button type="button" onclick="sendMeg()">发送消息</button>
<button type="button" onclick="connClose()">关闭连接</button>
<div id="content"></div>
<script>
var text=document.getElementById("content");
var link="ws://localhost:6060/ws";
var conn={
    connect:function(link) {
        this.ws=new WebSocket(link);   
        //连接打开时触发 
        this.ws.onopen = function(evt) {  
            console.log("Connection open ...");
        };  
        //接收到消息时触发  
        this.ws.onmessage = function(evt) {  
            text.innerHTML=text.innerHTML+"<br />"+evt.data; 
        };  
        //连接关闭时触发  
        this.ws.onclose = function(evt) {  
            console.log("Connection closed.");  
        }; 
    }
}
function connnectWS(){
    conn.connect(link);
}
function sendMeg(){
  conn.ws.send("Hello WebSocket!");
}
function connClose(){
  conn.ws.close();
}
</script>
</body>
</html>

Go对ws的简单封装

我们先看看封装结构,之后我们在一个一个看方法。

type Connection struct {
    wsConn    *websocket.Conn //websocket.Conn对象
    inChan    chan []byte     //用于接收消息
    outChan   chan []byte     //用于发送消息
    closeChan chan byte       //帮助内部逻辑判断连接是否被中断
    mutex     sync.Mutex      //用于加锁
    isClose   bool            //用于避免重复关闭中的不安全因素
}

下面是“构造方法”

const (
    MAXMEGNUM = 1000    //最大消息数
)
//NewWS 构造一个WSConn
func NewWS(wsConn *websocket.Conn) (conn *Connection, err error) {
    conn = &Connection{
        wsConn:    wsConn,
        inChan:    make(chan []byte, MAXMEGNUM),
        outChan:   make(chan []byte, MAXMEGNUM),
        closeChan: make(chan byte, 1),
    }
    go conn.readLoop()  //执行内部读逻辑
    go conn.writeLoop() //执行内部写逻辑
    return
}

读取和写单条消息的逻辑

//ReadMessage 读取一条消息,即接收
func (conn *Connection) ReadMessage() (data []byte, err error) {
    select {
    case data = <-conn.inChan:
    case <-conn.closeChan:
        err = errors.New("连接已关闭。")
    }
    return
}
//WriteMessage 写一条消息,即发送
func (conn *Connection) WriteMessage(data []byte) (err error) {
    select {
    case conn.outChan <- data:
    case <-conn.closeChan:
        err = errors.New("连接已关闭。")
    }
    return
}

关闭连接的方法

func (conn *Connection) Close() {
    conn.wsConn.Close() //这个方法是线程安全的
    conn.mutex.Lock()   //上锁
    //对Chan进行处理,并标记连接已关闭
    if conn.isClose {
        close(conn.closeChan)
        conn.isClose = true
    }
    conn.mutex.Unlock() //解锁
}

内部实现的读写方法。避免死循环,我们使用select语句监听连接状态!

func (conn *Connection) readLoop() {
    for {
        megType, data, err := conn.wsConn.ReadMessage()
        //TODO 消息类型处理
        megType = megType
        if err != nil {
            util.ErrHandle(err)
            conn.Close()
            return
        }
        select {
        case conn.inChan <- data:
        case <-conn.closeChan:
            conn.Close()
            return
        }
    }
}
func (conn *Connection) writeLoop() {
    var data []byte
    for {
        select {
        case data = <-conn.outChan:
        case <-conn.closeChan:
            conn.Close()
            return
        }
        //TODO 消息类型处理
        err := conn.wsConn.WriteMessage(websocket.TextMessage, data)
        if err != nil {
            util.ErrHandle(err)
            conn.Close()
            return
        }
    }
}

这样我们就封装了一个相对安全的ws封装,发现内容太多,就不掩饰了。下面给一个测试代码,有兴趣的朋友可以自己试一下。

func main() {
    r := gin.Default()
    r.GET("/ws", func(c *gin.Context) {
        upgrader := websocket.Upgrader{CheckOrigin: func(r *http.Request) bool {
            return true
        }}
        wsConn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
        defer wsConn.Close()
        if err != nil {
            fmt.Println(err)
            return
        }
        conn, err := impl.NewWS(wsConn)
        defer conn.Close()
        if err != nil {
            fmt.Println(err)
            return
        }
        go func() {
            for {
                err := conn.WriteMessage([]byte("heartbeat"))
                if err != nil {
                    fmt.Println(err)
                    return
                }
                time.Sleep(time.Second * 3)
            }
        }()
        for {
            data, err := conn.ReadMessage()
            if err != nil {
                fmt.Println(err)
                return
            }
            err = conn.WriteMessage(data)
            if err != nil {
                fmt.Println(err)
                return
            }
        }
    })
    r.Run(":6060")
}

有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:孟南知

查看原文:Golang websocket.Conn的再封装

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

608 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传