【服务器编程】服务器编程实现逻辑和超级服务

来源:互联网 发布:tuxera ntfs for mac 编辑:程序博客网 时间:2024/06/18 14:51

【前言】

        由上一篇文章,我决定了服务器的控制逻辑,即多进程/多线程的服务器(Apache也是这个模型哦)。它掌控整个服务逻辑流程,但是具体服务是没有提供的,这就要我们自己再封装一些特定服务的类,然后在线程里调用。

       因为服务器相应的请求种类是很多的,包括登陆请求、下载文件请求、刷新请求等等,那么就要我们自己封装一些数据结构来判断客户请求类型。另一方面,在我看来,虽然请求是不同,但归咎都是一种请求类型,所以他们是有共同点的,可以封装一个类作为所有通信的基类。

       前面介绍逻辑和服务,源码在后面Github给出。

【上回战况】

上回,主要确定了服务器的控制逻辑(一个总进程/线程关系图)。

【最新战况】

下面,从一些主要逻辑代码讲解。

在进程池中的子进程中,我使用了epoll_wait在等待请求的到来,如果是外界信号/来自父进程的信息(新的连接请求),那就处理/accept连接然后向epoll注册该套接字。重要部分是,当已连接的请求来时,子进程epoll_wait返回,开始处理事件,怎么处理呢?我定义了一个 超级服务器 (XServer类,这个名称我从xinetd是inetd的改进版搞来的),它主要是控制逻辑(进程/线程)具体服务的桥梁。如下(从主进程->子进程->子线程->XServer解说结构):

主进程逻辑:

template<typename T>void ProcessPool<T>::run_parent(){       ........while (!m_stop){number = epoll_wait(m_epollfd, event, MAX_EVENT_NUMBER, -1);for (int i = 0; i < number; ++i){int sockfd = event[i].data.fd;/* 有新的连接,用轮询算法将accept等剩余工作递交给子进程 */if (sockfd == m_listenfd){// 向子进程通知,有新的连接请求来啦,你快点accept它........send(m_sub_process[idx].m_pipefd[0], (char *)&new_conn, sizeof(new_conn), 0);} /* 有来自外界的信号 */else if (sockfd == sig_pipefd[0] && (event[i].events & EPOLLIN)){// 处理自己的事务......}else continue;}}}
主进程主要处理两个事件。第一,有新的请求就通知其中一个子进程(通过unix域套接字,之前以建立);第二,接受到外界信号,处理就OK。

子进程逻辑:

template<typename T>void ProcessPool<T>::run_child(){        ........while (!m_stop){/* 如果设置非阻塞,那么cpu使用率很高,没必要设置非阻塞 */number = epoll_wait(m_epollfd, event, MAX_EVENT_NUMBER, -1);               ........for (int i = 0; i < number; ++i){int sockfd = event[i].data.fd;/* EPOLLIN事件来自父子进程通信的unix套接字,说明有新的用户连接服务器 */if (sockfd == pipefd && (event[i].events & EPOLLIN)){...........} /* 来子外界的信号,如在终端输入kill -signal PID给此进程时 */else if (sockfd == sig_pipefd[0] && (event[i].events & EPOLLIN)){............}/* 已经连接的用户发送请求的数据到达 */else if (event[i].events & EPOLLIN){tpool.Append(new T(sockfd));}elsecontinue;}}.......}
上面代码(省略号代表还有一些细节的东西),主要是 tpool变量,它就是一个线程池对象,在开启服务器时已经在每个子进程建立一个线程池;Append调用是往任务列表插入一个任务(因为用户请求来了嘛)。那线程池如何如何设计的呢?如下:
template<typename T>bool ThreadPool<T>::Append(T *request){pthread_mutex_lock(&m_QueMutex);......m_WorkList.push_back(request);        ......pthread_mutex_unlock(&m_QueMutex);sem_post(&m_QueSem);return true;}
这就是插入函数的操作,注意,插入任务后会调用sem_post发送一个信号,为什么?因为此进程里的所有线程都在等任务的到来嘛,如下就是每个线程执行的函数,整个生命期就是这个函数:

template<typename T>void ThreadPool<T>::Run(int index){while (!m_Stop){sem_wait(&m_QueSem);pthread_mutex_lock(&m_QueMutex);......T* request = m_WorkList.front();m_WorkList.pop_front();pthread_mutex_unlock(&m_QueMutex);........int sockfd = request->Process();........}}

线程就是调用sem_wait等待,没有任务是就阻塞,有任务就抢!注意这里用 信号量 、 互斥量并用的好处哦,这样可以避免无任务时,处理任务的线程老是获得锁而造成浪费cpu资源,但从另一方面考虑,互斥量是比信号量快的(详见《unix环境高级编程》p459)。想象一种情况,没有任务来时,子线程老是在加锁、解锁,而没有做半点有用的事。

好的,控制逻辑就是这样了,注意到上面的request变量吗,它是模板参数类型,那就是我们需要什么类型的服务就传递什么参数啦(我这里传递了XServer超级服务类为参数)!刚才我已经说过,XServer是一个超级服务类型,它是控制逻辑和具体服务的桥梁,我们通过它提供的Process函数调用开始具体的任务处理,那么如何实现呢?如下:

int Process(){while (1){nRead = recv(m_connfd, (char *)&msg, sizeof(msgPack), 0);........switch(ntohs(msg.type)){case :                               // 各种不同的服务对应不同的casedefault:break;}}}

这里同样忽略细节,注意到,因为这是一个超级服务类,提供的是所有服务的集合,而具体服务就应该是一个类,提供明确的服务的类。前面我说过整个项目的通信是自定义了一个数据结构确定不同客户请求的,XServer就是使用标签来识别具体任务,然后将任务交给具体服务类(分解msgpack信息包,将明确信息传递给具体类处理)。

那么,这些具体的服务类又如何设计并如何被调用呢?下一篇讲解我的想法。

【QT服务器成长状况】


① 有大脑,也就是控制逻辑啦。

② 有手有脚的哦,因为超级服务类嘛,但却不能做什么事哦,没有工具!!!



【Github】

可能没立刻更新,写了blog再整理提交^^

https://github.com/jammgit/LearnPlatformBaseQt

0 0