SPServer

来源:互联网 发布:java 线程安全类 编辑:程序博客网 时间:2024/05/16 04:41

在互联网企业里,*nux下的C/C++编程主要的焦点还是server开发,关于不同的server模型,在UNP30章里有过简单的讨论,这里得出的结论就是多线程和多进程的server模型效率较高。但书中缺乏对多路复用机制的讨论,而当前主流的server模型则是epoll+multi-threads/multi-processeslighttpd就属于这种模型。本文将探讨一个更加高效的server模型,half-synchorize/half-asynchorize模式。

half-sync/half-async模型

此模式最早是由著名的C++网络编程框架ACE的作者的一篇文章,其主要思想就是用异步的方式来处理IO事件,而用同步的方式来处理业务逻辑,同步和异步之间使用一个队列作为缓冲,如下图

此模型详细介绍可参考原文

spserver简介

引用作者对spserver的介绍:spserver 是一个实现了半同步/半异步(Half-Sync/Half-Async)和领导者/追随者(Leader/Follower) 模式的服务器框架,能够简化 TCP server 的开发工作。并且spserve使用了libevent作为底层的异步响应机制的实现。

我抱着一种学习的态度,阅读了其half-sync/half-async模式的实现代码,本文也可以看做是一篇spserver的源码分析。

代码结构

1) spserver:主文件,程序从start方法里启动。

2) speventcb:主要的回调函数实现逻辑。

3) spsession:代表一个会话。

4) sprequest:封装了客户端ipmessageDecoder,个人认为封装的不好。

5) spresponse:封装了响应的内容。

6) spiochannel:封装底层IO

7) spmsgdecoder:消息解析器,判断消息是否完整。

8) spbuffer:对libevent里的buffer的简单封装。

9) spthreadspthreadpoolspexecutor:线程池的封装。

代码分析

spsession代表一个连接的会话,它属于half-sync/half-async模型的3层结构中的queue,业务逻辑和网络IO通过session中的input bufferoutput list来通信。

先来看一下SP_Session类的成员:

SP_Sid_t mSid; //session id,详见下一节

     struct event * mReadEvent; //session关联的读写事件

     struct event * mWriteEvent;

     SP_Handler * mHandler; //用户实现业务逻辑的handler

     void * mArg; //event args

     SP_Buffer * mInBuffer; //输入缓冲队列

     SP_Request * mRequest; //对请求的封装

     int mOutOffset; //输出偏移量,以字节为单位,表明out list中已输出的字节数

     SP_ArrayList * mOutList; // list of msg,输出的缓冲队列

     char mStatus; //当前session的状态,可为eNormal, eWouldExit, eExit

     char mRunning; //是否在运行

     char mWriting; //是否在写

     char mReading; //是否在读

     unsigned int mTotalRead, mTotalWrite; //session已读写的字节数

     SP_IOChannel * mIOChannel; //关联的IOChannel

session是通过SP_SessionManager来进行管理的,它实际上是一个64*1024的二维数组,entry的类型定义如下:

typedef struct tagSP_SessionEntry {

     uint16_t mSeq;

     uint16_t mNext;

     SP_Session * mSession;

} SP_SessionEntry;

其中每个entry都是通过一个key来标识其在matrix中的坐标,key=1024*row+col,而seq标识这个entry被使用过多少次,通过keyseq就可以形成一个ssion id了。entry中的mNext成员指向list中下一个成员的key,这样就可以快速的定位和分配listtailor

程序从SP_Server中的start方法开始,绑定server的地址和端口后,注册signal handler,然后注册listenfdonAccept异步回调函数。这里值得一提的是libevent对所有的异步调用做了抽象,甚至包括signal handler

程序的流程是通过libevent对不同的事件来回调不同的函数推进的,主要的回调函数有onAcceptonReadonWriteonResponse,下面将逐一讨论它们的流程。

onAccept

下面结合流程图对代码分析:


onAccept中完成了连接的初始化,主要包括:

1) accept并为返回的clientfd注册读写事件。

2) session manager中为连接分配一个session

3) 调用SP_EventHelper::doStart方法。

以上这些过程虽然都是通过事件触发的,但都是在主线程的main loop里的event_base_loop中调用的。而HS-HA模式主要是通过doStart方法来体现的。

doStart实际上是把一个task放到一个task queue里,而后task由一个线程完成。其主要逻辑在SP_EventHelper :: start中实现。

start中的执行步骤如下:

1) 初始化IOChannel

2) 分配response对象。

3) 调用业务逻辑实现SP_Handler中的start方法。SP_Handler中的start方法可以调用block函数,这也是为何此函数会由一个线程来调用。其实凡是用户实现的业务逻辑都是通过线程来异步调用的。

4) 调用msgqueue_push,将经过start中用户返回的response对象放入msgqueue中。msgqueue是一个带异步通知机制的队列,当response对象放入队列后,msgqueue_pop方法将被调用,它实际将response对象作为参数,调用onResponse方法。

onResponse

onResponse函数并不是通过libevent的回调机制触发的,实际上它是SP_EventArg中的response queue(实际为event_msgqueue类型)的回调函数,当队列中有response对象时,对每个response对象均调用onResponse函数。onResponse函数的主要作用就是将response对象中包含的msg对象加入到sessionoutList中。这里值得一提的是,SP_MessageSP_Session式多对多的关系,这样可以节省重复的SP_Message占用的内存。

onRead

onRead函数在fd可读和超时的情况下被调用,其主要流程为:

1)判断触发事件是否为可读,若为超时,则调用SP_EventHelper::doTimeout,它将用户实现的timeout函数封装为tasktaskpusheventArg中的InputResultQueue中,而后由executor来执行。

2)若为可读,则读入数据并解码,解码成功则调用SP_EventHelper::doWork,它将用户实现的handle函数封装为tasktaskpusheventArg中的InputResultQueue中,而后由executor来执行。

onWrite

onRead函数在fd可写和超时的情况下被调用,其主要流程为:

1)判断触发事件是否为可写,若为超时,则调用SP_EventHelper::doTimeout,它将用户实现的timeout函数封装为task,由线程调用。taskpusheventArg中的InputResultQueue中,而后由executor来执行。

2)若为可写,则将session中的outList中的msg发往client,值得注意的是,这里是通过writev来一次尽可能的多的写出数据。

总结

由上可知,一般来说Server是通过session来管理不同的连接,且在session中保留输入缓冲和输出缓冲,而后通过异步事件机制来向网络中读写数据,这便形成了half-async端。而用户实现自身的业务逻辑,且这些业务逻辑中可调用阻塞式的函数,这便形成了half-sync端,而为了提高效率,在half-sync端可使用多线程机制。

原创粉丝点击