在Go语言中为WebSockets启用消息压缩。

huangapple go评论103阅读模式
英文:

Enable message compression for WebSockets in Go

问题

我有一个简单的客户端-服务器WebSocket通信,并且我想知道是否可以为WebSocket启用消息压缩。我正在使用Golang库gorila/websocket

有一些配置项,如EnableCompression boolEnableWriteCompression(bool)方法,但它们似乎无法按预期工作,或者我可能无法弄清如何使用它们。

期望的行为是:

我期望发送一个50kb的消息,并将其压缩为10-20kb或类似的大小。但似乎EnableWriteCompression不按预期工作,或者我没有正确使用它。

代码如下:

server.go:

  1. package main
  2. import (
  3. "fmt"
  4. "github.com/gorilla/websocket"
  5. "log"
  6. "net/http"
  7. )
  8. var upgrader = websocket.Upgrader{}
  9. func socketHandler(w http.ResponseWriter, r *http.Request) {
  10. conn, err := upgrader.Upgrade(w, r, nil)
  11. conn.EnableWriteCompression(true)
  12. if err != nil {
  13. log.Print("Error during upgrade:", err)
  14. return
  15. }
  16. defer conn.Close()
  17. n := 0
  18. for n <= 10 {
  19. messageType, message, err := conn.ReadMessage()
  20. if err != nil {
  21. log.Println("Error during message reading:", err)
  22. break
  23. }
  24. log.Printf("Received: %s", message)
  25. err = conn.WriteMessage(messageType, message)
  26. if err != nil {
  27. log.Println("Error during message writing:", err)
  28. break
  29. }
  30. n++
  31. }
  32. }
  33. func home(w http.ResponseWriter, r *http.Request) {
  34. fmt.Fprintf(w, "Index Page")
  35. }
  36. func main() {
  37. http.HandleFunc("/socket", socketHandler)
  38. http.HandleFunc("/", home)
  39. log.Fatal(http.ListenAndServe("localhost:8080", nil))
  40. }

client.go:

  1. // client.go
  2. package main
  3. import (
  4. "log"
  5. "os"
  6. "os/signal"
  7. "time"
  8. "github.com/gorilla/websocket"
  9. )
  10. var done chan interface{}
  11. var interrupt chan os.Signal
  12. func receiveHandler(connection *websocket.Conn) {
  13. defer close(done)
  14. for {
  15. _, msg, err := connection.ReadMessage()
  16. if err != nil {
  17. log.Println("Error in receive:", err)
  18. return
  19. }
  20. log.Printf("Received: %s\n", msg)
  21. }
  22. }
  23. func main() {
  24. done = make(chan interface{}) // Channel to indicate that the receiverHandler is done
  25. interrupt = make(chan os.Signal) // Channel to listen for interrupt signal to terminate gracefully
  26. signal.Notify(interrupt, os.Interrupt) // Notify the interrupt channel for SIGINT
  27. socketUrl := "ws://localhost:8080" + "/socket"
  28. conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil)
  29. if err != nil {
  30. log.Fatal("Error connecting to Websocket Server:", err)
  31. }
  32. defer conn.Close()
  33. go receiveHandler(conn)
  34. // Our main loop for the client
  35. // We send our relevant packets here
  36. for {
  37. select {
  38. case <-time.After(time.Duration(1) * time.Millisecond * 1000):
  39. conn.EnableWriteCompression(true)
  40. conn.SetCompressionLevel(1)
  41. err := conn.WriteMessage(websocket.TextMessage, []byte("Some message to send!"))
  42. if err != nil {
  43. log.Println("Error during writing to websocket:", err)
  44. return
  45. }
  46. case <-interrupt:
  47. log.Println("Received SIGINT interrupt signal. Closing all pending connections")
  48. err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, ""))
  49. if err != nil {
  50. log.Println(err)
  51. return
  52. }
  53. select {
  54. case <-done:
  55. log.Println("Exiting....")
  56. case <-time.After(time.Duration(1) * time.Second):
  57. log.Println("Exiting....")
  58. }
  59. return
  60. }
  61. }
  62. }
英文:

I have simple client - server websocket communication and I want to know if it's possible to enable message compression for the websockets. I am using Golang library gorila/websocket.

And there are configurations like EnableCompression bool or EnableWriteCompression(bool) method, but it does not working as expected or maybe I cannot figure out how to use it.

Expected behaviour:

I am expecting to send for example - 50kb message and to be compressed to 10-20kb or something like this. But it seems that EnableWriteCompression is not working as expected or I am not using it in the right way.

The code:

server.go:

  1. package main
  2. import (
  3. &quot;fmt&quot;
  4. &quot;github.com/gorilla/websocket&quot;
  5. &quot;log&quot;
  6. &quot;net/http&quot;
  7. )
  8. var upgrader = websocket.Upgrader{}
  9. func socketHandler(w http.ResponseWriter, r *http.Request) {
  10. conn, err := upgrader.Upgrade(w, r, nil)
  11. conn.EnableWriteCompression(true)
  12. if err != nil {
  13. log.Print(&quot;Error during upgrade:&quot;, err)
  14. return
  15. }
  16. defer conn.Close()
  17. n := 0
  18. for n &lt;= 10 {
  19. messageType, message, err := conn.ReadMessage()
  20. if err != nil {
  21. log.Println(&quot;Error during message reading:&quot;, err)
  22. break
  23. }
  24. log.Printf(&quot;Received: %s&quot;, message)
  25. err = conn.WriteMessage(messageType, message)
  26. if err != nil {
  27. log.Println(&quot;Error during message writing:&quot;, err)
  28. break
  29. }
  30. n++
  31. }
  32. }
  33. func home(w http.ResponseWriter, r *http.Request) {
  34. fmt.Fprintf(w, &quot;Index Page&quot;)
  35. }
  36. func main() {
  37. http.HandleFunc(&quot;/socket&quot;, socketHandler)
  38. http.HandleFunc(&quot;/&quot;, home)
  39. log.Fatal(http.ListenAndServe(&quot;localhost:8080&quot;, nil))
  40. }

client.go:

  1. // client.go
  2. package main
  3. import (
  4. &quot;log&quot;
  5. &quot;os&quot;
  6. &quot;os/signal&quot;
  7. &quot;time&quot;
  8. &quot;github.com/gorilla/websocket&quot;
  9. )
  10. var done chan interface{}
  11. var interrupt chan os.Signal
  12. func receiveHandler(connection *websocket.Conn) {
  13. defer close(done)
  14. for {
  15. _, msg, err := connection.ReadMessage()
  16. if err != nil {
  17. log.Println(&quot;Error in receive:&quot;, err)
  18. return
  19. }
  20. log.Printf(&quot;Received: %s\n&quot;, msg)
  21. }
  22. }
  23. func main() {
  24. done = make(chan interface{}) // Channel to indicate that the receiverHandler is done
  25. interrupt = make(chan os.Signal) // Channel to listen for interrupt signal to terminate gracefully
  26. signal.Notify(interrupt, os.Interrupt) // Notify the interrupt channel for SIGINT
  27. socketUrl := &quot;ws://localhost:8080&quot; + &quot;/socket&quot;
  28. conn, _, err := websocket.DefaultDialer.Dial(socketUrl, nil)
  29. if err != nil {
  30. log.Fatal(&quot;Error connecting to Websocket Server:&quot;, err)
  31. }
  32. defer conn.Close()
  33. go receiveHandler(conn)
  34. // Our main loop for the client
  35. // We send our relevant packets here
  36. for {
  37. select {
  38. case &lt;-time.After(time.Duration(1) * time.Millisecond * 1000):
  39. conn.EnableWriteCompression(true)
  40. conn.SetCompressionLevel(1)
  41. err := conn.WriteMessage(websocket.TextMessage, []byte(&quot;Some message to send!&quot;))
  42. if err != nil {
  43. log.Println(&quot;Error during writing to websocket:&quot;, err)
  44. return
  45. }
  46. case &lt;-interrupt:
  47. log.Println(&quot;Received SIGINT interrupt signal. Closing all pending connections&quot;)
  48. err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, &quot;&quot;))
  49. if err != nil {
  50. log.Println(err)
  51. return
  52. }
  53. select {
  54. case &lt;-done:
  55. log.Println(&quot;Exiting....&quot;)
  56. case &lt;-time.After(time.Duration(1) * time.Second):
  57. log.Println(&quot;Exiting....&quot;)
  58. }
  59. return
  60. }
  61. }
  62. }

答案1

得分: 4

根据文档:
> EnableWriteCompression 启用或禁用后续文本和二进制消息的写入压缩。如果与对等方未协商压缩,则此函数无效。

您需要在 UpdaterDialer 级别上设置压缩,以便在连接升级期间进行协商:

  1. // 服务器端
  2. var upgrader = websocket.Upgrader{
  3. EnableCompression: true,
  4. }
  5. // 客户端
  6. dialer := websocket.Dialer{
  7. Proxy: http.ProxyFromEnvironment, // 来自默认拨号器
  8. HandshakeTimeout: 45 * time.Second, // 来自默认拨号器
  9. EnableCompression: true,
  10. }
  11. ...
  12. conn, _, err := dialer.Dial(socketUrl, nil)

然而,您的示例不会显示消息是否被压缩,因为这由库处理。

您可以使用类似 Wireshark 的工具进行验证:

  1. Sec-WebSocket-Extensions: permessage-deflate ...

以及在消息中:

  1. .1.. .... = Per-Message Compressed: True

您可能还需要调整压缩级别以获得您期望的结果(最大值似乎为9)。

英文:

As per documentation:
> EnableWriteCompression enables and disables write compression of
subsequent text and binary messages. This function is a noop if compression was not negotiated with the peer.

You need to setup the compression on Updater and Dialer level, so that it can be negotiated during the connection upgrade:

  1. // for server
  2. var upgrader = websocket.Upgrader{
  3. EnableCompression: true,
  4. }
  5. // for client
  6. dialer := websocket.Dialer{
  7. Proxy: http.ProxyFromEnvironment, // From default dialer
  8. HandshakeTimeout: 45 * time.Second, // From default dialer
  9. EnableCompression: true,
  10. }
  11. ...
  12. conn, _, err := dialer.Dial(socketUrl, nil)

Your example however will not show that the messages are compressed as this is handled by the library.

You can verify it using something like Wireshark:

  1. Sec-WebSocket-Extensions: permessage-deflate ...

and on messages:

  1. .1.. .... = Per-Message Compressed: True

You might also need to adjust the compression level to see the result you expect (max seems to be 9).

huangapple
  • 本文由 发表于 2022年1月25日 18:48:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/70847343.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定