前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >使用Go开发WebSocket应用:单房间聊天室

使用Go开发WebSocket应用:单房间聊天室

原创
作者头像
HullQin
发布于 2022-09-29 13:07:34
发布于 2022-09-29 13:07:34
6.6K7
举报
文章被收录于专栏:教你做小游戏教你做小游戏

背景

大家好,我是公众号「线下聚会游戏」作者,开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏。其中的核心技术就是WebSocket,快关注HullQin一起学习吧!

还没学过Go,要先看什么?

建议你花1天时间,看一下Go的原理简介、基础语法。什么教程都可以,知名的教程就行。

至少要明白:各种数据类型,控制流(for、if等)写法,弄懂channel和goroutine,如何加锁。

一定要自己写写goroutine和channel试一下,了解一下基础语法。

此外,还要了解常用包的用法,包括fmt、net/http。

技术选型

面对自己不熟悉的语言和不熟悉的框架,该怎么做技术选型呢?

我告诉你个小技巧,直接在Github上搜索,看Star最多的那个仓库,就可以啦~

image.png
image.png

看吧,我们搜到了gorilla/websocket,star数以显著差异甩开了后面几名。这就没有什么好纠结的了,果断使用它。

新建项目

在使用GoLand时,新建Go Project会有2个选项:

image.png
image.png

我们选用第一个即可。

如果你没有GoLand,也可以手动创建文件夹,在里面新建文件go.mod(我是使用的目前最新稳定版1.18)

代码语言:text
AI代码解释
复制
module echo

go 1.18

安装依赖

代码语言:shell
AI代码解释
复制
go get github.com/gorilla/websocket

拷贝chat代码

gorilla/websocket的官方demo拷贝过来即可,我们慢慢分析:

你需要这4个文件:

  • main.go
  • hub.go
  • client.go
  • index.html

第一步,看主函数

代码语言:go
AI代码解释
复制
func main() {
   flag.Parse()
   hub := newHub()
   go hub.run()
   http.HandleFunc("/", serveHome)
   http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
      serveWs(hub, w, r)
   })
   err := http.ListenAndServe(*addr, nil)
   if err != nil {
      log.Fatal("ListenAndServe: ", err)
   }
}

上篇已经介绍了flaghttp.HandleFunc,这里跟上篇是一模一样的。

这里还开启了一个goroutine,注意它是写在main函数里的,不是写在http.HandleFunc里的。所以不管有多少客户端连接,这个服务只开启了一个goroutine。newHub().run()。我们下一步看newHub(),在hub.go文件中。

再看下注册的2个请求处理函数:

  • serveHome是一个HTTP服务,把html文件返回给请求方(浏览器)。
  • 针对/ws路由,则会调用serveWs,我们下下一步看serveWs做了什么,在clent.go文件中。

第二步,看hub.go

Hub定义和newHub函数定义

代码语言:go
AI代码解释
复制
type Hub struct {
   clients map[*Client]bool
   broadcast chan []byte
   register chan *Client
   unregister chan *Client
}

func newHub() *Hub {
   return &Hub{
      clients:    make(map[*Client]bool),
      register:   make(chan *Client),
      unregister: make(chan *Client),
      broadcast:  make(chan []byte),
   }
}

可以看到newHub只是新建了一个空白的Hub。而1个Hub包含4个东西:

  • clients,保存了每个客户端的引用的Map(其实这个Map的value没有用到,key是客户端的引用,可以当作是其它语言的set)。
  • register,用于注册客户端的channel。每当有客户端建立websocket连接时,通过register,把客户端保存到clients引用中。
  • unregister,用于注销客户端的channel。每当有客户端断开websocket连接时,通过unregister,把客户端引用从clients中删除。
  • broadcast,用于发送广播的channel。把消息存到这个channel后,之后会有其它goroutine遍历clients,把消息发送给所有客户端。

服务开启时启动的goroutine: hub.run()

代码语言:go
AI代码解释
复制
func (h *Hub) run() {
   for {
      select {
      case client := <-h.register:
         h.clients[client] = true
      case client := <-h.unregister:
         if _, ok := h.clients[client]; ok {
            delete(h.clients, client)
            close(client.send)
         }
      case message := <-h.broadcast:
         for client := range h.clients {
            select {
            case client.send <- message:
            default:
               close(client.send)
               delete(h.clients, client)
            }
         }
      }
   }
}

一个死循环:不断从channel读取数据。读取到register,就注册客户端。读取到unregister,就断开客户端连接,删除引用。读取到broadcast,就遍历clients,广播消息(通过把消息写入每个客户端的client.sendchannel中,实现广播),正是下一步要看的逻辑。

下一步,我们看client

第三步,看client.go

Client定义

代码语言:go
AI代码解释
复制
type Client struct {
   hub *Hub
   conn *websocket.Conn
   send chan []byte
}
  • hub: 每个Client客户端保存了Hub的引用。(虽然目前全局只有1个hub,但是为了可扩展性,还是保存一份吧,因为将来会有多hub,下篇文章我们就介绍!)
  • conn: 即跟客户端的websocket连接,通过这个conn可以跟客户端交互(即收发消息)。
  • send: 一个channel,在第二步已经见识到了,broadcast时,就是把消息写入了每个Client的send channel中。通过从这个channel读取消息,发送消息给客户端。

main函数用到的serveWs函数

代码语言:go
AI代码解释
复制
func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
   conn, err := upgrader.Upgrade(w, r, nil)
   if err != nil {
      log.Println(err)
      return
   }
   client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
   client.hub.register <- client

   // Allow collection of memory referenced by the caller by doing all work in
   // new goroutines.
   go client.writePump()
   go client.readPump()
}

在hub中,注册了一下。

随后启动了2个goroutine: client.writePump()client.readPump(),然后这个函数逻辑就结束了。

这2个goroutine,分别用于处理写入消息和读取消息。

client.writePump

代码语言:go
AI代码解释
复制
func (c *Client) writePump() {
   ticker := time.NewTicker(pingPeriod)
   defer func() {
      ticker.Stop()
      c.conn.Close()
   }()
   for {
      select {
      case message, ok := <-c.send:
         c.conn.SetWriteDeadline(time.Now().Add(writeWait))
         if !ok {
            c.conn.WriteMessage(websocket.CloseMessage, []byte{})
            return
         }
         w, err := c.conn.NextWriter(websocket.TextMessage)
         if err != nil {
            return
         }
         w.Write(message)
         if err := w.Close(); err != nil {
            return
         }
      case <-ticker.C:
         c.conn.SetWriteDeadline(time.Now().Add(writeWait))
         if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
            return
         }
      }
   }
}

首先开启了一个ping计时器。会固定周期发送Ping消息给客户端。这是WebSocket协议要求的,参考《RFC6455》。你在浏览器上抓包看不到这个Ping消息。这种方式,可以将没响应的连接清理掉。

然后,这个goroutine,声明了defer执行的逻辑:关闭计时器,关闭连接。

最重要的部分,这个goroutine有个死循环:不断读取client.send这个channel中的数据。只要hub.broadcast给它传了消息,那么就由这个goroutine来处理。c.conn.NextWriterw.Write(message)是真正的发消息的逻辑。

此外,每隔一段时间(定时器设置的时间间隔),服务器都会发送一个Ping给浏览器。浏览器会自动回复一个Pong(不需要客户端开发者关注,客户端开发者通常是JS开发者)。

client.readPump

代码语言:go
AI代码解释
复制
func (c *Client) readPump() {
   defer func() {
      c.hub.unregister <- c
      c.conn.Close()
   }()
   c.conn.SetReadLimit(maxMessageSize)
   c.conn.SetReadDeadline(time.Now().Add(pongWait))
   c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
   for {
      _, 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))
      c.hub.broadcast <- message
   }
}

readPump就是读取消息,收到客户端消息后,就借助hub.broadcast广播出去。

此外,这个goroutine有个重要的任务:关闭连接后,负责hub.unregisterconn.Close

总结!最重要的一个图!

为了帮助大家理解,我绘制了这个图:

image.png
image.png

其中,彩色矩形表示goroutine,彩色线条是各个channel(从A指向B表示,由goroutine A写入数据,由goroutine B读取数据)。

User和Client图中只画了2个,是可以继续增加的。

写在最后

我是HullQin,公众号线下聚会游戏的作者(欢迎关注我,交个朋友)。转发本文前需获得作者HullQin授权。我独立开发了《联机桌游合集》,是个网页,可以很方便的跟朋友联机玩斗地主、五子棋等游戏,不收费无广告。还开发了《Dice Crush》参加Game Jam 2022。喜欢可以关注我噢~我有空了会分享做游戏的相关技术,会在这个专栏里分享:《教你做小游戏》

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
7 条评论
热度
最新
你这代码不是和go-zero-example的chat案例一模一样么,那个案例21年就发出来了,全文也没见你提起呢
你这代码不是和go-zero-example的chat案例一模一样么,那个案例21年就发出来了,全文也没见你提起呢
111举报
提到了 github.com/gorilla/websocket/tree/master/examples/chat
提到了 github.com/gorilla/websocket/tree/master/examples/chat
回复回复点赞举报
请问多房间的时候,怎么存client合适呢
请问多房间的时候,怎么存client合适呢
回复回复点赞举报
,学习新知
,学习新知
回复回复点赞举报
来看看
来看看
回复回复点赞举报
简称用Go开房😜😝
简称用Go开房😜😝
回复回复点赞举报
666 学会了
666 学会了
回复回复点赞举报
推荐阅读
编辑精选文章
换一批
[Go WebSocket] 多房间的聊天室(三)自动清理无人房间
第一篇文章:《为什么我选用Go重构Python版本的WebSocket服务?》,介绍了我的目标。
用户6256742
2024/06/01
1370
[Go WebSocket] 多房间的聊天室(三)自动清理无人房间
Go WebSocket开发与测试实践【gorilla/websocket】
前文提到Go WebSocket开发与测试实践【/net/websocket】,今天分享一下另外一个Go WebSocket实现库gorilla/websocket,这个类库功能这也是我找到资料最多的实现方式。gorilla/websocket得到官方认可的库,如果大家使用Go语言做WebSocket的开发测试的话,我建议使用gorilla/websocket。
FunTester
2021/12/02
2.2K0
探索 Golang 云原生游戏服务器开发,根据官方示例实战 Gorilla WebSocket 的用法
服务器应用程序定义两种类型,Client 和 Hub。服务器为每个 websocket 连接创建一个 Client 类型的实例。 Client 充当 websocket 连接和 Hub 类型的单个实例之间的中介。Hub 维护一组注册的客户端,并向客户端广播消息。
为少
2021/05/27
1.8K0
探索 Golang 云原生游戏服务器开发,根据官方示例实战 Gorilla WebSocket 的用法
beego聊天室的生成
        username := this.GetString("username")
公众号-利志分享
2022/04/25
3390
gin websocket 一对一聊天
依赖包 github.com/gin-gonic/gin github.com/gorilla/websocket 代码 创建ws/ws.go package ws import ( "encoding/json" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" "log" "net/http" ) // ClientManager is a websocket manager type Clie
孤烟
2020/09/27
1.6K0
基于websocket单台机器支持百万连接分布式聊天(IM)系统
使用golang实现websocket通讯,单机可以支持百万连接,使用gin框架、nginx负载、可以水平部署、程序内部相互通讯、使用grpc通讯协议。
link1st
2019/09/19
7.5K0
基于websocket单台机器支持百万连接分布式聊天(IM)系统
go进阶-GO创建web服务+websocket详解
go提供了一系列用于创建web服务器的标准,而非常简单。只需要调用net/http包中的ListenAndServe函数并传入网络地址和负责处理的处理器就ok了。net/http库实现了整套的http服务中的客户端、服务端接口,可以基于此轻松的发起HTTP请求或者对外提供HTTP服务。
黄规速
2024/05/24
2.4K0
go进阶-GO创建web服务+websocket详解
给Go的Gin web框架增加 WebSocket 功能
Gin 是一个 go 的 web 框架,它具有轻量级,高性能,运行速度快,分组的路由器,良好的崩溃捕获和错误处理,非常好的支持中间件,rest api和json。
杨永贞
2021/01/18
8.2K0
.NET Core 基于Websocket的在线聊天室
我们在传统的客户端程序要实现实时双工通讯第一想到的技术就是socket通讯,但是在web体系是用不了socket通讯技术的,因为http被设计成无状态,每次跟服务器通讯完成后就会断开连接。 在没有websocket之前web系统如果要做双工通讯往往使用http long polling技术。http long polling 每次往服务器发送请求后,服务端不会立刻返回信息来结束请求,而是一直挂着直到有数据需要返回,或者等待超时了才会返回。客户端在结束上一次请求后立刻再发送一次请求,如此反复。http long polling虽然能实现web系统的双工通讯,但是有个很大的问题,就是基于http协议客户端每次发送请求都需要携带巨大的头部。在并发交互少量数据的时候非常不划算,对服务器资源的消耗也是巨大的。 websocket很好的改善了以上问题。它基于tcp重新设计了一套协议,同时又兼容http,默认跟http一样使用80/443端口。websocket链接建立本质上就是一次http请求,直接使用http协议的upgrade头来标识这是一次websocket请求,服务端回复101状态码表示“握手”成功。
李明成
2020/03/18
1.1K0
WebSocket协议-源码分析
本文是WebSocket系列文章的第3篇,从源码角度理解WebSocket是如何实现的。分析的是gorilla websocket,即WebSocket协议-实战中服务端使用的WebSocket库。
数据小冰
2024/07/04
2080
WebSocket协议-源码分析
Go语言实现Websocket服务端
如果想多个协程处理,handleMessages()调用多次即可,是不会导致处理信息重复的。
码客说
2024/03/29
1700
石墨文档 Websocket 百万长连接技术实践
在石墨文档的部分业务中,例如文档分享、评论、幻灯片演示和文档表格跟随等场景,涉及到多客户端数据同步和服务端批量数据推送的需求,一般的 HTTP 协议无法满足服务端主动 Push 数据的场景,因此选择采用 WebSocket 方案进行业务开发。
肉眼品世界
2021/12/09
7430
石墨文档 Websocket 百万长连接技术实践
Go实现基于WebSocket的弹幕服务
1、数据更新频率低,则大多数请求是无效的 2、在线用户量多,则服务端的查询负载高 3、定时轮询拉取,实时性低
Clive
2018/12/06
1.8K0
尝试用Go goroutine实现一个简单的聊天服务
我们用 Go 并发来实现一个聊天服务器,这个程序可以让一些用户通过服务器向其它所有用户广播文本消息。
架构精进之路
2023/08/18
2050
尝试用Go goroutine实现一个简单的聊天服务
Golang之chan/goroutine
最近在team内部培训golang,目标是看看golang能否被C工程师快速掌握。我定了个一个月,共计20小时的培训计划,首先花10个小时(两周,每天1小时)让大家掌握golang的基本要素,能写一些入门级的程序,之后再花两周时间做一个1000行代码规模的Proof of concept项目。为了能在培训的slides上直接运行go code,我做了个简单的 coderunnerd ,可以接受websocket传过来的code,编译运行再把stdout返回给websocket,为了更清晰地说明gorouti
李海彬
2018/03/23
9900
go语言+vue实现实时聊天
执行 source ~/.zshrc 或 source ~/.bash_profile 生效
iwhao
2024/07/03
1270
golang实现聊天室
package main import ( "fmt" "io" "net" "runtime" "sync" ) //创建读写锁,在高并发时保护公共区的数据,不会出现数据混乱 var rwMutex sync.RWMutex //创建全局的Client结构体 type Client struct { name string //初始name与addr一样 addr string C chan string } //创建全局在线用户列表 var onlineMap = m
用户8785253
2022/08/29
1.1K0
golang实现聊天室
完结篇-手把手带大家用go开发一个匿名在线聊天室
上一篇文章我们讲到了,join页面前端和后端建立了websocket连接,在建立连接的同时我们需要做其他几件事,首先启动一个检测心跳的服务,然后需要启动一个管理消息广播的服务。为了理解这个文章的主要内容,我先画了个图,方面大家了解接下来要干什么。
公众号-利志分享
2022/04/25
5400
完结篇-手把手带大家用go开发一个匿名在线聊天室
python实现单工、半双工、全双工聊天室
半双工实现是连接建立以后,服务器等待客户端发送消息,客户端发送消息后等待接收服务器,这样一来一回循环往复下去。直到出现quit,关闭连接。
Ewdager
2020/07/14
1.7K0
WebSocket原来还能这么玩
WebSocket是一种在单个TCP连接上进行全双工通信的协议。该协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。这种通信方式使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,客户端和服务器只需要完成一次握手,之后两者之间就可以直接创建持久性的连接,并进行双向数据传输。
闫同学
2024/04/21
3021
WebSocket原来还能这么玩
相关推荐
[Go WebSocket] 多房间的聊天室(三)自动清理无人房间
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档