1. 目標
- 使用 Golang 編寫一個WebSocker 服務端
- 能接受客戶端的WebSocket連接
- 啟動服務器時接收命令行參數
- 能響應客戶端數據
- 模式一 : webSocket 回音模式
- 模式二 : 圖靈機器人聊天模式
- 使用Golang 編寫一個WebSocket 客戶端
- 使用 Vue 編寫一個WebSocket 客戶端
- 能和服務端通信
- 能主動獲取WebSocket當前狀態(tài)
- 能主動關閉WebSocket連接
- 能主動重新連接WebSokct連接
- 能顯示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去實現(xiàn)
<!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" >
</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">發(fā)送信息</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狀態(tài)</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
}
},
// 初始創(chuàng)建
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 狀態(tài)查詢
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
}
},
// 執(zhí)行關閉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