『C++实战项目 负载均衡式在线OJ』二、编译模块编写(持续更新)
文章目录
- 一、新建文件 compiler.hpp
- 二、代码编写
- Compile 函数实现
- 实用工具类实现
- 一、新建文件 util.hpp
- 二、代码编写
- 本节完整源码
- 目录结构
- 源码
- compiler.hpp
- util.hpp
我们首先来编写 complier
模块。compiler
模块的主要功能是使用 g++
完成对指定文件的编译。
该模块我们将用 fork
创建子进程 + fcnt
程序替换来完成。由于我已经在之前的文章中讲解过子进程和程序替换的相关知识了,所以在此不做赘述。需要的小伙伴可以点这里:
fork 相关
:『Linux从入门到精通』第 ⑬ 期 - 进程概念与fork初识;程序替换相关
:『Linux从入门到精通』第 ⑱ 期 - 学会了程序替换,我决定手写一个简易版shell玩一玩…
接下来进入代码的编写。
一、新建文件 compiler.hpp
进入项目目录,在 compiler
目录下新建 compiler.hpp
文件。目录结构:
online-judge -------- compiler -------- compiler.hpp
二、代码编写
- 定义
compiler
类,该类包含一个静态成员函数Compile
; - 该函数
Compile
声明为static
,使得我们无需创建对象就能调用该函数;
Compile 函数实现
- 创建子进程;
// filename: 需要编译的目标文件 不包含文件后缀(如 main)
static bool Compile(const std::string &filename) {pid_t pid = fork();if(pid < 0) {ERROR("compiler: 创建子进程失败");return false;}else if(pid == 0) {// 子进程// ...}else {// 父进程// ...}return false;
}
- 实现子进程功能;
子进程主要负责程序替换来执行 g++
进行编译。注意下面的代码中使用了文件工具类中的几个函数,但是我们目前还没有实现它们。在这里我们只需要知道它们的功能即可。函数实现放在后文。
Exe
: 为文件名添加后缀.exe
。代表我们生成的可执行文件名为 filename.exe;Src
: 为文件名添加后缀.cpp
。代表我们编译的源文件为 filename.cpp;ERROR
: 我的日志系统项目中实现的函数 ERROR。原理同std::cerr << "Error: 启动编译器失败" << std::endl;
。
// filename: 需要编译的目标文件 不包含文件后缀(如 main)
static bool Compile(const std::string &filename) {pid_t pid = fork();if(pid < 0) {ERROR("compiler: 创建子进程失败");return false;}else if(pid == 0) {// 子进程// 子进程:调用编译器,完成对代码的编译工作// g++ -o target src -lpthread -std=c++11execlp("g++", "g++", "-o", PathUtil::Exe(filename).c_str(), \PathUtil::Src(filename).c_str(), "-D", "COMPILER_ONLINE", "-std=c++11", nullptr);ERROR("compiler: 启动编译器失败");exit(2);}else {// 父进程// ...}return false;
}
- 实现父进程功能;
父进程主要负责等待子进程,检查子进程是否成功完成任务。
IsFileExists
: 判断指定文件是否存在的函数;
// filename: 需要编译的目标文件 不包含文件后缀(如 main)
static bool Compile(const std::string &filename) {pid_t pid = fork();if(pid < 0) {ERROR("compiler: 创建子进程失败");return false;}else if(pid == 0) {// 子进程// 子进程:调用编译器,完成对代码的编译工作// g++ -o target src -lpthread -std=c++11execlp("g++", "g++", "-o", PathUtil::Exe(filename).c_str(), \PathUtil::Src(filename).c_str(), "-D", "COMPILER_ONLINE", "-std=c++11", nullptr);ERROR("compiler: 启动编译器失败");exit(2);}else {// 父进程// 等待子进程waitpid(pid, nullptr, 0);// 判断编译是否成功:是否生成对应的可执行程序文件if(FileUtil::IsFileExists(PathUtil::Exe(filename))) {INFO("compiler: 编译成功");return true;}ERROR("compiler: 编译失败, 未找到可执行程序");}return false;
}
实用工具类实现
在上文中,我们用到了许多还没有实现的工具函数。这些函数往往具有简单的功能,且实现起来也很简单,并且使用率较高。所以我们放在一个文件中统一来实现它们。
一、新建文件 util.hpp
- 创建
comm
目录和util.hpp
文件。
# online-judge目录下
mkdir comm
touch util.hpp
二、代码编写
由于这些函数实现起来非常简单,这里就不做赘述了。
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>namespace ns_util {
class FileUtil {
public:static bool IsFileExists(const std::string & filepath) {struct stat st;if(stat(filepath.c_str(), &st) == 0) {// 获取文件属性成功,证明文件已经存在return true;}return false;}
class PathUtil {
public:static std::string AddSuffix(const std::string &filename, const std::string &suffix) {std::string path = temp_path;path += filename;path += suffix;return path;} static std::string CompilerErr(const std::string & filename) {return AddSuffix(filename, ".compile_err");}static std::string Src(const std::string & filename) {return AddSuffix(filename, ".cpp");}static std::string Exe(const std::string & filename) {return AddSuffix(filename, ".exe");}// 在线测试用户提交的代码时,有时用到标准输入、标准输出、标准错误流.// 这里分别用 .in, .out, .err 文件存储起来static std::string In(const std::string & filename) {return AddSuffix(filename, ".in");}static std::string Out(const std::string & filename) {return AddSuffix(filename, ".out");}static std::string Err(const std::string & filename) {return AddSuffix(filename, ".err");}
};
}
本节完整源码
目录结构
online-judge -------- compiler -------- compiler.hpp|--------- comm ------------ util.hpp
源码
compiler.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "../log/logs/log.h"
#include "../comm/util.hpp"/*********************************** 编译模块** 使用子进程 + 程序替换实现
**********************************/
namespace ns_compiler {using namespace ns_util;
using namespace LOG;class Compiler {
public:Compiler() {}~Compiler() {}
public:// filename: 需要编译的目标文件 不包含文件后缀(如 main)static bool Compile(const std::string &filename) {pid_t pid = fork();if(pid < 0) {ERROR("compiler: 创建子进程失败");return false;}else if(pid == 0) {// 子进程// 程序替换 不影响进程的文件描述符表// 子进程:调用编译器,完成对代码的编译工作// g++ -o target src -lpthread -std=c++11execlp("g++", "g++", "-o", PathUtil::Exe(filename).c_str(), \PathUtil::Src(filename).c_str(), "-D", "COMPILER_ONLINE", "-std=c++11", nullptr);ERROR("compiler: 启动编译器失败");exit(2);}else {// 父进程waitpid(pid, nullptr, 0);// 判断编译是否成功:是否生成对应的可执行程序文件if(FileUtil::IsFileExists(PathUtil::Exe(filename))) {INFO("compiler: 编译成功");return true;}ERROR("compiler: 编译失败, 未找到可执行程序");}return false;}
};
}
util.hpp
#pragma once#include <iostream>
#include <string>
#include <vector>
#include <fstream>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>namespace ns_util {
class FileUtil {
public:static bool IsFileExists(const std::string & filepath) {struct stat st;if(stat(filepath.c_str(), &st) == 0) {// 获取文件属性成功,证明文件已经存在return true;}return false;}
class PathUtil {
public:static std::string AddSuffix(const std::string &filename, const std::string &suffix) {std::string path = temp_path;path += filename;path += suffix;return path;} static std::string CompilerErr(const std::string & filename) {return AddSuffix(filename, ".compile_err");}static std::string Src(const std::string & filename) {return AddSuffix(filename, ".cpp");}static std::string Exe(const std::string & filename) {return AddSuffix(filename, ".exe");}// 在线测试用户提交的代码时,有时用到标准输入、标准输出、标准错误流.// 这里分别用 .in, .out, .err 文件存储起来static std::string In(const std::string & filename) {return AddSuffix(filename, ".in");}static std::string Out(const std::string & filename) {return AddSuffix(filename, ".out");}static std::string Err(const std::string & filename) {return AddSuffix(filename, ".err");}
};
}