加入收藏 | 设为首页 | 会员中心 | 我要投稿 衡阳站长网 (https://www.0734zz.cn/)- 数据集成、设备管理、备份、数据加密、智能搜索!
当前位置: 首页 > 站长资讯 > 外闻 > 正文

实例:一个服务器程序的架构介绍

发布时间:2019-01-29 18:45:33 所属栏目:外闻 来源:高性能服务器开发
导读:本文将介绍我曾经做过的一个项目的服务器架构和服务器编程的一些重要细节。 一、程序运行环境 操作系统:Centos 7.0 编译器:gcc/g++ 4.8.3、cmake 2.8.11 mysql数据库:5.5.47 项目代码管理工具:Visual Studio 2013 一、程序结构 该程序总共有 17 个线程
副标题[/!--empirenews.page--]

本文将介绍我曾经做过的一个项目的服务器架构和服务器编程的一些重要细节。

一、程序运行环境

操作系统:Centos 7.0

编译器:gcc/g++ 4.8.3、cmake 2.8.11

mysql数据库:5.5.47

项目代码管理工具:Visual Studio 2013

一、程序结构

该程序总共有 17 个线程,其中分为 9 个数据库工作线程 D 和一个日志线程 L,6 个普通工作线程 W,一个主线程 M。(以下会用这些字母来代指这些线程)

(一)、数据库工作线程的用途

9 个数据库工作线程在线程启动之初,与 mysql 建立连接,也就是说每个线程都与 mysql 保持一路连接,共 9 个数据库连接。

每个数据库工作线程同时存在两个任务队列,第一个队列 A 存放需要执行数据库增删查改操作的任务 sqlTask,第二个队列 B 存放 sqlTask 执行完成后的结果。sqlTask 执行完成后立即放入结果队列中,因而结果队列中任务也是一个个的需要执行的任务。大致伪代码如下:

  1. void db_thread_func()  {   
  2.     while (!m_bExit)   
  3.     {   
  4.         if (NULL != (pTask = m_sqlTask.Pop()))   
  5.         {   
  6.             //从m_sqlTask中取出的任务先执行完成后,pTask将携带结果数据   
  7.             pTask->Execute();               
  8.             //得到结果后,立刻将该任务放入结果任务队列   
  9.             m_resultTask.Push(pTask);   
  10.             continue;   
  11.         }   
  12.  
  13.         sleep(1000);   
  14.     }//end while-loop  
  15.  }   

现在的问题来了:

任务队列 A 中的任务从何而来,目前只有消费者,没有生产者,那么生产者是谁?

任务队列 B 中的任务将去何方,目前只有生产者没有消费者。

这两个问题先放一会儿,等到后面我再来回答。

(二)工作线程和主线程

在介绍主线程和工作线程具体做什么时,我们介绍下服务器编程中常常抽象出来的几个概念(这里以 tcp 连接为例):

  1. TcpServer 即 Tcp 服务,服务器需要绑定ip地址和端口号,并在该端口号上侦听客户端的连接(往往由一个成员变量 TcpListener 来管理侦听细节)。所以一个 TcpServer 要做的就是这些工作。除此之外,每当有新连接到来时,TcpServer 需要接收新连接,当多个新连接存在时,TcpServer 需要有条不紊地管理这些连接:连接的建立、断开等,即产生和管理下文中说的TcpConnection 对象。
  2. 一个连接对应一个 TcpConnection 对象,TcpConnection 对象管理着这个连接的一些信息:如连接状态、本端和对端的 ip 地址和端口号等。
  3. 数据通道对象 Channel,Channel 记录了 socket 的句柄,因而是一个连接上执行数据收发的真正执行者,Channel 对象一般作为 TcpConnection 的成员变量。
  4. TcpSession 对象,是将 Channel 收取的数据进行解包,或者对准备好的数据进行装包,并传给 Channel 发送。

归纳起来:一个 TcpServer 依靠 TcpListener 对新连接的侦听和处理,依靠TcpConnection 对象对连接上的数据进行管理,TcpConnection 实际依靠 Channel 对数据进行收发,依靠 TcpSession 对数据进行装包和解包。也就是说一个 TcpServer 存在一个 TcpListener,对应多个 TcpConnection,有几个TcpConnection 就有几个TcpSession,同时也就有几个 Channel。

以上说的 TcpServer、TcpListener、TcpConnection、Channel 和 TcpSession 是服务器框架的网络层。一个好的网络框架,应该做到与业务代码脱耦。即上层代码只需要拿到数据,执行业务逻辑,而不用关注数据的收发和网络数据包的封包和解包以及网络状态的变化(比如网络断开与重连)。

拿数据的发送来说:

当业务逻辑将数据交给 TcpSession,TcpSession 将数据装好包后(装包过程后可以有一些加密或压缩操作),交给 TcpConnection::SendData(),而TcpConnection::SendData() 实际是调用 Channel::SendData(),因为 Channel 含有 socket 句柄,所以Channel::SendData() 真正调用send()/sendto()/write() 方法将数据发出去。

实例:一个服务器程序的架构介绍

对于数据的接收,稍微有一点不同:

通过 select()/poll()/epoll() 等IO multiplex技术,确定好了哪些 TcpConnection 上有数据到来后,激活该 TcpConnection 的 Channel 对象去调用recv()/recvfrom()/read() 来收取数据。数据收到以后,将数据交由 TcpSession来处理,最终交给业务层。注意数据收取、解包乃至交给业务层是一定要分开的。我的意思是:最好不要解包并交给业务层和数据收取的逻辑放在一起。因为数据收取是 IO 操作,而解包和交给业务层是逻辑计算操作。IO 操作一般比逻辑计算要慢。到底如何安排要根据服务器业务来取舍,也就是说你要想好你的服务器程序的性能瓶颈在网络 IO 还是逻辑计算,即使是网络 IO,也可以分为上行操作和下行操作,上行操作即客户端发数据给服务器,下行即服务器发数据给客户端。有时候数据上行少,下行大。(如游戏服务器,一个 npc 移动了位置,上行是该客户端通知服务器自己最新位置,而下行确是服务器要告诉在场的每个客户端)。

(编辑:衡阳站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

热点阅读