zl程序教程

您现在的位置是:首页 >  其他

当前栏目

GO --- 实时通信Melody包的介绍和例子

2023-03-14 22:42:00 时间

简介

一个用于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 + "
";
        chat.innerText += line;
      };
      text.onkeydown = function (e) {
        if (e.keyCode === 13 && text.value !== "") {
          ws.send("<" + name + "> " + text.value);
          text.value = "";
        }
      };
    </script>
  </body>
</html>

展示图:

image

使用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
王企鹅

展示图

image

使用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>

展示图

image

使用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 + "
";
        chat.innerText += line;
      };
      text.onkeydown = function (e) {
        if (e.keyCode === 13 && text.value !== "") {
          ws.send("<" + name + "> " + text.value);
          text.value = "";
        }
      };
    </script>
  </body>
</html>

展示图

image

听我狡辩

为了避免出现“你也个写博客的不交咋用,光给个例子是什么意思”之类的发言,在此声明,Melody官网就只有例子,虽然给了文档,但是好像只有翻墙才能查看,我可是光明正大的良民,不知道咋翻墙,更不看P站。如果你们有代理的话可以去看看,请看完顺带发篇博客并@我去学习,国内网上都找不到这个包的信息,只有Github上的还算正规,所以拜托了。

image