这篇文章我们要基于beego + websocket 向页面推送数据(一)把功能整合到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是有问题的,你看上面的时间,都出现重复了
咱们来先解决第一个问题, 现在咱们的逻辑是:
跳转到index.htmljs在页面上发送get请求到localhost:8080/ws进入ws.go(MyWebSocketController)ws.go(MyWebSocketController)在Get()方法中注册成为websocket,然后for循环发送数据到broadcast这个chan里再被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 // } }现在项目长这样: 我们把项目跑起来看看(嗯你没看错我改了端口):
而问题到这里并没有结束: - 你应该注意到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() }嗯,基本就是这个样子,不过如果用这个的话好像退出定时器又成了一个问题。 一点浅见,欢迎指点。