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

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

简介

一个用于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上的还算正规,所以拜托了。


相关文章
|
4天前
|
编译器 Go 开发者
go语言中导入相关包
【11月更文挑战第1天】
14 3
|
24天前
|
存储 Go 数据库
Go语言Context包源码学习
【10月更文挑战第21天】Go 语言中的 `context` 包用于在函数调用链中传递请求上下文信息,支持请求的取消、超时和截止时间管理。其核心接口 `Context` 定义了 `Deadline`、`Done`、`Err` 和 `Value` 方法,分别用于处理截止时间、取消信号、错误信息和键值对数据。包内提供了 `emptyCtx`、`cancelCtx`、`timerCtx` 和 `valueCtx` 四种实现类型,满足不同场景需求。示例代码展示了如何使用带有超时功能的上下文进行任务管理和取消。
|
2月前
|
存储 Go
Golang语言基于go module方式管理包(package)
这篇文章详细介绍了Golang语言中基于go module方式管理包(package)的方法,包括Go Modules的发展历史、go module的介绍、常用命令和操作步骤,并通过代码示例展示了如何初始化项目、引入第三方包、组织代码结构以及运行测试。
43 3
|
3月前
|
编译器 数据库连接 Go
Go Sync 包:并发的 6 个关键概念
Go Sync 包:并发的 6 个关键概念
|
3月前
|
Go 开发者
|
3月前
|
编译器 Go 开发者
|
3月前
|
存储 SQL Go
一文弄懂Go语言的Context包,值得收藏!
在开发高效的Go应用程序时,处理超时、取消操作和传递请求范围的数据至关重要。Go标准库中的`context`包提供了一套强大的工具,用于在不同层级间传递取消信号、超时和截止时间等信息。本文首先概述了`context`包的核心功能,接着详细介绍了关键方法,如`WithCancel`、`WithDeadline`、`WithTimeout`和`WithValue`的使用场景。通过创建和派生上下文,开发者能更好地管理请求的生命周期,控制长时间运行的操作,并在并发环境中传递必要的数据。
50 1
|
3月前
|
缓存 Go
Go引用包版本更新但是被引用的包的子包并没有出现在vendor中的问题和解决方案
文章讨论了在Go模块项目中升级依赖包版本时遇到的子包未出现在vendor目录的问题,并提供了直接删除旧版本引用并重新执行`go mod vendor`的解决方案。
36 0
|
4月前
|
存储 Go
go语言中fmt格式化包和内置函数汇总
【7月更文挑战第10天】本文介绍fmt包和`Errorf`用于创建格式化的错误消息。`fmt`包还涉及一些接口,如`Formatter`、`GoStringer`、`ScanState`、`Scanner`和`Stringer`,支持自定义格式化和输入/输出处理。
73 1
|
5月前
|
Go
go安装三方包并使用
go安装三方包并使用