maltadirk 发表于 2022-1-10 21:08

181. 【go,聊天室】认识 WebSocket

一、那年毕业设计

学习《Go 语言编程之旅》接着 gRPC 之后,现在开始学 websocket 在 go 语言里的使用了。这里是要跟着做一个聊天室。不禁让我想起那年毕业设计:基于 Java 的即时通讯系统。

大学毕业那会,我一个人搞定了三个毕业设计,一个是邮件收发系统,一个是学生管理系统,我自己的是即时通讯系统。很遗憾,别人的毕设一次性就过了,我自己的反倒去了二辩。不过那时我也不慌,因为都是自己做的,不可能不过。

当时我正在学 Spring 框架,就画了下面的系统架构图,技术栈:HTML 5 + CSS 3 + Jquery + Spring MVC + Spring + Hibernate + MySQL。很遗憾,当时学的是 SpringMVC,其实当时已经有很多人用 SpringBoot 了,因此我选技术的时候就落伍了(当时太年轻,并不是自己选的),这也导致我后来在 Java web 上不愿意再做提升了,就是觉得很遗憾。


系统架构

看看下面界面,这么一个小即时通讯系统,当时觉得还是挺酷的。当时觉得自己懂的好多,HTML 5、 CSS 3 一边学着,学完立马就往即时通讯系统里硬灌,虽然现在看来不成样子,但当时真的特别开心。

记得答辩那天,有个哥们掏出来一个 cmd 命令行程序,界面就是黑窗口,被老师狠狠批了一顿,我当时看着自己的五颜六色的界面,还是挺开心的(虽然四不像,哈哈)。即使一辩挂了回忆还是美美的。


二、WebSocket 简介

WebSocket是一种网络传输协议,可在单个TCP连接上进行全双工通信,位于OSI模型的应用层。WebSocket协议在2011年由IETF标准化为RFC 6455,后由RFC 7936补充规范。Web IDL中的WebSocket API由W3C标准化。

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

较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。更好的压缩效果。相对于HTTP压缩,Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。
四、nhooyr.io/websocket 的使用

毕业设计自然是玩笑,其实大半年前,我也做过另一个即时通讯系统,最开始使用的是腾讯云现成的即时通讯SDK,每个月都是收费的,而且聊天记录不能永久保存。后来我基于 Elasticsearch 和 gorilla/websocket 做了系统重构,替代了腾讯云的收费 SDK。

这次使用 nhooyr.io/websocket 是因为教程里用的是这个库,而且写的优点也挺吸引人的,因此我还是愿意再重试一下这个库。

核心特色如下:
有很小且符合 Go 习惯的 API。核心代码 2200 行。一流的 context.Context 支持。有全面的测试。不依赖任何第三方库。支持 JSON 和 ProtoBuf。默认就有高度的性能优化。开箱即用的并发支持。全面的 Wasm 支持。CLose handshake。
服务端代码

package mainimport (    "context"    "fmt"    "log"    "net/http"    "nhooyr.io/websocket"    "nhooyr.io/websocket/wsjson"    "time")func main() {    http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {      fmt.Fprintln(w, "HTTP, Hello")    })    http.HandleFunc("/ws", func(w http.ResponseWriter, req *http.Request) {      conn, err := websocket.Accept(w, req, nil)      if err != nil {            log.Println(err)            return      }      defer conn.Close(websocket.StatusInternalError, "内部出错了")      ctx, cancel := context.WithTimeout(req.Context(), time.Second*10)      defer cancel()      var v interface{}      err = wsjson.Read(ctx, conn, &v)      if err != nil {            log.Println(err)            return      }      log.Printf("接收到客户端: %v\n", v)      err = wsjson.Write(ctx, conn, "Hello WebSocket Client")      if err != nil {            log.Println(err)            return      }      conn.Close(websocket.StatusNormalClosure, "")    })    log.Fatal(http.ListenAndServe(":2022", nil))}客户端代码

package mainimport (    "context"    "fmt"    "nhooyr.io/websocket"    "nhooyr.io/websocket/wsjson"    "time")func main() {    ctx, cancel := context.WithTimeout(context.Background(), time.Minute)    defer cancel()    c, _, err := websocket.Dial(ctx, "ws://localhost:2022/ws", nil)    if err != nil {      panic(err)    }    defer c.Close(websocket.StatusInternalError, "内部错误!")    err = wsjson.Write(ctx, c, "Hello WebSocket Server")    if err != nil {      panic(err)    }    var v interface{}    err = wsjson.Read(ctx, c, &v)    if err != nil {      panic(err)    }    fmt.Printf("接收到服务端响应: %v\n", v)    c.Close(websocket.StatusNormalClosure, "")}运行结果


服务端


客户端

TheLudGamer 发表于 2022-1-10 21:09

说我看了你这篇文章,看了代码我的头大了。

IT圈老男孩1 发表于 2022-1-10 21:09

我记录的东西,主题跳跃还是蛮大的 这篇偏技术的文章,你能看几段,我就感觉很难得了
页: [1]
查看完整版本: 181. 【go,聊天室】认识 WebSocket