C++实战演练---负载均衡在线oj项目 || 编译服务模块设计

顾得泉:个人主页

个人专栏:《Linux操作系统》 《C++从入门到精通》  《LeedCode刷题》

键盘敲烂,年薪百万!


前言

       上一篇预热文章简单说过我们的编写思路,首先要实现的是编译功能,那么在编译功能版块中,我们首先要考虑一些问题:

     1.编译出来的临时文件我们应该放在哪里?

     2.每次编译出来的临时文件名称重复如何解决?

     3.编译正确与否的相关信息在哪显示?

注:博客中项目相关代码展示不完整!!!


一、comm模块

       为了解决上述问题,我们首先列出一个公共模块,里面存放两个主要功能的文件:

       1.<util.hpp>: 生成存放临时文件的路径,生成各个文件唯一的文件名

       2.<log.hpp>: 生成开放式日志接口,生成编译相关信息

1.util.hpp

 1.拼接成固定临时文件路径

       我们希望用户代码及其编译的临时文件都存放在temp文件夹下,所以我们可以设计一下拼接成文件完整路径的方法,定义在PathUtil类中,它所提供的方法都设置为public的静态方法,供外部直接调用。

class PathUtil
{
public:
    static std::string AddSuffix(const std::string &file_name, const std::string &suffix)
    {
        std::string path_name = temp_path;
        path_name += file_name;
        path_name += suffix;
        return path_name;
    }
    //编译时需要的临时文件
    //构建源文件路径+后缀的完整文件名
    //1234 -> ./temp/1234.cpp
    static std::string Src(const std::string &file_name)
    {
        return AddSuffix(file_name, ".cpp");
    }
    //构建可执行程序的完整路径+后缀名
    static std::string Exe(const std::string &file_name)
    {
        return AddSuffix(file_name, ".exe");
    }
    static std::string CompilerError(const std::string &file_name)
    {
        return AddSuffix(file_name, ".compiler_error");
    }

    //运行时需要的临时文件
    static std::string Stdin(const std::string &file_name)
    {
        return AddSuffix(file_name, ".stdin");
    }
    static std::string Stdout(const std::string &file_name)
    {
        return AddSuffix(file_name, ".stdout");
    }
    //构建该程序对应的标准错误完整的路径+后缀名
    static std::string Stderr(const std::string &file_name)
    {
        return AddSuffix(file_name, ".stderr");
    }
};

2.获取秒级时间戳

       为了下面的日志功能做铺垫,因为我们希望打印出来的文件名各不相同。

class TimeUtil
{
public:
    static std::string GetTimeStamp()
    {
        struct timeval _time;
        gettimeofday(&_time, nullptr);
        return std::to_string(_time.tv_sec);
    }
    //获得毫秒时间
    static std::string GetTimeMs()
    {
        struct timeval _time;
        gettimeofday(&_time, nullptr);
        return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);
    }
};
static std::string UniqFileName()
{
    std::atomic_uint id(0);
    id++;
    //毫秒级时间戳+原子性递增唯一值:保证唯一性
    std::string ms = TimeUtil::GetTimeMs();
    std::string uniq_id = std::to_string(id);
   
    return ms + "." + uniq_id;
}

2.log.hpp

常见日志等级包括:

  1. DEBUG(调试):用于调试目的的详细日志信息,通常不在生产环境中启用。

  2. INFO(信息):用于提供一般信息的日志,例如应用程序的启动、关闭、关键操作等。

  3. WARNING(警告):用于表示可能的问题或潜在的错误,但不会导致应用程序的终止或异常。

  4. ERROR(错误):用于表示错误或异常情况,可能会导致应用程序的中止或异常。

  5. FATAL(致命错误):用于表示严重的错误或不可恢复的情况,可能会导致应用程序的崩溃或无法继续运行。

实现简单的日志接口:

// 日志等级
enum
{
    INFO, //就是整数
    DEBUG,
    WARNING,
    ERROR,
    FATAL
};

inline std::ostream &Log(const std::string &level, const std::string &file_name, intline)
{
    // 添加日志等级
    std::string message = "[";
    message += level;
    message += "]";

    // 添加报错文件名称
    message += "[";
    message += file_name;
    message += "]";

    // 添加报错行
    message += "[";
    message += std::to_string(line);
    message += "]";

    // 日志时间戳
    message += "[";
    message += TimeUtil::GetTimeStamp();
    message += "]";

    // cout 本质 内部是包含缓冲区的
    std::cout << message; //不要endl进行刷新

    return std::cout;
}
// LOG(INFo) << "message" << "\n";
// 开放式日志
#define LOG(level) Log(#level, __FILE__, __LINE__)

二、编译服务整体架构

       这个模块只负责进行代码编译,意味着我们目前默认是能够接收远端提交的代码文件,并且这个文件对于我们编译模块来说是一个临时文件,编译成功后也会形成一个临时的可执行文件,这些文件的存放位置已经解决,那么现在就开启编译之路。


三、编译服务主体

       现在我们要做的就是把用户提交的代码进行编译形成可执行程序,对结果并进行展示。程序运行无非就是三种结果:

       1.代码跑完,结果正确

       2.代码跑完,结果不正确

       3.代码没跑完,发生异常了

static bool Compile(const std::string &file_name)
{
    pid_t pid = fork();
    if(pid < 0)
    {
         LOG(ERROR) << "内部错误,创建子进程失败" << "\n";
         return false;
    }
    else if(pid == 0)
    {
         umask(0);
         int _stderr = open(PathUtil::CompilerError(file_name).c_str(), O_CREAT | O_WRONLY, 0644);
         if(_stderr < 0)
         {
               LOG(WARNING) << "没有成功形成stderr文件" << "\n";
               exit(1);
         }
         //重定向标准错误到_stderr
         dup2(_stderr, 2);
         //程序替换,并不影响进程的文件描述符表

         //子进程:调用编译器,完成对代码的编译工作
         //g++ -o target src -std=c++11
         execlp("g++", "g++", "-o", PathUtil::Exe(file_name).c_str(), 
         PathUtil::Src(file_name).c_str(), "-std=c++11", nullptr/*不要忘记*/);
         LOG(ERROR) << "启动编译器g++失败,可能是参数错误" << "\n";
         exit(2);
    }
    else
    {
         waitpid(pid, nullptr, 0);
         //编译是否成功,就看有没有形成对应的可执行程序
         if(FileUtil::IsFileExists(PathUtil::Exe(file_name)))
         {
              LOG(INFO) << PathUtil::Src(file_name) << "编译成功" << "\n";
              return true;
         }
    }
    LOG(ERROR) << "编译失败,没有形成可执行程序" << "\n";
    return false;
}

       调用dup2系统调用的时机在调用execlp之前,也就是说我们在程序替换前完成了重定向,而重定向不会影响进程已经打开的文件,也就是不影响子进程现存的文件描述符表,那么g++编译的报错信息就合乎预期地打印到了我们重定向的文件中了。


四、功能测试加其他

1.mkaefile助手

compile_server:compile_server.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp -lpthread
.PHONY:clean
clean:
	rm -f compile_server

2.测试

int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    Server svr;

    svr.Get("/hello",[](const Request &req, Response &resp)
    {
        //用来进行基本测试
        resp.set_content("hello httplib,你好", "text/plain; charset=utf-8");
    });
    
    svr.Post("/compile_and_run", [](const Request &req, Response &resp)
    {
        //用户请求的服务正文是我们想要的json string
        std::string in_json = req.body;
        std::string out_json;
        if(!in_json.empty())
        {
            CompileAndRun::Start(in_json, &out_json);
            resp.set_content(out_json, "application/json;charset=utf-8");
        }
    });

    svr.listen("0.0.0.0", atoi(argv[1]));
    return 0;
}

测试过程:

测试结果:


结语:关于项目本次的这里就结束了,如果大家有什么问题,欢迎大家在评论区留言~~~ 

相关推荐

  1. 负载均衡在线OJ项目day5】OJ服务模块概要

    2024-05-14 17:32:02       10 阅读
  2. 负载均衡在线OJ项目day3】运行模块

    2024-05-14 17:32:02       14 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-05-14 17:32:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-05-14 17:32:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-05-14 17:32:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-05-14 17:32:02       18 阅读

热门阅读

  1. PMP快速刷题记录分享

    2024-05-14 17:32:02       10 阅读
  2. Linux多线程http服务器技术点分析

    2024-05-14 17:32:02       9 阅读
  3. Pipenv 安装依赖包的源码

    2024-05-14 17:32:02       9 阅读
  4. 初步了解json文件

    2024-05-14 17:32:02       9 阅读
  5. html5关于WebSocket的一些特点与用例

    2024-05-14 17:32:02       11 阅读
  6. Kubernetes——命令指南

    2024-05-14 17:32:02       7 阅读
  7. C#如何通过反射获取外部dll的函数

    2024-05-14 17:32:02       11 阅读
  8. 力扣阶段练习(1).消失的数字

    2024-05-14 17:32:02       13 阅读
  9. 通过vue2来类比学习vue3

    2024-05-14 17:32:02       11 阅读
  10. Python 自动化脚本系列:第4集

    2024-05-14 17:32:02       9 阅读
  11. DOTCPP题目 2782: 整数大小比较

    2024-05-14 17:32:02       10 阅读
  12. vue2响应式和vue3响应式

    2024-05-14 17:32:02       10 阅读
  13. [Python]锁

    2024-05-14 17:32:02       11 阅读
  14. spring boot 线程池的应用

    2024-05-14 17:32:02       14 阅读