Go语言实现即时通讯-下(后端)

简介: Go 语言打造高并发 web 即时聊天应用,主要实现功能:发送文字、表情包、图片、语音和群聊。

Go 语言打造高并发 web 即时聊天应用,主要实现功能:发送文字、表情包、图片、语音和群聊。

Go 后端逻辑

后端核心在于形成 用户id(userid) 和 Node 的映射关系。每个用户都有自己的 Node 节点。

Node :

Node 结构体,包含

  • 此用户的长连接,Conn,类型为建立的 websocket 连接,我们通过这个连接跟用户进行信息的交互。
  • 消息队列,DataQueue,类型为 channel ,我们将要发送的消息写入到此channel 中,后续监听此channel,如果有数据读取到,就通过上面的连接发送出去。
  • 群聊集合,GroupSets,类型我们选择使用 Set ,用来存储用户所加入的群聊 id。

然后将所有用户通过 userid 和 Node 关联起来,后续我们可以通过 userid 来找到用户的 Node。

//本核心在于形成userid和Node的映射关系
type Node struct {
  Conn *websocket.Conn
  //并行转串行
  DataQueue chan []byte
  GroupSets set.Interface
}
//映射关系表
var clientMap map[int64]*Node = make(map[int64]*Node, 0)
复制代码

Set 库

Set 是 Golang 中一个基本且简单的、基于散列的 Set 数据结构实现,提供通用集合数据结构的线程安全和非线程安全实现。此处我们不做详细讲解,详情可查看官方文档:

文档:gopkg.in/fatih/set.v…

gorilla/websocket

我们使用 gorilla/websocket 来实现 websocket

1. 升级请求

websocket 是由 http 升级而来,前端请求首先会发送附带 Upgrade 请求头的 Http 请求,所以我们需要在处理 Http 请求时拦截请求并判断其是否为 websocket 升级请求,如果是则调用Upgrader实例来升级请求。

var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin:     checkOrigin,
}
func checkOrigin(r *http.Request) bool {
    return true
}
复制代码

然后调用 Upgrade 方法升级为websocket连接,并返回一个conn实例,之后的发送接收操作都是由 conn 完成,其类型为 websocket.Conn。

2. 向客户端发送消息

使用 WriteMessage(messageType int, data []byte),参数1为消息类型,参数2为消息内容。

示例:

func sendproc(node *Node) {
  for {
    select {
    //从 DataQueue 中读取消息,如果读到则将消息发送给客户端
    case data := <-node.DataQueue:
      //向客户端发送消息WriteMessage(messageType int, data []byte),参数1为消息类型,参数2为消息内容
      err := node.Conn.WriteMessage(websocket.TextMessage, data)
      if err != nil {
        log.Println(err.Error())
        return
      }
    }
  }
}
复制代码

3. 接受客户端消息

使用 ReadMessage(),该操作会阻塞线程,所以建议运行在其他协程上。 该函数有三个返回值分别是,接收消息类型、接收消息内容、发生的错误,正常执行时错误为 nil,一旦连接关闭返回值类型为-1可用来终止读操作。

示例:

func recvproc(node *Node) {
  for {
    //接受客户端消息ReadMessage(),作会阻塞线程所以建议运行在其他协程上。
    //返回值:接收消息类型、接收消息内容、发生的错误。
    _, data, err := node.Conn.ReadMessage()
    if err != nil {
      log.Println(err.Error())
      return
    }
    log.Printf("recv[ws]<=%s\n", data)
    //调度消息给目标用户
    dispatch(data)
  }
}
复制代码

上面示例中,有一个 dispatch 函数,用来将消息转发给对应的目标用户。此函数接收一个参数 data,为前端发送过来的数据,数据包含 消息来源 userid ,目标用户 dstid,如:

{userid:2,dstid:3,cate:1,media:1,content:"hello"}  
复制代码

我们只需要通过 dstid 找到对应的 Node,然后使用 Node 下的 Conn 链接进行发送,目标用户便可收到消息。

//调度逻辑处理
func dispatch(data []byte) {
  //解析data为message
  msg := Message{}
  err := json.Unmarshal(data, &msg)
  if err != nil {
    log.Println(err.Error())
    return
  }
  _ = recordService.Record(msg.Userid, msg.Dstid, msg.Cate, data)
  //根据cate对逻辑进行处理
  switch msg.Cate {
  case CMD_SINGLE_MSG: //单聊
    sendMsg(msg.Dstid, data)
  case CMD_ROOM_MSG:  //群聊
    //群聊转发逻辑
    for userId, v := range clientMap {
      if v.GroupSets.Has(msg.Dstid) {
        //自己排除,不发送
        if msg.Userid != userId {
          v.DataQueue <- data
        }
      }
    }
  case CMD_HEART:
    //一般啥都不做
  }
}
复制代码

打包部署

1. 编译为 windows 程序

build.bat

rd /s/q release
md release
::go build -ldflags "-H windowsgui" -o chat.exe
go build -o chat.exe
MOVE chat.exe release\
COPY favicon.ico release\favicon.ico
XCOPY asset\*.* release\asset\  /s /e
XCOPY view\*.* release\view\  /s /e
XCOPY config\*.* release\config\  /s /e
复制代码
  • RD : 删除文件夹(remove directory)的命令
  • /S : 表示除目录本身外,还将删除指定目录下的所有子目录和文件。用于删除目录树。
  • /Q : 安静模式,带/S删除目录树时不要求确认。
  • -ldflags "-H windowsgui" : 将输出打印到命令窗口
  • COPY : 复制文件
  • XCOPY : 复制文件和目录树
  • COPY 不能复制文件夹,当需要复制文件夹的时候使用 XCOPY
  • /S 复制目录和子目录,除了空的;/E 复制目录和子目录,包括空的。
  • 选用/S时对源目录下及其子目录下的所有文件进行COPY。除非指定/E参数,否则/S不会拷贝空目录。

2. 编译为 linux 程序

build.sh

#!/bin/sh
rm -rf ./release
mkdir  release
go build -o chat
chmod +x ./chat
cp chat ./release/
cp favicon.ico ./release/
cp -arf ./asset ./release/
cp -arf ./view ./release/
cp -arf ./config ./release/
复制代码
  • cp : 复制文件或目录
  • -a : 此选项通常在复制目录时使用,它保留链接、文件属性,并复制目录下的所有内容。其作用等于dpR参数组合。
  • -r : 若给出的源文件是一个目录文件,此时将复制该目录下所有的子目录和文件。
  • -f : 覆盖已经存在的目标文件而不给出提示。

3. 访问

http://127.0.0.1:8088/chat.html?id=1

id 为用户 userid 。


image.png

4. 源码

整套源码已上传到云仓库:

5. 部分截图

image.png

image.png

image.png

相关文章
|
8天前
|
JSON 中间件 Go
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
本文详细介绍了如何在Go项目中集成并配置Zap日志库。首先通过`go get -u go.uber.org/zap`命令安装Zap,接着展示了`Logger`与`Sugared Logger`两种日志记录器的基本用法。随后深入探讨了Zap的高级配置,包括如何将日志输出至文件、调整时间格式、记录调用者信息以及日志分割等。最后,文章演示了如何在gin框架中集成Zap,通过自定义中间件实现了日志记录和异常恢复功能。通过这些步骤,读者可以掌握Zap在实际项目中的应用与定制方法
go语言后端开发学习(四) —— 在go项目中使用Zap日志库
|
1天前
|
安全 Java Go
探索Go语言在高并发环境中的优势
在当今的技术环境中,高并发处理能力成为评估编程语言性能的关键因素之一。Go语言(Golang),作为Google开发的一种编程语言,以其独特的并发处理模型和高效的性能赢得了广泛关注。本文将深入探讨Go语言在高并发环境中的优势,尤其是其goroutine和channel机制如何简化并发编程,提升系统的响应速度和稳定性。通过具体的案例分析和性能对比,本文揭示了Go语言在实际应用中的高效性,并为开发者在选择合适技术栈时提供参考。
|
5天前
|
运维 Kubernetes Go
"解锁K8s二开新姿势!client-go:你不可不知的Go语言神器,让Kubernetes集群管理如虎添翼,秒变运维大神!"
【8月更文挑战第14天】随着云原生技术的发展,Kubernetes (K8s) 成为容器编排的首选。client-go作为K8s的官方Go语言客户端库,通过封装RESTful API,使开发者能便捷地管理集群资源,如Pods和服务。本文介绍client-go基本概念、使用方法及自定义操作。涵盖ClientSet、DynamicClient等客户端实现,以及lister、informer等组件,通过示例展示如何列出集群中的所有Pods。client-go的强大功能助力高效开发和运维。
22 1
|
5天前
|
SQL 关系型数据库 MySQL
Go语言中使用 sqlx 来操作 MySQL
Go语言因其高效的性能和简洁的语法而受到开发者们的欢迎。在开发过程中,数据库操作不可或缺。虽然Go的标准库提供了`database/sql`包支持数据库操作,但使用起来稍显复杂。为此,`sqlx`应运而生,作为`database/sql`的扩展库,它简化了许多常见的数据库任务。本文介绍如何使用`sqlx`包操作MySQL数据库,包括安装所需的包、连接数据库、创建表、插入/查询/更新/删除数据等操作,并展示了如何利用命名参数来进一步简化代码。通过`sqlx`,开发者可以更加高效且简洁地完成数据库交互任务。
13 1
|
5天前
|
算法 NoSQL 中间件
go语言后端开发学习(六) ——基于雪花算法生成用户ID
本文介绍了分布式ID生成中的Snowflake(雪花)算法。为解决用户ID安全性与唯一性问题,Snowflake算法生成的ID具备全局唯一性、递增性、高可用性和高性能性等特点。64位ID由符号位(固定为0)、41位时间戳、10位标识位(含数据中心与机器ID)及12位序列号组成。面对ID重复风险,可通过预分配、动态或统一分配标识位解决。Go语言实现示例展示了如何使用第三方包`sonyflake`生成ID,确保不同节点产生的ID始终唯一。
go语言后端开发学习(六) ——基于雪花算法生成用户ID
|
6天前
|
JSON 缓存 监控
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
Viper 是一个强大的 Go 语言配置管理库,适用于各类应用,包括 Twelve-Factor Apps。相比仅支持 `.ini` 格式的 `go-ini`,Viper 支持更多配置格式如 JSON、TOML、YAML
go语言后端开发学习(五)——如何在项目中使用Viper来配置环境
|
7天前
|
安全 Go API
go语言中的Atomic操作与sema锁
在并发编程中,确保数据一致性和程序正确性是关键挑战。Go语言通过协程和通道提供强大支持,但在需精细控制资源访问时,Atomic操作和sema锁变得至关重要。Atomic操作确保多协程环境下对共享资源的访问是不可分割的,如`sync/atomic`包中的`AddInt32`等函数,底层利用硬件锁机制实现。sema锁(信号量锁)控制并发协程数量,其核心是一个uint32值,当大于零时通过CAS操作实现锁的获取与释放;当为零时,sema锁管理协程休眠队列。这两种机制共同保障了Go语言并发环境下的数据完整性和程序稳定性。
|
3天前
|
NoSQL Go Redis
Go语言中如何扫描Redis中大量的key
在Redis中,遍历大量键时直接使用`KEYS`命令会导致性能瓶颈,因为它会一次性返回所有匹配的键,可能阻塞Redis并影响服务稳定性。为解决此问题,Redis提供了`SCAN`命令来分批迭代键,避免一次性加载过多数据。本文通过两个Go语言示例演示如何使用`SCAN`命令:第一个示例展示了基本的手动迭代方式;第二个示例则利用`Iterator`简化迭代过程。这两种方法均有效地避免了`KEYS`命令的性能问题,并提高了遍历Redis键的效率。
9 0
|
4天前
|
监控 Serverless Go
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
Golang 开发函数计算问题之Go 语言中切片扩容时需要拷贝原数组中的数据如何解决
|
4天前
|
关系型数据库 MySQL 数据库连接
Go语言中使用sqlx来操作事务
在应用中,数据库事务保证操作的ACID特性至关重要。`github.com/jmoiron/sqlx`简化了数据库操作。首先安装SQLX和MySQL驱动:`go get github.com/jmoiron/sqlx`和`go get github.com/go-sql-driver/mysql`。导入所需的包后,创建数据库连接并使用`Beginx()`方法开始事务。通过`tx.Commit()`提交或`tx.Rollback()`回滚事务以确保数据一致性和完整性。
8 0