简介

gotalk专一于过程间的通信,致力于简化通信协议和流程。同时它:

  • 提供简洁、清晰的 API;
  • 反对 TCP,WebSocket 等协定;
  • 采纳非常简单而又高效的传输协定格局,便于抓包调试;
  • 内置了 JavaScript 文件gotalk.js,不便开发基于 Web 网页的客户端程序;
  • 内含丰盛的示例可供学习参考。

那么,让咱们来玩一下吧~

疾速应用

本文代码应用 Go Modules。

创立目录并初始化:

$ mkdir gotalk && cd gotalk$ go mod init github.com/darjun/go-daily-lib/gotalk

装置gotalk库:

$ go get -u github.com/rsms/gotalk

接下来让咱们来编写一个简略的 echo 程序,服务端间接返回收到的客户端信息,不做任何解决。首先是服务端:

// get-started/server/server.gopackage mainimport (  "log"  "github.com/rsms/gotalk")func main() {  gotalk.Handle("echo", func(in string) (string, error) {    return in, nil  })  if err := gotalk.Serve("tcp", ":8080", nil); err != nil {    log.Fatal(err)  }}

通过gotalk.Handle()注册音讯解决,它承受两个参数。第一个参数为音讯名,字符串类型,保障惟一且可辨识即可。第二个参数为处理函数,收到对应名称的音讯,调用该函数解决。处理函数承受一个参数,返回两个值。失常解决实现通过第一个返回值传递处理结果,出错时通过第二个返回值示意谬误类型。

这里的处理器函数比较简单,承受一个字符串参数,间接原样返回。

而后,调用gotalk.Serve()启动服务器,监听端口。它承受 3 个参数,协定类型、监听地址、处理器对象。此处咱们应用 TCP 协定,监听本地8080端口,应用默认处理器对象,传入nil即可。

服务器外部始终循环解决申请。

而后是客户端:

func main() {  s, err := gotalk.Connect("tcp", ":8080")  if err != nil {    log.Fatal(err)  }  for i := 0; i < 5; i++ {    var echo string    if err := s.Request("echo", "hello", &echo); err != nil {      log.Fatal(err)    }    fmt.Println(echo)  }  s.Close()}

客户端首先调用gotalk.Connect()连贯服务器,它承受两个参数:协定和地址(IP + 端口)。咱们应用与服务器统一的协定和地址即可。连贯胜利会返回一个连贯对象。调用连贯对象的Request()办法,即可向服务器发送音讯。Request()办法承受 3 个参数。第一个参数为音讯名,这对应于服务器注册的音讯名,申请一个不存在的音讯名会返回谬误。第二个参数是传给服务器的参数,有且只能有一个参数,对应处理器函数的入参。第三个参数为返回值的指针,用于承受服务器返回的后果。

如果申请失败,返回谬误err。应用实现之后不要遗记敞开连贯对象。

先运行服务器:

$ go run server.go

在开启一个命令行,运行客户端:

$ go run client.gohellohellohellohellohello

实际上如果理解规范库net/http,你应该就会发现,应用gotalk的服务端代码与应用net/http编写 Web 服务器十分类似。都非常简单,清晰:

// get-started/http/main.gopackage mainimport (  "fmt"  "log"  "net/http")func index(w http.ResponseWriter, r *http.Request) {  fmt.Fprintln(w, "hello world")}func main() {  http.HandleFunc("/", index)  if err := http.ListenAndServe(":8888", nil); err != nil {    log.Fatal(err)  }}

运行:

$ go run main.go

应用 curl 验证:

$ curl localhost:8888hello world

WebSocket

除了 TCP,gotalk还反对基于 WebSocket 协定的通信。上面咱们应用 WebSocket 重写下面的服务端程序,而后编写一个简略 Web 页面与之通信。

服务端:

func main() {  gotalk.Handle("echo", func(in string) (string, error) {    return in, nil  })  http.Handle("/gotalk/", gotalk.WebSocketHandler())  http.Handle("/", http.FileServer(http.Dir(".")))  if err := http.ListenAndServe(":8080", nil); err != nil {    log.Fatal(err)  }}

gotalk音讯处理函数的注册还是与后面的一样。不同的是这里将 HTTP 门路/gotalk/的申请交由gotalk.WebSocketHandler()解决,这个处理器负责 WebSocket 申请。同时,在当前工作目录开启一个文件服务器,挂载到 HTTP 门路/上。文件服务器是为了客户端不便地申请index.html页面。最初调用http.ListenAndServe()开启 Web 服务器,监听端口 8080。

而后是客户端,gotalk为了不便 Web 程序的编写,将 WebSocket 通信细节封装在一个 JavaScript 文件gotalk.js中。能够间接从仓库中的 js 目录下获取应用。接着咱们编写页面index.html,引入gotalk.js

<!DOCTYPE HTML><html lang="en">  <head>    <meta charset="utf-8">    <script type="text/javascript" src="gotalk/gotalk.js"></script>  </head>  <body>    <input id="txt">    <button id="snd">send</button><br>    <script>    let c = gotalk.connection()      .on('open', () => log(`connection opened`))      .on('close', reason => log(`connection closed (reason: ${reason})`))    let btn = document.querySelector("#snd")    let txt = document.querySelector("#txt")    btn.onclick = async () => {      let content = txt.value      if (content.length === 0) {        alert("no message")        return      }      let res = await c.requestp('echo', content)      log(`reply: ${JSON.stringify(res, null, 2)}`)      return false    }    function log(message) {      document.body.appendChild(document.createTextNode(message))      document.body.appendChild(document.createElement("br"))    }    </script>  </body></html>

首先调用gotalk.connection()连贯服务端,返回一个连贯对象。调用此对象的on()办法,别离注册连贯建设和断开的回调。而后给按钮增加回调,每次点击将输入框中的内容发送给服务端。调用连贯对象的requestp()办法发送申请,第一个参数为音讯名,对应在服务端应用gotalk.Handle()注册的名字。第二个即为解决参数,会一并发送给服务端。这里应用 Promise 解决异步申请和响应,为了编写不便和易于了解应用async-await同步的写法。响应的内容间接显示在页面上:

留神,gotalk.js文件须要放在服务器运行目录的gotalk目录下。

协定格局

gotalk采纳基于 ASCII 的协定格局,设计为不便人类浏览且灵便的。每条传输的音讯都分为几个局部:类型标识、申请ID、操作、音讯内容。

  • 类型标识:只用一个字节,用来示意音讯的类型,是申请音讯还是响应音讯,流式音讯还是非流式的,谬误、心跳和告诉也都有其特定的类型标识。
  • 申请 ID:用 4 个字节示意,不便匹配响应。因为gotalk能够同时发送任意个申请并接管之前申请的响应。所以须要有一个 ID 来标识接管到的响应对应之前发送的哪条申请。
  • 操作:即为咱们下面定义的音讯名,例如"echo"。
  • 音讯内容:应用长度 + 理论内容格局。

看一个官网申请的示例:

+------------------ SingleRequest|   +---------------- requestID   "0001"|   |      +--------- operation   "echo" (text3Size 4, text3Value "echo")|   |      |       +- payloadSize 25|   |      |       |r0001004echo00000019{"message":"Hello World"}
  • r:示意这是一个单条申请。
  • 0001:申请 ID 为 1,这里采纳十六进制编码。
  • 004echo:这部分示意操作为"echo",在理论字符串内容前须要指定长度,否则接管方不晓得内容在哪里完结。004批示"echo"长度为 4,同样采纳十六进制编码。
  • 00000019{"message":"Hello World"}:这部分是音讯的内容。同样须要指定长度,十六进制00000019示意长度为 25。

具体格局能够查看官网文档。

应用这种可浏览的格局给问题排查带来了极大的便当。然而在理论应用中,可能须要思考平安和隐衷的问题。

聊天室

examples内置一个基于 WebSocket 的聊天室示例程序。个性如下:

  • 能够创立房间,默认创立 3 个房间animals/jokes/golang
  • 在房间聊天(基本功能);
  • 一个简略的 Web 页面。

运行:

$ go run server.go

关上浏览器,输出"localhost:1235",显示如下:

接下来就能够创立房间,在房间聊天了。

整个实现的有几个要点:

其一,gotalk.WebSocketHandler()创立的 WebSocket 处理器能够设置连贯回调:

gh := gotalk.WebSocketHandler()gh.OnConnect = onConnect

在回调中设置随机用户名,并将以后连贯的gotalk.Sock存储下来,不便音讯播送:

func onConnect(s *gotalk.WebSocket) {  socksmu.Lock()  defer socksmu.Unlock()  socks[s] = 1  username := randomName()  s.UserData = username}

其二,gotalk设置处理器函数能够有两个参数,第一个示意以后连贯,第二个才是理论接管到的音讯参数。

其三,enableGracefulShutdown()函数实现了 Web 服务器的优雅敞开,十分值得学习。接管到SIGINT信号,先敞开所有的连贯,再退出程序。留神监听信号和运行 HTTP 服务器并不是同一个 goroutine,看它们是如何合作的:

func enableGracefulShutdown(server *http.Server, timeout time.Duration) chan struct{} {  server.RegisterOnShutdown(func() {    // close all connected sockets    fmt.Printf("graceful shutdown: closing sockets\n")    socksmu.RLock()    defer socksmu.RUnlock()    for s := range socks {      s.CloseHandler = nil // avoid deadlock on socksmu (also not needed)      s.Close()    }  })  done := make(chan struct{})  quit := make(chan os.Signal, 1)  signal.Notify(quit, syscall.SIGINT)  go func() {    <-quit // wait for signal    fmt.Printf("graceful shutdown initiated\n")    ctx, cancel := context.WithTimeout(context.Background(), timeout)    defer cancel()    server.SetKeepAlivesEnabled(false)    if err := server.Shutdown(ctx); err != nil {      fmt.Printf("server.Shutdown error: %s\n", err)    }    fmt.Printf("graceful shutdown complete\n")    close(done)  }()  return done}

接管到SIGINT信号后done通道敞开,server.ListenAndServe()返回http.ErrServerClosed谬误,退出循环:

done := enableGracefulShutdown(server, 5*time.Second)// Start serverfmt.Printf("Listening on http://%s/\n", server.Addr)if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {  panic(err)}<- done

整个聊天室性能比较简单,代码也比拟短,倡议深刻了解。在此基础之上做扩大也比较简单。

总结

gotalk实现了一个简略、易用的通信库。并且提供了 JavaScript 文件gotalk.js,不便 Web 程序的开发。协定格局清晰,易调试。内置丰盛的示例。整个库的代码也不长,倡议深刻理解。

大家如果发现好玩、好用的 Go 语言库,欢送到 Go 每日一库 GitHub 上提交 issue

参考

  1. gotalk GitHub:https://github.com/rsms/gotalk
  2. Go 每日一库 GitHub:https://github.com/darjun/go-daily-lib

我的博客:https://darjun.github.io

欢送关注我的微信公众号【GoUpUp】,独特学习,一起提高~