目录

概要

流程图

注册

登陆

发送消息

退出登录

源码分析

api模块

Router.go文件

Hander文件夹

Rpc.go文件

logic模块

Logic.go文件

publish.go文件

Rpc.go文件

Task模块

Subscribe.go文件

Push.go文件

Rpc.go文件

Connect模块

Rpc.go文件

Server.go文件

Websocket.go文件


概要

源码地址:https://github.com/LockGit/gochat

gochat是一个golang编写的开源im系统,同时是一套可扩展的分布式系统,虽然功能并不复杂,但实现了最核心的点对点的消息发送(用户对用户)和点对面的消息发送(用户在房间里发送消息)

gochat的作者已经对系统架构有了详细的解释,本文可以作为其补充,主要添加了流程图和源码级的解释,从另一个角度去理解这套系统

流程图

客户端与服务端、服务端之间均需要通过etcd作为服务发现,这块逻辑在流程图中被隐藏。

注册

  1. 客户端带上uid(或手机号)和登陆密码访问api模块;
  2. Api模块将请求转发给logic模块;
  3. Logic模块判断该用户注册信息的有效性,无误后将用户信息写入mysql数据库中;
  4. Logic模块返回给api模块;
  5. Api模块返回给客户端;

登陆

  1. 客户端带上uid(或手机号)和登陆密码访问api模块;
  2. Api模块将请求转发给logic模块;
  3. Logic模块验证用户的登录密码正确性;
  4. 生成用户唯一的authtoken,并保存在redis中;
  5. Logic模块返回给api模块;
  6. Api模块返回给客户端;
  7. 客户端带上新获取到的authtoken连接某一台connect服务器;
  8. Connect服务将接收到的请求转发给logic模块;
  9. Logic模块验证authtoken的有效性,同时返回用户基本信息(uid),保存用户连接在哪台connect服务器上的信息,如果有用户所在的房间信息也一并存入;
  10. Logic模块返回给connect模块;
  11. 完成客户端和connect模块的长链接;

发送消息

分为用户对用户的消息发送和用户给房间内广播的消息发送

  1. 客户端将要发送的消息传给api模块,给用户发送的消息带上uid,给房间发送的消息带上room_id;
  2. Api模块将请求转发给logic模块;
  3. 如果是给用户发送的消息,logic模块会去询问redis接收用户链接所在的connect服务器;
  4. 将消息打包(room_id, server_id)后放入redis中;
  5. Task模块从redis中拉取消息;
  6. 如果是给用户发送的消息,发送到指定的connect服务器上;如果是给房间发送广播消息,遍历所有的connect服务器;
  7. 通过长链接发送给接收用户

退出登录

  1. 客户端关闭长链接(杀端则通过ping pong超时的方式感知);
  2. Connect模块接收到关闭通知后,告知logic模块;
  3. 删除用户在redis中的信息(authtoken, connect server id),如果用户在房间里,还需要广播房间内所有用户(重走上面的4-7步);
  4. Logic模块返回给connect模块;
  5. Connect模块终端与客户端的长链接;

源码分析

核心模块包括api模块,connect模块,logic模块和task模块,模块间通过rpc的方式进行通信,或者使用redis作为mq进行通信,下面分别进行解释

api模块

它是整个系统的接入模块(另外还有connect模块是和客户端直接连通的)。它的主要作用是接收客户端发送的请求,基本验证后发送给LOGIC模块进行处理。主要功能分为路由和底层rpc调用。

Router.go文件

使用gin网络框架,设置路由规则。同时这个模块会对token进行鉴权,和协议跨域的设置。

Hander文件夹

有push.go和user.go两个文件,分别处理消息推送和用户登陆相关的逻辑。主要的功能是拼凑请求的结构体。

Rpc.go文件

实现了一个调用logic模块的客户端,对路由的方法进行了一次封装。

logic模块

它是处理业务逻辑的核心模块,主要的作用是分发用户的msg

Logic.go文件

主要是启动redis客户端,同时开启rpc服务。

Redis客户端的作用是发送msg,包括to user的msg和to room的msg。

publish.go文件

to user的msg在RedisPublishChannel函数中实现,to room的msg在RedisPublishRoomInfo函数中实现,两个函数均在publish.go文件中。它们的作用均是将消息打包和通过redis的publish方法发送的redis 服务器上,task模块通过subscribe命令拉取消息。向房间内发送的消息除了带上uid外还需要添加room id相关的信息,task模块处理to user的msg和to room的msg的方式也不同。

Rpc.go文件

Logic模块会同时收到api模块和connect模块发来的请求。

Api的请求又分为用户类的请求和消息发送类的请求。用户类的请求包括注册(Register),登陆(Login),鉴权(CheckAuth)和退出登陆(Logout),值得一提的是用户发送的每条消息都会被鉴权,通过token的方式获取到用户的uid。消息发送类的请求包括发送给特定用户的(Push)和发送给房间里所有人的(PushRoom),另外还有获取到某个房间所有用户的接口(GetRoomInfo)。

Connect模块发送的请求主要为通知长链接(作者实现了websocket或tcp两种方式)所在的服务器,这样task模块在分发消息的时候就可以发送到该用户链接所在的服务器上,如果是发送给某个房间的会通过广播的方式进行发送。Connect函数是connect模块建立好长链接后给logic模块发送的注册请求,DisConnect表示用户下线(相应的房间用户信息会重新推送一次)。

Task模块

它的上游是redis服务,通过订阅的方式获取到logic模块发送的消息。它的下游是connect模块,task模块的主要功能是进行消息的分发,将特定用户的消息发送到用户(链接)所在connect服务器上。

Subscribe.go文件

从redis服务器上拉取消息,同时调用task. Push函数。

Push.go文件

Push函数的主要作用是将消息体打包后推送另一组队列中(队列A)。目前只是对于给用户推送使用的这种方式,其实给房间推送消息也应该使用相同的方式。GoPush函数启动若干协程,并行拉取队列组A中的消息,并调用task.pushSingleToConnect函数进行处理。由此可见task服务内部也实现了消息队列缓存的机制,它的好处是避免消息在上游服务(redis)堆积。

Rpc.go文件

InitConnectRpcClient函数实现了和connect模块所有服务器的rpc连接(并没有考虑到下游服务变化的情况)。pushSingleToConnect函数根据消息体中所带的serverId参数,使用对应的rpc客户端访问connect服务。

Connect模块

Connect模块会和三方打交道。一是和客户端,它会保持一个长链接(websocket或tcp实现),当用户收到新的消息后会通过该连接发给客户端;二是和logic模块,任何用户在初始化长链接之初,均会进行一次鉴权校验,识别该token是那个一个用户id,并将该链接放入特定的桶里;三是启动一个rpc server,task模块会通过该server接口推送消息。

Rpc.go文件

InitLogicRpcClient表示启动一个连接logic模块的rpc客户端,Connect函数和DisConnect函数表示使用该客户端获取用户的id。 InitConnectWebsocketRpcServer函数启动一个rpc服务,供task模块调用。PushSingleMsg函数实现了task模块向本服务推送一条给特定用户的消息,PushRoomMsg、PushRoomCount、PushRoomInfo实现了task模块向本服务推送一条给特定房间所有用户的消息。

Server.go文件

Bucket函数表示将特定用户映射到某一个桶里,使用多个桶是为了防止锁的冲突,因为添加房间和添加用户均需要对桶内的数据结构进行加锁处理。writePump函数是向一个链接写消息,它首先会启动一个周期性触发器,定期向客户端发送ping消息;另外它会不停的从该链接对应的通道拉取消息(来自于task模块),一旦有新的消息出现,它会主动发送给客户端。readPump函数实现了读取长链接中的消息,解析消息体中的authtoken,通过Rpc.go文件的Connect函数获取到用户信息,再通过uid映射到特定的bucket中,在该bucket里添加该链接信息。readPump函数同时会读取客户端的pong消息,若超过限制时间未读到,则会关闭链接。

Websocket.go文件

InitWebsocket函数会注册一个路由handler到一个地址上,serveWs函数实际处理每个连接请求。它会先将http协议升级到websocket协议,然后新建一个channel,启动两个协程分别调用Server.go文件中的readPump函数和writePump函数。

gochat源码解析相关推荐

  1. 谷歌BERT预训练源码解析(二):模型构建

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明. 本文链接:https://blog.csdn.net/weixin_39470744/arti ...

  2. 谷歌BERT预训练源码解析(三):训练过程

    目录 前言 源码解析 主函数 自定义模型 遮蔽词预测 下一句预测 规范化数据集 前言 本部分介绍BERT训练过程,BERT模型训练过程是在自己的TPU上进行的,这部分我没做过研究所以不做深入探讨.BE ...

  3. 谷歌BERT预训练源码解析(一):训练数据生成

    目录 预训练源码结构简介 输入输出 源码解析 参数 主函数 创建训练实例 下一句预测&实例生成 随机遮蔽 输出 结果一览 预训练源码结构简介 关于BERT,简单来说,它是一个基于Transfo ...

  4. Gin源码解析和例子——中间件(middleware)

    在<Gin源码解析和例子--路由>一文中,我们已经初识中间件.本文将继续探讨这个技术.(转载请指明出于breaksoftware的csdn博客) Gin的中间件,本质是一个匿名回调函数.这 ...

  5. Colly源码解析——结合例子分析底层实现

    通过<Colly源码解析--框架>分析,我们可以知道Colly执行的主要流程.本文将结合http://go-colly.org上的例子分析一些高级设置的底层实现.(转载请指明出于break ...

  6. libev源码解析——定时器监视器和组织形式

    我们先看下定时器监视器的数据结构.(转载请指明出于breaksoftware的csdn博客) /* invoked after a specific time, repeatable (based o ...

  7. libev源码解析——定时器原理

    本文将回答<libev源码解析--I/O模型>中抛出的两个问题.(转载请指明出于breaksoftware的csdn博客) 对于问题1:为什么backend_poll函数需要指定超时?我们 ...

  8. libev源码解析——I/O模型

    在<libev源码解析--总览>一文中,我们介绍过,libev是一个基于事件的循环库.本文将介绍其和事件及循环之间的关系.(转载请指明出于breaksoftware的csdn博客) 目前i ...

  9. libev源码解析——调度策略

    在<libev源码解析--监视器(watcher)结构和组织形式>中介绍过,监视器分为[2,-2]区间5个等级的优先级.等级为2的监视器最高优,然后依次递减.不区分监视器类型和关联的文件描 ...

最新文章

  1. oracle ORACLE_SID使用上的意义
  2. 腾讯实时音视频开发应用大赛火热开赛,两大主题、60万奖品,等你来拿!
  3. 随机森林算法4种实现方法对比测试:DolphinDB速度最快,XGBoost表现最差
  4. ios 标准 #pragma mark的用法
  5. CVPR 2020 三篇有趣的论文解读
  6. 用观察者模式编写一个可被其他对象拓展复用自定义事件系统
  7. sourcemap总结
  8. mysql计算用户平均下单周期
  9. 1.VBA实现EXCEL中Sheet1的 甲列 相同数值的行对应的乙列的数的和作为Sheet2中丙列中与Sheet1中甲列 相同的行对应的丁列的值...
  10. 【论文】本周论文推荐(迁移学习、阅读理解、对话系统、图神经网络、对抗生成网络等)...
  11. 看斯皮尔伯格大爷,看政治与奥运
  12. jsp高校科研项目管理系统
  13. 如何在VMware Workstation上安装Windows Home Server Beta“ Vail”
  14. matlab潮流计算编程教学,潮流计算 程序_牛拉法潮流计算程序_matlab潮流计算教程...
  15. 经验分享——家校互动系统功能教程资源
  16. 【Linux】 浅谈 NCSI 及其在 Linux 上的实现
  17. matlab coder 4.0,利用MATLAB Coder将MATLAB代码生成C/C++代码
  18. 一个简单的txt分割器
  19. 国际音标 英式音标 美式音标
  20. Ubuntu进入桌面后,左侧菜单栏和窗口菜单栏不见了的解决方法

热门文章

  1. 前端-基础篇-HTML-HTML基本结构
  2. Linux入门到精通
  3. 时钟周期、机器周期、指令周期、总线周期的区别
  4. PMP 备考知识点集锦
  5. oracle 没有锁 ora00054,ORA-00054锁等待问题
  6. execvp函数详解_如何在C / C ++中使用execvp()函数
  7. MySql 全文检索
  8. WDF开发USB设备驱动教程(2)
  9. Android 开发环境搭建
  10. 元末明初为什么会爆发农民起义