【负载均衡式在线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
相关推荐
小陈phd13 分钟前
Vscode LinuxC++环境配置
linux·c++·vscode
运维&陈同学16 分钟前
【zookeeper01】消息队列与微服务之zookeeper工作原理
运维·分布式·微服务·zookeeper·云原生·架构·消息队列
是阿建吖!17 分钟前
【Linux】进程状态
linux·运维
hzyyyyyyyu31 分钟前
内网安全隧道搭建-ngrok-frp-nps-sapp
服务器·网络·安全
草莓base32 分钟前
【手写一个spring】spring源码的简单实现--容器启动
java·后端·spring
明明跟你说过1 小时前
Linux中的【tcpdump】:深入介绍与实战使用
linux·运维·测试工具·tcpdump
Ljw...1 小时前
表的增删改查(MySQL)
数据库·后端·mysql·表的增删查改
编程重生之路1 小时前
Springboot启动异常 错误: 找不到或无法加载主类 xxx.Application异常
java·spring boot·后端
薯条不要番茄酱1 小时前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea