当前位置: 首页 > 工具软件 > SPServer > 使用案例 >

spserver 开源服务器框架研究与分析

凌朗
2023-12-01
网上开源的C/C++服务器框架 还是比较少的。 最近研究了 spserver , 里面用了较多的设计模式,使用设计模式的目的是把不变的东西和可变的东西分离并且封装起来,避免以后修改代码, 所谓 “对修改关闭,对扩展开放”,但是滥用设计模式会把简单的问题复杂话了。

      spserver代码量比较少,比较容易看懂,如果只是做一个简单的echo服务器的话,spserver 可以给新手一个快速搭建服务器的框架。用vld测试后, 发现spserver有很多内存泄露的地方。并且 msgqueue_destroy 函数会造成线程阻塞,程序无法正常结束。

1、SP_Handler类 负责处理业务逻辑。一个连接 对应一个SP_Handler对象实例。

2、SP_MSFactory 有新链接的时候负责创建 SP_Handler 对象.

3、MS_Decoder 负责解析数据包。注意:当 buffer为空不要返回 eOk.否则会占用CPU过高。 返回eOk会触发 SP_Handler::handle 事件。

4、SP_Handler 无论是超时, 还是出错。最终都会调用 SP_Handler ::close

5、标识在线用户的数据类型是 SP_Sid_t 可以通过response->getFromSid() 来获得。

6、给在线用户发送消息是 new 一个SP_Message 然后使用 SP_Response::addMessage方法丢给 response对象。当然要用 msg->getToList()->add()来添加要接受数据的用户的 SP_Sid_t.。

      spserver 里面的半同步半异步模式相当于主线程负责所有的数据收发, 工作者线程负责逻辑。 主线程与工作者线程之间通过消息队列进行通信。主线程收到数据后,通知工作者线程,有数据,工作者线程进行报文解析与处理, 把要发送的数据放入队列,然后通知主线程,有数据了, 你去发送把。这种模型可以应对逻辑复杂, 处理时间长, 需要发送和接收的数据比较少的情况比较合适。但是如果是需要处理类似文件服务器这样,IO的数据很多,但是逻辑很简单的服务器,则不合适, 主线程会成为瓶颈,也即是说IO会成为瓶颈。另外是spserver 没有实现作为客户端连接其他服务器的代码,需要用户自己实现连接到其他服务器的情况。  

      对SPServer,我感兴趣的还是它的线程池实现,可能是因为应用场景关系,SPServer对线程池实现相对简单,没有一些复杂的花样。

      对线程池的封装主要提供了dispatch函数,将一个将要调用的函数分配给线程池里面的线程。

class SP_ThreadPool {
public:
    typedef void ( * DispatchFunc_t )( void * );
    SP_ThreadPool( int maxThreads, const char * tag = 0 );
    ~SP_ThreadPool();
    /// @return 0 : OK, -1 : cannot create thread
    int dispatch( DispatchFunc_t dispatchFunc, void *arg );
    int getMaxThreads();
private:
    char * mTag;
    int mMaxThreads;
    int mIndex;
    int mTotal;
    int mIsShutdown;

下面是dispatch函数代码:
int SP_ThreadPool :: dispatch( DispatchFunc_t dispatchFunc, void *arg )
{
    int ret = 0;
    pthread_attr_t attr;
    SP_Thread_t * thread = NULL;
    pthread_mutex_lock( &mMainMutex );
    if( mIndex <= 0 && mTotal >= mMaxThreads ) {
        pthread_cond_wait( &mIdleCond, &mMainMutex );
    }
    if( mIndex <= 0 ) {
        SP_Thread_t * thread = ( SP_Thread_t * )malloc( sizeof( SP_Thread_t ) );
        thread->mId = 0;
        pthread_mutex_init( &thread->mMutex, NULL );
        pthread_cond_init( &thread->mCond, NULL );
        thread->mFunc = dispatchFunc;
        thread->mArg = arg;
        thread->mParent = this;
        pthread_attr_init( &attr );
        pthread_attr_setdetachstate( &attr,PTHREAD_CREATE_DETACHED );
        if( 0 == pthread_create( &( thread->mId ), &attr, wrapperFunc, thread ) ) {
            mTotal++;
            syslog( LOG_NOTICE, "[tp@%s] create thread#%ld\n", mTag, thread->mId );
        } else {
            ret = -1;
            syslog( LOG_WARNING, "[tp@%s] cannot create thread\n", mTag );
            pthread_mutex_destroy( &thread->mMutex );
            pthread_cond_destroy( &thread->mCond );
            free( thread );
        }
    } else {
        mIndex--;
        thread = mThreadList[ mIndex ];
        mThreadList[ mIndex ] = NULL;
        thread->mFunc = dispatchFunc;
        thread->mArg = arg;
        thread->mParent = this;
        pthread_mutex_lock( &thread->mMutex );
        pthread_cond_signal( &thread->mCond ) ;
        pthread_mutex_unlock ( &thread->mMutex );
    }
    pthread_mutex_unlock( &mMainMutex );
    return ret;
}

如果当前没有空闲的线程,那么创建一个新线程来处理该事务。每个线程运行一个包裹函数
void * SP_ThreadPool :: wrapperFunc( void * arg )
{
    SP_Thread_t * thread = ( SP_Thread_t * )arg;
    for( ; 0 == thread->mParent->mIsShutdown; ) {
        thread->mFunc( thread->mArg );
        pthread_mutex_lock( &thread->mMutex );
        if( 0 == thread->mParent->saveThread( thread ) ) {
            pthread_cond_wait( &thread->mCond, &thread->mMutex );
            pthread_mutex_unlock( &thread->mMutex );
        } else {
            pthread_mutex_unlock( &thread->mMutex );
            pthread_cond_destroy( &thread->mCond );
            pthread_mutex_destroy( &thread->mMutex );
            free( thread );
            break;
        }
    }
    pthread_mutex_lock( &thread->mParent->mMainMutex );
    thread->mParent->mTotal--;
    if( thread->mParent->mTotal <= 0 ) {
        pthread_cond_signal( &thread->mParent->mEmptyCond );
    }
    pthread_mutex_unlock( &thread->mParent->mMainMutex );
    return NULL;
}

在该函数中,如果处里完事务,那么调用pthread_cond_wait等待新任务。
      从上面实现中,从线程池角度来说,它并没有缓冲待处理的作务,这是因为在spserver中,采用了一个线程来接收对msgquene队列中的消息,对于接收到的每一个消息,调用threadPool中dispatch函数来处理该任务,如果线程池中没有空闲的线程而且线程数达到最大线程数,当前线程就等待。(这里或许就是所谓的Leader/Followers模式应用吧)。
     由于任务入队列与从队列中接收队列在不同线程中,添加任务线程与接收队列线程Worker通信主要采用unix域套接字,当有任务添加到任务队列中时,通过写文件通知读端,有任务添加到任务队列中。
 类似资料: