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

一.前言

前面两天完成了编译和运行两个子模块,今天的任务是完成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)

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

* 必填:

* 1.status:状态码

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

* 2.reason:请求结果

* 选填:

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

* 4.stderr:错误结果

四.代码实现

CompileRun.hpp:

cpp 复制代码
#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());
            }
        }
    };
}

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

cpp 复制代码
#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:

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
相关推荐
leoufung22 分钟前
vim 多个关键字高亮插件介绍
linux·编辑器·vim
Karoku0661 小时前
【CI/CD】CI/CD环境搭建流程和持续集成环境配置
运维·ci/cd·docker·容器·kubernetes·prometheus
Nerd Nirvana3 小时前
软考—系统架构设计(案例 | 论文)
linux·系统架构·软件工程·软考·计算机基础
勤奋的凯尔森同学4 小时前
webmin配置终端显示样式,模仿UbuntuDesktop终端
linux·运维·服务器·ubuntu·webmin
闲猫5 小时前
go orm GORM
开发语言·后端·golang
丁卯4045 小时前
Go语言中使用viper绑定结构体和yaml文件信息时,标签的使用
服务器·后端·golang
chengooooooo5 小时前
苍穹外卖day8 地址上传 用户下单 订单支付
java·服务器·数据库
人间打气筒(Ada)7 小时前
MySQL主从架构
服务器·数据库·mysql
落笔画忧愁e8 小时前
FastGPT快速将消息发送至飞书
服务器·数据库·飞书
小冷爱学习!8 小时前
华为动态路由-OSPF-完全末梢区域
服务器·网络·华为