简介

gotalk
gotalk.js

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

快速使用

本文代码使用 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.go
package main

import (
  "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()8080nil

服务器内部一直循环处理请求。

然后是客户端:

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()Request()Request()
err

先运行服务器:

$ go run server.go

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

$ go run client.go
hello
hello
hello
hello
hello
net/httpgotalknet/http
// get-started/http/main.go
package main

import (
  "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:8888
hello world

WebSocket

gotalk

服务端:

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/gotalk/gotalk.WebSocketHandler()/index.htmlhttp.ListenAndServe()
gotalkgotalk.jsindex.htmlgotalk.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()async-await

[图片上传失败...(image-bb0d81-1623109177730)]

gotalk.jsgotalk

协议格式

gotalk
gotalk

看一个官方请求的示例:

+------------------ SingleRequest
|   +---------------- requestID   "0001"
|   |      +--------- operation   "echo" (text3Size 4, text3Value "echo")
|   |      |       +- payloadSize 25
|   |      |       |
r0001004echo00000019{"message":"Hello World"}
r0001004echo00400000019{"message":"Hello World"}00000019

详细格式可以查看官方文档。

使用这种可阅读的格式给问题排查带来了极大的便利。但是在实际使用中,可能需要考虑安全和隐私的问题。

聊天室

examples
animals/jokes/golang

运行:

$ go run server.go

打开浏览器,输入"localhost:1235",显示如下:

[图片上传失败...(image-36fbe0-1623109177730)]

接下来就可以创建房间,在房间聊天了。

整个实现的有几个要点:

gotalk.WebSocketHandler()
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()SIGINT
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
}
SIGINTdoneserver.ListenAndServe()http.ErrServerClosed
done := enableGracefulShutdown(server, 5*time.Second)

// Start server
fmt.Printf("Listening on http://%s/\n", server.Addr)
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
  panic(err)
}

<- done

整个聊天室功能比较简单,代码也比较短,建议深入理解。在此基础之上做扩展也比较简单。

总结

gotalkgotalk.js

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

参考

欢迎关注我的微信公众号【GoUpUp】,共同学习,一起进步~