4.1 Server v0.3 组件图

gochat-server-uml

由上图可以看出,服务器程序在逻辑上主要由存储(storage)、业务逻辑(biz)和程序入口(main)三个部分组成。

数据存储

storage 模块负责存储用户帐号和消息数据,数据会写入 sqlite 存储文件。其他程序模块可通过 storage 模块读写 Account 和 Message 表。Message 表只是临时存储用户的未读消息,在该用户登录前会一直存储在表中,当用户登录后,服务器会自动把用户未读消息发给客户端,并从表中删除。

业务逻辑

biz 模块主要负责处理客户端请求并返回响应数据,主要由注册(signup)、登录(signin)、验证 token(val-token)、获取用户列表(users)、接收消息(recv-msg)、登出(signout)和 ping组成。所有的以上具体 struct 都实现了 biz_i 接口,并且都嵌入了 base_t 结构体,而 base_t 内部封装了 poster 和 push 组件,分别用来给客户端发送响应(response)数据包和推送(push)数据包。

其中 signup、signin 和 val-token 三个组件涉及到登录后 token 的生成与验证,需要调用 auth 组件。

程序入口

main 模块是程序执行入口,第一步会注册信号监听处理器(signalHandler),当进程收到中断(Interupt)或 Kill 信号时可以清理资源并退出进程。

第二步会启动独立协程 handlePush,该协程会收集并保存所有已建立的客户端连接,并会接收来自其他模块的 push。

第三步进入 for 循环,不断接受(Accept)新连接,并为新连接启动独立协程 handleConn,而主线程会阻塞在 Accept 调用上直到有新的连接进来。

现在,我们把目光投向刚刚为新连接启动的协程 handleConn。为了能够独立的接收与发送数据包,协程 handleConn 内部会再启动一个独立协程 recvFrom,接收来自客户端的请求数据包,并调用 biz 模块的不同组件实现具体的业务逻辑。然后协程 handleConn 会调用 sendTo 函数并一直阻塞直到连接断开。sendTo 函数内部会监听 3 个通道(channel)。

  • biz 模块会通过 poster 发送响应数据包,该数据包会被发往第 1 个通道,然后 sendTo 会把该响应数据包发往客户端。

  • biz 模块还会通过 push 组件发送推送数据到协程 handlePush,而 handlePush 协程通过通道收到该推送后,会转发给 sandTo 监听的第 2 个通道,最后由 sandTo 把该推送数据包发往客户端。

  • sendTo 中监听的第 3 个通道是一个计时器(Ticker),每隔 100ms 会想通道写入当前时间。可以实现每隔 100ms 读取 storage,检查当前登录用户的未读消息,并把未读消息数据包发往客户端。