Go Websocket使用 | 青训营笔记

101 阅读3分钟

Websocket

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

image.png

浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。


服务端代码

该websocket服务器将在常规的http服务器上实现。我们将net/http用于提供原始HTTP连接。

现在,在中server.go,让我们编写常规的HTTP服务器,并添加一个socketHandler()函数来处理websocket逻辑。

// server.go
package main

import (
   "fmt"
   "github.com/gorilla/websocket"
   "log"
   "net/http"
)

var upgrader = websocket.Upgrader{ // 解决跨域问题
   CheckOrigin: func(r *http.Request) bool {
      return true
   }, // use default options
}

func socketHandler(w http.ResponseWriter, r *http.Request) {
   // Upgrade our raw HTTP connection to a websocket based one
   conn, err := upgrader.Upgrade(w, r, nil)
   if err != nil {
      log.Print("Error during connection upgradation:", err)
      return
   }
   defer conn.Close()

   // The event loop
   for {
      messageType, message, err := conn.ReadMessage()
      if err != nil {
         log.Println("Error during message reading:", err)
         break
      }
      log.Printf("Received: %s", message)
      err = conn.WriteMessage(messageType, message)
      if err != nil {
         log.Println("Error during message writing:", err)
         break
      }
   }
}

func home(w http.ResponseWriter, r *http.Request) {
   fmt.Fprintf(w, "Index Page")
}

func main() {
   http.HandleFunc("/socket", socketHandler)
   http.HandleFunc("/", home)
   log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

我们首先用websocket工具测试一下是否能够成功,这里我使用的是apifox提供的工具,如果没有apifox也可以使用其他的,比如wstool.js.org/;这里可能需要设置跨域请求,这是需要注意的地方。

image.png

image.png

客户端代码

// client.go
package main

import (
   "log"
   "os"
   "os/signal"
   "time"

   "github.com/gorilla/websocket"
)

var done chan interface{}
var interrupt chan os.Signal

func receiveHandler(connection *websocket.Conn) {
   defer close(done)
   for {
      _, msg, err := connection.ReadMessage()
      if err != nil {
         log.Println("Error in receive:", err)
         return
      }
      log.Printf("Received: %s\n", msg)
   }
}

func main() {
   done = make(chan interface{})    // Channel to indicate that the receiverHandler is done
   interrupt = make(chan os.Signal) // Channel to listen for interrupt signal to terminate gracefully

   signal.Notify(interrupt, os.Interrupt) // Notify the interrupt channel for SIGINT

   socketUrl := "ws://localhost:8080" + "/socket"
   conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil)
   if err != nil {
      log.Fatal("Error connecting to Websocket Server:", err)
   }
   defer conn.Close()
   go receiveHandler(conn)

   // Our main loop for the client
   // We send our relevant packets here
   for {
      select {
      case <-time.After(time.Duration(1) * time.Millisecond * 1000):
         // Send an echo packet every second
         err := conn.WriteMessage(websocket.TextMessage, []byte("Hello from GolangDocs!"))
         if err != nil {
            log.Println("Error during writing to websocket:", err)
            return
         }

      case <-interrupt:
         // We received a SIGINT (Ctrl + C). Terminate gracefully...
         log.Println("Received SIGINT interrupt signal. Closing all pending connections")

         // Close our websocket connection
         err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
         if err != nil {
            log.Println("Error during closing websocket:", err)
            return
         }

         select {
         case <-done:
            log.Println("Receiver Channel Closed! Exiting....")
         case <-time.After(time.Duration(1) * time.Second):
            log.Println("Timeout in closing receiving channel. Exiting....")
         }
         return
      }
   }
}

看这段代码的逻辑很简单。定时向服务端发送Hello from GolangDocs!,然后打印相应的输出等;

image.png

总结

WebSocket和HTTP都是基于TCP协议。两个完全不同的应用层协议;

WebSocket依赖于HTTP连接进行第一次握手;

Socket不是协议,它是在程序层面上对传输层协议的接口封装,可以理解为一个能够提供端对端的通信的调用接口(API);