【负载均衡式在线OJ项目day2】编译模块和日志功能

前言

服务端有两个大模块,第一个是编译运行CompileServer,第二个是OJServer。

CompileServer分为编译模块Compiler,运行模块Runner,最后还需要一个CompileRun对前两个进行整合,并且打包成网络服务。

本文实现编译模块,并且引入日志功能

一.思路

编译模块仅仅用于提供编译服务,所以前提是我们已经有了要编译的源文件。我们先让提供编译运行服务的进程fork创建子进程,然后再让子进程程序替换成g++,去编译源文件。父进程则等待子进程退出,检查子进程是否完成编译任务。要检查编译是否完成,可以分析g++的退出码,但有更简单的方式——检查当前目录下是否有对应的可执行程序

子进程编译代码,要么编译成功,形成可执行程序,要么代码有语法错误,向标准错误输出错误信息,然后退出进程。

我们不应该让g++向屏幕打印信息,这样会很混乱。并且编译出错的信息将来是要返回给用户的,所以我们应该用文件来保存。因此,在子进程程序替换成g++之前,应该将标准错误重定向到compileError文件。

二.接口设计 

compile函数:

返回值:bool -> true则编译成功,生成可执行程序,否则失败(我们写的代码有问题或者用户提供的代码有语法错误)

参数:const string& fileName -> fileName是源文件的文件名,不带路径和后缀,需要我们在函数内部自己补充路径和后缀,以定位该文件。

三.日志组件

一条日志的信息包含日志等级,调用日志函数处所在的文件,所在代码行,调用时间,最后是信息。文件名和代码函数可以使用C语言中的__FILE__和__LINE__两个预定义的宏。

Log.hpp:

#pragma once
#include <iostream>
#include <string>
#include "Util.hpp"
namespace ns_log
{
    using namespace ns_util;
    enum LogLevel
    {
        INFO = 0,
        DEBUG,
        WARNING,
        ERROR,
        FATAL
    };
    /************
     * 参数:
     * 1.line 调用日志的代码所在行
     * 2.fileName 调用日志的代码所在文件
     * **********/
    inline std::ostream &Log(const std::string &level, const std::string &fileName, int line)
    {
        std::string message = "[" + level + "]";
        message += ("[" + fileName + "]");
        message += ("[" + std::to_string(line) + "]");
        message += ("[" + TimeUtil::getTimeStamp() + "]");

        std::cout << message; // 不使用endl刷新

        return std::cout;
    }

#define LOG(level) Log(#level, __FILE__, __LINE__)

}

四.代码实现

Compiler.hpp:

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <cstdlib>
#include "../Common/Util.hpp"
#include "../Common/Log.hpp"
// 只负责编译

namespace ns_complier
{
    using namespace ns_util;
    using namespace ns_log;
    class Compiler
    {
    public:
        Compiler()
        {
        }
        ~Compiler()
        {
        }

        /**********************
         * 返回值:编译成功,true;否则false
         * 输入参数:要编译的文件名(不含后缀)
         **********************/
        static bool compile(const std::string &fileName)
        {
            pid_t pid = fork();
            if (pid < 0)
            {
                LOG(ERROR) << "内部错误,创建子进程失败" << std::endl;
                return false; 
            }
            else if (pid == 0)
            {
                //这里重定向标准错误,是为了使程序替换后的g++把错误信息写入到临时文件
                umask(0);
                int _compileError = open(PathUtil::complieError(fileName).c_str(), O_CREAT | O_WRONLY, 0664);
                if (_compileError < 0)
                {
                    LOG(WARNING) << "没有成功形成complieError文件" << std::endl;
                    exit(1);
                }
                dup2(_compileError, 2);

                // g++ -o target src -std=c++11
                execlp("g++", "g++", "-o",\
                       PathUtil::exe(fileName).c_str(),\
                       PathUtil::src(fileName).c_str(), "-std=c++11", nullptr);

                LOG(ERROR) << "g++启动失败,可能是参数不正确" << std::endl;
                exit(1);
            }
            else
            {
                waitpid(pid, nullptr, 0);
                // 编译是否成功的判断方法-是否形成可执行程序
                if (FileUtil::isFileExists((PathUtil::exe(fileName))))
                {
                    LOG(INFO) << "编译成功" << std::endl;
                    return true;
                }
            }

            LOG(INFO) << "编译失败,没有形成可执行程序" << std::endl;
            return false;
        }
    };

};

Util.hpp:

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

    class TimeUtil
    {
    public:
        /**********
         * 功能:返回时间戳
         * ********/
        static std::string getTimeStamp()
        {
            struct timeval time;
            gettimeofday(&time, nullptr);
            return std::to_string(time.tv_sec);
        }
    };


    const std::string tmpPath = "./Tmp/";
    class PathUtil
    {
    public:
        /***************
         * 功能:给文件添加路径和后缀
         ***************/
        // 给文件添加路径和后缀
        static std::string addSuffix(const std::string &fileName, const std::string &suffix)
        {
            std::string pathName = tmpPath;
            pathName += fileName;
            pathName += suffix;
            return std::move(pathName);
        }

        /***************
         * 功能:构建源文件路径+后缀的完整文件名
         * 如1234-> ./Tmp/1234.cpp
         ***************/
        static std::string src(const std::string &fileName)
        {
            return addSuffix(fileName, ".cpp");
        }

        /***************
         * 功能:构建可执行程序路径+后缀的完整文件名
         * 如1234-> ./Tmp/1234.exe
         ***************/
        static std::string exe(const std::string &fileName)
        {
            return addSuffix(fileName, ".exe");
        } 

        /***************
         * 功能:构建标准错误文件路径+后缀的完整文件名
         * 如1234-> ./Tmp/1234.compile_error
         ***************/
        static std::string complieError(const std::string &fileName)
        {
            return addSuffix(fileName, ".compile_error");
        }
    };

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

  • 6
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值