beego:通过 websocket 向页面推送数据

淩亂°似流年 2022-04-22 15:48 570阅读 0赞

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

  1. package routers
  2. import (
  3. "WebSocketInBeego/controllers"
  4. "github.com/astaxie/beego"
  5. )
  6. func init() {
  7. beego.Router("/", &controllers.MainController{
  8. })
  9. beego.Router("/ws", &controllers.MyWebSocketController{
  10. })
  11. }

models/Message.go

  1. package models
  2. type Message struct {
  3. Message string `json:"message"`
  4. }

controllers/default.go

  1. package controllers
  2. import (
  3. "github.com/astaxie/beego"
  4. )
  5. type MainController struct {
  6. beego.Controller
  7. }
  8. func (c *MainController) Get() {
  9. c.TplName = "index.html"
  10. }

controllers/ws.go

  1. package controllers
  2. import (
  3. "log"
  4. "github.com/astaxie/beego"
  5. "github.com/gorilla/websocket"
  6. "WebSocketInBeego/models"
  7. "time"
  8. "github.com/astaxie/beego/toolbox"
  9. )
  10. type MyWebSocketController struct {
  11. beego.Controller
  12. }
  13. var upgrader = websocket.Upgrader{
  14. }
  15. func (c *MyWebSocketController) Get() {
  16. ws, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
  17. if err != nil {
  18. log.Fatal(err)
  19. }
  20. // defer ws.Close()
  21. clients[ws] = true
  22. //不断的广播发送到页面上
  23. for {
  24. //目前存在问题 定时效果不好 需要在业务代码替换时改为beego toolbox中的定时器
  25. time.Sleep(time.Second * 3)
  26. msg := models.Message{
  27. Message: "这是向页面发送的数据 " + time.Now().Format("2006-01-02 15:04:05")}
  28. broadcast <- msg
  29. }
  30. }

controllers/sendmsg.go

  1. package controllers
  2. import (
  3. "WebSocketInBeego/models"
  4. "fmt"
  5. "log"
  6. "github.com/gorilla/websocket"
  7. )
  8. var (
  9. clients = make(map[*websocket.Conn]bool)
  10. broadcast = make(chan models.Message)
  11. )
  12. func init() {
  13. go handleMessages()
  14. }
  15. //广播发送至页面
  16. func handleMessages() {
  17. for {
  18. msg := <-broadcast
  19. fmt.Println("clients len ", len(clients))
  20. for client := range clients {
  21. err := client.WriteJSON(msg)
  22. if err != nil {
  23. log.Printf("client.WriteJSON error: %v", err)
  24. client.Close()
  25. delete(clients, client)
  26. }
  27. }
  28. }
  29. }

index.html:

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8"/>
  5. <title>Sample of websocket with golang</title>
  6. <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
  7. <script>
  8. $(function() {
  9. var ws = new WebSocket('ws://' + window.location.host + '/ws');
  10. ws.onmessage = function(e) {
  11. $('<li>').text(event.data).appendTo($ul);
  12. };
  13. var $ul = $('#msg-list');
  14. });
  15. </script>
  16. </head>
  17. <body>
  18. <h1>登录后会在此显示ip</h1>
  19. <ul id="msg-list"></ul>
  20. </body>
  21. </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

  1. package controllers
  2. import (
  3. "github.com/astaxie/beego"
  4. "time"
  5. "WebSocketInBeego/models"
  6. )
  7. type LoginController struct {
  8. beego.Controller
  9. }
  10. func (c *LoginController) Get() {
  11. msg := models.Message{
  12. Message: time.Now().Format("2006-01-02 15:04:05") + " : ip为 " + c.Ctx.Input.IP() + "的用户登录"}
  13. broadcast <- msg
  14. c.TplName = "login.html"
  15. }

添加login.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8"/>
  5. <title>Sample of websocket with golang</title>
  6. </head>
  7. <body>
  8. <h1>进入此页面会发送ip</h1>
  9. </body>
  10. </html>

修改ws.go

  1. package controllers
  2. import (
  3. "log"
  4. "github.com/astaxie/beego"
  5. "github.com/gorilla/websocket"
  6. "WebSocketInBeego/models"
  7. "time"
  8. "github.com/astaxie/beego/toolbox"
  9. )
  10. type MyWebSocketController struct {
  11. beego.Controller
  12. }
  13. var upgrader = websocket.Upgrader{
  14. }
  15. func (c *MyWebSocketController) Get() {
  16. ws, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
  17. if err != nil {
  18. log.Fatal(err)
  19. }
  20. // defer ws.Close()
  21. clients[ws] = true
  22. //不断的广播发送到页面上
  23. // for {
  24. // //目前存在问题 定时效果不好 需要在业务代码替换时改为beego toolbox中的定时器
  25. // time.Sleep(time.Second * 3)
  26. // msg := models.Message{Message: "这是向页面发送的数据 " + time.Now().Format("2006-01-02 15:04:05")}
  27. // broadcast <- msg
  28. // }
  29. }

修改route.go

  1. package routers
  2. import (
  3. "github.com/astaxie/beego"
  4. "websocket/controllers"
  5. )
  6. func init() {
  7. beego.Router("/", &controllers.MainController{
  8. })
  9. beego.Router("/ws", &controllers.MyWebSocketController{
  10. })
  11. beego.Router("/login", &controllers.LoginController{
  12. })
  13. }

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

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

  • 你应该注意到defer ws.Close()被注释掉了,这是为了关闭后就无法接收推送,而这样的做法在实际项目中绝对不行
  • 那么应该咋关闭他呢?不关不行,一直开着也不行
    在oschina翻译的那篇文章中,发现了这样的做法(如果你没看上一篇建议回头看下):

    for {

    1. var msg models.Message // Read in a new message as JSON and map it to a Message object
    2. err := ws.ReadJSON(&msg)
    3. if err != nil {
    4. log.Printf("页面可能断开啦 ws.ReadJSON error: %v", err)
    5. delete(clients, ws)
    6. break
    7. }
    8. broadcast <- msg
    9. }

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

  1. package controllers
  2. import (
  3. "log"
  4. "WebSocketInBeego/models"
  5. "time"
  6. "github.com/astaxie/beego"
  7. "github.com/astaxie/beego/toolbox"
  8. "github.com/gorilla/websocket"
  9. "fmt"
  10. )
  11. type MyWebSocketController struct {
  12. beego.Controller
  13. }
  14. var upgrader = websocket.Upgrader{}
  15. func (c *MyWebSocketController) Get() {
  16. ws, err := upgrader.Upgrade(c.Ctx.ResponseWriter, c.Ctx.Request, nil)
  17. if err != nil {
  18. log.Fatal(err)
  19. }
  20. //defer ws.Close()
  21. clients[ws] = true
  22. //不断的广播发送到页面上
  23. // for {
  24. // //目前存在问题 定时效果不好 需要在业务代码替换时改为beego toolbox中的定时器
  25. // time.Sleep(time.Second * 3)
  26. // msg := models.Message{Message: "这是向页面发送的数据 " + time.Now().Format("2006-01-02 15:04:05")}
  27. // broadcast <- msg
  28. // }
  29. //如果从 socket 中读取数据有误,我们假设客户端已经因为某种原因断开。我们记录错误并从全局的 “clients” 映射表里删除该客户端,这样一来,我们不会继续尝试与其通信。
  30. //另外,HTTP 路由处理函数已经被作为 goroutines 运行。这使得 HTTP 服务器无需等待另一个连接完成,就能处理多个传入连接。
  31. for {
  32. time.Sleep(time.Second * 3)
  33. var msg models.Message // Read in a new message as JSON and map it to a Message object
  34. err := ws.ReadJSON(&msg)
  35. if err != nil {
  36. log.Printf("页面可能断开啦 ws.ReadJSON error: %v", err)
  37. delete(clients, ws)
  38. break
  39. } else {
  40. fmt.Println("接受到从页面上反馈回来的信息 ", msg.Message)
  41. }
  42. broadcast <- msg
  43. }
  44. }

修改index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="UTF-8"/>
  5. <title>Sample of websocket with golang</title>
  6. <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
  7. <script>
  8. $(function () {
  9. var ws = new WebSocket('ws://' + window.location.host + '/ws');
  10. ws.onmessage = function (e) {
  11. $('<li>').text(event.data).appendTo($ul);
  12. //添加返回消息 告诉服务器 此页面还存在
  13. //{"message":"data"}
  14. var obj = {
  15. };
  16. obj.message = 'index.html 还活着 ' + new Date().toLocaleString();
  17. console.log(obj);
  18. obj = JSON.stringify(obj);
  19. ws.send(obj);
  20. };
  21. var $ul = $('#msg-list');
  22. });
  23. </script>
  24. </head>
  25. <body>
  26. <h1>登录后会在此显示ip</h1>
  27. <ul id="msg-list"></ul>
  28. </body>
  29. </html>

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

  1. func timeTask() {
  2. timeStr := "0/3 * * * * *" //每隔3秒执行
  3. t1 := toolbox.NewTask("timeTask", timeStr, func() error {
  4. //todo do what you want
  5. return nil
  6. })
  7. toolbox.AddTask("tk1", t1)
  8. toolbox.StartTask()
  9. }

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

发表评论

表情:
评论列表 (有 0 条评论,570人围观)

还没有评论,来说两句吧...

相关阅读