前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >(三)服务器端的程序架构介绍1

(三)服务器端的程序架构介绍1

作者头像
范蠡
发布2018-04-04 15:17:39
1K0
发布2018-04-04 15:17:39
举报

通过上一节的编译与部署,我们会得到TeamTalk服务器端以下部署程序:

db_proxy_server

file_server

http_msg_server

login_server

msfs

msg_server

push_server

router_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)

注意:上图中并没有push_server和http_push_server。如果你不调试ios版本的客户端,可以暂且不启动push_server,另外http_push_server也可以暂不启动。

启动顺序:

一般来说,前端的服务会依赖后端的服务,所以一般先启动后端服务,再启动前端服务。建议按以下顺序启动服务:

1、启动db_proxy。 2、启动route_server,file_server,msfs 3、启动login_server 4、启动msg_server

那么我就按照服务端的启动顺序去讲解服务端的一个流程概述。 第一步:启动db_proxy后,db_proxy会去根据配置文件连接相应的MySQL实例,以及redis实例。 第二步:启动route_server,file_server,msfs后,各个服务端都会开始监听相应的端口。 第三步:启动login_server,login_server就开始监听相应的端口(8080),等待客户端的连接,而分配一个负载相对较小的msg_server给客户端。 第四步:启动msg_server(端口8000),msg_server启动后,会去主动连接route_server,login_server,db_proxy_server,会将自己的监听的端口信息注册到login_server去,同时在用户上线,下线的时候会将自己的负载情况汇报给login_server.

各个服务的端口号 (注意:如果出现部署完成后但是服务进程启动有问题或者只有部分服务进程启动了,请查看相应的log日志,请查看相应的log日志,请查看相应的log日志。)

服务

端口

login_server

8080/8008

msg_server

8000

db_proxy_server

10600

route_server

8200

http_msg_server

8400

file_server

8600/8601

服务网络通信框架介绍:

上面介绍的每一个服务都使用了相同的网络通信框架,该通信框架可以单独拿出来做为一个通用的网络通信框架。该网络框架是在一个循环里面不断地检测IO事件,然后对检测到的事件进行处理。流程如下:

1. 使用IO复用技术(linux和windows平台用select、mac平台用kevent)分离网络IO。

2. 对分离出来的网络IO进行操作,分为socket句柄可读、可写和出错三种情况。

当然再加上定时器事件,即检测一个定时器事件列表,如果有定时器到期,则执行该定时器事件。

整个框架的伪码大致如下:

[cpp] view plain copy

  1. while (running)
  2. {
  3. //处理定时器事件
  4. _CheckTimer();
  5. //IO multiplexing
  6. int n = select(socket集合, ...);
  7. //事件处理
  8. if (某些socket可读)
  9. {
  10. pSocket->OnRead();
  11. }
  12. if (某些socket可写)
  13. {
  14. pSocket->OnWrite();
  15. }
  16. if (某些socket出错)
  17. {
  18. pSocket->OnClose();
  19. }
  20. }

处理定时器事件的代码如下:

[cpp] view plain copy

  1. void CEventDispatch::_CheckTimer()
  2. {
  3. uint64_t curr_tick = get_tick_count();
  4. list<TimerItem*>::iterator it;
  5. for (it = m_timer_list.begin(); it != m_timer_list.end(); )
  6. {
  7. TimerItem* pItem = *it;
  8. it++; // iterator maybe deleted in the callback, so we should increment it before callback
  9. if (curr_tick >= pItem->next_tick)
  10. {
  11. pItem->next_tick += pItem->interval;
  12. pItem->callback(pItem->user_data, NETLIB_MSG_TIMER, 0, NULL);
  13. }
  14. }
  15. }

即遍历一个定时器列表,将定时器对象与当前时间(curr_tick)做比较,如果当前时间已经大于或等于定时器设置的时间,则表明定时器时间已经到了,执行定时器对象对应的回调函数。

在来看看OnRead、OnWrite和OnClose这三个函数。在TeamTalk源码中每一个socket连接被封装成一个CBaseSocket对象,该对象是一个使用引用计数的类的子类,通过这种方法来实现生存期自动管理。

[cpp] view plain copy

  1. void CBaseSocket::OnRead()
  2. {
  3. if (m_state == SOCKET_STATE_LISTENING)
  4. {
  5. _AcceptNewSocket();
  6. }
  7. else
  8. {
  9. u_long avail = 0;
  10. if ( (ioctlsocket(m_socket, FIONREAD, &avail) == SOCKET_ERROR) || (avail == 0) )
  11. {
  12. m_callback(m_callback_data, NETLIB_MSG_CLOSE, (net_handle_t)m_socket, NULL);
  13. }
  14. else
  15. {
  16. m_callback(m_callback_data, NETLIB_MSG_READ, (net_handle_t)m_socket, NULL);
  17. }
  18. }
  19. }

OnRead()方法根据状态标识m_state确定一个socket是侦听的socket还是普通与客户端连接的socket,如果是侦听sokcet则接收客户端的连接;如果是与客户端连接的socket,则先检测socket上有多少字节可读,如果没有字节可读或者检测字节数时出错,则关闭socket,反之调用设置的回调函数。

[cpp] view plain copy

  1. void CBaseSocket::OnWrite()
  2. {
  3. #if ((defined _WIN32) || (defined __APPLE__))
  4. CEventDispatch::Instance()->RemoveEvent(m_socket, SOCKET_WRITE);
  5. #endif
  6. if (m_state == SOCKET_STATE_CONNECTING)
  7. {
  8. int error = 0;
  9. socklen_t len = sizeof(error);
  10. #ifdef _WIN32
  11. getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char*)&error, &len);
  12. #else
  13. getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (void*)&error, &len);
  14. #endif
  15. if (error) {
  16. m_callback(m_callback_data, NETLIB_MSG_CLOSE, (net_handle_t)m_socket, NULL);
  17. } else {
  18. m_state = SOCKET_STATE_CONNECTED;
  19. m_callback(m_callback_data, NETLIB_MSG_CONFIRM, (net_handle_t)m_socket, NULL);
  20. }
  21. }
  22. else
  23. {
  24. m_callback(m_callback_data, NETLIB_MSG_WRITE, (net_handle_t)m_socket, NULL);
  25. }
  26. }

OnWrite()函数则根据m_state标识检测socket是否是尝试连接的socket(connect函数中的socket),用于判断socket是否已经连接成功,反之则是与客户端保持连接的socket,调用预先设置的回调函数。

[cpp] view plain copy

  1. void CBaseSocket::OnClose()
  2. {
  3. m_state = SOCKET_STATE_CLOSING;
  4. m_callback(m_callback_data, NETLIB_MSG_CLOSE, (net_handle_t)m_socket, NULL);
  5. }

OnClose()方法将标识m_state设置为需要关闭状态,并调用预先设置的回调函数。

每个服务程序都使用一个stl hash_map来管理所有的socket,键是socket句柄,值是CBaseSocket对象指针:

[cpp] view plain copy

  1. typedef hash_map<net_handle_t, CBaseSocket*> SocketMap;
  2. SocketMap g_socket_map;

所以在删除或者新增socket时,实际上就是从这个hash_map中删除或者向这个hash_map中增加对象。多线程操作,需要一个锁来进行保护:

[cpp] view plain copy

  1. void CEventDispatch::AddEvent(SOCKET fd, uint8_t socket_event)
  2. {
  3. CAutoLock func_lock(&m_lock);
  4. if ((socket_event & SOCKET_READ) != 0)
  5. {
  6. FD_SET(fd, &m_read_set);
  7. }
  8. if ((socket_event & SOCKET_WRITE) != 0)
  9. {
  10. FD_SET(fd, &m_write_set);
  11. }
  12. if ((socket_event & SOCKET_EXCEP) != 0)
  13. {
  14. FD_SET(fd, &m_excep_set);
  15. }
  16. }

代码CAutoLock func_lock(&m_lock);即保护该hash_map的锁对象。

而管理以上功能的是一个单例类CEventDispatch,所以不难才出CEventDispatch提供的接口:

[cpp] view plain copy

  1. class CEventDispatch
  2. {
  3. public:
  4. virtual ~CEventDispatch();
  5. void AddEvent(SOCKET fd, uint8_t socket_event);
  6. void RemoveEvent(SOCKET fd, uint8_t socket_event);
  7. void AddTimer(callback_t callback, void* user_data, uint64_t interval);
  8. void RemoveTimer(callback_t callback, void* user_data);
  9. void AddLoop(callback_t callback, void* user_data);
  10. void StartDispatch(uint32_t wait_timeout = 100);
  11. void StopDispatch();
  12. bool isRunning() {return running;}
  13. static CEventDispatch* Instance();
  14. protected:
  15. CEventDispatch();
  16. private:
  17. void _CheckTimer();
  18. void _CheckLoop();
  19. typedef struct {
  20. callback_t callback;
  21. void* user_data;
  22. uint64_t interval;
  23. uint64_t next_tick;
  24. } TimerItem;
  25. private:
  26. #ifdef _WIN32
  27. fd_set m_read_set;
  28. fd_set m_write_set;
  29. fd_set m_excep_set;
  30. #elif __APPLE__
  31. int m_kqfd;
  32. #else
  33. int m_epfd;
  34. #endif
  35. CLock m_lock;
  36. list<TimerItem*> m_timer_list;
  37. list<TimerItem*> m_loop_list;
  38. static CEventDispatch* m_pEventDispatch;
  39. bool running;
  40. };

其中StartDispatch()和StopDispatcher()分别用于启动和停止整个循环流程。一般在程序初始化的时候StartDispatch(),在程序退出时StopDispatcher()。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-03-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 高性能服务器开发 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 各个服务的端口号 (注意:如果出现部署完成后但是服务进程启动有问题或者只有部分服务进程启动了,请查看相应的log日志,请查看相应的log日志,请查看相应的log日志。)
相关产品与服务
负载均衡
负载均衡(Cloud Load Balancer,CLB)提供安全快捷的流量分发服务,访问流量经由 CLB 可以自动分配到云中的多台后端服务器上,扩展系统的服务能力并消除单点故障。负载均衡支持亿级连接和千万级并发,可轻松应对大流量访问,满足业务需求。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档