简介
一个用于Go的极简Websocket框架
Melody是基于 github.com/gorilla/websocket框架的,并抽象处理了里面的繁杂部分。
它可以让你搭建一个实时通讯的app,功能包括:
- 接口简单易用类似于net/http或Gin。
- 提供给所有广播以及给选择连接会话广播的简单途径。
- 消息缓冲区使并发写入变得安全。
- 自动处理ping/pong和会话超时。
- 在会话中存储数据。
例子
使用Gin框架完成的聊天室功能
下载依赖包
$ go get -u gopkg.in/olahol/melody.v1 $ go get -u github.com/gin-gonic/gin
main.go
package main import ( "github.com/gin-gonic/gin" "gopkg.in/olahol/melody.v1" "net/http" ) func main() { r := gin.Default() m := melody.New() r.GET("/", func(c *gin.Context) { http.ServeFile(c.Writer, c.Request, "index.html") }) r.GET("/ws", func(c *gin.Context) { m.HandleRequest(c.Writer, c.Request) }) m.HandleMessage(func(s *melody.Session, msg []byte) { m.Broadcast(msg) }) r.Run(":5000") }
index.heml
<html> <head> <title>Melody example: chatting</title> </head> <style> #chat { text-align: left; background: #f1f1f1; width: 500px; min-height: 300px; padding: 20px; } </style> <body> <center> <h3>Chat</h3> <pre id="chat"></pre> <input placeholder="say something" id="text" type="text"> </center> <script> var url = "ws://" + window.location.host + "/ws"; var ws = new WebSocket(url); var name = "Guest" + Math.floor(Math.random() * 1000); var chat = document.getElementById("chat"); var text = document.getElementById("text"); var now = function () { var iso = new Date().toISOString(); return iso.split("T")[1].split(".")[0]; }; ws.onmessage = function (msg) { var line = now() + " " + msg.data + "\n"; chat.innerText += line; }; text.onkeydown = function (e) { if (e.keyCode === 13 && text.value !== "") { ws.send("<" + name + "> " + text.value); text.value = ""; } }; </script> </body> </html>
展示图:
使用Gin完成文件的实时监控
下载
$ github.com/fsnotify/fsnotify $ github.com/gin-gonic/gin $ gopkg.in/olahol/melody.v1
main.go
package main import ( "github.com/fsnotify/fsnotify" "github.com/gin-gonic/gin" "gopkg.in/olahol/melody.v1" "io/ioutil" "net/http" ) func main() { file := "file.txt" r := gin.Default() m := melody.New() w, _ := fsnotify.NewWatcher() r.GET("/", func(c *gin.Context) { http.ServeFile(c.Writer, c.Request, "index.html") }) r.GET("/ws", func(c *gin.Context) { m.HandleRequest(c.Writer, c.Request) }) m.HandleConnect(func(s *melody.Session) { content, _ := ioutil.ReadFile(file) s.Write(content) }) go func() { for { ev := <-w.Events if ev.Op == fsnotify.Write { content, _ := ioutil.ReadFile(ev.Name) m.Broadcast(content) } } }() w.Add(file) r.Run(":5001") }
index.html
<html> <head> <title>Melody example: file watching</title> </head> <style> #file { text-align: left; background: #f1f1f1; width: 500px; min-height: 300px; padding: 20px; } </style> <body> <center> <h3>Watching a file</h3> <pre id="file"></pre> </center> <script> var url = 'ws://' + window.location.host + '/ws'; var c = new WebSocket(url); c.onmessage = function(msg){ var el = document.getElementById("file"); el.innerText = msg.data; } </script> </body> </html>
file.txt
监控文章修改的状况 wqe 王企鹅
展示图
使用Gin完成图片的实时交互
下载
$ go get -u gopkg.in/olahol/melody.v1 $ go get -u github.com/gin-gonic/gin
main.go
package main import ( "net/http" "strconv" "strings" "sync" "github.com/gin-gonic/gin" "gopkg.in/olahol/melody.v1" ) // GopherInfo contains information about the gopher on screen type GopherInfo struct { ID, X, Y string } func main() { router := gin.Default() mrouter := melody.New() gophers := make(map[*melody.Session]*GopherInfo) lock := new(sync.Mutex) counter := 0 router.GET("/", func(c *gin.Context) { http.ServeFile(c.Writer, c.Request, "index.html") }) router.GET("/ws", func(c *gin.Context) { mrouter.HandleRequest(c.Writer, c.Request) }) mrouter.HandleConnect(func(s *melody.Session) { lock.Lock() for _, info := range gophers { s.Write([]byte("set " + info.ID + " " + info.X + " " + info.Y)) } gophers[s] = &GopherInfo{strconv.Itoa(counter), "0", "0"} s.Write([]byte("iam " + gophers[s].ID)) counter++ lock.Unlock() }) mrouter.HandleDisconnect(func(s *melody.Session) { lock.Lock() mrouter.BroadcastOthers([]byte("dis "+gophers[s].ID), s) delete(gophers, s) lock.Unlock() }) mrouter.HandleMessage(func(s *melody.Session, msg []byte) { p := strings.Split(string(msg), " ") lock.Lock() info := gophers[s] if len(p) == 2 { info.X = p[0] info.Y = p[1] mrouter.BroadcastOthers([]byte("set "+info.ID+" "+info.X+" "+info.Y), s) } lock.Unlock() }) router.Run(":5002") }
index.html
<html> <head> <meta charset="utf-8"> <title>goofy gophers</title> <style> body { cursor: none; overflow: hidden; } .gopher { background-image: url('https://gimg2.baidu.com/image_search/src=http%3A%2F%2Ffile.elecfans.com%2Fweb1%2FM00%2F8F%2FDB%2FpIYBAFzBUUGAPWqXAAAu20XeRuU336.png&refer=http%3A%2F%2Ffile.elecfans.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=auto?sec=1654849209&t=fe13e4758e193bf3c5aa66d7baa79a9d'); width: 95px; height: 95px; background-size: cover; position: absolute; left: 0px; top: 0px; } </style> </head> <body> <script> var url = "ws://" + window.location.host + "/ws"; var ws = new WebSocket(url); var myid = -1; ws.onmessage = function (msg) { var cmds = {"iam": iam, "set": set, "dis": dis}; if (msg.data) { var parts = msg.data.split(" ") var cmd = cmds[parts[0]]; if (cmd) { cmd.apply(null, parts.slice(1)); } } }; function iam(id) { myid = id; } function set(id, x, y) { var node = document.getElementById("gopher-" + id); if (!node) { node = document.createElement("div"); document.body.appendChild(node); node.className = "gopher"; node.style.zIndex = id + 1; node.id = "gopher-" + id; } node.style.left = x + "px"; node.style.top = y + "px"; } function dis(id) { var node = document.getElementById("gopher-" + id); if (node) { document.body.removeChild(node); } } window.onmousemove = function (e) { if (myid > -1) { set(myid, e.pageX, e.pageY); ws.send([e.pageX, e.pageY].join(" ")); } } </script> </body> </html>
展示图
使用Gin框架完成分组的聊天室
下载
$ go get -u gopkg.in/olahol/melody.v1 $ go get -u github.com/gin-gonic/gin
main.go
package main import ( "github.com/gin-gonic/gin" "gopkg.in/olahol/melody.v1" "net/http" ) func main() { r := gin.Default() m := melody.New() r.GET("/", func(c *gin.Context) { http.ServeFile(c.Writer, c.Request, "index.html") }) r.GET("/channel/:name", func(c *gin.Context) { http.ServeFile(c.Writer, c.Request, "chan.html") }) r.GET("/channel/:name/ws", func(c *gin.Context) { m.HandleRequest(c.Writer, c.Request) }) m.HandleMessage(func(s *melody.Session, msg []byte) { m.BroadcastFilter(msg, func(q *melody.Session) bool { return q.Request.URL.Path == s.Request.URL.Path }) }) r.Run(":5003") }
index.html
<html> <head> <title>Melody example: chatting</title> </head> <style> #chat { text-align: left; background: #f1f1f1; width: 500px; min-height: 300px; padding: 20px; } </style> <body> <center> <h3>Join a channel</h3> <input placeholder="channel" id="channel" type="text"><button id="join">Join</button> </center> <script> var chan = document.getElementById("channel"); var join = document.getElementById("join"); join.onclick = function () { if (chan.value != "") { window.location = "/channel/" + chan.value; } }; </script> </body> </html>
chan.html
<html> <head> <title>Melody example: chatting</title> </head> <style> #chat { text-align: left; background: #f1f1f1; width: 500px; min-height: 300px; padding: 20px; } </style> <body> <center> <h3 id="name"></h3> <pre id="chat"></pre> <input placeholder="say something" id="text" type="text"> </center> <script> var url = "ws://" + window.location.host + window.location.pathname + "/ws"; var ws = new WebSocket(url); var name = "Guest" + Math.floor(Math.random() * 1000); var channelName = window.location.pathname.split("/")[2]; document.getElementById("name").innerText = "Channel: " + channelName; var chat = document.getElementById("chat"); var text = document.getElementById("text"); var now = function () { var iso = new Date().toISOString(); return iso.split("T")[1].split(".")[0]; }; ws.onmessage = function (msg) { var line = now() + " " + msg.data + "\n"; chat.innerText += line; }; text.onkeydown = function (e) { if (e.keyCode === 13 && text.value !== "") { ws.send("<" + name + "> " + text.value); text.value = ""; } }; </script> </body> </html>
展示图
听我狡辩
为了避免出现“你也个写博客的不交咋用,光给个例子是什么意思”之类的发言,在此声明,Melody官网就只有例子,虽然给了文档,但是好像只有翻墙才能查看,我可是光明正大的良民,不知道咋翻墙,更不看P站。如果你们有代理的话可以去看看,请看完顺带发篇博客并@我去学习
,国内网上都找不到这个包的信息,只有Github上的还算正规,所以拜托了。