- Created by 郭强, last modified by 张金富 on Jun 03, 2023
使用goframe
框架进行websocket
开发相当简单。我们以下通过实现一个简单的echo服务器
来演示goframe
框架的websocket
的使用(客户端使用HTML5实现)。
HTML5客户端
先上H5
客户端的代码
<!DOCTYPE html> <html lang="zh"> <head> <title>gf websocket echo server</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.3.5/css/bootstrap.min.css"> <script src="//cdn.bootcss.com/jquery/1.11.3/jquery.min.js"></script> </head> <body> <div class="container"> <div class="list-group" id="divShow"></div> <div> <div><input class="form-control" id="txtContent" autofocus placeholder="请输入发送内容"></div> <div><button class="btn btn-default" id="btnSend" style="margin-top:15px">发 送</button></div> </div> </div> </body> </html> <script type="application/javascript"> // 显示提示信息 function showInfo(content) { $("<div class=\"list-group-item list-group-item-info\">" + content + "</div>").appendTo("#divShow") } // 显示警告信息 function showWaring(content) { $("<div class=\"list-group-item list-group-item-warning\">" + content + "</div>").appendTo("#divShow") } // 显示成功信息 function showSuccess(content) { $("<div class=\"list-group-item list-group-item-success\">" + content + "</div>").appendTo("#divShow") } // 显示错误信息 function showError(content) { $("<div class=\"list-group-item list-group-item-danger\">" + content + "</div>").appendTo("#divShow") } $(function () { const url = "ws://127.0.0.1:8199/ws"; let ws = new WebSocket(url); try { // ws连接成功 ws.onopen = function () { showInfo("WebSocket Server [" + url +"] 连接成功!"); }; // ws连接关闭 ws.onclose = function () { if (ws) { ws.close(); ws = null; } showError("WebSocket Server [" + url +"] 连接关闭!"); }; // ws连接错误 ws.onerror = function () { if (ws) { ws.close(); ws = null; } showError("WebSocket Server [" + url +"] 连接关闭!"); }; // ws数据返回处理 ws.onmessage = function (result) { showWaring(" > " + result.data); }; } catch (e) { alert(e.message); } // 按钮点击发送数据 $("#btnSend").on("click", function () { if (ws == null) { showError("WebSocket Server [" + url +"] 连接失败,请F5刷新页面!"); return; } const content = $.trim($("#txtContent").val()).replace("/[\n]/g", ""); if (content.length <= 0) { alert("请输入发送内容!"); return; } $("#txtContent").val("") showSuccess(content); ws.send(content); }); // 回车按钮触发发送点击事件 $("#txtContent").on("keydown", function (event) { if (event.keyCode === 13) { $("#btnSend").trigger("click"); } }); }) </script>
注意我们这里的服务端连接地址为:ws://127.0.0.1:8199/ws
。
客户端的功能很简单,主要实现了这几个功能:
- 与服务端
websocket
连接状态保持及信息展示; - 界面输入内容并发送信息到
websocket
服务端; - 接收到
websocket
的返回信息后回显在界面上;
WebSocket服务端
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/glog" ) var ctx = gctx.New() func main() { s := g.Server() s.BindHandler("/ws", func(r *ghttp.Request) { ws, err := r.WebSocket() if err != nil { glog.Error(ctx, err) r.Exit() } for { msgType, msg, err := ws.ReadMessage() if err != nil { return } if err = ws.WriteMessage(msgType, msg); err != nil { return } } }) s.SetServerRoot(gfile.MainPkgPath()) s.SetPort(8199) s.Run() }
可以看到,服务端的代码相当简单,这里需要着重说明的是这几个地方:
- WebSocket方法
websocket
服务端的路由注册方式和普通的http
回调函数注册方式一样,但是在接口处理中我们需要通过ghttp.Request.WebSocket
方法(这里直接使用指针对象r.WebSocket()
)将请求转换为websocket
操作,并返回一个WebSocket对象
,该对象用于后续的websocket
通信操作。当然,如果客户端请求并非为websocket
操作时,转换将会失败,该方法会返回错误信息,使用时请注意判断方法的error
返回值。
- ReadMessage & WriteMessage
读取消息以及写入消息对应的是websocket
的数据读取以及写入操作(ReadMessage & WriteMessage
),需要注意的是这两个方法都有一个msgType
的变量,表示请求读取及写入数据的类型,常见的两种数据类型为:字符串数据或者二进制数据。在使用过程中,由于接口双方都会约定统一的数据格式,因此读取和写入的msgType
几乎都是一致的,所以在本示例中的返回消息时,数据类型参数直接使用的是读取到的msgType
。
HTTPS的WebSocket
如果需要支持HTTPS
的WebSocket
服务,只需要依赖的WebServer
支持HTTPS
即可,访问的WebSocket
地址需要使用 wss://
协议访问。以上客户端HTML5
页面中的WebSocket
访问地址需要修改为:wss://127.0.0.1:8199/wss
。服务端示例代码:
package main import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gctx" "github.com/gogf/gf/v2/os/gfile" "github.com/gogf/gf/v2/os/glog" ) var ctx = gctx.New() func main() { s := g.Server() s.BindHandler("/wss", func(r *ghttp.Request) { ws, err := r.WebSocket() if err != nil { glog.Error(ctx, err) r.Exit() } for { msgType, msg, err := ws.ReadMessage() if err != nil { return } if err = ws.WriteMessage(msgType, msg); err != nil { return } } }) s.SetServerRoot(gfile.MainPkgPath()) s.EnableHTTPS("../../https/server.crt", "../../https/server.key") s.SetPort(8199) s.Run() }
示例结果展示
我们首先执行示例代码main.go
,随后访问页面 http://127.0.0.1:8199/,随意输入请求内容并提交,随后在服务端关闭程序。可以看到,页面会回显提交的内容信息,并且即时展示websocket
的连接状态的改变,当服务端关闭时,客户端也会即时地打印出关闭信息。
Websocket安全校验
GoFrame
框架的websocket
模块并不会做同源检查(origin
),也就是说,这种条件下的websocket允许完全跨域。
安全的校验需要由业务层来处理,安全校验主要包含以下几个方面:
origin
的校验: 业务层在执行r.WebSocket()
之前需要进行origin
同源请求的校验;或者按照自定义的处理对请求进行校验(如果请求提交参数);如果未通过校验,那么调用r.Exit()
终止请求。websocket
通信数据校验: 数据通信往往都有一些自定义的数据结构,在这些通信数据中加上鉴权处理逻辑;
WebSocket Client 客户端
package main import ( "crypto/tls" "fmt" "net/http" "time" "github.com/gogf/gf/v2/net/gclient" "github.com/gorilla/websocket" ) func main() { client := gclient.NewWebSocket() client.HandshakeTimeout = time.Second // 设置超时时间 client.Proxy = http.ProxyFromEnvironment // 设置代理 client.TLSClientConfig = &tls.Config{} // 设置 tls 配置 conn, _, err := client.Dial("ws://127.0.0.1:8199/ws", nil) if err != nil { panic(err) } defer conn.Close() err = conn.WriteMessage(websocket.TextMessage, []byte("hello word")) if err != nil { panic(err) } mt, data, err := conn.ReadMessage() if err != nil { panic(err) } fmt.Println(mt, string(data)) }
- No labels
31 Comments
edzhao
可以给个服务端创建链接的例子么
Tom
可以拉最新 gf master 代码
参照 https://goframe.org/pages/viewpage.action?pageId=1114298#WebSocket服务-WebSocketclient客户端 就可以创建服务端 websocket 客户端
海亮
他要的是服务端,文档中本来就有服务端。或者说他表达不对,其实是想要客户端。
edzhao
感谢,已收到!
铁盒的序
2022-4-23 11:35:34 文档没更新,v2 使用 gclient.NewWebSocket()
ayamzh
请教下 怎么广播给房间内所有人
edzhao
自己维护下
scoketList
例如kkstun
websocket有没有绑定到controller和action的例子
aries
服务端示例
s.BindHandler("/wss", func(r *ghttp.Request) {
这里就是吧
kkstun
这明显不是。gf对用php的来说很亲切。我之前用的swoole,有个例子不错
kkstun
目前对golang还不熟练。不知道怎么写了。
aries
参考这里:https://github.com/gogf/gf-demos/blob/master/app/api/chat.go
kkstun
看了,写的不错的。主要还是自身golang不够熟练。想捡个轮子。
糖水不加糖
https://gitee.com/tsbjt/goframe-websocket/blob/master/app/service/websocket/logic/logic.go
糖水不加糖
把service下的websocket整个提取出来就可以直接用
kkstun
十分感谢。
icecream
concurrent write to websocket connection
并发比较大的时候 遇到了这个问题~
海亮
https://blog.csdn.net/taoerchun/article/details/108402296
icecream
https://github.com/gogf/gf-demos
这个demo里面的群发消息
users.RlockFunc 改为 users.LockFunc 就解决问题了
kokais
长连业务,并发数据下发,最好不要用锁,用channel通过队列下发。可以参考nano游戏框架源码
SmallRuralDog
V2版本websocket 应用demo
https://github.com/SmallRuralDog/gf-websocket
moru li
大佬可以写点例子吗?萌新感觉看懂了又没完全看懂
张永
大佬,结合上面的HTML5 客户端,写点例子、
呜
大佬, 给个加入房间的demo
nickdlk
示例代码:https://github.com/gogf/gf/v2/tree/master/.example/net/ghttp/server/websocket 无法访问
冰之焰
WebSocket client 客户端 代码用不了,
v2里没有了,需要怎么弄,请会的指点一下
angelandy
文档上
https://github.com/gogf/gf-demos 自动跳转去了 https://github.com/gogf/gf-demo-user
这个不是websocket 例子
下面这个才是
https://github.com/gogf/gf-demo-chat
付清鑫
webSocket 如何向客户端发送一个html文件,让客户端显示啊?能不能提供一个示例
糖水不加糖
把内容当做字符发就可以(发送body部分让前端动态load).但是不建议这么做.
风扬
这个方法发的消息,前端都是在onMessage回调里面收到的,有什么方法能让前端在onOpen的时候就收到消息呢?我看了js,说onOpen的时候是可以收到一条消息的
edzhao
遇到一个问题。使用tsl单项认证后,使用wss连接,控制台会一直输出tsl