当前位置:首页 > 资讯 > 正文

TeamTalk源码分析(五) —— 服务器端msg_server源码分析

TeamTalk源码分析(五) —— 服务器端msg_server源码分析

在分析msg_server的源码之前,我们先简单地回顾一下msg_server在整个服务器系统中的位置和作用:

 

各个服务程序的作用描述如下:

 

  • LoginServer (C++): 负载均衡服务器,分配一个负载小的MsgServer给客户端使用
  • MsgServer (C++): 消息服务器,提供客户端大部分信令处理功能,包括私人聊天、群组聊天等
  • RouteServer (C++): 路由服务器,为登录在不同MsgServer的用户提供消息转发功能
  • FileServer (C++): 文件服务器,提供客户端之间得文件传输服务,支持在线以及离线文件传输
  • MsfsServer (C++): 图片存储服务器,提供头像,图片传输中的图片存储服务
  • DBProxy (C++): 数据库代理服务器,提供mysql以及redis的访问服务,屏蔽其他服务器与mysql与redis的直接交互
  • HttpMsgServer(C++) :对外接口服务器,提供对外接口功能。(目前只是框架)
  • PushServer(C++): 消息推送服务器,提供IOS系统消息推送。(IOS消息推送必须走apns)

 

从上面的介绍中,我们可以看出TeamTalk是支持分布式部署的一套聊天服务器程序,通过分布式部署可以实现分流和支持高数量的用户同时在线。msg_server是整个服务体系的核心系统,可以部署多个,不同的用户可以登录不同的msg_server。这套体系有如下几大亮点:

 

1. login_server可以根据当前各个msg_server上在线用户数量,来决定一个新用户登录到哪个msg_server,从而实现了负载平衡;

2. route_server可以将登录在不同的msg_server上的用户的聊天消息发给目标用户;

3. 通过单独的一个数据库操作服务器db_proxy_server,避免了msg_server直接操作数据库,将数据库操作的入口封装起来。

 

在前一篇文章《TeamTalk源码分析(四) —— 服务器端db_proxy_server源码分析》中,我介绍了每个服务如何接收连接、读取数据并解包、以及组装数据包发包的操作,这篇文章我将介绍作为客户端,一个服务如何连接另外一个服务。这里msg_server在启动时会同时连接db_proxy_server,login_server,file_server,route_server,push_server。在msg_server服务main函数里面有如下初始化调用:

 


其中每个连接服务的流程都是一样的。我们这里以第一个连接file_server为例:

 

 

 


模板函数serv_init展开参数后实际上是调用CFileServConn->Connect(),我们看这个函数的调用:

 

 

 

 

在这个函数里面创建连接socket,将该socket加入全局map g_file_server_conn_map中保存,map的key是socket句柄值,值是当前连接对象CFileServConn的指针。注意这里设置了回调函数imconn_callback。我们来看netlib_connect()实际连接的代码:

 

 


注意这里有以下几点:

 

1. 将socket设置成非阻塞的。这样如果底层连接函数connect()不能立马完成,connect会立刻返回。

2. 将socket的状态设置成SOCKET_STATE_CONNECTING。

3. AddBaseSocket(this)将该socket加入一个全局map中。

4. 关注该socket的所有事件(SOCKET_ALL)。

 

 

 

 

 

 

因为socket是非阻塞,所以connect可能没连接成功,也会立即返回。那连接成功以后,我们如何得知呢?还记得上一篇文章中介绍的主线程的消息泵吗?TeamTalk每个服务的主线程都有一个这样的消息泵:

 


当socket连接成功以后,该socket立马会变的可写。此时会触发第5步中的可写事件:

 

 


由于该socket的状态是SOCKET_STATE_CONNECTING,会走第一个if分支。在不出错的情况下,以参数NETLIB_MSG_CONFIRM调用之前设置的回调函数imconn_callback。

 

 


这次走pConn->OnConfirm();分支,由于pConn实际是CImConn的子类对象,根据C++多态性,会调用CFileServConn的OnConfirm()函数:

 

 


连接上file_server后,msg_server会立即给file_server发一个数据包,以获得file_server的ip地址等信息。

 

 

这就是msg_server作为客户端连接其他服务的流程。与这些服务之间的连接都对应一个连接对象:

file_server                           CFileServConn

db_proxy_server                 CDBServConn

login_server     CLoginServConn

route_server     CRouteServConn

push_server     CPushServConn

 

而且,和连接file_server一样,msg_server在连接这些服务成功以后,可能会需要将自己的一些状态信息告诉对方:

 

  • 连接file_server成功后,给对方发包获取对方的ip地址等信息
  • 连接login_server成功以后,告诉login_server自己的ip地址、端口号和当前登录的用户数量和可容纳的最大用户数量,这样login_server将来对于一个需要登录的用户,会根据不同的msg_server的负载状态来决定用户到底登录哪个msg_server。

 

  • 连接route_server成功以后,给route_server发包告诉当前登录在本msg_server上有哪些用户(用户id、用户状态、用户客户端类型)。这样将来A用户给B发聊天消息,msg_server将该聊天消息转给route_server,route_server就知道用户B在哪个msg_server上了,以便将该聊天消息发给B所在的msg_server。


再来提一下,心跳包机制,和上一篇文章中介绍个与db_proxy_server一样,都是在定时器里面做的,这里不再赘述了,简单地贴出与file_server的心跳包代码吧:

 


在注册的定时器回调函数里面调用CFileServConn::OnTimer函数:

 

接下来的就是每个连接上的业务处理代码了,主消息泵收到数据后触发OnRead函数,然后收取数据解包,然后根据包的命令号处理包,所以每个连接对象根据自己的业务都有一个HandlePdu()函数,例如CFileServConn的:

 

当然有些数据包,msg_server直接自己装包应答就可以了。有些必须发到其他服务进行进一步处理,比如登录请求,发给db_proxy_server拿到mysql中校验用户名和密码,db_proxy_server校验完成后,再应答msg_server,msg_server再应答客户端。

这大概就是msg_server服务的结构和源码了吧。具体业务代码你可以查看每个连接对象的HandlePdu()函数来看具体的流程细节。

 

需要指出的是:连接服务器、接受连接、收取数据解包、发送数据这四个模块是一个完整的网路库必须具有的东西。这篇文章和上一篇文章完整地介绍了这四个模块,而TeamTalk的实现手法也是目前主流网络库的通用做法。如果从事服务器开发,必须熟练掌握这里面的具体每个细节。而teamtalk服务器这种分布式架构设计的思想也是非常值得学习和借鉴的。

 

 

如果您对服务器开发技术感兴趣,可以关注我的微信公众号『高性能服务器开发』,这个微信公众号致力于将服务器开发技术通俗化、平民化,让服务器开发技术不再神秘,其中整理了将服务器开发需要掌握的一些基础技术归纳整理,既有基础理论部分,也有实战部分。