Golang WebSocket Vue构建通信

1. 目标

  1. 使用 Golang 编写一个WebSocker 服务端
    1. 能接受客户端的WebSocket连接
    2. 启动服务器时接收命令行参数
    3. 能响应客户端数据
      1. 模式一 : webSocket 回音模式
      2. 模式二 : 图灵机器人聊天模式
  2. 使用Golang 编写一个WebSocket 客户端
  3. 使用 Vue 编写一个WebSocket 客户端
    1. 能和服务端通信
    2. 能主动获取WebSocket当前状态
    3. 能主动关闭WebSocket连接
    4. 能主动重新连接WebSokct连接
    5. 能显示WebSocket通信数据

2. 项目目录

|__common
|____service_robot.go
| server.go
| myClient.go
common\service_robot.go

图灵机器人服务 , 提供给其他服务调用

package common

import (
    "bytes"
    "encoding/json"
    "errors"
    "io/ioutil"
    "log"
    "net/http"
)

var err error

const (
    // 你的图灵机器人 apikey
    APIKEY = ""
    // 你的 userId
    USERID = ""
    // 图灵的接口请求地址
    URL    = "http://openapi.tuling123.com/openapi/api/v2"
)

type ReqData struct {
    ReqType    int                    `json:"reqType"`
    Perception map[string]interface{} `json:"perception"`
    UserInfo   map[string]string      `json:"userInfo"`
}
type Result struct {
    ResultType string                 `json:"resultType"`
    Values     map[string]interface{} `json:"values"`
    GroupType  int                    `json:"groupType"`
}
type RespData struct {
    Intent  map[string]interface{} `json:"intent"`
    Results []Result               `json:"results"`
}

func NewRobot() *ReqData {
    info := map[string]string{
        "apiKey": APIKEY,
        "userId": USERID,
    }
    return &ReqData{
        ReqType:    0,
        Perception: nil,
        UserInfo:   info,
    }
}
func (r *ReqData) Chat(v interface{}) (error, []interface{}) {
    if val, ok := v.(string); ok {
        inputText := map[string]string{
            "text": val,
        }
        r.Perception = make(map[string]interface{})
        r.Perception["inputText"] = inputText
        jsonData, err := json.Marshal(r)
        if err == nil {
            return r.Post(jsonData)
        }
    } else {
        err = errors.New("format error")
    }
    return err, nil
}
func (r *ReqData) Post(data []byte) (error, []interface{}) {
    defer func() {
        if err != nil {
            log.Print("service_robot:post", err)
        }
    }()
    body := bytes.NewBuffer([]byte(data))
    req, err := http.NewRequest("POST", URL, body)
    if err == nil {
        req.Header.Add("Accept", "application/json")
        req.Header.Add("Content-Type", "application/json")
        resp, err := http.DefaultClient.Do(req)
        defer resp.Body.Close()
        if err != nil {
            return err, nil
        }
        result, err := ioutil.ReadAll(resp.Body)
        if err != nil {
            return err, nil
        }
        log.Println(string(result))
        var response RespData
        err = json.Unmarshal(result, &response)
        if err != nil {
            return err, nil
        }
        var outPut []interface{}
        for _, v := range response.Results {
            for _, value := range v.Values {
                outPut = append(outPut, value)
            }
        }
        return nil, outPut
    }
    return err, nil
}

server.go

WebSocket 服务端

package main

import (
    "GoNote/chapter1/ws/common"
    "flag"
    "github.com/gorilla/websocket"
    "log"
    "net/http"
)

var modeDescribe = `webSocket response model :
--echo default value
--robot The Turing robot responded
`
var addr = flag.String("addr", "", "http service addr")
var model = flag.String("model", "", modeDescribe)
var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

func echoFunc(w http.ResponseWriter, r *http.Request) {
    c, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println("upgrader :", err)
        return
    }
    defer func() {
        log.Println(c.RemoteAddr().String(), "connect close")
    }()
    defer c.Close()
    log.Println(c.RemoteAddr().String(), "connect success")
    var robot = common.NewRobot()
    for {
        mt, message, err := c.ReadMessage()
        if err != nil {
            break
        }
        //t := time.Now().Format("2006-01-02 15:04:05")
        m := string(message)
        log.Println("server receive message :", m)
        // robot 机器人应答
        if *model == "robot" {
            err, result := robot.Chat(m)
            if err == nil {
                for _, k := range result {
                    if s, ok := k.(string); ok {
                        message = []byte(s)
                        err = c.WriteMessage(mt, message)
                    }
                }
            }
        } else if *model == "echo" {
            // echo 回音服务
            message = []byte(m)
            err = c.WriteMessage(mt, message)
            if err != nil {
                log.Println("write :", err)
            }
        }
    }
}
func main() {
    flag.Parse()
    log.SetFlags(0)
    log.Println("addr : ", *addr)
    http.HandleFunc("/echo", echoFunc)
    log.Fatal(http.ListenAndServe(*addr, nil))
}

myclient.go

WebSocket 客户端

package main

import (
    "flag"
    "fmt"
    "github.com/gorilla/websocket"
    "log"
    "net/url"
    "os"
    "os/signal"
)

var addr2 = flag.String("addr", "127.0.0.1:8080", "ws service addr")
var key string

func main() {
    defer func() {
        if err := recover(); err != nil {
            log.Println("error : ", err)
        }
    }()
    flag.Parse()
    log.SetFlags(0)
    interrupt := make(chan os.Signal, 1)
    signal.Notify(interrupt, os.Interrupt)
    u := url.URL{
        Scheme: "ws",
        Host:   *addr2,
        Path:   "/echo",
    }
    log.Printf("connecting to %s", u.String())
    c, _, err := websocket.DefaultDialer.Dial(u.String(), nil)
    if err != nil {
        log.Fatal("websocket connect error : ", err)
    }
    defer c.Close()
    receiveData := make(chan string)
    responseMessage := make(chan string)
    go func() {
        for {
            fmt.Print("请输入聊天内容 : ")
            fmt.Scan(&key)
            if key != "" {
                receiveData <- key
            }
            data := <-responseMessage
            fmt.Println("I receive message : ", data)
        }
    }()
    for {
        select {
        case <-interrupt:
            log.Println("interrupt")
            err := c.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
            if err != nil {
                log.Println("write close:", err)
                return
            }
            close(receiveData)
        case data := <-receiveData:
            err := c.WriteMessage(websocket.TextMessage, []byte(data))
            if err != nil {
                log.Println("write:", err)
                return
            }
            _, message, err := c.ReadMessage()
            if err != nil {
                log.Println("read from server err:", err)
            } else {
                responseMessage <- string(message)
            }
        }
    }
}
WebSocket 客户端测试
$ go run server.go -addr 127.0.0.1:8080 -model robot
addr :  127.0.0.1:8080
127.0.0.1:52402 connect success
server receive message : 北京天气
{"intent":{"actionName":"","code":10008,"intentName":"","parameters":{"date":"","city":"北京"}},"results":[{"groupType":1,"resultType":"text","values":{"text":"北京:周六 05月30日,雷阵雨 南风,最低气温18度,最高气温30度。"}}]}

$ go run myClient.go
connecting to ws://127.0.0.1:8080/echo
请输入聊天内容 : 北京天气
I receive message :  北京:周六 05月30日,雷阵雨 南风,最低气温18度,最高气温30度。
请输入聊天内容 :

Vue WebSocket 客户端

这边我们使用的 JavaScript 库是 Vue

也可以就用简单的html去实现

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- import Vue before Element -->
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <!-- import JavaScript -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>

<body>
    <div id="app">
        <el-row :gutter="20">
            <el-col :span="16">
                <div class="grid-content bg-purple">
                    <el-input v-model="message" placeholder="请输入内容" @keyup.enter.native="submitMessage"></el-input>
                </div>
            </el-col>
            <el-col :span="8">
                <div class="grid-content bg-purple">
                    <el-button @click="submitMessage">发送信息</el-button>
                </div>
            </el-col>
        </el-row>
        <el-row>
            <el-col :span="24">
                <div class="grid-content bg-purple-dark">
                    <el-button type="primary" @click="GetWsStatus">获取WS状态</el-button>
                    <el-button type="success" @click="defaultWs">重新连接WS</el-button>
                    <el-button type="info" @click="reloadPage">重新加载页面</el-button>
                    <el-button type="warning" @click="showRespData" v-model="showButtonTitle">{{showButtonTitle}}数据</el-button>
                    <el-button type="danger" @click="doCloseWS">关闭WS连接</el-button>
                </div>
            </el-col>
        </el-row>
        <el-row>
            <el-col :span="24">
                <div class="grid-content bg-purple-light" v-show="showSwitch && showData != ''" v-html="showData"> 
                </div>
            </el-col>
        </el-row>
    </div>
    <script>
        new Vue({
            el: '#app',
            data() {
                return {
                    addr: '127.0.0.1',
                    port: '8080',
                    message: '',
                    showSwitch: false,
                    showData: '',
                    showButtonTitle:'显示',
                    ws: null
                }
            },
            // 初始创建
            created() {
                this.defaultWs()
            },
            // 离开页面时销毁ws
            destroyed() {
                this.ws.close()
            },
            methods: {
                defaultWs() {
                    console.log('try to connect webSocket')
                    if ("WebSocket" in window) {
                        console.log('support webSocket')
                        var connectInfo = 'ws://' + this.addr + ':' + this.port + '/echo'
                        this.ws = new WebSocket(connectInfo)
                        this.ws.onopen = this.wsOnopen
                        this.ws.onmessage = this.wsOnMessage
                        this.ws.onclose = this.wsOnClose
                    }
                },
                wsOnopen() {
                    console.log('connect success')
                },
                wsOnMessage(e) {
                    console.log('receive data: ')
                    this.showData += '<span style="float:right">'+ e.data + ' : say server' + '</span><br />'
                    console.log(e.data)
                    console.log(this.showData)
                },
                wsOnClose(e) {
                    this.ws.close()
                },
                wsOnSend(data) {
                    this.ws.send(data)
                },
                // 提交信息
                submitMessage() {
                    if (this.message !== '') {
                        console.log('submit some data....')
                        this.wsOnSend(this.message)
                        this.showData += 'I say:' + this.message+'<br />'
                        this.message = ''
                    }
                },
                // ws 状态查询
                GetWsStatus() {
                    switch (this.ws.readyState) {
                        // 正在连接
                        case WebSocket.CONNECTING:
                            this.$message({
                                message: 'WebSocket正在连接',
                            })
                            break
                            // 连接成功    
                        case WebSocket.OPEN:
                            this.$message({
                                message: 'WebSocket连接成功',
                                type:'success'
                            })
                            break
                            // 已经关闭    
                        case WebSocket.CLOSED:
                            this.$message({
                                message: 'WebSocket连接已经关闭',
                                type: 'error'
                            })
                            break
                            // 正在关闭    
                        case WebSocket.CLOSING:
                            this.$message({
                                message: 'WebSocket连接正在关闭',
                            })
                            break
                    }
                },
                // 执行关闭ws
                doCloseWS() {
                    this.wsOnClose()
                    this.$message({
                        message: 'WebSocket关闭成功',
                        type: 'success'
                    });
                },
                // 重新加载页面
                reloadPage() {
                    location.reload()
                },
                // 显示获取的数据
                showRespData() {
                    this.showSwitch ? this.showButtonTitle = '显示' : this.showButtonTitle='隐藏'
                    this.showSwitch ? this.showSwitch = false : this.showSwitch = true
                }
            },

        })
    </script>
</body>
<style>
    .el-row {
        margin-top: 10px;
    }

    .bg-purple-light {
        background-color: aliceblue;
    }
    .speak-input {
        height: 200px;
    }
    
</style>

</html>
前端的 WebSocket 测试
websocket-vue-test.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341