1

Introduction

gotalk focuses on inter-process communication and is committed to simplifying communication protocols and processes. At the same time it:

  • Provide concise and clear API;
  • Support TCP, WebSocket and other protocols;
  • Use a very simple and efficient transmission protocol format, which is convenient for packet capture and debugging;
  • Built-in JavaScript file gotalk.js to facilitate the development of client programs based on Web pages;
  • Contains rich examples for learning and reference.

So, let's play it~

Quick to use

The code in this article uses Go Modules.

Create a directory and initialize:

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

Install the gotalk library:

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

Next, let us write a simple echo program, the server directly returns the received client information without any processing. The first is the server side:

// 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)
  }
}

Register message processing through gotalk.Handle() , it accepts two parameters. The first parameter is the name of the message and the type of character string. It is only necessary to ensure that it is unique and identifiable. The second parameter is the processing function. When a message with the corresponding name is received, this function is called for processing. The processing function accepts one parameter and returns two values. When normal processing is completed, the processing result is passed through the first return value, and when an error occurs, the second return value is used to indicate the type of error.

The processor function here is relatively simple, accepts a string parameter and returns directly as it is.

Then, call gotalk.Serve() start the server and listen on the port. It accepts 3 parameters, protocol type, listening address, and processor object. Here we use the TCP protocol to monitor the local 8080 , use the default processor object, and pass in nil .

The server has been processing requests in a loop.

Then the client:

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()
}

The client first calls gotalk.Connect() connect to the server, which accepts two parameters: protocol and address (IP + port). We can use the same protocol and address as the server. A successful connection will return a connection object. Call the Request() method of the connection object to send a message to the server. Request() method accepts 3 parameters. The first parameter is the message name, which corresponds to the message name registered by the server. Requesting a message name that does not exist will return an error. The second parameter is the parameter passed to the server, and there can only be one parameter, which corresponds to the input parameter of the processor function. The third parameter is a pointer to the return value, which is used to accept the result returned by the server.

If the request fails, error err returned. Don't forget to close the connection object after use.

Run the server first:

$ go run server.go

After opening a command line, run the client:

$ go run client.go
hello
hello
hello
hello
hello

In fact if you know the standard library net/http , should you will find that the use of gotalk of server-side code and use net/http write Web server is very similar. All are very simple and clear:

// 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)
  }
}

run:

$ go run main.go

Use curl to verify:

$ curl localhost:8888
hello world

WebSocket

In addition to TCP, gotalk also supports communication based on the WebSocket protocol. Below we use WebSocket to rewrite the above server-side program, and then write a simple Web page to communicate with it.

Server:

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 message processing function is still the same as before. The difference is that /gotalk/ is handed over to gotalk.WebSocketHandler() processing, and this processor is responsible for WebSocket requests. At the same time, start a file server in the current working directory and mount it on the HTTP path / . The file server is for the client to conveniently request the index.html page. Finally, call http.ListenAndServe() start the Web server and listen on port 8080.

Then there is the client, gotalk encapsulates the details of WebSocket communication in a JavaScript file gotalk.js in order to facilitate the writing of Web programs. It can be used directly from the js directory in the warehouse. Then we write the page index.html and introduce 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>

First call gotalk.connection() connect to the server and return a connection object. on() method of this object to register the connection establishment and disconnection callbacks respectively. Then add a callback to the button, and send the content of the input box to the server every time it is clicked. requestp() method of the connection object to send the request. The first parameter is the message name, which corresponds to the name registered gotalk.Handle() The second is the processing parameter, which will be sent to the server. Here, Promise is used to process asynchronous requests and responses. For the convenience of writing and easy to understand, use async-await synchronous writing. The content of the response is displayed directly on the page:

Note that the gotalk.js file needs to be placed in the gotalk directory of the server running directory.

Protocol format

gotalk adopts ASCII-based protocol format, which is designed to be easy for humans to read and flexible. Each transmitted message is divided into several parts: type identification, request ID, operation, and message content.

  • Type identification: Only one byte is used to indicate the type of message, whether it is a request message or a response message, streaming message or non-streaming, and error, heartbeat, and notification also have their specific type identification.
  • Request ID: It is represented by 4 bytes, which is convenient for matching and responding. Because gotalk can send any request at the same time and receive the response of the previous request. So there needs to be an ID to identify which request the received response corresponds to before.
  • Operation: This is the message name we defined above, such as "echo".
  • Message content: use length + actual content format.

Look at an example of an official request:

+------------------ SingleRequest
|   +---------------- requestID   "0001"
|   |      +--------- operation   "echo" (text3Size 4, text3Value "echo")
|   |      |       +- payloadSize 25
|   |      |       |
r0001004echo00000019{"message":"Hello World"}
  • r : Indicates that this is a single request.
  • 0001 : The request ID is 1, and hexadecimal coding is used here.
  • 004echo : This part indicates that the operation is "echo", and the length needs to be specified before the actual string content, otherwise the receiver does not know where the content ends. 004 indicates that the length of "echo" is 4, and it also uses hexadecimal encoding.
  • 00000019{"message":"Hello World"} : This part is the content of the message. Also need to specify the length, hexadecimal 00000019 means the length is 25.

The detailed format can be viewed in the official document.

Using this readable format brings great convenience to troubleshooting. However, in actual use, security and privacy issues may need to be considered.

chatroom

examples built-in sample program for a chat room based on WebSocket. Features are as follows:

  • Room can be created, 3 rooms animals/jokes/golang created by default;
  • Chat in the room (basic function);
  • A simple Web page.

run:

$ go run server.go

Open the browser, enter "localhost:1235", the display is as follows:

Then you can create a room and chat in the room.

There are several main points of the whole realization:

First, gotalk.WebSocketHandler() can set the connection callback:

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

Set a random username in the callback, and gotalk.Sock facilitate message broadcasting:

func onConnect(s *gotalk.WebSocket) {
  socksmu.Lock()
  defer socksmu.Unlock()
  socks[s] = 1

  username := randomName()
  s.UserData = username
}

Second, the gotalk setting processor function can have two parameters, the first one represents the current connection, and the second one is the actual received message parameter.

Third, the enableGracefulShutdown() function realizes the graceful shutdown of the Web server, which is worth learning. After receiving the SIGINT signal, first close all connections, and then exit the program. Note that listening for signals and running the HTTP server are not the same goroutine, see how they work together:

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
}

After receiving the SIGINT signal, the done channel is closed, server.ListenAndServe() returns a http.ErrServerClosed error, and exits the loop:

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

The function of the entire chat room is relatively simple, and the code is relatively short, it is recommended to understand in depth. It is also relatively simple to expand on this basis.

to sum up

gotalk implements a simple and easy-to-use communication library. And a JavaScript file gotalk.js is provided to facilitate the development of Web programs. The protocol format is clear and easy to debug. Built-in rich examples. The code of the entire library is not long, it is recommended to learn more.

If you find a fun and useful Go language library, welcome to submit an issue on the Go Daily Library GitHub😄

reference

  1. gotalk GitHub:https://github.com/rsms/gotalk
  2. Go daily one library GitHub: https://github.com/darjun/go-daily-lib

I

My blog: https://darjun.github.io

Welcome to follow my WeChat public account [GoUpUp], learn together and make progress together~


darjun
2.9k 声望358 粉丝