这篇文章我们要基于 https://blog.csdn.net/u012210379/article/details/72901387 把功能整合到beego项目中去。
一个新的beego项目长这样:
在这里插入图片描述
router.go:

package routers

import (
    "WebSocketInBeego/controllers"
    "github.com/astaxie/beego"
)

func init() {
    beego.Router("/", &controllers.MainController{})
    beego.Router("/ws", &controllers.MyWebSocketController{})
}

models/Message.go

package models

type Message struct {
    Message string `json:"message"`
}

controllers/default.go

package controllers

import (
    "github.com/astaxie/beego"
)

type MainController struct {
    beego.Controller
}

func (c *MainController) Get() {
    c.TplName = "index.html"
}

controllers/ws.go

package controllers

import (
    "log"

    "github.com/astaxie/beego"
    "github.com/gorilla/websocket"
    "WebSocketInBeego/models"
    "time"
    "github.com/astaxie/beego/toolbox"
)

type MyWebSocketController struct {
    beego.Controller
}

var upgrader = websocket.Upgrader{}

func (c *MyWebSocketController) Get() {

    ws, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
    if err != nil {
        log.Fatal(err)
    }
    //  defer ws.Close()

    clients[ws] = true

    //不断的广播发送到页面上
        for {
            //目前存在问题 定时效果不好 需要在业务代码替换时改为beego toolbox中的定时器
            time.Sleep(time.Second * 3)
            msg := models.Message{Message: "这是向页面发送的数据 " + time.Now().Format("2006-01-02 15:04:05")}
            broadcast <- msg
        }
}

controllers/sendmsg.go

package controllers

import (
    "WebSocketInBeego/models"
    "fmt"
    "log"

    "github.com/gorilla/websocket"
)

var (
    clients   = make(map[*websocket.Conn]bool)
    broadcast = make(chan models.Message)
)

func init() {
    go handleMessages()
}

//广播发送至页面
func handleMessages() {
    for {
        msg := <-broadcast
        fmt.Println("clients len ", len(clients))
        for client := range clients {
            err := client.WriteJSON(msg)
            if err != nil {
                log.Printf("client.WriteJSON error: %v", err)
                client.Close()
                delete(clients, client)
            }
        }
    }
}

index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Sample of websocket with golang</title>
    <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>

    <script>
        $(function() {
            var ws = new WebSocket('ws://' + window.location.host + '/ws');
            ws.onmessage = function(e) {
                $('<li>').text(event.data).appendTo($ul);

            };
            var $ul = $('#msg-list');
        });
    </script>
</head>
<body>
<h1>登录后会在此显示ip</h1>
<ul id="msg-list"></ul>
</body>
</html>

现在咱们的项目长这样:
在这里插入图片描述

项目跑起来后,效果跟上一篇文章是一样的(请允许我偷个懒,截图太累了)
在这里插入图片描述

注意,如果你和我一样在windows下,使用的是Gogland,那么你直接在main.go使用IDE的build and run,会报 can not find template index.html的错误,这个锅得IDE来背,具体原因可以百度搜索下,最简单的解决办法就是:用liteIDE来 build and run,想自己手敲也没问题。

现在我们已经发现了几个问题:

  • 现在是自己给自己for循环发送消息并显示,跟咱们的预期目标:由其他地方发消息推到本页面——不一致啊
  • 在chan里,timesleep是有问题的,你看上面的时间,都出现重复了

咱们来先解决第一个问题, 现在咱们的逻辑是:

  1. 跳转到index.html
  2. js在页面上发送get请求到localhost:8080/ws
  3. js在页面上发送get请求到localhost:8080/wsjs在页面上发送get请求到localhost:8080/ws
  4. 进入ws.go(MyWebSocketController)进入ws.go(MyWebSocketController) ws.go(MyWebSocketController)在Get()方法中注册成为websocket,然后for循环发送数据到broadcast这个chan里
  5. 再被sendmsg.go广播发送至所有(已经注册成为websockt)页面再被sendmsg.go广播发送至所有(已经注册成为websockt)页面

看来,要解决自己发自己接的问题,就要修改第4步,变成别人发我来接。
那么我们可以试着实现这样的场景:别人登录login页面,在我的index页面上推送并显示登录ip。
添加controllers/login.go

package controllers

import (
    "github.com/astaxie/beego"
    "time"
    "WebSocketInBeego/models"
)

type LoginController struct {
    beego.Controller
}

func (c *LoginController) Get() {
    msg := models.Message{Message: time.Now().Format("2006-01-02 15:04:05") + " : ip为 " + c.Ctx.Input.IP() + "的用户登录"}
    broadcast <- msg
    c.TplName = "login.html"
}

添加login.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Sample of websocket with golang</title>
</head>
<body>
<h1>进入此页面会发送ip</h1>
</body>
</html>

修改ws.go

package controllers

import (
    "log"

    "github.com/astaxie/beego"
    "github.com/gorilla/websocket"
    "WebSocketInBeego/models"
    "time"
    "github.com/astaxie/beego/toolbox"
)

type MyWebSocketController struct {
    beego.Controller
}

var upgrader = websocket.Upgrader{}

func (c *MyWebSocketController) Get() {

    ws, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
    if err != nil {
        log.Fatal(err)
    }
    //  defer ws.Close()

    clients[ws] = true

    //不断的广播发送到页面上
    //  for {
    //      //目前存在问题 定时效果不好 需要在业务代码替换时改为beego toolbox中的定时器
    //      time.Sleep(time.Second * 3)
    //      msg := models.Message{Message: "这是向页面发送的数据 " + time.Now().Format("2006-01-02 15:04:05")}
    //      broadcast <- msg
    //  }
}

修改route.go

package routers

import (
	"github.com/astaxie/beego"
	"websocket/controllers"
)

func init() {
	beego.Router("/", &controllers.MainController{})
	beego.Router("/ws", &controllers.MyWebSocketController{})
	beego.Router("/login", &controllers.LoginController{})
}

现在项目长这样:
在这里插入图片描述
我们把项目跑起来看看(嗯你没看错我改了端口):
在这里插入图片描述

而问题到这里并没有结束:

  • 你应该注意到defer ws.Close()被注释掉了,这是为了关闭后就无法接收推送,而这样的做法在实际项目中绝对不行
  • 那么应该咋关闭他呢?不关不行,一直开着也不行
    在oschina翻译的那篇文章中,发现了这样的做法(如果你没看上一篇建议回头看下):
for {
        var msg models.Message // Read in a new message as JSON and map it to a Message object
        err := ws.ReadJSON(&msg)
        if err != nil {
            log.Printf("页面可能断开啦 ws.ReadJSON error: %v", err)
            delete(clients, ws)
            break
        }

        broadcast <- msg
    }

太无耻了,他居然让前端做配合来决定什么时候来关闭。那我们就来借(chao)鉴(xi)下。
修改ws.go

package controllers

import (
    "log"

    "WebSocketInBeego/models"
    "time"

    "github.com/astaxie/beego"
    "github.com/astaxie/beego/toolbox"
    "github.com/gorilla/websocket"
    "fmt"
)

type MyWebSocketController struct {
    beego.Controller
}

var upgrader = websocket.Upgrader{}

func (c *MyWebSocketController) Get() {

    ws, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
    if err != nil {
        log.Fatal(err)
    }
    //defer ws.Close()

    clients[ws] = true

    //不断的广播发送到页面上
    //  for {
    //      //目前存在问题 定时效果不好 需要在业务代码替换时改为beego toolbox中的定时器
    //      time.Sleep(time.Second * 3)
    //      msg := models.Message{Message: "这是向页面发送的数据 " + time.Now().Format("2006-01-02 15:04:05")}
    //      broadcast <- msg
    //  }

    //如果从 socket 中读取数据有误,我们假设客户端已经因为某种原因断开。我们记录错误并从全局的 “clients” 映射表里删除该客户端,这样一来,我们不会继续尝试与其通信。
    //另外,HTTP 路由处理函数已经被作为 goroutines 运行。这使得 HTTP 服务器无需等待另一个连接完成,就能处理多个传入连接。
    for {
        time.Sleep(time.Second * 3)

        var msg models.Message // Read in a new message as JSON and map it to a Message object
        err := ws.ReadJSON(&msg)
        if err != nil {
            log.Printf("页面可能断开啦 ws.ReadJSON error: %v", err)
            delete(clients, ws)
            break
        } else {
            fmt.Println("接受到从页面上反馈回来的信息 ", msg.Message)
        }

        broadcast <- msg
    }

}

修改index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8"/>
    <title>Sample of websocket with golang</title>
    <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>

    <script>
        $(function () {
            var ws = new WebSocket('ws://' + window.location.host + '/ws');
            ws.onmessage = function (e) {
                $('<li>').text(event.data).appendTo($ul);
                //添加返回消息 告诉服务器 此页面还存在
                //{"message":"data"}
                var obj = {};
                obj.message = 'index.html 还活着 ' + new Date().toLocaleString();
                console.log(obj);
                obj = JSON.stringify(obj);
                ws.send(obj);

            };
            var $ul = $('#msg-list');
        });
    </script>
</head>
<body>
<h1>登录后会在此显示ip</h1>
<ul id="msg-list"></ul>
</body>
</html>

2个文件,修改的内容很少,另:js真恶心。
跑起来看下效果:
在这里插入图片描述
在这里插入图片描述
貌似会多发了一次,嗯,换下一个话题。
效果还是很不错的,实现了页面退出后不再推送的目标。
至于定时器,我在以前的项目里这么搞过,大家可以自己去看beego/toolbox里的代码。

func timeTask() {

    timeStr := "0/3 * * * * *" //每隔3秒执行

    t1 := toolbox.NewTask("timeTask", timeStr, func() error {

        //todo do what you want

        return nil
    })

    toolbox.AddTask("tk1", t1)
    toolbox.StartTask()
}

嗯,基本就是这个样子,不过如果用这个的话好像退出定时器又成了一个问题。