负载均衡的在线OJ项目
负载均衡的在线OJ项目
- 所用技术与开发环境
- 项目的宏观结构
- 我们的项目要实现的最重要的功能:
- 我们项目的整体结构:
- 项目编写思路
- compile_server模块
- compiler模块设计与编写
- Runner模块设计与编写
- 细节
- setrlimit系统调用函数
- 程序流程图
- Compile_and_run模块设计与编写
- 程序流程图
- Compile_server.cc的实现
- 测试Compile_server模块
- Comm模块编写
- 代码
- stat函数介绍
- unlink函数介绍
- gettimeofday与time函数介绍
- boost::split函数介绍
- 生成唯一文件名的方法介绍(原子计数器+毫秒级的时间戳)
- Oj_Server模块编写
- Oj_Server模块的功能
- 基于MVC结构的Oj_Server模块的设计
- 代码实现
- 程序流程图
- 负载均衡功能介绍
- 一键上线功能介绍
- 测试Oj_Server模块
- 前端的设计
- 顶层makefile设计
- 本项目的亮点
- 项目扩展方向
- 1. 功能扩展
- 2. 性能优化
- 3. 安全性增强
- 4. 运维与监控
本项目旨在实现一个负载均衡式的在线OJ系统。码云直达
所用技术与开发环境
所用技术:
C++ STL
标准库。Boost
标准库。cpp-httplib
开源轻量级网络库。Ctemplate
网页渲染库。jsoncpp
库对结构化数据进行序列化、反序列化。- 多进程、多线程。
MYSQL C++ connect
。- ace在线编辑器的简单使用(前端,简单使用,参考AI)。
html/css/js/jquery/ajax
(前端,简单使用,使用AI生成)。
开发环境:
ubuntu 24.04
云服务器。VScode
。mysql workbench
。
项目的宏观结构
本项目主要分为三个核心模块:
Comm
公共模块:主要包含一些头文件,这些头文件中的方法是在很多模块中都会使用的。compile_server
模块:提供编译运行的网络服务。oj_server
模块:提供获取题库、获取单个题目列表、负载均衡的选择后端编译主机等功能,后续详细介绍。
我们的项目要实现的最重要的功能:
- 主要的业务逻辑:让用户能像
leetcode
等在线OJ平台一样查看题目列表、在线编写代码并提交代码,并获取结果的功能。
我们项目的整体结构:
项目编写思路
- 先完成
compile_server
编译运行服务的编写,编写完通过测试工具测试。 - 再编写OJ_Server模块的编写,完成
MYSQL
版本的OJ_Server
。 - 前端编写。
- 顶层
makefile
编写,实现一键编译项目。 - 结项与项目扩展思路。
compile_server模块
compiler模块设计与编写
compile.hpp
:
#pragma once
#include<string>
#include<unistd.h>
#include <fcntl.h>
#include"../Comm/Util.hpp"
#include"../Comm/Log.hpp"
#include <sys/stat.h>
#include <sys/wait.h>
#include<iostream>
namespace Compile_ns
{using namespace util_ns;using namespace Log_ns;class Compiler{public:Compiler(){}~Compiler(){}static bool Compile(const std::string& filename){pid_t pid = fork();if(pid < 0){//打印错误信息LOG(ERROR) << "内部错误,进程创建失败" << "\n";return false;}else if(pid == 0){//子进程//新建一个文件,用来存编译时的错误信息(如果有)umask(0);int _stderr = open(Path_Util::Compile_Error_Path(filename).c_str(),O_CREAT | O_WRONLY,0644);if(_stderr < 0){//文件打开失败了,无法继续执行LOG(WARNNING) << "Compile_Error file 创建失败" << "\n";exit(1);}//重定向标准错误流,让它将内容写到我们的文件中dup2(_stderr,2);//可以开始执行源代码了//g++ -o target src -std-c++11execlp("g++","g++","-o",Path_Util::Exe_Path(filename).c_str(),Path_Util::Src_Path(filename).c_str(),"-std=c++11","-D","COMPILER_ONLINE",nullptr);//内部错误LOG(ERROR) << "启动编译器失败,可能是参数传错了" << "\n";exit(2);}else if(pid > 0){//父进程waitpid(pid,nullptr,0);//阻塞式的等待子进程编译完代码//判断是否编译成功,就看exe文件是否生成了if(!File_Util::Is_Exist_File(Path_Util::Exe_Path(filename)))//编译失败{LOG(ERROR) << "编译失败,没有形成可执行文件" << "\n";return false;}}//走到这里一定编译成功了LOG(INFO) << "编译成功" << "\n";return true;}};
};
编译服务,程序流程图:
Runner模块设计与编写
该模块主要是负责运行编译成功后的可执行程序,所以如果用户提交的代码编译失败,是不会运行这个模块的相关函数的。
Runner.hpp
:
#pragma once
#include<string>
#include<fcntl.h>
#include<unistd.h>
#include"../Comm/Util.hpp"
#include"../Comm/Log.hpp"
#include<sys/wait.h>#include <sys/resource.h>namespace run_ns
{using namespace Log_ns;using namespace util_ns;class Runner{private:/* data */public:Runner(/* args */);~Runner();static void SetProcLimit(int _cpu_rlim,int _mem_rlim)//_mem_rlim的单位是kB,_cpu_rlim是s{struct rlimit cpu_rlimit;cpu_rlimit.rlim_cur = _cpu_rlim;cpu_rlimit.rlim_max = RLIM_INFINITY;setrlimit(RLIMIT_CPU,&cpu_rlimit);struct rlimit mem_rlimit;mem_rlimit.rlim_cur = _mem_rlim*1024;//1KBmem_rlimit.rlim_max = RLIM_INFINITY;setrlimit(RLIMIT_AS,&mem_rlimit); }static int Run(const std::string& file_name,int cpu_rlim,int mem_rlim){//1.创建错误文件、输出文件、输入文件umask(0);int _stderr = open(Path_Util::Stderr_Path(file_name).c_str(),O_CREAT | O_WRONLY,0644);int _stdout = open(Path_Util::Stdout_Path(file_name).c_str(),O_CREAT | O_WRONLY,0644);int _stdin = open(Path_Util::Stdin_Path(file_name).c_str(),O_CREAT | O_RDONLY,0644);if(_stderr < 0 || _stdin < 0 || _stdout < 0){LOG(ERROR) << "运行时打开标准文件失败" << "\n";return -1;}int pid = fork();if(pid < 0){LOG(ERROR) << "创建进程失败" << "\n";::close(_stderr);::close(_stdin);::close(_stdout);return -2;}if(pid == 0){//设置当前进程的CPU时间和内存占用上限SetProcLimit(cpu_rlim,mem_rlim);//重定向标准输入、错误、输出到对应的文件中dup2(_stderr,2);dup2(_stdout,1);dup2(_stdin,0);//子进程execl(Path_Util::Exe_Path(file_name).c_str(),Path_Util::Exe_Path(file_name).c_str(),nullptr);//走到这里说明execl执行失败了LOG(ERROR) << "可执行程序执行失败,可能是参数传错了" << "\n";exit(1); }else{//父进程int status;waitpid(pid,&status,0);::close(_stderr);::close(_stdin);::close(_stdout);LOG(INFO) << "运行完成,INFO: " << (status & 0x7f) << "\n";return (status & 0x7f);//返回信号码}}};}
细节
-
一般的在线oj平台给每道题都会设置超时时间和内存上限值,但是可能不会展示给用户,这两个功能我们的项目中也有体现,只需要调用系统调用
setrlimit
就可以设置程序执行的最大cpu
时间和使用内存,如果一但超过这个值,OS就会给进程发送信号,终止它。 -
打开输入、输出、错误等临时文件放在主进程中,是为了给上层报告错误,如果放在子进程中,不好返回,放在主进程中,这样子进程就只会出现运行错误了,会返回信号码(一般程序替换不会执行失败,除非参数错误,这些我们不考虑,打印日志就行,不必返回给用户)。
setrlimit系统调用函数
int setrlimit(int resource, const struct rlimit *rlim);//设置
int getrlimit(int resource, struct rlimit *rlim);//获取struct rlimit {rlim_t rlim_cur; // 软限制rlim_t rlim_max; // 硬限制
};
-
函数功能:用于设置每个进程资源限制。它允许你控制诸如最大文件大小、最大CPU时间、最大堆栈大小等资源的使用量。
-
软限制与硬限制:
rlim_max
:硬限制只有超级用户才能够设置,这是所有进程的软限制可以被设置的最大值。rlim_cur
(软限制):这是当前生效的限制值。如果当前进程尝试分配超过此值的资源,则该分配操作将失败,还会收到相关的信号,导致进程被终止或操作失败(如malloc
申请内存失败返回NULL
)。
-
函数参数:
-
int resource
:指定你要查询或修改的资源类型。- RLIMIT_CPU: 最大CPU时间。如果进程超过了这个限制,则会收到
SIGXCPU
信号。默认情况下,这会导致进程终止。可以通过设置更宽松的限制来改变这一行为,或者处理该信号以执行其他操作。 - RLIMIT_FSIZE: 文件大小限制。若进程试图超过此限制,则会产生
SIGXFSZ
信号。通常,这也意味着相关操作失败,并可能记录错误信息。 - RLIMIT_STACK, RLIMIT_AS, RLIMIT_CORE 等:对于这些限制,如果进程尝试超出其软限制,结果取决于具体的资源和操作。例如,增加栈空间失败可能导致程序崩溃;而对核心转储文件大小的限制不会直接产生信号,但会影响核心转储的行为。
RLIMIT_AS
则是对进程地址空间的大小做限制。
- RLIMIT_CPU: 最大CPU时间。如果进程超过了这个限制,则会收到
-
const struct rlimit *rlim
:输入型参数,用于添加当前进程对某个资源的软限制、硬限制。我们通常将硬限制设置为RLIM_INFINITY
,这是一个宏表示无限大。防止默认的硬限制比我们的软限制还小,影响预期结果。
-
程序流程图
Compile_and_run模块设计与编写
这个模块主要将我们之前写的编译、运行模块整合在一起,给外部提供一个简单的接口,让它可以不不关注我们这个编译运行模块的细节。
- 外部只需要给我们提供用户提交来的完整代码(
json
串的形式),最终我们这个模块帮它执行编译运行后,也给外部返回一个特定格式的json
串。
Compile_and_run.hpp
:
#pragma once
#include "Compile.hpp"
#include "Runner.hpp"
#include <jsoncpp/json/json.h>namespace compile_and_run_ns
{using namespace Compile_ns;using namespace run_ns;class CompileAndRun{public:/***去除此时编译运行形成的临时文件**/static void RemoveTempFile(const std::string &file_name){// src / exe /stderr/stdout/stdin/compile_errstd::string SrcPath = Path_Util::Src_Path(file_name);if (File_Util::Is_Exist_File(SrcPath.c_str()))unlink(SrcPath.c_str());std::string ExePath = Path_Util::Exe_Path(file_name);if (File_Util::Is_Exist_File(ExePath.c_str()))unlink(ExePath.c_str());std::string StderrPath = Path_Util::Stderr_Path(file_name);if (File_Util::Is_Exist_File(StderrPath.c_str()))unlink(StderrPath.c_str());std::string StdoutPath = Path_Util::Stdout_Path(file_name);if (File_Util::Is_Exist_File(StdoutPath.c_str()))unlink(StdoutPath.c_str());std::string StdinPath = Path_Util::Stdin_Path(file_name);if (File_Util::Is_Exist_File(StdinPath.c_str()))unlink(StdinPath.c_str());std::string CompileErrPath = Path_Util::Compile_Error_Path(file_name);if (File_Util::Is_Exist_File(CompileErrPath.c_str()))unlink(CompileErrPath.c_str());}/*** code < 0 -> 内部错误/代码为空* code == 0 -> 代码执行无问题* code > 0 -> 被信号终止,运行中出现了问题*/static std::string CodeToDesc(int code, const std::string &file_name){std::string desc;switch (code){case 0:desc = "编译运行成功";break;case -1:desc = "代码为空";break;case -2:desc = "内部错误";break;case -3:File_Util::ReadFile(Path_Util::Compile_Error_Path(file_name), &desc, true);break;case SIGABRT: // 6desc = "内存超过范围";break;case SIGXCPU: // 24desc = "CPU使用超时";break;case SIGFPE: // 8desc = "浮点数溢出";break;default:desc = "未知错误" + std::to_string(code);break;}return desc;}/**** 输入:* [code]:* [cpu_limit]* [mem.limit]* [input],留出来但我们不做处理* 输出:* 必填字段:* [statuscode]:状态码* [reason]:状态码对应的原因* 选填:* [stderr]:错误原因* [stdout]:代码的输出结果*/static void Start(const std::string &json_in, std::string *json_out){// 反序列化Json::Value in_value;Json::Reader reader;reader.parse(json_in,in_value);std::string code = in_value["code"].asCString();std::string input = in_value["input"].asCString();int cpu_limit = in_value["cpu_limit"].asInt();int mem_limit = in_value["mem_limit"].asInt();std::string _file_name; // 形成唯一的文件名std::string _stderr;std::string _stdout;std::string reason;Json::Value out_value;int status_code = 0;int run_result = 0;if (code.size() == 0){status_code = -1; // 代码为空goto END;}_file_name = File_Util::UniqueFileName();// 将code写入到code.cpp文件中if (!File_Util::WriteFile(Path_Util::Src_Path(_file_name), code)){status_code = -2; // 未知错误goto END;}// 开始编译if (!Compiler::Compile(_file_name)){status_code = -3; // 编译错误goto END;}// 开始运行run_result = Runner::Run(_file_name, cpu_limit, mem_limit);if (run_result == 0){status_code = 0; // 运行成功了}else if (run_result < 0){status_code = -2; // 未知错误}else if (run_result > 0){// 运行错误status_code = run_result;}END:reason = CodeToDesc(status_code, _file_name);if (status_code == 0){// 运行没有问题File_Util::ReadFile(Path_Util::Stdout_Path(_file_name), &_stdout, true);File_Util::ReadFile(Path_Util::Stderr_Path(_file_name), &_stderr, true);out_value["stdout"] = _stdout;out_value["stderr"] = _stderr;}out_value["code"] = status_code;out_value["reason"] = reason;Json::StyledWriter Writer;(*json_out) = Writer.write(out_value);RemoveTempFile(_file_name);}};
};
程序流程图
Compile_server.cc的实现
Compile_server.cc
文件主要对Compile_and_run
模块的功能做一个调用,然后以网络通信的形式给外部提供出去。
我们采用httplib
库来快速构建网络服务,httplib
是一个轻量级的C++11 HTTP/HTTPS
库,由 Yoshito Umaoka 开发。它旨在简化在C++中进行HTTP
请求和响应处理的过程。httplib
支持同步通信,适用于需要简单而高效地与HTTP服务器交互的应用场景。
- 创建
httplib::server
对象。 - 添加一个
POST
方法到容器中,客户端访问这个路径就会直接路由到这个方法中执行相关操作。 - 调用
http::Server
对象的listen
函数, bindip
地址和port
,并开始提供网络服务(死循环监听外部连接)。
#include "Compile_and_run.hpp"
#include "../Comm/httplib.h"using namespace compile_and_run_ns;
using namespace httplib;void Usage(char *proc)
{std::cout << "Usage:" << proc << "port" << std::endl;exit(1);
}
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);}Server svr;svr.Post("/Compile_Run", [](const Request &req, Response &response){std::string json_in = req.body;std::string json_out;if(!json_in.empty()){CompileAndRun::Start(json_in,&json_out);response.set_content(json_out,"application/json; charset=UTF-8");} else{response.set_content("请求错误","text/html; charset=UTF-8");} });svr.listen("0.0.0.0",atoi(argv[1]));return 0;
}
测试Compile_server模块
我们采用postman
工具来进行Compile_server
模块的测试:
postman
是windows
上的一个测试工具,它可以向部署在公网上的服务发送http
请求报文(支持设置请求方法、请求体、请求头),并拿到响应报文。它是图形化界面,使用起来十分方便快捷。
-
选择
POST
方法,按照我们在Compile_server
模块精心设计好的json
字符串的格式构建(key
值一定要对的上),json
字符串中必须有以下几个k-v
:code
:你想提交给Compile_server
模块的代码。cpu_limit
:代码执行的最大cpu
时间。单位1/smem_limit
:代码可以申请的虚拟内存的上限。单位1/KBinput
:这是选填字段,内容可以为空,但必须要有,否则Compile_server
模块在进行反序列化的时候就会出错。
-
填写要发送的
json
串: -
填写服务的
url
后,一键发送得到编译运行结果:
至此我们的后端服务的Compile_server
模块编写、测试完成。
Comm模块编写
代码
Log.hpp
:提供日志服务,显示日志的行号、文件名、时间戳、日志等级、基本信息。
#pragma once
#include<iostream>
#include<string>
#include"Util.hpp"namespace Log_ns
{using namespace util_ns;enum{INFO = 1,DEBUG,WARNNING,ERROR,FATAL
};inline std::ostream& Log(const std::string& level,const std::string& filename,int line)
{std::string message;//添加日志水平message += "[";message += level;message += "]";//添加文件名message += "[";message += filename;message += "]";//添加文件的行号message += "[";message += std::to_string(line);message += "]";//添加时间戳信息message += "[";message += Time_Util::GetTimeStamp();message += "]";//将message加入到缓冲区中std::cout << message;//先不要将内容从缓冲区中刷新出来return std::cout;
}#define LOG(level) Log(#level,__FILE__,__LINE__)
};
Util.hpp
:提供一些常用的方法,放在特定的工具类中,以静态成员函数的方式存在,让外部无需创建对象可以直接使用:
#pragma once
#include <string>
#include <sys/stat.h>
#include <sys/time.h>
#include <atomic>
#include <fstream>
#include <vector>
#include <boost/algorithm/string.hpp>namespace util_ns
{const static std::string temp = "./temp/";class Time_Util{public:static std::string GetTimeStamp(){struct timeval tv;gettimeofday(&tv, nullptr);return std::to_string(tv.tv_sec);}static std::string GetMsTimeStamp() // 得到毫秒级的时间戳{struct timeval tv;gettimeofday(&tv, nullptr); // 不关心时区return std::to_string(tv.tv_sec * 1000 + tv.tv_usec / 1000);}};class Path_Util{private:static std::string Spilt_Path(const std::string &filename, const std::string &suffic){return temp + filename + suffic;}public:static std::string Compile_Error_Path(const std::string &filename){return Spilt_Path(filename, ".CompileError");}static std::string Src_Path(const std::string &filename){return Spilt_Path(filename, ".cpp");}static std::string Exe_Path(const std::string &filename){return Spilt_Path(filename, ".exe");}static std::string Stderr_Path(const std::string &filename){return Spilt_Path(filename, ".stderr");}static std::string Stdin_Path(const std::string &filename){return Spilt_Path(filename, ".stdin");}static std::string Stdout_Path(const std::string &filename){return Spilt_Path(filename, ".stdout");}};class File_Util{public:static bool Is_Exist_File(const std::string &file_name){struct stat st;if (stat(file_name.c_str(), &st) == 0){// 成功获取文件属性,说明该文件是存在的return true;}return false;}/*** 毫秒级的时间戳+原子计数器保证文件名的唯一性*/static std::string UniqueFileName(){static std::atomic<int> id(0);std::string res = Time_Util::GetMsTimeStamp();res += ("_" + std::to_string(id));id++;return res;}/***写入文件->将内容输出进文件*/static bool WriteFile(const std::string &file_name, const std::string &content){std::ofstream out(file_name);if (!out.is_open()){return false;}out.write(content.c_str(), content.size());out.close();return true;}/**从文件中读取内容->将文件中的内容输入到用户缓冲区**/static bool ReadFile(const std::string &file_name, std::string *content, bool keep){(*content).clear();std::ifstream in(file_name);if (!in.is_open()){return false;}std::string line;while (std::getline(in, line)){(*content) += line;(*content) += keep ? "\n" : "";}return true;}};class Spilt_Util{public:static void Split(const std::string &str, std::vector<std::string> *out, const std::string &seq){// 调用boost库,并分割str,存入outboost::split(*out, str, boost::is_any_of(seq), boost::algorithm::token_compress_on);}};
}
stat函数介绍
int stat(const char *restrict pathname,struct stat *restrict statbuf);
- 函数功能:得到以
pathname
为路径的文件的属性的信息。 - 函数参数:
const char *restrict pathname
:输入型参数,指定你要查看属性的文件的路径。struct stat *restrict statbuf
:输出型参数,可以传nullptr
,如果你不关心文件的属性信息的话。
- 返回值:获取成功返回0,获取失败返回-1。
我们使用这个函数,主要用来判读某个文件是否存在,例如在编译后,调用它通过判断可执行文件是否生成来知道是否编译成功/在使用unlink
函数删除一个文件前也需要调用这个函数,保证代码的健壮性。
unlink函数介绍
int unlink(const char *pathname);
- 函数功能:如果没有进程打开这个文件 && 这个文件是最后一个硬链接,这个文件的内容才会被删除,空间被系统回收。注:这个函数不能删除目录文件,若需要可以调用
rmdir
函数。
gettimeofday与time函数介绍
这两个函数似乎都可以获取时间戳(时间戳(timestamp)指的是自 1970年1月1日 00:00:00 UTC(称为Unix纪元或Epoch)以来经过的秒数)。
示例程序:
#include<iostream>
#include<time.h>
#include <sys/time.h>int main()
{time_t t1 = time(nullptr);std::cout << t1 << std::endl;struct timeval tv;gettimeofday(&tv,nullptr);std::cout << "tv.sec " << tv.tv_sec << " tv.usec: " << tv.tv_usec << std::endl;return 0;
}
运行结果:
两者的区别:
-
头文件不同,
time
在头文件time.h
中声明,而gettimeofday
在头文件sys/time.h
中声明。 -
精度不同:这是两者最大的区别,
time()
只提供了秒级别的精度,而gettimeofday
提供微妙级的精度。 -
输出型参数不同:两者都可以通过传参拿到当前时间戳,但是
gettimeofday
的是一个描述时间的结构体(包括秒、微秒),但是time
只是一个简单的time_t
的整数。struct timeval {time_t tv_sec; /* Seconds */suseconds_t tv_usec; /* Microseconds */ };
-
返回值不同:
time
的返回值也是一个time_t
的整数,表示当前时间戳,给用户提供两种方式获取时间戳,当参数为nullptr
时就可以通过返回值来获取,而gettimeofday
的返回值则是0/-1,用来标识函数是否执行成功。
boost::split函数介绍
boost
库是对标准库的补充,它有很多标准库未提供的功能,它是一个开源的库,很多标准库在经过一段时间的成熟和发展后,被标准库所采纳,如智能指针。
boost::split 函数是Boost库的一部分,具体来说,它位于 Boost.StringAlgorithms
库中。
template <typename SequenceSequenceT, typename RangeT, typename PredicateT>
SequenceSequenceT& split(SequenceSequenceT& Result, const RangeT& Input, PredicateT Pred, token_compress_mode_type eCompress = token_compress_off);
- 函数功能:这个函数用于将一个字符串按照指定的分隔符分割成多个子字符串,并将这些子字符串存储在一个容器中(如 std::vector
<std::string>
)。 - 函数参数:
SequenceSequenceT& Result
:用来存放分割后的子字符串的容器。const RangeT& Input
:要被分割的源字符串。PredicateT
:谓词(predicate),定义了如何识别分隔符。可以是一个字符或一组字符,也可以是满足特定条件的判断函数。- 可选参数,控制是否压缩连续的分隔符,默认为
token_compress_off
,即不压缩;如果设置为token_compress_on
,则会忽略连续出现的分隔符之间的空隙。
- 返回值:该函数的返回值也是返回一个容器,该容器中存储切割后的字符串。
- 头文件:
<boost/algorithm/string.hpp>
示例函数:
#include<iostream>
#include<time.h>
#include<boost/algorithm/string.hpp>
#include<vector>int main()
{std::string src = "每天,都要,吃饭,对吗???????????你";std::vector<std::string> res1;std::vector<std::string> res2;res2 = boost::split(res1,src,boost::is_any_of("?,"));std::cout << "res1: ";for(auto& s:res1)std::cout << s << " ";std::cout << "\n";std::cout << "res2: ";for(auto& s:res2)std::cout << s << " ";std::cout << "\n";return 0;
}
注意:is_any_of
是boost
库中的一个函数,这个函数的作用是生成一个谓词对象,它用来检查一个字符是否属于给定的一组字符。
运行结果:
这是为压缩的结果,压缩后,想让boost:spilt
函数有压缩效果,最后一个参数需要传token_compress_on
,表示开启压缩,默认是off
:
生成唯一文件名的方法介绍(原子计数器+毫秒级的时间戳)
采用两种方式保证文件名的唯一性:
- 原子计数器:生成一个静态的原子计数器变量,每次使用之后
++
,C++11
的atomic
类提供类似功能,它对数据做访问、修改操作都是原子的。 - 毫秒级时间戳:获取当前时间戳,调用
gettimeofday
函数,拿到毫秒级的时间戳。
static std::string UniqueFileName(){static std::atomic<int> id(0);std::string res = Time_Util::GetMsTimeStamp();res += ("_" + std::to_string(id));id++;return res;}
Oj_Server模块编写
Oj_Server模块的功能
- 向客户端(浏览器)提供网络服务,浏览器给客户端发送请求后,
Oj_Server
处理。 - 如果客户端请求的是获取所有题目和获取单个题目,就无需和编译服务交互,直接返回渲染后的
html
。 - 如果用户提交代码,
Oj_Server
需要负载均衡的选择编译服务的主机,让其提供编译运行的服务,最后Oj_Server
模块将Compile_Server
服务返回的结果直接返回给客户端。
基于MVC结构的Oj_Server模块的设计
我们的Oj_Server
模块主要分为以下三个小的模块:
Oj_control
模块:控制模块,里面实现直接为客户端提供服务的方法,我们Oj_Server
模块的核心业务逻辑在这个模块中,包括获取题库、获取单个题目、判题功能,未来可能还会增加登录、注册的功能。Oj_Model
模块:这个模块主要与数据打交道,例如题目的数据(题目编号、题目的难度、题目的描述、题目的给用户的代码、测试用例代码)等,我们将题目相关的信息都存在数据库中,后面可能加入与用户相关的数据,也放在这个模块中。所以这个模块就主要负责与数据进行交互。Oj_View
模块:这个模块主要和前端相关,它主要负责网页渲染的功能,当Oj_Model
模块取出题目数据后,由这个模块将题目的数据渲染成一张html
的网页后返回给客户端,我们的网页是服务器动态生成的(利用已经准备好的模板)。
代码实现
Oj_control.hpp
#pragma once
#include<iostream>
#include"../Comm/Log.hpp"
#include"../Comm/Util.hpp"
#include<string>
#include<vector>
#include"OJ_Model.hpp"
#include"Oj_View.hpp"
#include<jsoncpp/json/json.h>
#include<mutex>
#include<fstream>
#include<assert.h>
#include"../Comm/httplib.h"
#include<algorithm>//核心控制模块
namespace control_ns
{using namespace model_ns;using namespace httplib;using namespace view_ns;using namespace util_ns;using namespace httplib;class HostMachine{public:std::string _ip;//编译运行服务主机的ip地址int _port;//端口号uint64_t _load;//负载情况std::mutex* _mutex;public:HostMachine():_load(0),_mutex(nullptr){}~HostMachine(){}public:void IncLoad(){_mutex->lock();++_load;_mutex->unlock();}void DecLoad(){_mutex->lock();--_load;_mutex->unlock();}void ReLoad(){_mutex->lock();_load = 0;_mutex->unlock();}int GetLoad(){uint64_t load = 0;_mutex->lock();load = _load;_mutex->unlock();return load;}};static const std::string hostpath = "./Config/compile_service_host.conf";class LoadBalance{private:std::vector<HostMachine> _host;//所有的主机std::vector<int> _online;//所有在线的主机std::vector<int> _offline;//所有离线的主机std::mutex _mut;//锁,保护当前类中的数据安全public:LoadBalance(){assert(LoadConfig());}~LoadBalance(){}public:bool LoadConfig(){//0.导入配置文件进入内存std::ifstream ifs(hostpath);//1.判断是否正确打开文件if(!ifs.is_open()){LOG(FATAL) << "编译服务配置打开失败,请检查路径是否设置正确..." << "\n";return false;}//2.开始按行读取内容,同时配置主机std::string line;while(std::getline(ifs,line)){std::vector<std::string> res;Spilt_Util::Split(line,&res,":");if(res.size() != 2){LOG(WARNNING) << "此行配置出错..." << "\n";continue;}HostMachine machine;machine._ip = res[0];machine._port = atoi(res[1].c_str());machine._mutex = new std::mutex;//将主机添加到_host中_online.emplace_back(_host.size());// std::cout << "_onlinesize" << _online.size() << std::endl;_host.emplace_back(machine);}LOG(INFO) << "导入主机配置成功..." << "\n";return true;}//id,hm都是输出型参数bool SmartChoice(int* id,HostMachine** hm){_mut.lock();//加锁保护在线主机、离线主机列表等公共数据int onlinenum = _online.size();if(onlinenum == 0){_mut.unlock();LOG(FATAL) << "所有后端编译主机的已经离线,服务无法继续..." << "\n";return false;}//负载均衡算法//1.随机数+hash//2.轮询+hash->我们选择这种(*id) = _online[0];(*hm) = &_host[*id];uint64_t min_load = _host[_online[0]].GetLoad();for(int i = 1;i < _online.size();++i){uint64_t cur_load = _host[_online[i]].GetLoad();if(cur_load < min_load){min_load = cur_load;(*id) = _online[i];(*hm) = &_host[*id];}}_mut.unlock();return true;}void OfflineHost(int which){_mut.lock();for(auto iter = _online.begin();iter != _online.end();++iter){if((*iter) == which){_host[which].ReLoad();_online.erase(iter);_offline.emplace_back(which);break;}}_mut.unlock();}void OnlineHost(){_mut.lock();//将所有的离线主机中的全加入在线主机中_online.insert(_online.end(),_offline.begin(),_offline.end());_offline.erase(_offline.begin(),_offline.end());_mut.unlock();LOG(INFO) << "所有主机都上线了" << "\n";}void ShowHostlist(){_mut.lock();//打印在线主机列表std::cout << "在线主机列表: ";for(int i = 0;i < _online.size();++i){std::cout << _online[i] << " ";}std::cout << "\n";std::cout << "离线主机列表: ";for(int i = 0;i < _offline.size();++i){std::cout << _offline[i] << " ";}std::cout << "\n";_mut.unlock();}};class Control{private:Model _model;View _view;LoadBalance _LoadBalance;public:Control(){}~Control(){}public:void RecoveryMachine(){_LoadBalance.OnlineHost();}//返回给外层回调一个包含所有题目的html文件,要经过网页渲染,View中的功能bool AllQuestions(std::string* html){bool ret;//将所有的题目的题目列表信息放入我们的question_v中std::vector<Question*> question_v;if(_model.GetAllQuestions(&question_v)){sort(question_v.begin(),question_v.end(),[](Question* a,Question*b)->bool{return atoi(a->number.c_str()) < atoi(b->number.c_str());});//升序排序_view.AllRenderHtml(question_v,html);ret = true;}else{*html = "获取所有题目列表失败";ret = false;}return true;}bool OneQuestion(const std::string& number,std::string* html){Question* question;bool ret;if(_model.GetOneQuestion(number,&question)){LOG(INFO) << "获取题目成功,number is " << number << "\n";_view.OneRenderHtml(question,html);}else{LOG(INFO) << "获取题目失败,number is " << number << "\n";ret = false;*html = "获取题目失败,number is " + number;}return ret;}void Judge(const std::string& number,const std::string& in_json,std::string * out_json){//0.根据题目编号,拿到题目细节Question* question;_model.GetOneQuestion(number,&question);//1.1将用户提交的in_json,解析出来 -- 反序列化Json::Value in_Value;Json::Reader reader;reader.parse(in_json,in_Value);//1.1拿到结构化数据std::string submit_code = in_Value["code"].asCString();//用户提交的代码std::string input = in_Value["input"].asCString();//2.构建给后端编译服务的请求json串//2.1将拿到用户提交的代码和测试用例拼接在一起std::string code = submit_code+"\n"+ question->tail;Json::Value out_Value;//2.2构建json数据out_Value["code"] = code;out_Value["input"] = input;out_Value["cpu_limit"] = question->cpu_limit;out_Value["mem_limit"] = question->mem_limit;Json::FastWriter writer;//2.3反序列化,得到发送给compile_run服务的json串std::string compile_json = writer.write(out_Value);//3.负载均衡式的选择主机while(true){int id = 0;HostMachine* host;//3.1负载均衡式的获取主机if(!_LoadBalance.SmartChoice(&id,&host)){LOG(ERROR) << "主机都未上线,id is " << id << "\n";break;}//走到这里,一定获取到了一个在线主机// 开始发送http post请求//3.2创建httplib clientClient client(host->_ip,host->_port);host->IncLoad();//设置client的超时时间client.set_read_timeout(std::chrono::seconds(20));//20s超时client.set_connection_timeout(std::chrono::seconds(20));LOG(INFO) << "当前请求的主机,id: " << id << " 主机详情: " << host->_ip << ":" << host->_port << "\n";//发送post请求向目标主机auto res = client.Post("/Compile_Run",compile_json,"application/json;charset=utf-8");if(res){if(res->status == 200)//响应成功{host->DecLoad();(*out_json) = res->body;LOG(INFO) << "编译运行成功" << "\n";break;}(*out_json) = res->body;host->DecLoad();LOG(INFO) << "请求成功" << "\n";break;}else{LOG(ERROR) << "请求编译运行服务失败,可能已经离线当前主机id: " << id << " 主机详情: " << host->_ip << ":" << host->_port << "\n";_LoadBalance.OfflineHost(id);_LoadBalance.ShowHostlist();}}}};
}
Oj_view.hpp
:
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include"OJ_Model.hpp"
#include<ctemplate/template.h>namespace view_ns
{using namespace model_ns;using namespace ctemplate;const static std::string comm_path = "./Template_html/";class View//帮助我们完成网页的渲染工作{public:View(){}~View(){}public:void AllRenderHtml(std::vector<Question*> question_arr,std::string* html){//1.创建字典TemplateDictionary root("AllQuestionList");//2.往字典中插入数据for(int i = 0;i < question_arr.size();++i){TemplateDictionary* sub = root.AddSectionDictionary("OneQuestionList");sub->SetValue("number",question_arr[i]->number);sub->SetValue("title",question_arr[i]->title);sub->SetValue("star",question_arr[i]->level);}//3.加载模板htmlTemplate* tpl = Template::GetTemplate(comm_path+"All_Question.html",DO_NOT_STRIP);//4.渲染模板tpl->Expand(html,&root);for(auto& ptr:question_arr)delete ptr;}void OneRenderHtml(Question* question,std::string* html){LOG(INFO) << "开始渲染网页了..." << "\n";//1.创建字典TemplateDictionary root("OneQuestion");//2.设置变量的值root.SetValue("desc",question->desc);root.SetValue("star",question->level);root.SetValue("pre_code",question->header);root.SetValue("number",question->number);root.SetValue("title",question->title);//3.加载模板文件Template* tpl = Template::GetTemplate(comm_path+"One_Question.html",DO_NOT_STRIP);//4.将模板文件渲染tpl->Expand(html,&root);delete question;}};
}
Oj_Model1.hpp
:
#pragma once
#include <string>
#include <vector>
#include <assert.h>
#include <fstream>
#include "../Comm/Log.hpp"
#include "../Comm/Util.hpp"
#include <unordered_map>
#include "include/mysql/jdbc.h"namespace model_ns
{using namespace Log_ns;using namespace util_ns;struct Question{std::string number; // 题目编号std::string title; // 题目名std::string level; // 题目难度int cpu_limit;int mem_limit;std::string desc; // 题目的内容/描述std::string header; // 给用户的编写代码区域std::string tail; // 测试用例,最终要与用户返回的代码拼接在一起,交给后台的编译运行服务};const static std::string path = "./Questions/Questions.list"; // 题目列表文件路径const static std::string comm = "./Questions/"; // 文件的公共目录const static int port = 3306;const static std::string schem = "oj";//哪个数据库const static std::string hostname = "127.0.0.1";//ip地址/urlconst static std::string passward = "123456";//ip地址/urlconst static std::string username = "oj_client";//ip地址/urlconst static std::string tablename = "questions";// 1.给Control提供对题库增删查改的功能class Model{private:std::unordered_map<std::string, Question *> questions;public:Model(){}~Model() {}public:bool GetQuery(const std::string &sql, std::vector<Question *> *out){LOG(INFO) << "开始连接数据库了... " << "\n";// 1.连接数据库sql::mysql::MySQL_Driver *driver;sql::Connection *con;sql::ConnectOptionsMap connection_detail;sql::Statement* st;sql::ResultSet* res;connection_detail[OPT_PORT] = port;connection_detail[OPT_SCHEMA] = schem;connection_detail[OPT_HOSTNAME] = hostname;connection_detail[OPT_PASSWORD] = passward;connection_detail[OPT_USERNAME] = username;connection_detail[OPT_CHARSET_NAME] = "utf8mb4";//客户端和服务器之间的所有通信(双向)。connection_detail[OPT_RECONNECT] = true;//设置该连接的编码格式//初始化driverdriver = sql::mysql::get_mysql_driver_instance();if(!(con = driver->connect(connection_detail))){LOG(FATAL) << "连接数据库失败,检查参数..." << "\n";return false;}LOG(INFO) << "连接数据库成功..." << "\n";st = con->createStatement();assert(st);res = st->executeQuery(sql);assert(res);while(res->next()){LOG(INFO) << "拿到一行数据了" << "\n";Question* question = new Question;question->number = res->getString("number");question->desc = res->getString("question_desc");question->header = res->getString("header");question->tail = res->getString("tail");question->level = res->getString("star");question->mem_limit = res->getInt("mem_limit");question->cpu_limit = res->getInt("cpu_limit");question->title = res->getString("title");(*out).emplace_back(question);}delete st;delete con;delete res;return true;}// 读取所有题目的内容bool GetAllQuestions(std::vector<Question *> *out){std::string sql = "select * from ";sql += tablename;return GetQuery(sql,out);}// 获取一个题目的信息bool GetOneQuestion(const std::string &number, Question **question){std::string sql = "select * from ";sql += tablename;sql += " where number=";sql += number;std::vector<Question*> res;if(GetQuery(sql,&res)){*question = res[0];return true;}return false;}};
}
Oj_Server.cc
:
#include <iostream>
#include <string>
#include "../Comm/httplib.h"
#include "OJ_Control.hpp"
#include<signal.h>
using namespace httplib;
using namespace control_ns;Control* controlptr;void HandlerSignal(int signal)
{controlptr->RecoveryMachine();
}int main()
{signal(SIGQUIT,HandlerSignal);//ctrl /Server svr;Control control;controlptr = &control;// 1.可以看到首页// 2.可以请求所有题目列表 -GET// 3.可以看到详细的单个题目 -GET// 4.提供判题功能 -POSTsvr.Get("/All_Questions", [&control](const Request &req, Response &resp){std::string html;control.AllQuestions(&html);resp.set_content(html,"text/html; charset=UTF-8"); });svr.Get(R"(/Question/(\d+))", [&control](const Request &req, Response &resp)// 正则表达式要加括号,括号表示定义捕获组{ std::string number = req.matches[1]; // 下标[1]存储的是第一个匹配的结果std::string html;control.OneQuestion(number, &html);resp.set_content(html, "text/html; charset=UTF-8");});svr.Post(R"(/judge/(\d+))", [&control](const Request &req, Response &resp){std::string number = req.matches[1];std::string out_json;std::string in_json = req.body;if(!in_json.empty()){control.Judge(number,in_json,&out_json);}if(!out_json.empty())resp.set_content(out_json,"application/json; charset=UTF-8"); });svr.set_base_dir("./wwwroot");svr.listen("0.0.0.0", 8080);return 0;
}
程序流程图
-
当客户端请求
url
为/All_Questions
的服务:补:调用
model
模块中的GetAllQuestions
的程序流程图:C++连接/操作
mysql
不是我们项目的重点,不做过多介绍。补:调用
oj_view
模块中的AllRenderHtml
函数流程图: -
当客户端请求
url
为Question/(\d+)
的服务: -
当客户端请求
url
为judge/(\d+)
的服务:
负载均衡功能介绍
下面是我们负载均衡相关的代码:
class HostMachine{public:std::string _ip;//编译运行服务主机的ip地址int _port;//端口号uint64_t _load;//负载情况std::mutex* _mutex;public:HostMachine():_load(0),_mutex(nullptr){}~HostMachine(){}public:void IncLoad(){_mutex->lock();++_load;_mutex->unlock();}void DecLoad(){_mutex->lock();--_load;_mutex->unlock();}void ReLoad(){_mutex->lock();_load = 0;_mutex->unlock();}int GetLoad(){uint64_t load = 0;_mutex->lock();load = _load;_mutex->unlock();return load;}};static const std::string hostpath = "./Config/compile_service_host.conf";class LoadBalance{private:std::vector<HostMachine> _host;//所有的主机std::vector<int> _online;//所有在线的主机std::vector<int> _offline;//所有离线的主机std::mutex _mut;//锁,保护当前类中的数据安全public:LoadBalance(){assert(LoadConfig());}~LoadBalance(){}public:bool LoadConfig(){//0.导入配置文件进入内存std::ifstream ifs(hostpath);//1.判断是否正确打开文件if(!ifs.is_open()){LOG(FATAL) << "编译服务配置打开失败,请检查路径是否设置正确..." << "\n";return false;}//2.开始按行读取内容,同时配置主机std::string line;while(std::getline(ifs,line)){std::vector<std::string> res;Spilt_Util::Split(line,&res,":");if(res.size() != 2){LOG(WARNNING) << "此行配置出错..." << "\n";continue;}HostMachine machine;machine._ip = res[0];machine._port = atoi(res[1].c_str());machine._mutex = new std::mutex;//将主机添加到_host中_online.emplace_back(_host.size());// std::cout << "_onlinesize" << _online.size() << std::endl;_host.emplace_back(machine);}LOG(INFO) << "导入主机配置成功..." << "\n";return true;}//id,hm都是输出型参数bool SmartChoice(int* id,HostMachine** hm){_mut.lock();//加锁保护在线主机、离线主机列表等公共数据int onlinenum = _online.size();if(onlinenum == 0){_mut.unlock();LOG(FATAL) << "所有后端编译主机的已经离线,服务无法继续..." << "\n";return false;}//负载均衡算法//1.随机数+hash//2.轮询+hash->我们选择这种(*id) = _online[0];(*hm) = &_host[*id];uint64_t min_load = _host[_online[0]].GetLoad();for(int i = 1;i < _online.size();++i){uint64_t cur_load = _host[_online[i]].GetLoad();if(cur_load < min_load){min_load = cur_load;(*id) = _online[i];(*hm) = &_host[*id];}}_mut.unlock();return true;}void OfflineHost(int which){_mut.lock();for(auto iter = _online.begin();iter != _online.end();++iter){if((*iter) == which){_host[which].ReLoad();_online.erase(iter);_offline.emplace_back(which);break;}}_mut.unlock();}void OnlineHost(){_mut.lock();//将所有的离线主机中的全加入在线主机中_online.insert(_online.end(),_offline.begin(),_offline.end());_offline.erase(_offline.begin(),_offline.end());_mut.unlock();LOG(INFO) << "所有主机都上线了" << "\n";}void ShowHostlist(){_mut.lock();//打印在线主机列表std::cout << "在线主机列表: ";for(int i = 0;i < _online.size();++i){std::cout << _online[i] << " ";}std::cout << "\n";std::cout << "离线主机列表: ";for(int i = 0;i < _offline.size();++i){std::cout << _offline[i] << " ";}std::cout << "\n";_mut.unlock();}};
负载均衡模块主要有两个类:
HostMachine类
:描述一台提供编译网络服务的主机,它有如下字段:std::string _ip
:主机部署的ip
地址。int _port
:主机服务进程对应的端口号。uint64_t _load
:主机当前的负载情况(正在给多少客户端提供编译运行服务)。std::mutex* _mutex
:锁,用于保持线程安全,httplib
内部是多线程的,可能有多个线程同时对同一主机的_load
进行操作,不加锁就会有数据不一致的情况。
LoadBalance类
:对主机进行管理,包括管理在线主机列表、离线主机列表,创建LoadBalance
对象时,同时会将配置文件加载到内存中,这个时候就会创建HostMachine
对象,使用vector
存储它,同时将下标作为id
添加进在线主机列表:std::vector<HostMachine> _host
:维护所有的主机对象。std::vector<int> _online
:维护在线主机的id(对应在_host
中的下标)。std::vector<int> _offline
:所有离线的主机的id。std::mutex _mut
:锁,保护当前类中的数据安全,当前类的_online
、_offline
可能会有多个线程对其进行add/del
,不加锁保护,就会出现数据不一致的情况。
SmartChoice
方法介绍:
bool SmartChoice(int *id, Machine **m)
- 函数功能:负载均衡的选择当前负载最小的主机。
- 函数参数:
int* id
:输出型参数,获得当前选择的主机的id
。Machine **m
:输出型参数,获得当前选择主机的地址。
- 函数返回值:
bool
值,true
表示选择成功,反之失败。
函数流程图:
因为是互斥锁,只能有一个线程同时获取,上锁之后由于其它会对online
数组的值有影响的操作都会申请这个互斥锁,所以在上述方法中加锁就可以保证一定不会出现数组越界的情况。
一键上线功能介绍
有时候我们的编译服务可能因为网络的原因离线了,在Oj_Server
服务中难道需要重启启动吗?并不需要,我们利用信号机制就可以做到一键上线所有主机,上线所有主机的过程就是将所有离线主机的id
都添加到在线主机列表,并清空离线主机列表中的内容:
"Oj_Control.hpp":class LoadBalance{
public:void OnlineHost(){_mut.lock();//将所有的离线主机中的全加入在线主机中_online.insert(_online.end(),_offline.begin(),_offline.end());_offline.erase(_offline.begin(),_offline.end());_mut.unlock();LOG(INFO) << "所有主机都上线了" << "\n";}}class Control
{
public:void RecoveryMachine(){_LoadBalance.OnlineHost();}
}
"Oj_Server.cc":
#include<signal.h>
using namespace control_ns;
Control* controlptr;void HandlerSignal(int signal)
{controlptr->RecoveryMachine();
}int main()
{signal(SIGQUIT,HandlerSignal);//ctrl /...
}
测试Oj_Server模块
现在我们没有前端页面,只能借助Postman
工具进行功能的基本测试。
-
测试获取所有题目的功能:
-
测试获取单个题目的详细信息的功能:
-
测试判题功能:
这里我们是随便输入的代码肯定会报编译错误。
前端的设计
-
主页:
<!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>在线OJ平台</title><style>/* 全局样式 */body {font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;margin: 0;padding: 0;background-color: #f5f5f5;color: #333;}/* 页头 */header {background-color: #2c3e50;color: white;padding: 40px 20px;text-align: center;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);}header h1 {font-size: 36px;margin-bottom: 10px;}header p {font-size: 18px;color: #ecf0f1;}/* 导航栏 */nav {background-color: #34495e;overflow: hidden;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);}nav a {float: left;display: block;color: white;text-align: center;padding: 14px 20px;text-decoration: none;transition: background-color 0.3s ease;}nav a:hover {background-color: #3b536b;}/* 内容区域 */.container {padding: 30px;max-width: 800px;margin: 0 auto;background-color: white;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);border-radius: 8px;margin-top: 30px;margin-bottom: 80px; /* 避免内容被页脚遮挡 */}.container h2 {color: #2c3e50;font-size: 28px;margin-bottom: 20px;}.container p {font-size: 16px;line-height: 1.6;color: #555;}.container ul {list-style-type: none;padding: 0;}.container ul li {background-color: #f8f9fa;margin: 10px 0;padding: 10px;border-radius: 4px;font-size: 16px;color: #333;box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);}/* 页脚 */footer {background-color: #34495e;color: white;text-align: center;padding: 15px;position: fixed;bottom: 0;width: 100%;box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1);}footer p {margin: 0;font-size: 14px;}</style> </head> <body><!-- 页头 --><header><h1>在线OJ平台</h1><p>欢迎来到在线编程评测平台</p></header><!-- 导航栏 --><nav><a href="/">首页</a><a href="/All_Questions">题库</a><a href="/">竞赛</a><a href="/">排名</a><a href="/">关于</a></nav><!-- 内容区域 --><div class="container"><h2>平台简介</h2><p>在线OJ平台是一个提供编程题目和在线评测的系统,支持多种编程语言,帮助用户提升编程能力。</p><h2>最新动态</h2><ul><li>新增了C++题目。</li><li>2025年秋季编程竞赛即将开始。</li><li>优化了评测系统的性能。</li></ul></div><!-- 页脚 --><footer><p>© 2025 在线OJ平台. 保留所有权利.</p></footer> </body> </html>
-
获取所有题的模板
html
:<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>在线OJ平台-题目列表</title><style>/* 起手式, 100%保证我们的样式设置可以不受默认影响 */* {/* 消除网页的默认外边距 */margin: 0px;/* 消除网页的默认内边距 */padding: 0px;box-sizing: border-box;}html,body {width: 100%;height: 100%;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background-color: #f5f5f5;}.container .navbar {width: 100%;height: 60px;background-color: #2c3e50;/* 给父级标签设置overflow,取消后续float带来的影响 */overflow: hidden;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);}.container .navbar a {/* 设置a标签是行内块元素,允许你设置宽度 */display: inline-block;/* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */width: 100px;/* 设置字体颜色 */color: #ecf0f1;/* 设置字体的大小 */font-size: 16px;/* 设置文字的高度和导航栏一样的高度 */line-height: 60px;/* 去掉a标签的下划线 */text-decoration: none;/* 设置a标签中的文字居中 */text-align: center;transition: background-color 0.3s ease;}/* 设置鼠标事件 */.container .navbar a:hover {background-color: #34495e;}.container .navbar .login {float: right;}.container .question_list {padding-top: 50px;width: 800px;height: 100%;margin: 0px auto;text-align: center;}.container .question_list table {width: 100%;font-size: 16px;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;margin-top: 30px;background-color: white;border-collapse: collapse;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);border-radius: 8px;overflow: hidden;}.container .question_list h1 {color: #2c3e50;font-size: 32px;margin-bottom: 20px;}.container .question_list table th,.container .question_list table td {padding: 15px;text-align: center;}.container .question_list table th {background-color: #34495e;color: white;font-weight: bold;}.container .question_list table tr:nth-child(even) {background-color: #f8f9fa;}.container .question_list table tr:hover {background-color: #e9ecef;}.container .question_list table .item a {text-decoration: none;color: #2c3e50;transition: color 0.3s ease;}.container .question_list table .item a:hover {color: #3498db;text-decoration: underline;}.container .footer {width: 100%;height: 60px;text-align: center;line-height: 60px;color: #7f8c8d;margin-top: 30px;background-color: #ecf0f1;box-shadow: 0 -2px 4px rgba(0, 0, 0, 0.1);}.container .footer h4 {font-size: 14px;font-weight: normal;}</style> </head><body><div class="container"><!-- 导航栏, 功能不实现--><div class="navbar"><a href="/">首页</a><a href="/All_Questions">题库</a><a href="/">竞赛</a><a href="/">讨论</a><a href="/">求职</a><a class="login" href="#">登录</a></div><div class="question_list"><h1>在线OJ题目列表</h1><table><tr><th class="item">编号</th><th class="item">标题</th><th class="item">难度</th></tr>{{#OneQuestionList}}<tr><td class="item">{{number}}</td><td class="item"><a href="/Question/{{number}}">{{title}}</a></td><td class="item">{{star}}</td></tr>{{/OneQuestionList}}</table></div><div class="footer"><h4>@小镇敲码人</h4></div></div> </body></html>
-
单个题目的html模板:
<!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{{number}}.{{title}}</title><!-- 引入ACE插件 --><script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js" type="text/javascript" charset="utf-8"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-language_tools.js" type="text/javascript"charset="utf-8"></script><!-- 引入jquery CDN --><script src="http://code.jquery.com/jquery-2.1.1.min.js"></script><style>* {margin: 0;padding: 0;box-sizing: border-box;}html,body {width: 100%;height: 100%;font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;background-color: #f5f5f5;}.container .navbar {width: 100%;height: 60px;background-color: #2c3e50;overflow: hidden;box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);}.container .navbar a {display: inline-block;width: 100px;color: white;font-size: 16px;line-height: 60px;text-decoration: none;text-align: center;transition: background-color 0.3s ease;}.container .navbar a:hover {background-color: #34495e;}.container .navbar .login {float: right;}.container .part1 {width: 100%;height: 600px;overflow: hidden;margin-top: 20px;}.container .part1 .left_desc {width: 50%;height: 600px;float: left;overflow: scroll;background-color: white;padding: 20px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);border-radius: 8px;margin-right: 10px;}.container .part1 .left_desc h3 {color: #2c3e50;font-size: 24px;margin-bottom: 15px;}.container .part1 .left_desc pre {font-size: 16px;line-height: 1.6;color: #555;white-space: pre-wrap;word-wrap: break-word;}.container .part1 .right_code {width: calc(50% - 10px);height: 600px;float: right;background-color: white;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);border-radius: 8px;}.container .part1 .right_code .ace_editor {height: 600px;border-radius: 8px;}.container .part2 {width: 100%;overflow: hidden;margin-top: 20px;}.container .part2 .result {width: calc(100% - 140px);float: left;background-color: white;padding: 20px;box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);border-radius: 8px;margin-right: 10px;}.container .part2 .result pre {font-size: 16px;color: #333;}.container .part2 .btn-submit {width: 120px;height: 50px;font-size: 16px;float: right;background-color: #26bb9c;color: white;border: none;border-radius: 4px;cursor: pointer;transition: background-color 0.3s ease;}.container .part2 .btn-submit:hover {background-color: #1f9c81;}</style> </head><body><div class="container"><!-- 导航栏 --><div class="navbar"><a href="/">首页</a><a href="/All_Questions">题库</a><a href="/">竞赛</a><a href="/">讨论</a><a href="/">求职</a><a class="login" href="#">登录</a></div><!-- 左右呈现,题目描述和预设代码 --><div class="part1"><div class="left_desc"><h3><span id="number">{{number}}</span>.{{title}}_{{star}}</h3><pre>{{desc}}</pre></div><div class="right_code"><pre id="code" class="ace_editor"><textarea class="ace_text-input">{{pre_code}}</textarea></pre></div></div><!-- 提交并且得到结果,并显示 --><div class="part2"><div class="result"></div><button class="btn-submit" onclick="submit()">提交代码</button></div></div><script>// 初始化ACE编辑器var editor = ace.edit("code");editor.setTheme("ace/theme/monokai");editor.session.setMode("ace/mode/c_cpp");editor.setFontSize(16);editor.getSession().setTabSize(4);editor.setReadOnly(false);// 启用提示菜单ace.require("ace/ext/language_tools");editor.setOptions({enableBasicAutocompletion: true,enableSnippets: true,enableLiveAutocompletion: true});function submit() {// 收集代码和题号var code = editor.getSession().getValue();var number = $("#number").text();var judge_url = "/judge/" + number;// 发起AJAX请求$.ajax({method: 'POST',url: judge_url,dataType: 'json',contentType: 'application/json;charset=utf-8',data: JSON.stringify({'code': code,'input': ''}),success: function (data) {show_result(data);}});}function show_result(data) {var result_div = $(".container .part2 .result");result_div.empty();var _status = data.status;var _reason = data.reason;var reason_lable = $("<p>", {text: _reason});reason_lable.appendTo(result_div);if (status == 0) {var _stdout = data.stdout;var _stderr = data.stderr;var stdout_lable = $("<pre>", {text: _stdout});var stderr_lable = $("<pre>", {text: _stderr});stdout_lable.appendTo(result_div);stderr_lable.appendTo(result_div);}}</script> </body></html>
顶层makefile设计
一键编译:
.PHONY:all
all:@cd Compile_Run;\make;\cd -;\cd Oj_Server;\make;\cd -;.PHONY:output
output:@mkdir -p output/Compile_Server;\mkdir -p output/Oj_Server;\cp -rf Compile_Run/Compile_Server output/Compile_Server;\cp -rf Compile_Run/temp output/Compile_Server;\cp -rf Oj_Server/Oj_Server output/Oj_Server;\cp -rf Oj_Server/Template_html output/Oj_Server;\cp -rf Oj_Server/lib output/Oj_Server;\cp -rf Oj_Server/include output/Oj_Server;\cp -rf Oj_Server/Config output/Oj_Server;\cp -rf Oj_Server/wwwroot output/Oj_Server;.PHONY:clean
clean:@cd Compile_Run;\make clean;\cd -;\cd Oj_Server;\make clean;\cd -;\rm -rf output;
本项目的亮点
-
技术选型与模块化设计:
-
核心技术栈:
项目采用 C++ 作为后端核心语言,结合
cpp-httplib
实现轻量级网络服务,利用Ctemplate
进行动态网页渲染,jsoncpp
处理数据序列化与反序列化,技术选型高效且轻量。
优势:高性能、低延迟,适合处理高并发编译请求。 -
模块化设计:
系统分为
Comm
(公共模块)、compile_server
(编译服务)、oj_server
(前端与负载均衡)三大模块,职责清晰。
亮点:- 通过
compile_server
独立处理编译运行,实现服务解耦。 oj_server
采用 MVC 模式(Model-View-Control),提升代码可维护性。
- 通过
-
-
负载均衡与容错机制
-
动态负载均衡:
基于主机负载的智能选择算法,优先分配低负载节点,避免单点故障。创新点:支持一键上线所有离线主机(通过信号机制),提升服务可用性。
-
错误隔离与恢复:
自动检测编译服务节点故障,将其移出在线列表,保障服务稳定性。
-
-
安全与资源控制
-
代码沙箱隔离:
使用setrlimit
限制进程资源(CPU 时间、内存),防止恶意代码耗尽系统资源。亮点:通过信号机制(如
SIGXCPU
)强制终止超限进程,确保系统安全。 -
临时文件管理:
编译生成的临时文件(源码、可执行文件)在运行后自动清理,避免磁盘空间泄漏。
-
-
前端与用户体验
-
动态网页渲染:
使用
Ctemplate
模板引擎动态生成题目列表和详情页。亮点:集成 ACE 编辑器,提供代码高亮、自动补全功能,用户体验接近主流 OJ 平台。
-
异步判题反馈:
前端通过AJAX
异步提交代码并实时显示编译结果,减少页面刷新。
-
项目扩展方向
1. 功能扩展
- 多语言支持:
扩展compile_server
,支持 Python、Java 等语言的编译/解释运行,需适配不同语言的沙箱配置。 - 用户系统与竞赛功能:
- 增加用户注册/登录模块,集成 JWT 鉴权。
- 支持创建编程竞赛,实时排名榜(基于 Redis 缓存提升性能)。
- 题目管理与测试用例自动化:
- 开发管理员后台,支持题目上传、测试用例批量导入。
- 实现自动化测试框架,验证题目正确性。
2. 性能优化
- 分布式编译集群:
将compile_server
部署为多节点集群,结合ZooKeeper
实现服务发现与动态扩缩容。 - 结果缓存与异步队列:
- 使用 Redis 缓存高频题目的编译结果,减少重复计算。
- 引入 RabbitMQ 异步处理判题请求,削峰填谷。
3. 安全性增强
- 代码沙箱强化:
- 使用 Docker 或 Linux Namespace 实现进程级隔离,彻底防止恶意代码逃逸。
- 静态代码分析(如 Clang 静态检查器),拦截危险系统调用(如
fork
)。
- 流量控制与防刷:
集成 Nginx 限流模块,防止高频提交攻击。
4. 运维与监控
- Prometheus + Grafana 监控:
采集编译节点负载、请求耗时等指标,实现可视化监控与告警。 - 日志聚合:
使用 ELK(Elasticsearch + Logstash + Kibana)集中管理日志,便于故障排查。
相关文章:
负载均衡的在线OJ项目
负载均衡的在线OJ项目 所用技术与开发环境项目的宏观结构我们的项目要实现的最重要的功能:我们项目的整体结构:项目编写思路 compile_server模块compiler模块设计与编写Runner模块设计与编写细节setrlimit系统调用函数程序流程图 Compile_and_run模块设计…...
CPP从入门到入土之类和对象Ⅱ
一、六大默认成员函数 默认成员函数是用户没有显式实现,编译器自动生成的成员函数。 一个类,我们在不写的情况下,编译器会默认生成六个默认成员函数 本文详细介绍构造函数和析构函数 二、构造函数 构造函数虽名为构造函数,但是…...
2025年 cocosCreator 1.8 定制 JavaScript 引擎
参考文档:https://docs.cocos.com/creator/1.9/manual/zh/advanced-topics/engine-customization.html PS: 1.8的文档已经没了,只能看1.9的,所幸这两个版本差别不大 获取 JS 引擎 原文中github上的分支已经找不到了,这里直接从c…...
「JavaScript深入」Socket.IO:基于 WebSocket 的实时通信库
Socket.IO Socket.IO 的核心特性Socket.IO 的架构解析Socket.IO 的工作流程Socket.IO 示例:使用 Node.js 搭建实时聊天服务器1. 安装 Socket.IO2. 服务器端代码(Node.js)3. 客户端代码(HTML JavaScript)4. 房间功能 高…...
turnjs图册翻书效果
npm install https://github.com/igghera/turn.js.git //或者 npm install turn.js //import $ from "jquery"; //记得引入jquery import turn.js; // 引入 Turn.jsimport turn from "/utils/turn.min.js";// 引入 Turn.jsinitBook(length) {var that thi…...
大语言模型的训练数据清洗策略
目录 大语言模型的训练数据清洗策略 1. 数据去重与标准化 问题 解决方案 示例代码(Python 实现数据去重): 2. 过滤有害内容 问题 解决方案 示例代码(基于关键词过滤有害内容): 3. 纠正数据不均衡 …...
在 Vue 项目中调用 DeepSeek API(示例篇)
在 Vue 项目中调用 DeepSeek(假设 DeepSeek 是一个提供 API 服务的第三方工具,例如用于搜索、数据分析等),通常需要通过 HTTP 请求与 DeepSeek 的 API 进行交互。以下是一个简单的示例,展示如何在 Vue 项目中调用 DeepSeek API。 实例如下: 安装依赖 首先,确保你的项目中…...
对接股票金融数据源API
StockTV 股票市场API StockTV 提供全面的实时和历史股市数据 API,涵盖全球股票、外汇、期货及市场新闻数据,助力投资者精准把握市场动态。 主要功能 实时和历史股市数据 API 获取全球股票市场的实时行情、历史数据及深度分析,支持多语言查询…...
蓝桥杯关于栈这个数据结构的一个算法题目
文章目录 1.题目概述解释2.思路分析3.代码解析 1.题目概述解释 找出来这个字符串里面重复出现的字符,类似于这个消消乐的游戏; 示例一里面的这个bb是连续的并且是一样的这个字符,因此删除bb,删除之后发现这个aa有一次相邻了&…...
SpringBoot配置文件加载优先级
在Spring Boot项目中,配置属性的优先级是一个重要的概念,它决定了当存在多个配置源时,哪个配置源的属性将被应用。以下是SpringBoot中配置属性的优先级,从最高到最低: 命令行参数: 命令行参数具有最高的优先…...
企业数据治理解决方案(46页PPT)(文末有下载方式)
资料解读:企业数据治理解决方案 详细资料请看本解读文章的最后内容。 在当今数字化时代,数据已成为企业的核心资产,对企业的发展起着至关重要的作用。然而,许多企业在数据管理方面面临诸多挑战,如数据不全、样式繁多、…...
版本控制器Git ,Gitee如何连接Linux Gitee和Github区别
📖 示例场景 假设你和朋友在开发一个「在线笔记网站」,代码需要频繁修改和协作: 只用本地文件管理 每次修改后手动复制文件,命名为 v1.html、v2.html 问题:无法追踪具体改动内容;多人修改易冲突࿱…...
[网安工具] 网安工具库 —— 工具管理手册
0x00:工具管理类 — Tools Management 0x01:信息收集类 — Information Gathering 自动化综合信息收集工具 — ARL 灯塔 0x02:漏洞探测类 — Vulnerability Identification 浏览器渗透辅助插件 —— HackBar 0x03:漏洞利用类…...
在LwIP中,`tcp_recved()`、`tcp_sndbuf()` 和 `tcp_write()`三个函数详细用法及示例
在LwIP中,tcp_recved()、tcp_sndbuf() 和 tcp_write() 是TCP协议栈的核心函数,用于管理接收和发送数据流。以下是它们的详细用法及示例: 1. tcp_recved() 功能 通知协议栈已处理接收数据:当应用层从接收缓冲区读取数据后&#x…...
外卖避雷方案 改进型(个人使用版)
昨天怒花100请教了双尾彗星对外卖避雷的计划。 总结下来是行不通。 1.很容易被水军冲击数据真实性, 1.这种方案是从末端来解决问题,食品卫生问题,最好还是从解决分成方面的问题。 2.这种方案没有解决人们对食品安全的焦虑。 既然这样那只能先弄个只给自己用的避雷程序,打造…...
深度学习与传统算法在人脸识别领域的演进:从Eigenfaces到ArcFace
一、传统人脸识别方法的发展与局限 1.1 Eigenfaces:主成分分析的经典实践 算法原理 Eigenfaces是基于主成分分析(PCA)的里程碑式方法。其核心思想是将人脸图像视为高维向量,通过协方差矩阵计算特征向量(即特征脸&…...
druid开启防火墙之后的bug
bug以及解决方案 不允许执行多个语句不允许有注释部分数据有误识别,抛出异常,导致原本正常执行的语句被中断 解决方案 application.yaml中对于druid配置如下: wall:enabled: true # 开启防火墙config:multi-statement-allow: true # 允许多个…...
代码随想录_动态规划
代码随想录 动态规划 509.斐波那契数 509. 斐波那契数 斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) 0,F(1) 1 F(n…...
指令系统3(算数运算指令)
一.加法指令(ADD): 用于执行 16 位或 32 位的加法运算。 指令格式及功能: ADD Rd, Rm:将寄存器Rd和Rm的值相加,结果存回Rd,即Rd Rd Rm。ADD Rd, Rn, Rm:把寄存器Rn和Rm的值相加&…...
VLLM专题(三十一)—架构概述
本文档提供了vLLM架构的概述。 1. 入口点 vLLM 提供了多个与系统交互的入口点。下图展示了它们之间的关系。 1.1 LLM 类 LLM 类提供了用于进行离线推理的主要 Python 接口,即在不使用单独的模型推理服务器的情况下与模型进行交互。 以下是 LLM 类的使用示例: from vll…...
【操作系统】进程间通信方式
进程间通信方式 前言 / 概述一、管道管道命名管道 二、消息队列三、共享内存四、信号量信号量概述互斥访问条件同步信号 五、socket总结 前言 / 概述 每个进程的用户地址空间都是独立的,⼀般而言是不能互相访问的,但内核空间是每个进程都共享的ÿ…...
Flutter 打包 ipa出现错误问题 exportArchive
一、错误信息: Encountered error while creating the IPA: error: exportArchive: "Runner.app" requires a provisioning profile with the Push Notifications feature. Try distributing the app in Xcode: open /project/your_app/build/ios/archive/Runner.…...
Ubutu20.04安装docker与docker-compose
系统:20.04.6 LTS (Focal Fossa)" 1.配置apt源(在/etc/apt/sources.list中输入以下内容) # deb cdrom:[Ubuntu 20.04.6 LTS _Focal Fossa_ - Release amd64 (20230316)]/ focal main restricted deb http://mirrors.aliyun.com/ubuntu/ focal main restricted …...
Java 反射机制
Java 反射机制 Java 反射机制1. 反射机制简介2. 反射的核心类3. 反射的基本步骤3.1 加载类,获取类的字节码:Class 对象3.2 获取类的构造器:Constructor 对象3.3 获取类的成员变量:Field 对象3.4 获取类的成员方法:Meth…...
前端流式输出实现详解:从原理到实践
前端流式输出实现详解:从原理到实践 前言一、流式输出核心原理1.1 什么是流式输出?1.2 技术优势对比1.3 关键技术支撑 二、原生JavaScript实现方案2.1 使用Fetch API流式处理关键点解析: 2.2 处理SSE(Server-Sent Eventsÿ…...
CTF题目《easy_tornado》(护网杯 2018)Write Up
题目背景与信息收集 初始访问 题目提供三个文件链接: /flag.txt:提示flag位于/fllllllllllllag文件/welcome.txt:关键词render,暗示模板渲染漏洞(SSTI)/hints.txt:提示签名算法md5(cookie_secre…...
暗光增强技术研究进展与产品落地综合分析(2023-2025)
一、引言 暗光增强技术作为计算机视觉与移动影像领域的核心研究方向之一,近年来在算法创新、硬件适配及产品落地方面取得了显著进展。本文从技术研究与产业应用两个维度,系统梳理近三年(2023-2025)该领域的关键突破,并对比分析主流手机厂商的影像技术优劣势。 二、暗光增…...
CAM350-14.6学习笔记-1:导入Gerber文件
CAM350-14.6学习笔记-1:导入Gerber文件 使用自动导入器导入Gerber1:导航栏Home下面的Import——Automatic Import——选择文件路径——Next2:设置每层的类型:3:设置叠层4:弹出层别显示框及Gerber显示 按照Allegro输出的…...
【Zephyr】【二】学习笔记【RTOS系统架构】
Zephyr RTOS 系统架构 整体架构 #mermaid-svg-MENZa2zm9JlGktYP {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-MENZa2zm9JlGktYP .error-icon{fill:#552222;}#mermaid-svg-MENZa2zm9JlGktYP .error-text{fill:#55…...
Web-Machine-N7靶机通关攻略
获取靶机ip arp-scan -l 端口扫描 nmap xxxx 访问80端口发现没用 扫描目录 gobuster dir -u http:/192.168.117.160 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium,txt -x php,html,txt ,zip 打开exploit.html 点击F12,修改localhost为靶机ip&#…...
CSS 用于图片的样式属性
CSS 设置图像样式 CSS中用于图片的样式属性主要包括以下几个方面: 边框和背景: border:可以设置图片的边框样式、宽度和颜色。例如,img { border: 1px solid #ddd; } 会给图片添加1像素的实线边框,颜色为灰色…...
【工具分享】vscode+deepseek的接入与使用
目录 第一章 前言 第二章 获取Deepseek APIKEY 2.1 登录与充值 2.2 创建API key 第三章 vscode接入deepseek并使用 3.1 vscode接入deepseek 3.2 vscode使用deepseek 第一章 前言 deepseek刚出来时有一段时间余额无法充值,导致小编没法给大家发完整的流程&…...
《Java核心三问:字符串、equals()、hashCode()的隐藏雷区与完美避坑手册》
目录 一.String、StringBuffer、StringBuilder的区别?1. 核心区别总结2. 具体实例演示示例1:不可变性 vs 可变性示例2:线程安全验证 2. 线程安全的关键:synchronized3. 对比StringBuilder的非线程安全4. 可视化执行流程5. 进一步验…...
【Java】TCP网络编程:从可靠传输到Socket实战
活动发起人小虚竹 想对你说: 这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!…...
背包问题——多重背包(C语言)
代码如下: #include<stdio.h>int knapsack(int weight[], int value[], int count[], int n, int capacity) {int* dp (int*)malloc(sizeof(int) * (capacity 1));for (int i 0; i < capacity; i){dp[i] 0;}for (int i 0; i < n; i)//核心代码{fo…...
微服务》》Kubernetes (K8S) 集群配置网络》》Calico
嘻嘻嘻 以Calico 为例子 Calico官网 官网上有安装Calico插件的步骤 步骤 要在主节点 主节点 主节点 执行 kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml kubectl get pod --all-namespaces kubec…...
寄存器(内部访问)
内存中字的储存 我们之前提到过,字由两个字节组成,当我们用16位寄存器来储存时,把字分别储存在连续的两个内存单元中,高位字节放在高地址单元中,低位字节则放在低位地址单元中。 例如上图,0、1两个单元存放…...
Vue2 watch侦听器(监听器)
watch侦听器(监听器)功能是监听数据变化,执行一些业务逻辑 或 异步操作。 核心选项 handler 监听函数,接收 (newVal, oldVal) 作为参数。deep: true 深度监听对象/数组内部变化。immediate: true 在组件创建时…...
【PCB工艺】基础:电子元器件
电子原理图(Schematic Diagram)是电路设计的基础,理解电子元器件和集成电路(IC)的作用,是画好原理图的关键。 本专栏将系统讲解 电子元器件分类、常见 IC、电路设计技巧,帮助你快速掌握电子电路…...
linux性能监控的分布式集群 prometheus + grafana 监控体系搭建
prometheusgrafana分布式集群资源监控体系搭建 前言一、安装 prometheus二、在要监控的服务器上安装监听器三、prometheus服务器配置四、grafana配置大屏五、创建Linux监控看板五、监控windows服务器注意事项 前言 Prometheus 是一个开源的 分布式监控系统 和 时间序列数据…...
Ubuntu AX200 iwlwifi-cc-46.3cfab8da.0.tgz无法下载的解决办法
文章目录 前言一、检查网卡是否被识别二、确认内核模块是否可用1.AX200 wifi 要求内核5.12.检查 iwlwifi.ko 是否存在:3.如果未找到,可能是内核模块未正确生成。尝试安装 linux-modules-extra:4.再次检查 iwlwifi.ko 是否存在:5.确…...
LinkedList与链表
ArrayList的缺陷 在上一篇博客中,小编已经较为详细得给大家介绍了ArrayList这个结构了。但是ArrayList存在一些缺陷:由于其底层是一段连续空间,当在ArrayList任意位置插入或者删除元素时,就需要将后序元素整体往前或者往后搬移&am…...
CCF 编程能力认证 C++ 四级宝典
CCF编程能力等级认证(以下简称GESP)2025年四次认证时间分别为:3月22日、6月28日、9月27日、12月20日,认证方式为线下机考,认证语言包括:C、Python和Scratch三种语言,其中Scratch认证为一到四级&…...
信创系统极速文件查找:locate 命令详解
原文链接:信创系统极速文件查找:locate 命令详解 Hello,大家好啊!今天给大家带来一篇信创终端操作系统上 locate 命令详解的文章。在 Linux 及信创终端操作系统(如 统信 UOS、麒麟 KOS)中,查找…...
数据集获取
sklearn数据集 sklearn有四部分数据。其中sklearn的数据集有两部分真实的数据,一部分嵌入到了sklearn库中,即安装好sklearn后就自带了一部分数据,这些数据的规模比较小称为small toy datasets ,还有一部分数据是需要在网上下载的,sklearn提供了下载的api接口,这些数据规…...
三格电子PLC数据采集网关-工业互联的智能枢纽
在工业自动化领域,设备间的数据互通与协议兼容是核心挑战之一。三格电子推出的PLC据采集网关SG-PLC-Private,凭借其多协议兼容、高稳定性和灵活配置能力,成为工业物联网(IIoT)中实现设备互联的关键设备。本文将从产品功…...
【sgFloatDialog】自定义组件:浮动弹窗,支持修改尺寸、拖拽位置、最大化、还原、最小化、复位
sgFloatDialog <template><div :class"$options.name" v-if"visible" :size"size" :style"style"><!-- 托盘头部 --><div class"header" ref"header" dblclick.stop.prevent"dblclic…...
conda 常用命令
常用 Conda 命令整理环境管理 conda create --name 环境名 :创建新环境 conda activate 环境名 :激活环境 conda deactivate:退出环境 conda env list:列出所有环境 conda remove --name 环境名 --all :删除环…...
神聖的綫性代數速成例題10. N維矢量綫性運算、矢量由矢量組綫性表示、N個N維矢量相關性質
N 維矢量綫性運算: 設,是維矢量,是數。加法:。數乘:。 矢量由矢量組綫性表示: 設是n維矢量,若存在一組數,使得,則稱矢量可由矢量組綫性表示。 N 個 N 維矢量相關性質&…...
力扣977. 有序数组的平方(双指针技巧)
Problem: 977. 有序数组的平方 文章目录 题目描述思路Code 题目描述 思路 由于数组是非递减且包含正、负数,则假如我们将数组中的每一个元素取平方并且将其从原始的某一个位置分开成两个数组来看待(一个数组从前往后看,一个数组从后往前看&am…...