这篇文章我们要基于 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是有问题的,你看上面的时间,都出现重复了
咱们来先解决第一个问题, 现在咱们的逻辑是:
- 跳转到index.html
- js在页面上发送get请求到localhost:8080/ws
- js在页面上发送get请求到localhost:8080/wsjs在页面上发送get请求到localhost:8080/ws
- 进入ws.go(MyWebSocketController)进入ws.go(MyWebSocketController) ws.go(MyWebSocketController)在Get()方法中注册成为websocket,然后for循环发送数据到broadcast这个chan里
- 再被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()
}
嗯,基本就是这个样子,不过如果用这个的话好像退出定时器又成了一个问题。