1.项目的创建
打开Visual Studio 2019
选择的创建新项目
然后选择C++、Linux、控制台里面的控制台应用程序
到目前为止,我们的项目算是建立了。
但是开发环境还需要一些设置。
首先需要确保虚拟机已经打开,并且虚拟系统Ubuntu已经启动。
然后我们需要在菜单→工具→选项→跨平台→连接管理器
2.进程和进程的创建
线程默认是进程内竞争,而进程是操作系统资源分配最小的调度单位。这也就意味着,如果要充分利用系统资源,最好的形式是多线程多进程模式。所以我们最好将一个整体功能,分散到多个进程之中,从而实现资源利用率的最大化。否则就只能多个线程在一个进程内进行竞争,没法充分利用系统的资源。
毕竟多个进程竞争资源,比一个进程竞争资源,要有利得多。
下图是服务器项目要实现的进程结构图
图中方框部分都是主进程模块,圆框则是子进程。
主进程只负责网络服务器部分,接入客户端,其他一概不管。
日志则由日志服务器进程来处理。接入客户端之后,发送给客户端处理进程。如果处理过程需要数据库,则和数据库进程进行交互。这样,将一个进程完成的事情,分成了四个进程进行。
而且每个进程中可以依据自己的需求,开启多个线程来完成。
在Linux中,开启进程一般通过exec系列函数或者fork函数来完成。
即使是exec函数,也会要使用到fork函数。所以开启进程,fork函数是无法绕开的。而fork函数会对线程造成影响,所以我们一定要先定好进程结构,然后再开启线程。
首先,由于线程无法被复制,所以在子进程中,一些线程会消失
(没有被复制过来)
其次,如果程序逻辑依赖多线程模式的时候,fork可能在子进程
中破坏掉这种模式,进而使得程序出现无法预料的问题。
所以一定要先准备好进程结构,再去使用线程!!!
由于数据库我们最后会使用MySQL,而MySQL进程是由第三方提供并随服务器启动而启动的服务程序。所以我们最终要生成的进程是志进程和客户端通信处理进程。
这意味着我们需要在一开始,就分离出两个子进程,分别处理日志和客户端由于日志进程在后台服务器程序中的重要作用。
所以日志子进程应该优先创建,然后再创建客户端处理子进程。
所以整个进程的创建顺序,会按照进程关系图中所示顺序,进行创建。
3.进程模块的实现方案
创建进程的流程和结构,我们现在已经知道了。
但是如何实现,还有几个问题,需要我们一个一个去解决。
首先,每个子进程的逻辑并不一样,所需要的参数可能相互冲突。
那么如何满足这些需求呢?
其次,客户端处理进程,需要处理客户端。
我们这是一个网络程序,主进程接收到客户端之后,如何通知子进
程去处理呢?
客户端这个时候是一个文件描述符,怎么告诉子进程去处理呢?
所以我们需要两个功能:
灵活的进程入口函数
进程间传递文件描述符
我们先讲讲第一个功能
这个功能可以有三种做法:
- 使用无属性的指针参数和固定参数的进程入口函数来实现
- 使用面向对象的参数和统一的进程入口函数来实现
- 使用模板函数来实现
这三种方式都可以实现,但是方便程度和安全性不一样。
第一种方式技术上最简单,但是类型在转换的时候,可能出现问题。而且可以传入的参数数量是固定的,以后其他项目很难复用此代码。
第二种方式比第一种好了不少。参数不是固定的,可移植性强了多。但是这种方式需要专门写一个参数封装和解析的代码。这种解析代码的复用性会比较差。因为每个进程的任务不一样,参数也不一样,参数的含义也可能大相径庭。
第三种方式难度最大,但是使用起来最方便,可以移植性最强。参数可以随时修改,函数也可以是类的成员函数。此外参数无需解析,直接原样转发到目标函数。实现起来也不需要太多代码,stl里面准备好了很多工具,可以直接使用。就是模板编程不太好理解。
我们这里将采取第三种方式来实现。
4.进程入口函数的实现
fork函数介绍
#include <unistd.h>
pid_t fork(void);
返回值:
主进程中,会返回子进程的pid。
子进程中,返回值为0。
如果失败,返回-1。
5.代码实现
#include <cstdio>
#include <unistd.h>
#include <functional>
class CFunctionBase
{
public:
virtual ~CFunctionBase() {}
virtual int operator()() = 0;
};
template<typename _FUNCTION_, typename... _ARGS_>
class CFunction :public CFunctionBase
{
public:
CFunction(_FUNCTION_ func, _ARGS_... args) {
}
virtual ~CFunction() {}
virtual int operator()() {
return m_binder();
}
std::_Bindres_helper<int, _FUNCTION_,
_ARGS_...>::type m_binder;
};
class CProcess
{
public:
CProcess() {
m_func = NULL;
}
~CProcess() {
if (m_func != NULL) {
delete m_func;
m_func = NULL;
}
}
template<typename _FUNCTION_, typename...
_ARGS_>
int SetEntryFunction(_FUNCTION_ func,
_ARGS_... args)
{
m_func = new CFunction(func, args...);
return 0;
int CreateSubProcess() {
if (m_func == NULL)return -1;
pid_t pid = fork();
if (pid == -1)return -2;
if (pid == 0) {
//子进程
return (*m_func)();
}
//主进程
m_pid = pid;
return 0;
}
private:
CFunctionBase* m_func;
pid_t m_pid;
};
int CreateLogServer(CProcess* proc)
{
return 0;
}
int CreateClientServer(CProcess* proc)
{
return 0;
}
int main()
{
CProcess proclog,proccliets;
proclog.SetEntryFunction(CreateLogServer,&proclog);
int ret = proclog.CreateSubProcess();
}
proccliets.SetEntryFunction(CreateClientServer,&proccliets);
ret = proccliets.CreateSubProcess();
return 0;
}