gochat源码解析

目录

 

概要

流程图

注册

登陆

发送消息

退出登录

源码分析

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函数。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值