前言

go-zero 开源之后,非常多的用户询问是否可以支持以及什么时候支持 websocket,终于在 v1.1.6 里面我们从框架层面让 websocket 的支持落地了,下面我们就以 chat 作为一个示例来讲解如何用 go-zero 来实现一个 websocket 服务。

整体设计

我们以 zero-example 中的 chat 聊天室为例来一步步一讲解 websocket 的实现,分为如下几个部分:

  1. 多客户端接入
  2. 消息广播
  3. 客户端的及时上线下线
  4. 全双工通信【客户端本身是发送端,也是接收端】

先放一张图,大致的数据传输:

中间有个 select loop 就是整个 chatengine。首先要支撑双方通信:

  • 得有一个交流数据的管道。客户端只管从 管道 读取/输送数据;
  • 客户端在线情况。不能说你下线了,还往你那传输数据;

数据流

数据流是 engine 的主要功能,先不急着看代码,我们先想 client 怎么接入并被 engine 感知:

  1. 首先是从前端发 websocket 请求;
  2. 建立连接;准备接收/发送通道;
  3. 注册到 engine

// HTML 操作 {js}
if (window["WebSocket"]) {
conn = new WebSocket("ws://" + document.location.host + "/ws");
conn.onclose = function (evt) {
var item = document.createElement("div");
item.innerHTML = "<b>Connection closed.</b>";
appendLog(item);
};
...
} // 路由
engine.AddRoute(rest.Route{
Method: http.MethodGet,
Path: "/ws",
Handler: func(w http.ResponseWriter, r *http.Request) {
internal.ServeWs(hub, w, r)
},
}) // 接入逻辑
func ServeWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
// 将http请求升级为websocket
conn, err := upgrader.Upgrade(w, r, nil)
...
// 构建client:hub{engine}, con{websocker conn}, send{channel buff}
client := &Client{
hub: hub,
conn: conn,
send: make(chan []byte, bufSize),
}
client.hub.register <- client
// 开始客户端双工的通信,接收和写入数据
go client.writePump()
go client.readPump()
}

这样,新接入的 client 就被加入到 注册 通道中。

hub engine

发出了 注册 的动作,engine 会怎么处理呢?

type Hub struct {
clients map[*Client]bool // 上线clients
broadcast chan []byte // 客户端发送的消息 ->广播给其他的客户端
register chan *Client // 注册channel,接收注册msg
unregister chan *Client // 下线channel
} func (h *Hub) Run() {
for {
select {
// 注册channel:存放到注册表中,数据流也就在这些client中发生
case client := <-h.register:
h.clients[client] = true
// 下线channel:从注册表里面删除
case client := <-h.unregister:
if _, ok := h.clients[client]; ok {
delete(h.clients, client)
close(client.send)
}
// 广播消息:发送给注册表中的client中,send接收到并显示到client上
case message := <-h.broadcast:
for client := range h.clients {
select {
case client.send <- message:
default:
close(client.send)
delete(h.clients, client)
}
}
}
}
}
  1. 接收注册消息 -> 加入全局注册表
  2. 如果 engine.broadcast 接收到,会将 msg 传递给 注册表client.sendChan

这样从 HTML -> client -> hub -> other client 的整个数据流就清晰了。

广播数据

上面说到 engine.broadcast 接收到数据,那从页面开始,数据是怎么发送到这?

func (c *Client) readPump() {
...
for {
// 1
_, message, err := c.conn.ReadMessage()
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("error: %v", err)
}
break
}
message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
// 2.
c.hub.broadcast <- message
}
}
  1. conn 中不断读取 msg【页面点击后传递】
  2. msg 传入 engine.broadcast,从而广播到其他的 client
  3. 当出现发送异常或者是超时,异常退出时,会触发下线 client

同时要知道,此时发送消息的 client 不止有一个,可能会有很多个。那发送到其他client,client 从自己的 send channel 中读取消息即可:

func (c *Client) writePump() {
// 写超时控制
ticker := time.NewTicker(pingPeriod)
...
for {
select {
case message, ok := <-c.send:
// 当接收消息写入时,延长写超时时间。
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
...
w, err := c.conn.NextWriter(websocket.TextMessage)
...
w.Write(message) // 依次读取 send 中消息,并write
n := len(c.send)
for i := 0; i < n; i++ {
w.Write(newline)
w.Write(<-c.send)
}
...
case <-ticker.C:
c.conn.SetWriteDeadline(time.Now().Add(writeWait))
...
}
}
}

上面也说了,send 有来自各自客户端中发送的msg:所以当检测到 send 有数据,就不断接收消息并写入当前 client;同时当写超时,会检测websocket长连接是否还存活,存活则继续读 send chan,断开则直接返回。

完整示例代码

https://github.com/zeromicro/zero-examples/tree/main/chat

总结

本篇文章从使用上介绍如何结合 go-zero 开始你的 websocket 项目,开发者可以按照自己的需求改造。

关于 go-zero 更多的设计和实现文章,可以持续关注我们。

https://github.com/tal-tech/go-zero

欢迎使用 go-zero 并 star 支持我们!

用 Go + WebSocket 快速实现一个 chat 服务的更多相关文章

  1. 分享在Linux下使用OSGi.NET插件框架快速实现一个分布式服务集群的方法

    在这篇文章我分享了如何使用分层与模块化的方法来设计一个分布式服务集群.这个分布式服务集群是基于DynamicProxy.WCF和OSGi.NET插件框架实现的.我将从设计思路.目标和实现三方面来描述. ...

  2. 通过express快速搭建一个node服务

    Node.js 是一个基于Chrome JavaScript 运行时建立的一个平台.可以理解为是运行在服务端的 JavaScript.如果你是一个前端程序员,不太擅长像PHP.Python或Ruby等 ...

  3. jquery+flask+keras+nsfw快速搭建一个简易鉴黄工具

    1. demo 地址:http://www.huchengchun.com:8127/porn_classification 接口说明: 1. http://www.huchengchun.com:8 ...

  4. 有了 serverless,前端也可以快速开发一个 Puppeteer 网页截图服务

    更多云原生技术资讯可关注阿里巴巴云原生技术圈. Puppeteer 是什么? puppeteer 官网的介绍如下: Puppeteer is a Node library which provides ...

  5. 用 Go 快速开发一个 RESTful API 服务

    何时使用单体 RESTful 服务 对于很多初创公司来说,业务的早期我们更应该关注于业务价值的交付,而单体服务具有架构简单,部署简单,开发成本低等优点,可以帮助我们快速实现产品需求.我们在使用单体服务 ...

  6. 使用MicroService4Net 快速创建一个简单的微服务

    “微服务架构(Microservice Architecture)”一词在过去几年里广泛的传播,它用于描述一种设计应用程序的特别方式,作为一套独立可部署的服务.目前,这种架构方式还没有准确的定义,但是 ...

  7. 适合新手:从零开发一个IM服务端(基于Netty,有完整源码)

    本文由“yuanrw”分享,博客:juejin.im/user/5cefab8451882510eb758606,收录时内容有改动和修订. 0.引言 站长提示:本文适合IM新手阅读,但最好有一定的网络 ...

  8. 基于websocket实现的一个简单的聊天室

    本文是基于websocket写的一个简单的聊天室的例子,可以实现简单的群聊和私聊.是基于websocket的注解方式编写的.(有一个小的缺陷,如果用户名是中文,会乱码,不知如何处理,如有人知道,请告知 ...

  9. 瞧一瞧,看一看呐,用MVC+EF快速弄出一个CRUD,一行代码都不用写,真的一行代码都不用写!!!!

    瞧一瞧,看一看呐用MVC+EF快速弄出一个CRUD,一行代码都不用写,真的一行代码都不用写!!!! 现在要写的呢就是,用MVC和EF弄出一个CRUD四个页面和一个列表页面的一个快速DEMO,当然是在不 ...

随机推荐

  1. VirtualBox All in One

    VirtualBox All in One 虚拟机 / VM / Virtual Machine x86 and AMD64/Intel64 VirtualBox is a powerful x86 ...

  2. how to read the 10th line of a text using shell script

    how to read the 10th line of a text using shell script shell script / bash script question https://l ...

  3. svg & stroke & style & class

    svg & stroke & style & class svg selected style methods style class, !important fill, st ...

  4. nasm astricmp函数 x86

    xxx.asm: %define p1 ebp+8 %define p2 ebp+12 %define p3 ebp+16 section .text global dllmain export as ...

  5. vue 使用stylus

    λ yarn add stylus stylus-loader --dev <style scoped lang="stylus"> div color #ff4488 ...

  6. NGK每日快讯2021.1.29日NGK公链第87期官方快讯!

  7. Techme INC解读基因魔剪,带来的是机遇还是风险?

    10月7日,诺贝尔化学奖颁给了法国美国生物学家Jennifer Doudna和生物化学家Emmanuelle Charpentier,以表彰她们对新一代基因技术CRISPR的贡献,全网沸腾. CRIS ...

  8. go-admin在线开发平台学习-2[程序结构分析]

    紧接着上一篇,本文我们对go-admin下载后的源码进行分析. 首先对项目所使用的第三方库进行分析,了解作者使用的库是否是通用的官方库可以有助于我们更快地阅读程序.接着对项目的main()方法进行分析 ...

  9. JDK环境解析,安装和目的

    目录 1. JDK环境解析 1.1 JVM 1.2 JRE 1.3 JDK 2. JDK安装 2.1 为什么使用JDK8 2.1.1 更新 2.1.2 稳定 2.1.3 需求 2.2 安装JDK 2. ...

  10. ElasticSearch 文档及操作

    公号:码农充电站pro 主页:https://codeshellme.github.io 本节介绍 ES 文档,索引及其基本操作. 1,ES 中的文档 在 ES 中,文档(Document)是可搜索数 ...