
前言
身为一名程序员,想必大家都有接触过像leetcode这样的刷题网站,不知你们在刷题的过程中是否思考过一个问题:它们是如何实现在线编译运行的功能。如果你对此感到好奇,那么本文将一步步带你来实现一个简易在线编译器。
项目概述
项目的基本逻辑:前端用户在网页上输入代码与参数,后端通过多进程的方式来编译运行代码,然后将标准输出、标准错误信息返回给前端页面。
前后端交互数据格式
// 前端发送
{
"code": "代码",
"cpu_limit": "CPU限制",
"mem_limit": "内存限制"
}
// 后端发送
{
"reason": "错误原因",
"status": "状态码",
"stderr": "错误输出",
"stdout": "标准输出"
}
使用的第三方库
后端:
-
cpp-httplib:用于处理HTTP请求和响应。
-
jsoncpp:用于解析和生成JSON数据。
-
spdlog:用于日志记录。
前端:
-
jquery:简化JavaScript操作,方便进行DOM操作和Ajax请求。
-
ace:提供代码编辑器功能,支持语法高亮和代码自动完成。
运行效果
具体实现
后端逻辑
后端分为编译模块和运行模块,均使用多进程的方式来运行,并根据用户所选语言的语言来选择不同的编译器和运行方式。后端代码分为四部分:公共模块(工具类)、编译模块、运行模块、编译运行模块(整合编译与运行)。
公共模块
日记类
日记系统对spdlog进行了最低程度的封装,实现了单例日记系统,并定义宏来简化其使用。
//
// Created by lang liu on 24-4-23.
//
#pragma once
#ifdef DEBUG
#define SPDLOG_ACTIVE_LEVEL SPDLOG_LEVEL_DEBUG
#endif
#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
namespace ns_log {
//TODO 初始化日记 完善
class Log {
public:
static Log &getInstance() {
std::call_once(_flag, []() {
_instance = new Log();
});
return *_instance;
}
auto getLogger()
->std::shared_ptr<spdlog::logger>
{
return _logger;
}
private:
Log() {
_logger = spdlog::stdout_color_mt("nil");
_logger->set_level(spdlog::level::debug);
_logger->set_pattern("[%^%l%$] [%Y-%m-%d %H:%M:%S] [%t] [%s:%#] %v");
}
~Log() {
spdlog::drop_all();
}
private:
static std::once_flag _flag;
static Log *_instance;
std::shared_ptr<spdlog::logger> _logger;
};
std::once_flag Log::_flag;
Log *Log::_instance = nullptr;
#define LOG_DEBUG(...) SPDLOG_LOGGER_DEBUG(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_INFO(...) SPDLOG_LOGGER_INFO(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_WARN(...) SPDLOG_LOGGER_WARN(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_ERROR(...) SPDLOG_LOGGER_ERROR(Log::getInstance().getLogger(), __VA_ARGS__)
#define LOG_CRITICAL(...) SPDLOG_LOGGER_CRITICAL(Log::getInstance().getLogger(), __VA_ARGS__)
}
// #else
//[x] 无spdlog
// #include <iostream>
// #include <format>
// #include "util.hpp"
// namespace ns_log {
// using namespace ns_util;
// enum {
// INFO,
// DEBUG,
// WARN,
// ERROR,
// CRITICAL
// };
// inline std::ostream &Log(const std::string &level, const std::string &str) {
// std::string msg = std::format("[{}] [{}] [{}:{}] {}", level, TimeUtil::GetTimeStamp(), __FILE__, __LINE__,
// str);
// // auto ret = __FILE_NAME__;
// return std::cout << msg;
// }
// #define LOG_INFO(...) Log("INFO", __VA_ARGS__)
// #define LOG_DEBUG(...) Log("DEBUG", __VA_ARGS__)
// #define LOG_WARN(...) Log("WARN", __VA_ARGS__)
// #define LOG_ERROR(...) Log("ERROR", __VA_ARGS__)
// #define LOG_CRITICAL(...) Log("CRITICAL", __VA_ARGS__)
// }
// #endif
工具类
工具类分为时间工具、文件工具、路径工具。
时间工具:时间工具类用于生成时间戳,辅助文件工具生成唯一的文件名(UUID)。
文件工具:用于实现读写、创建、删除文件。
路径工具:用于更改文件的后缀。
//
// Created by lang liu on 24-4-23.
//
#ifndef OJ_UTIL_HPP
#define OJ_UTIL_HPP
#include "log.hpp"
#include <sys/time.h>
#include <sys/stat.h>
#include <fstream>
#include <atomic>
#include <unordered_map>
#include <filesystem>
#include <vector>
namespace ns_util
{
using namespace ns_log;
// 定义文件后缀名的映射表
static inline std::unordered_map<std::string, std::string> suffixTable {
{
"c_cpp", ".cc"},
{
"csharp", ".cs"},
{
"python", ".py"},
{
"javascript", ".js"}
};
// 定义可执行文件后缀名的映射表
static inline std::unordered_map<std::string, std::string> excuteTable {
{
"c_cpp", ".exe"},
{
"csharp", ".cs"},
{
"javascript", ".js"},
{
"python", ".py"}
};
// 时间工具类
class TimeUtil
{
public:
// 获取时间戳(秒)
static std::string GetTimeStamp()
{
struct timeval _tv{
};
gettimeofday(&_tv, nullptr); // 获取时间戳
return std::to_string(_tv.tv_sec);
}
// 获取时间戳(毫秒),用于生成随机文件名
static std