【负载均衡式在线OJ项目day4】编译运行功能整合及打包网络服务

news2024/11/19 17:41:40

一.前言

前面两天完成了编译和运行两个子模块,今天的任务是完成CompileRun模块,它的任务如下:

  1. 解析来自客户端的Json字符串,反序列化提取编译运行需要的数据,即代码,时间限制和空间限制
  2. 把代码写入临时文件,形成编译的源文件
  3. 调用编译和运行模块,把各字段序列化构建Json字符串,返回给外部构建response的body部分
  4. 最后删除编译运行期间形成的临时文件

另外,在CompileServer.cpp文件将整套编译运行服务打包成网络服务,具体来说:

使用httplib库,使用post方法注册回调函数。当接收到客户端编译服务的请求时,会调用回调函数,request中的Json字符串交给CompileRun模块,得到Json字符串,用它构建response的body部分,然后返回给客户端

整个编译运行服务的逻辑如下:

 

二.设计思路

CompileRun:

首先对传入的inJson反序列化,提取出代码code,输入input,时间限制cpuLimit,空间限制memLimit。

然后在工具模块实现一个形成独一无二,不会产生冲突的文件名方法,可采用,毫秒级时间戳和原子级计数器组合来形成文件名。

接着将代码写入到该文件中,随后调用编译模块和运行模块。

如果编译报错,则填写状态码字段status和原因reason,直接把outJson返回;;如果由于其它原因导致失败,如打开文件,程序替换等,则是我们服务端内部出现问题,与用户无关,则填写status和reason,直接返回outJson;如果编译运行成功,则除了填写以上两个字段,还有标准输出stdout和标准错误stderr,然后返回Json字符串

最后还要使用unlink接口,将编译运行形成的.cpp,.compile_error, .exe,.stdin, .stdout, .stderr临时文件全部清空

 三.接口设计

参数:

1. const std::string &inJson, 输入型参数,来自客户端的Json字符串,包含以下字段:

         * 1.code:用户提交的代码

         * 2.input:用户提交的输入,不做处理

         * 3.cpuLimit:CPU限制时间(s)

         * 4.memLimit:虚拟内存限制大小(KB)

2. std::string *outJson, 输出型参数,将来发送给客户端的Json字符串,包含以下字段:

         * 必填:

         * 1.status:状态码

         *    0:运行成功 -1:代码为空 -2:编译错误 -3:未知错误 >0:收到信号异常终止

         * 2.reason:请求结果

         * 选填:

         * 3.stdout:程序输出运行结果

         * 4.stderr:错误结果

四.代码实现

CompileRun.hpp:

#pragma once

#include <jsoncpp/json/json.h>
#include <string>
#include "Compiler.hpp"
#include "Runner.hpp"
#include "../Common/Log.hpp"
#include "../Common/Util.hpp"
namespace ns_compile_run
{
    using namespace ns_runner;
    using namespace ns_complier;
    using namespace ns_util;
    using namespace httplib;
    class CompileRun
    {
    public:
        /**********************
         * 参数:
         * 1.inJson:通过http来自client的json字符串
         * 2.outJson:输出型参数,将来要发送给client
         *
         * inJson字段:
         * 1.code:用户提交的代码
         * 2.input:用户给自己提交代码对应的输入,不做处理
         * 3.cpuLimit:CPU限制时间(s)
         * 4.memLimit:虚拟内存限制大小(KB)
         *
         * outJson字段:
         * 必填:
         * 1.status:状态码
         *    0:运行成功 -1:代码为空 -2:编译错误 -3:未知错误 >0:收到信号异常终止
         * 2.reason:请求结果
         * 选填:
         * 3.stdout:程序输出运行结果
         * 4.stderr:错误结果
         * *******************/
        static void start(const std::string &inJson, std::string *outJson)
        {
            // 反序列化
            Json::Value inValue;
            Json::Reader reader;
            reader.parse(inJson, inValue);
            std::string code = inValue["code"].asString();
            std::string input = inValue["input"].asString();
            int cpuLimit = inValue["cpuLimit"].asInt();
            int memLimit = inValue["memLimit"].asInt();

            int status = 0;
            int runRet = 0;
            std::string fileName;

            if (code.size() == 0)
            {
                status = -1; // 代码为空
                goto END;
            }

            fileName = FileUtil::uniqFileName();                     // 形成唯一文件名
            if (!FileUtil::writeFile(PathUtil::src(fileName), code)) // 形成临时源文件
            {
                status = -3; // 未知错误
                goto END;
            }

            if (!Compiler::compile(fileName))
            {
                status = -2; // 编译错误
                goto END;
            }

            runRet = Runner::run(fileName, cpuLimit, memLimit);
            if (runRet < 0)
            {
                status = -3; // 未知错误
            }
            else if (runRet > 0)
            {
                status = runRet; // 异常终止
            }
            else
            {
                status = 0; // 运行成功
            }

        END:
            Json::Value outValue;
            outValue["status"] = status;
            outValue["reason"] = statusToDesc(status, fileName);

            if (status == 0) // 编译运行成功
            {
                std::string _stderr;
                std::string _stdout;
                FileUtil::readFile(PathUtil::stderr(fileName), &_stderr, true);
                FileUtil::readFile(PathUtil::stdout(fileName), &_stdout, true);
                outValue["stderr"] = _stderr;
                outValue["stdout"] = _stdout;
            }

            Json::StyledWriter writer;
            *outJson = writer.write(outValue);

            //removeTmpFiles(fileName);
        }

        /***************************
         * 功能:根据状态码返回相应的reason
         * ************************/
        static std::string statusToDesc(int status, const std::string &fileName)
        {
            std::string desc;
            switch (status)
            {
            case 0:
                desc = "编译运行运行成功";
                break;
            case -1:
                desc = "代码为空";
                break;
            case -2:
                FileUtil::readFile(PathUtil::complieError(fileName), &desc, true);
                break;
            case -3:
                desc = "未知错误";
                break;
            case SIGFPE: // 8
                desc = "浮点错误";
                break;
            case SIGXCPU: // 24
                desc = "运行超时";
                break;
            case SIGABRT: // 6
                desc = "内存超出限制";
                break;
            default:
                desc = "未知";
                break;
            }
            return desc;
        }

        /************************
         * 功能:删除编译运行形成的临时文件
         * 最多有:.cpp, .compile_error, .exe, .stdin, .stdout, .stderr
         * ******************/
        static void removeTmpFiles(const std::string &fileName)
        {
            std::string _src = PathUtil::src(fileName);
            std::string _compileError = PathUtil::complieError(fileName);
            std::string _exe = PathUtil::exe(fileName);
            std::string _stdin = PathUtil::stdin(fileName);
            std::string _stdout = PathUtil::stdout(fileName);
            std::string _stderr = PathUtil::stderr(fileName);

            if (FileUtil::isFileExists(_src))
            {
                unlink(_src.c_str());
            }
            if (FileUtil::isFileExists(_compileError))
            {
                unlink(_compileError.c_str());
            }
            if (FileUtil::isFileExists(_exe))
            {
                unlink(_exe.c_str());
            }
            if (FileUtil::isFileExists(_stdin))
            {
                unlink(_stdin.c_str());
            }
            if (FileUtil::isFileExists(_stdout))
            {
                unlink(_stdout.c_str());
            }
            if (FileUtil::isFileExists(_stderr))
            {
                unlink(_stderr.c_str());
            }
        }
    };
}

工具模块用到的一些方法:

#pragma once
#include <string>
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/time.h>
#include <atomic>
#include <fstream>
namespace ns_util
{

    class TimeUtil
    {
    public:
        /**********
         * 功能:当前毫秒级时间
         * ********/
        static std::string getTimeMs()
        {
            struct timeval time;
            gettimeofday(&time, nullptr);
            return std::to_string(time.tv_sec + time.tv_usec / 1000);
        }
    };

    class FileUtil
    {
    public:
        /***************
         * 功能:判定文件是否存在
         * 参数:pathName是完整文件名
         * 如1234-> ./tmp/1234.stderr
         ***************/
        static bool isFileExists(const std::string &pathName)
        {
            struct stat st;
            if (stat(pathName.c_str(), &st) == 0)
            {
                // 获取属性成功,说明文件存在
                return true;
            }
            return false;
        }

        /************************
         * 参数:
         * 1.target:带路径和后缀的完整文件名
         * 2.content:要写入的内容
         * *********************/
        static bool writeFile(const std::string& target, const std::string& content)
        {
            std::ofstream out(target.c_str());
            if (!out.is_open())
            {
                return false;
            }

            out.write(content.c_str(), content.size());
            return true;
        }

        /************************
         * 参数:
         * 1.target:带路径和后缀的完整文件名
         * 2.content:输入型参数,把文件内容写到它里面
         * 3.keep:是否保留'\n'(getline不会读取换行符)
         * *********************/
        static bool readFile(const std::string& target, std::string* content, bool keep = false)
        {
            std::ifstream in(target.c_str());
            if (!in.is_open())
            {
                return false;
            }
            
            std::string line;
            while (std::getline(in, line))
            {
                line += keep ? "\n" : "";
                (*content) += line;
            }
            return true;
        }

        /*****************
         * 功能:用时间和原子计数器生成一个独一无二,不产生冲突的文件名
         * ******************/
        static std::string uniqFileName()
        {
            static std::atomic<int> id(0);
            std::string fileName = TimeUtil::getTimeMs();
            fileName += "_";
            fileName += to_string(id);
            id++;
            return fileName;
        }
    };
};

CompileServer.cpp:

#include <iostream>
#include <string>
#include <cstdlib>
#include "../Common/httplib.h"
#include "CompileRun.hpp"
#include "../Common/Util.hpp"
#include "../Common/Log.hpp"

using namespace ns_compile_run;
using namespace httplib;
using namespace ns_log;

void Usage(const char* proc)
{
    std::cout << proc << "serverPort" << std::endl;
}
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        return 1;
    }
    Server svr;
    svr.Post("/compile_and_run", [](const Request& req, Response& resp)
    {
        std::string inJson = req.body;
        if (!inJson.empty())
        {
            std::string outJson;
            CompileRun::start(inJson, &outJson);
            resp.set_content(outJson, "application/json;charset=utf-8");
        }
    }); //注册回调方法

    svr.listen("0.0.0.0", atoi(argv[1])); //启动网络服务
    
    return 0;
}

五.备注

  1. httplib是一个只需要包含头文件,而无需安装动态库的“only header”库,它的方法定义都在httplib.h中了,我们只需将它拷贝到项目目录下即可包含使用
  2. 要使用httplib,必须使用高版本gcc编译器(7,8,9),否则编译或者运行时会出现问题
  3. 想要对编译运行模块测试,可以使用postman工具向服务端发送request

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

太阳能无人机的多元化应用

随着新能源技术的不断发展和成熟&#xff0c;太阳能在无人机的应用技术已经成熟。太阳能无人机得到了量产和广泛的应用。传统无人机相比&#xff0c;太阳能无人机无需燃油&#xff0c;运行费用低廉&#xff0c;搭载多种高科技设备&#xff0c;能够高效、多元化地采集和分析各类…

公司活动想找媒体报道宣传怎样联系媒体?

作为公司宣传负责人,我深知媒体报道对于企业活动宣传的重要性。然而,在过去,每当有重要活动需要媒体曝光时,我总会被繁琐的媒体联系工作所困扰。 那时,我需要一家家地查询媒体联系方式,发送邮件、打电话,甚至亲自前往媒体机构进行沟通。然而,这样的过程不仅费时费力,而且效率低…

成本降低 90%,出海社交平台 Typing 基于 Databend 的大数据探

Typing&#xff08;输入中科技&#xff09;成立于 2022 年&#xff0c;是一家主要面向东南亚、拉美、中东等海外地区提供社交平台的出海企业。其社交平台类似于国内的 Soul、陌陌等&#xff0c;提供视频直播、语音聊天室、短视频、生活分享、文字聊天等社交功能&#xff0c;注册…

英语学习笔记5——Nice to meet you.

Nice to meet you. 很高兴见到你。 词汇 Vocabulary Mr. 先生 用法&#xff1a;自己全名 / 姓 例如&#xff1a;Mr. Zhang Mingdong 或 Mr. Zhang&#xff0c;绝对不能是 Mr. Mingdong&#xff01; Miss 女士&#xff0c;小姐 未婚 用法&#xff1a;自己全名 / 姓 例如&#…

区块链(打新)如何被割韭菜

看上去&#xff0c;像我只要去每个都买一遍新发行的代币&#xff0c;一定可以成功的 但是好像没有想象中这么简单&#xff0c;因为这些山寨币&#xff0c;庄家可以自己控盘的&#xff0c;看上去好像有跌宕起伏的买卖&#xff0c;但是一单掀桌子&#xff0c;庄家他自己都不玩了…

SOCKET编程(3):相关结构体与函数

相关结构体与函数 sockaddr、sockaddr_in结构体 sockaddr和sockaddr_in详解 struct sockaddr共16字节&#xff0c;协议族(family)占2字节&#xff0c;IP地址和端口号在sa_data字符数组中 /* Structure describing a generic socket address. */ struct sockaddr {__SOCKADDR…

推荐3个实用的github开源项目

目录&#xff1a; 1、AI生成高清短视频 2、媒体平台爬虫 3、文本转语音项目

ubuntu server 22.04 安装docker、docker-compose

ubuntu server 22.04安装docker有两种方式&#xff0c;第一种是使用ubuntu镜像源的软件包进行安装&#xff0c;第二种使用官方GPG密钥手动添加Docker存储库方式进行安装&#xff0c;两种方式都可以&#xff0c;但第二种方式略复杂&#xff0c;这里介绍第一种比较简单的安装方式…

图像ISP——AGC参数解析

前言 AWB和AGC是两种常见的自动调整功能。AWB用于自动调整图像的白平衡&#xff0c;以确保颜色在不同光照条件下仍然看起来自然。而AGC则用于自动调整图像的增益&#xff0c;以在不同的亮度条件下保持适当的曝光。 代码例程 static AWB_AGC_TABLE_S g_stAwbAgcTable {/* bvali…

深入解析Java中Set接口

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。运营社区&#xff1a;C站/掘金/腾讯云&#xff1b;欢迎大家常来逛逛 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一…

【前端基础】CSS样式+Vue中绘制时间轴

深度选择器 在 Vue.js 中&#xff0c;/deep/、>>>、:deep 和 ::v-deep 这些都是深度选择器&#xff0c;用于修改子组件的样式。它们主要用于解决作用域样式和组件样式之间的冲突问题。 1. /deep/ 或 >>> /deep/ 和 >>> 是相同的选择器&#xff0c;…

spring boot参数验证注解@NotNull、@NotBlank和@NotEmpty区别

目录 前言说明举例 前言 使用spring boot参数验证是常常会使用NotNull、NotBlank和NotEmpty三个判断是否不为空的注解&#xff0c;中文都有不能为空的意思&#xff0c;大部分使用者都傻傻分清它们之间到底有什么区别。今天就让咱们来一起探索它们之间的不同吧。 说明 注解名…

一文了解webpack和vite中Tree-Shaking

1、什么是Tree-Shaking 1.1 摇树优化&#xff08;Tree Shaking&#xff09;是Webpack中一种用于优化JavaScript代码的技术。它的目标是通过静态分析&#xff0c;从代码中剔除未被使用的模块&#xff0c;从而减少最终打包文件的大小。 1.2 Tree-shaking 它的名字来源于通过摇晃…

【线性代数】英语版听课笔记

线性代数 - 北京航天航空大学&#xff08;英文版&#xff09;_哔哩哔哩_bilibili 39.concept of vector space in this lecture we will studyvector space&#xff0c; the concept of basis dimension and coordinates 向量空间的维数&#xff1a;向量空间的基底所含向量的…

在 Ubuntu系统中,可以使用以下几种方法查看网络速率

1 使用终端命令&#xff1a;可以使用ifconfig命令查看网络接口的信息&#xff0c;包括网络接口名称、IP地址、子网掩码等。也可以使用nload命令查看网络流量和传输速率。 2 使用网络监控工具&#xff1a;例如nethogs&#xff0c;可以更加直观地查看网络吞吐量。 3 使用网络测…

*****水上飞机:继承,虚函数,虚继承

一题目 请设计以下航行器、飞机、船、水上飞机等 4 个类。 CRAFT 为航行器类&#xff0c;是公共基类&#xff0c;提供航行器的基本特性。包括&#xff1a; 一个保护数据成员&#xff1a;speed(速度)。 三个公有成员函数&#xff1a;构造函数(初始化速度)、析构函数和 Show 函数…

量化交易基础知识

目录 1 什么是量化交易2 谁可以成为一个量化交易员3 量化交易的优势与特点4 量化交易员的成长之路 1 什么是量化交易 量化交易也称算法交易&#xff0c;是一种严格按照计算机算法进行买卖证券决策的交易方式。更具体一点解释就是&#xff0c;量化交易利用数学模型、统计分析和计…

Ansys界面设计:ACT入门

目录 Introduction What is customization? What is extensibility? What is ACT? What capabilities does ACT provide? What skills are required for using ACT? How do I begin using ACT? Where can I find published ACT apps? Introduction 来自官方帮助…

软件开发故事 - 我对 CTO 撒谎并挽救了项目

原文&#xff1a;GrumpyOldDev - 2024.04.18 这是几年前的事情了。还记得在我职业生涯的初期&#xff0c;父亲曾告诉我&#xff0c;做好工作往往意味着要在上司的阻碍下做好需要做的事情。他的意思是&#xff0c;你可以让上司成功并感到快乐&#xff1b;也可以让上司做每一个决…

window10设置静态IP

右键桌面网络图标 点击属性 点击要查看的网络 点击详细信息 获得网络连接详细信息 右键WiFi符号 或者其他方式进入网络与internet中心 点击 WLAN 点击属性 点击编辑&#xff08;点击一个即可&#xff09; 选择手动将刚才的信息方进入即可 完成