使用 goframe 框架进行 websocket 开发相当简单。我们以下通过实现一个简单的 echo服务器 来演示 goframe 框架的 websocket 的使用(客户端使用HTML5实现)。

HTML5客户端

先上 H5 客户端的代码

  1. <!DOCTYPE html>
  2. <html lang="zh">
  3. <head>
  4. <title>gf websocket echo server</title>
  5. <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
  6. <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css">
  7. <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script>
  8. </head>
  9. <body>
  10. <div class="container">
  11. <div class="list-group" id="divShow"></div>
  12. <div>
  13. <div><input class="form-control" id="txtContent" autofocus placeholder="请输入发送内容"></div>
  14. <div><button class="btn btn-default" id="btnSend" style="margin-top:15px">发 送</button></div>
  15. </div>
  16. </div>
  17. </body>
  18. </html>
  19. <script type="application/javascript">
  20. // 显示提示信息
  21. function showInfo(content) {
  22. $("<div class=\"list-group-item list-group-item-info\">" + content + "</div>").appendTo("#divShow")
  23. }
  24. // 显示警告信息
  25. function showWaring(content) {
  26. $("<div class=\"list-group-item list-group-item-warning\">" + content + "</div>").appendTo("#divShow")
  27. }
  28. // 显示成功信息
  29. function showSuccess(content) {
  30. $("<div class=\"list-group-item list-group-item-success\">" + content + "</div>").appendTo("#divShow")
  31. }
  32. // 显示错误信息
  33. function showError(content) {
  34. $("<div class=\"list-group-item list-group-item-danger\">" + content + "</div>").appendTo("#divShow")
  35. }
  36. $(function () {
  37. const url = "ws://127.0.0.1:8199/ws";
  38. let ws = new WebSocket(url);
  39. try {
  40. // ws连接成功
  41. ws.onopen = function () {
  42. showInfo("WebSocket Server [" + url +"] 连接成功!");
  43. };
  44. // ws连接关闭
  45. ws.onclose = function () {
  46. if (ws) {
  47. ws.close();
  48. ws = null;
  49. }
  50. showError("WebSocket Server [" + url +"] 连接关闭!");
  51. };
  52. // ws连接错误
  53. ws.onerror = function () {
  54. if (ws) {
  55. ws.close();
  56. ws = null;
  57. }
  58. showError("WebSocket Server [" + url +"] 连接关闭!");
  59. };
  60. // ws数据返回处理
  61. ws.onmessage = function (result) {
  62. showWaring(" > " + result.data);
  63. };
  64. } catch (e) {
  65. alert(e.message);
  66. }
  67. // 按钮点击发送数据
  68. $("#btnSend").on("click", function () {
  69. if (ws == null) {
  70. showError("WebSocket Server [" + url +"] 连接失败,请F5刷新页面!");
  71. return;
  72. }
  73. const content = $.trim($("#txtContent").val()).replace("/[\n]/g", "");
  74. if (content.length <= 0) {
  75. alert("请输入发送内容!");
  76. return;
  77. }
  78. $("#txtContent").val("")
  79. showSuccess(content);
  80. ws.send(content);
  81. });
  82. // 回车按钮触发发送点击事件
  83. $("#txtContent").on("keydown", function (event) {
  84. if (event.keyCode === 13) {
  85. $("#btnSend").trigger("click");
  86. }
  87. });
  88. })
  89. </script>

注意我们这里的服务端连接地址为: ws://127.0.0.1:8199/ws

客户端的功能很简单,主要实现了这几个功能:

  • 与服务端 websocket 连接状态保持及信息展示;
  • 界面输入内容并发送信息到 websocket 服务端;
  • 接收到 websocket 的返回信息后回显在界面上;

WebSocket服务端

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. "github.com/gogf/gf/v2/os/gfile"
  7. "github.com/gogf/gf/v2/os/glog"
  8. )
  9. var ctx = gctx.New()
  10. func main() {
  11. s := g.Server()
  12. s.BindHandler("/ws", func(r *ghttp.Request) {
  13. ws, err := r.WebSocket()
  14. if err != nil {
  15. glog.Error(ctx, err)
  16. r.Exit()
  17. }
  18. for {
  19. msgType, msg, err := ws.ReadMessage()
  20. if err != nil {
  21. return
  22. }
  23. if err = ws.WriteMessage(msgType, msg); err != nil {
  24. return
  25. }
  26. }
  27. })
  28. s.SetServerRoot(gfile.MainPkgPath())
  29. s.SetPort(8199)
  30. s.Run()
  31. }

可以看到,服务端的代码相当简单,这里需要着重说明的是这几个地方:

  1. WebSocket方法

websocket 服务端的路由注册方式和普通的 http 回调函数注册方式一样,但是在接口处理中我们需要通过 ghttp.Request.WebSocket 方法(这里直接使用指针对象 r.WebSocket())将请求转换为 websocket 操作,并返回一个 WebSocket对象,该对象用于后续的 websocket 通信操作。当然,如果客户端请求并非为 websocket 操作时,转换将会失败,该方法会返回错误信息,使用时请注意判断方法的 error 返回值。

  1. ReadMessage & WriteMessage

读取消息以及写入消息对应的是 websocket 的数据读取以及写入操作( ReadMessage & WriteMessage),需要注意的是这两个方法都有一个 msgType 的变量,表示请求读取及写入数据的类型,常见的两种数据类型为:字符串数据或者二进制数据。在使用过程中,由于接口双方都会约定统一的数据格式,因此读取和写入的 msgType 几乎都是一致的,所以在本示例中的返回消息时,数据类型参数直接使用的是读取到的 msgType

HTTPS的WebSocket

如果需要支持 HTTPSWebSocket 服务,只需要依赖的 WebServer 支持 HTTPS 即可,访问的 WebSocket 地址需要使用 wss:// 协议访问。以上客户端 HTML5 页面中的 WebSocket 访问地址需要修改为: wss://127.0.0.1:8199/wss。服务端示例代码:

  1. package main
  2. import (
  3. "github.com/gogf/gf/v2/frame/g"
  4. "github.com/gogf/gf/v2/net/ghttp"
  5. "github.com/gogf/gf/v2/os/gctx"
  6. "github.com/gogf/gf/v2/os/gfile"
  7. "github.com/gogf/gf/v2/os/glog"
  8. )
  9. var ctx = gctx.New()
  10. func main() {
  11. s := g.Server()
  12. s.BindHandler("/wss", func(r *ghttp.Request) {
  13. ws, err := r.WebSocket()
  14. if err != nil {
  15. glog.Error(ctx, err)
  16. r.Exit()
  17. }
  18. for {
  19. msgType, msg, err := ws.ReadMessage()
  20. if err != nil {
  21. return
  22. }
  23. if err = ws.WriteMessage(msgType, msg); err != nil {
  24. return
  25. }
  26. }
  27. })
  28. s.SetServerRoot(gfile.MainPkgPath())
  29. s.EnableHTTPS("../../https/server.crt", "../../https/server.key")
  30. s.SetPort(8199)
  31. s.Run()
  32. }

示例结果展示

我们首先执行示例代码 main.go,随后访问页面 http://127.0.0.1:8199/,随意输入请求内容并提交,随后在服务端关闭程序。可以看到,页面会回显提交的内容信息,并且即时展示 websocket 的连接状态的改变,当服务端关闭时,客户端也会即时地打印出关闭信息。

WebSocket服务 - 图1

Websocket安全校验

GoFrame 框架的 websocket 模块并不会做同源检查( origin),也就是说,这种条件下的websocket允许完全跨域。

安全的校验需要由业务层来处理,安全校验主要包含以下几个方面:

  1. origin 的校验: 业务层在执行 r.WebSocket() 之前需要进行 origin 同源请求的校验;或者按照自定义的处理对请求进行校验(如果请求提交参数);如果未通过校验,那么调用 r.Exit() 终止请求。
  2. websocket 通信数据校验: 数据通信往往都有一些自定义的数据结构,在这些通信数据中加上鉴权处理逻辑;

WebSocket Client 客户端

  1. package main
  2. import (
  3. "crypto/tls"
  4. "fmt"
  5. "net/http"
  6. "time"
  7. "github.com/gogf/gf/v2/net/gclient"
  8. "github.com/gorilla/websocket"
  9. )
  10. func main() {
  11. client := gclient.NewWebSocket()
  12. client.HandshakeTimeout = time.Second // 设置超时时间
  13. client.Proxy = http.ProxyFromEnvironment // 设置代理
  14. client.TLSClientConfig = &tls.Config{} // 设置 tls 配置
  15. conn, _, err := client.Dial("ws://127.0.0.1:8199/ws", nil)
  16. if err != nil {
  17. panic(err)
  18. }
  19. defer conn.Close()
  20. err = conn.WriteMessage(websocket.TextMessage, []byte("hello word"))
  21. if err != nil {
  22. panic(err)
  23. }
  24. mt, data, err := conn.ReadMessage()
  25. if err != nil {
  26. panic(err)
  27. }
  28. fmt.Println(mt, string(data))
  29. }