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;
}

测试过程:

测试结果:


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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/626565.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java面试八股之float和double的区别

Java中float和double的区别 存储空间与精度&#xff1a; double&#xff1a;占据64位&#xff08;8字节&#xff09;存储空间&#xff0c;属于双精度浮点数。它可以提供较高的精度&#xff0c;通常能够精确表示大约15到17位十进制数字&#xff0c;适合用于需要较高精度计算或…

SSM【Spring SpringMVC Mybatis】—— Spring(二)

如果对于Spring的一些基础理论感兴趣可见&#x1f447; SSM【Spring SpringMVC Mybatis】—— Spring&#xff08;一&#xff09; 目录 1、Spring中bean的作用域 1.1 语法 1.2 四个作用域 2、Spring中bean的生命周期 2.1 bean的生命周期 2.2 bean的后置处理器 2.3 添加后…

upload-labs靶场通关详解(1-15)

1.pass-01 查看源代码 是js&#xff0c;属于前端校验 可以通过禁用js来上传文件 2.pass-02 根据提示是MIME绕过 MIME&#xff1a;是设定某种扩展名的文件 用一种应用程序来打开的方式类型&#xff0c;当该扩展名文件被访问的时候&#xff0c;浏览器会自动使用指定应用程序来…

OpenAI发布新品GPT-4o,电影《HER》演绎的世界真的来了!

5月14日&#xff0c;OpenAI宣布推出最新旗舰生成式AI模型GPT-4o&#xff0c;它可以实时处理音频、视觉、并对文本进行推理。可以说这是一种全新的交互模式&#xff0c;它完美复刻电影《Her》的世界&#xff0c;标志着人工智能全感知时代的到来。 GpuMall智算云 | 省钱、好用、…

swiper 点击事件失效问题

在刚开始使用是 swiper 时&#xff0c;点击事件有时失效&#xff0c;这些个问题的原因是&#xff1a;swiper设置looptrue时&#xff0c;会生成虚拟slide进行填充&#xff0c;对这些虚拟slide元素进行操作是无效的。 简单粗暴的方法就是将 loop 置为 false ....................…

JVM面试题:85道JVM虚拟机面试题及答案

面试题 1 .简述Java堆的结构&#xff1f;什么是堆中的永久代(Perm Gen space)? JVM整体结构及内存模型 试题回答参考思路&#xff1a; 1、堆结构 JVM的堆是运行时数据区&#xff0c;所有类的实例和数组都是在堆上分配内存。它在JVM启动的时候被创建。对象所占的堆内存是由自…

开发测试必须知道的 10种 常见软件架构模式

你是否想知道企业大规模系统是如何设计的? 在软件开发开始之前&#xff0c;我们必须选择一个合适的架构&#xff0c;能提供所需的功能和质量特性。因此&#xff0c;在将架构应用到我们的设计之前&#xff0c;我们应该了解各种不同架构的特点。 01 什么是架构模式 根据维基百…

一文分享:抖音外卖城市合伙人如何申请合作?

在当今数字化时代&#xff0c;外卖和团购业务蓬勃发展&#xff0c;商家们纷纷寻求在多个平台上拓宽销售渠道&#xff0c;以获取更多的订单和利润&#xff0c;这也给创业者提供创来机会。在这其中&#xff0c;抖音外卖作为一股新势力&#xff0c;自然吸引了众多创业者的目光&…

解锁客户需求密码:银行业数据分析在业务决策中的关键作用

一、引言 在数字化和大数据时代的浪潮下&#xff0c;银行业正经历着前所未有的变革。作为数据分析领域的资深专家&#xff0c;我深知数据分析在银行业务发展中的重要性和价值。本文将从银行业数据分析的角度出发&#xff0c;深入探讨相关业务场景下的数据分析应用&#xff0c;…

斥巨资!韩国力撑芯片产业

KlipC报道&#xff1a;韩国为加强本国半导体产业竞争力&#xff0c;将推进一项超过10万亿韩元的支持计划。 大型科技公司微软、亚马逊和谷歌为了保持自己大型语言模型的竞争力&#xff0c;投入了大量的资金&#xff0c;全球对可生成AI应用的需求在不断增长&#xff0c;市场上对…

Vision Mamba 代码调试---Pycharm+AutoDL

《AutoDL使用手册》 1. 服务器租用与配置 先上项目链接&#xff1a; GitHub - hustvl/Vim: Vision Mamba: Efficient Visual Representation Learning with Bidirectional State Space Model 1.1 服务器租用与配置 根据环境要求&#xff0c;去租一个服务器&#xff1a;AutoDL算…

Ajax 学习

文章目录 1. 前置知识1.1 ajax 介绍1.2 XML 简介 2. AJAX 学习2.1 AJAX基础学习&#xff08;1&#xff09;AJAX的特点&#xff08;2&#xff09;AJAX 初体验&#xff08;3&#xff09;服务端响应json 数据 2.2 IE 缓存问题2.3 请求超时和网络异常2.4 手动取消请求2.5 重复请求2…

鸿蒙 DevEcoStudio:用户名密码获取保存

【使用首选项实现用户名密码保存获取】 打开src/main/ets/entryability路径下的EntryAbility.ts文件 在 export default class EntryAbility extends UIAbility {onCreate(want, launchParam) {hilog.info(0x0000, testTag, %{public}s, Ability onCreate);下边添加内容&…

【JVM】调优工具

这里简单介绍一下各种调优用到的工具 一&#xff0c;环境准备 首先我们需要准备好Java环境&#xff0c;和win上的jdk环境&#xff08;图形化界面如jconsole只有jdk中有&#xff09;。 有这样一个类Prolem&#xff0c;每个线程都会带来100个垃圾对象&#xff0c;线程new完100…

智慧公厕,提升公共厕所管理效率的信息化变革

现代社会中&#xff0c;公共厕所的管理成为一个不可忽视的问题。随着城市化进程的加快&#xff0c;人们对公厕的需求日益增加&#xff0c;但公厕的管理却面临诸多困难。为了解决这一问题&#xff0c;智慧公厕应运而生&#xff0c;通过信息化的变革&#xff0c;提高公厕的管理效…

QT函数整理

目录 1. 适应高分辨率函数 1. 适应高分辨率函数 自动适应调整设备安装QT的UI分辨率&#xff1a; QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 加载位置&#xff1a;

第四届辽宁省大学生程序设计竞赛

比赛经历&#xff1a;2024.5.14简单vp了一个小时只写出了签到题4个然后跑路了 补题&#xff1a;感觉其他题有点太抽象了主要补了一题&#xff0c;在区间问题中数据结构的使用 比赛链接[点我即可] 目录 A.欢迎来到辽宁省赛 B.胜率 F.隔板与水槽 H.取石子 L.区间与绝对值 …

第188题|幂级数的展开的常规方法(一)|武忠祥老师每日一题

解题思路&#xff1a;求幂级数有两种方法&#xff0c;一种是直接法&#xff0c;这里显然不太好求&#xff0c;还有一种是利用现有展开式展开&#xff0c;我们看到分母 可以分解因式成(x6)(x-1),进而拆解成一次式。拆解成一次式的目的是为了使用一下两个展开式。 第一步&#xf…

NARUTO 复现记录

1 环境配置 下载项目&#xff0c;一定要 git 下载全项目&#xff0c;下载完后要检查third_parities 里面的coslam和neural_slam_eval 文件全不全。 git clone --recursive https://github.com/oppo-us-research/NARUTO.git 环境配置 注意 bash scripts/installation/conda…

【哔哩哔哩下载】Bilidown,B站下载工具网站,永久免费使用NO.98

本文一共:448 个字,需要阅读:2 分钟,更新时间:2024年5 月14日,部分内容具有时效性,如有失效请留言,阅读量:0 输入B站视频网址就可以下载了 除了可以下载视频&#xff0c;还能获取弹幕、查看封面&#xff0c;如果你想解锁1080P画质&#xff0c;就需要扫码登录。 点击右下角的齿…