这个项目使用了前后端,实现一个丐版的LeetCode刷题网站,并根据每台主机的实际情况,选择对应的主机,负载均衡的调度
所用技术:
由于一般日志都会带上时间, 这里还需要实现一个得到当前时间的函数,则我又在util.hpp把得到时间函数的类封装成了一个类
由于会频繁的调用日志进行打印信息,也为了更简便的调用,我进行了以下处理
由于引入了日志,则就可以把之前所有的输出信息,换成日志输出
程序运行: 1)代码跑完,结果正确 2)代码跑完,结果不正确, 3)代码没跑完,异常了
程序结果是否正确,是由oj_server中的测试用例决定的,则run模块只考虑是否正确运行完毕
- #include
- #include
- #include
- #include
- #include
- #include
// fork接口需要 -
- #include "../comm/log.hpp"
- #include "../comm/util.hpp"
- namespace ns_runner
- {
-
- using namespace ns_util;
- using namespace ns_log;
- class Runner
- {
- public:
- Runner(){}
- ~Runner(){}
- static int Run(const std::string &file_name){
- std::string _execute = PathUtil::Exe(file_name);// 可执行
- std::string _stdin = PathUtil::Stdin(file_name);// 输入
- std::string _stdout = PathUtil::Stdout(file_name);// 输出
- std::string _stderr = PathUtil::Stderr(file_name);// 错误
-
- umask(0);
- int _stdin_fd = open(_stdin.c_str(),O_CREAT | O_RDONLY,0644);
- int _stdout_fd = open(_stdout.c_str(),O_CREAT | O_WRONLY,0644);
- int _stderr_fd = open(_stderr.c_str(),O_CREAT | O_WRONLY,0644);
-
- if(_stdin_fd < 0 || _stdout_fd < 0 || _stderr_fd < 0){
- LOG(ERROR) << "运行时打开标准文件失败" << "\n";
- return -1;// 代表打开文件失败
- }
-
- pid_t pid = fork();
- if(pid < 0){
- LOG(ERROR) << "运行创建子进程失败" << "\n";
- close(_stdin_fd);
- close(_stdout_fd);
- close(_stderr_fd);
- return -2;// 代表创建自己失败
- }
- else if(pid == 0){
- // 子进程
- dup2(_stdin_fd,0);
- dup2(_stdout_fd,1);
- dup2(_stderr_fd,2);
- LOG(INFO) << "123";// 是不是有问题啊
- // 这个程序替换等价于 ./tmp/code.exe ./tmp/code.exe
- execl(_execute.c_str(),_execute.c_str(),nullptr);
- exit(1);
- }
- else{
- close(_stdin_fd);
- close(_stdout_fd);
- close(_stderr_fd);
- int status = 0;// 表示输出型参数
- waitpid(pid,&status,0);// 阻塞式等待
-
- // 程序运行异常,一定是因为收到信号
- LOG(INFO) << "运行完毕,infor: " << (status & 0x7f) << "\n";
- return status & 0x7f;
- }
- }
- };
- }
返回值 > 0: 程序异常了,退出时收到了信号,返回值就是对应的信号编号
返回值 == 0: 正常运行完毕的,结果保存到了对应的临时文件中
返回值 < 0: 内部错误
解释waitpid第2个输出型参数
那6个程序替换的系统接口,具体使用那个看实际情况
这个项目走到这里就需要编写compile_run.hpp,将编译和运行的逻辑连接在一起,且code.cpp需要被处理的源文件,不应该是我们自己添加的,而是需要再客户端中导入的
这个模块要做的是:a)适配用户请求,引入json定制通信协议字段b)形成唯一文件名c)正确调用compile and run
在centos中安装: sudo yum install json-c-devel
头文件 #include <jsoncpp/json/json.h>
complie_run.hpp
- #pragma once
-
- #include "compiler.hpp"
- #include "runner.hpp"
- #include "../comm/log.hpp"
- #include "../comm/util.hpp"
- #include
-
- namespace ns_compile_and_run
- {
- using namespace ns_log;
- using namespace ns_util;
- using namespace ns_compiler;
- using namespace ns_runner;
-
- // in_json: {"code": "#include...", "input": "","cpu_limit":1, "mem_limit":10240}
- // out_json: {"status":"0", "reason":"","stdout":"","stderr":"",}
- static void Start(const std::string &in_json,std::string *out_json){
- // step1: 反序列化过程
- Json::Value in_value;
- Json::Reader reader;
- // 把in_json中的数据写到in_value中
- reader.parse(in_json,in_value);// 最后再处理差错问题
-
- std::string code = in_value["code"].asString();
- std::string input = in_value["input"].asString();
- int cpu_limit = in_value["cpu_limit"].asInt();
- int mem_limit = in_value["mem_limit"].asInt();
-
- Json::Value out_value;
- int status_code = 0;
- int run_result = 0;
- std::string file_name;// 唯一文件名
- if(code.size() == 0){
- status_code = -1;// 代码为空
- goto END;
- }
- // 形成的文件名只居有唯一性,没有目录没有后缀
- // 使用: 毫秒级时间戳 + 原子性递增唯一值 : 来保证唯一性
- file_name = FileUtil::UniqFileName();
- // 形成临时的src文件
- if(!FileUtil::WriteFile(PathUtil::Src(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){
- // runnem模块内部错误
- status_code = -2;// 未知错误
- }
- else if(run_result > 0){
- // 程序运行崩溃
- status_code = run_result;// 这里的run_result是信号
- }
- else{
- // 运行成功
- status_code = 0;
- }
- END:
- out_value["status"] = status_code;
- out_value["reason"] = CodeToDest(status_code,file_name);// 得到错误信息字符串
- if(status_code == 0){
- // 整个过程全部成功
- std::string _stdout;
- FileUtil::ReadFile(PathUtil::Stdout(file_name),&_stdout,true);
- out_value["stdout"] = _stdout;
-
- std::string _stderr;
- FileUtil::ReadFile(PathUtil::Stdout(file_name),&_stderr,true);
- out_value["stdout"] = _stdout;
- }
-
- // step2: 序列化
- Json::StyledWriter writer;
- *out_json = writer.write(out_value);
-
-
- }
-
-
- }
getline:不保存行分割符,有些时候需要保留\n,getline内部重载了强制类型转化
- #include "compile_run.hpp"
- using namespace ns_compile_and_run;
-
- int main()
- {
- std::string in_json;
- Json::Value in_value;
- // R"()", raw string
- in_value["code"] = R"(#include
- int main(){
- std::cout << "你可以看见我了" << std::endl;
- return 0;
- })";
- in_value["input"] = "";
- in_value["cpu_limit"] = 1;
- in_value["mem_limit"] = 10240 * 3;
- Json::FastWriter writer;
- in_json = writer.write(in_value);
- // std::cout << in_json << std::endl;
- // 这个是将来给客户端返回的json串
- std::string out_json;
- CompileAndRun::Start(in_json, &out_json);
- std::cout << out_json << std::endl;
- return 0;
- }
安装scl : sudo yum install centos-release-scl scl-utils-build
安装新版本gcc: sudo yum install - y devtoolset - 9 - gcc devtoolset - 9 - gcc - c ++
如果不更新在使用cpp-httplib时可能会报错, 用老的编译器,要么编译不通过,要么直接运行报错
compiler_server.cc
- #include "compile_run.hpp"
- #include "../comm/httplib.h"// 引入
-
- using namespace ns_compile_and_run;
- using namespace httplib;// 引入
-
- void Usage(std::string proc){
- std::cerr << "Usage: " << "\n\t" << proc << " port" << std::endl;
- }
-
- //./copile_server port
- 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){
- // 用户请求的服务正文是我们想要的json string
- std::string in_json = req.body;
- std::string out_json;
- if(!in_json.empty()){
- CompileAndRun::Start(in_json,&out_json);
- resp.set_content(out_json,"application/json;charset=uft-8");
- }
- });
- svr.listen("0.0.0.0",atoi(argv[1]));
- return 0;
- }
-
-
1. 获取首页,用题目列表充当2. 编辑区域页面3. 提交判题功能(编译并运行)
Model , 通常是和数据交互的模块 ,比如,对题库进行增删改查(文件版, MySQL )
sudo yum install -y boost-devel //是boost 开发库
header.cpp
- #include
- #include
- #include
- #include
- #include
-
- using namespace std;
-
- class Solution{
- public:
- bool isPalindrome(int x)
- {
- //将你的代码写在下面
-
- return true;
- }
- };
tail.cpp
- #ifndef COMPILER_ONLINE
- #include "header.cpp"
- #endif
-
- // 这里先把测试用例 暴露出来
- void Test1()
- {
- // 通过定义临时对象,来完成方法的调用
- bool ret = Solution().isPalindrome(121);
- if(ret){
- std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
- }
- else{
- std::cout << "没有通过用例1, 测试的值是: 121" << std::endl;
- }
- }
-
- void Test2()
- {
- // 通过定义临时对象,来完成方法的调用
- bool ret = Solution().isPalindrome(-10);
- if(!ret){
- std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
- }
- else{
- std::cout << "没有通过用例2, 测试的值是: -10" << std::endl;
- }
- }
-
- int main()
- {
- Test1();
- Test2();
-
- return 0;
- }
数据交互 && 提供接口
- #pragma once
- // 文件版本
- #include "../comm/util.hpp"
- #include "../comm/log.hpp"
- #include
- #include
- #include
-
- namespace ns_model
- {
- using namespace std;
- using namespace ns_log;
- using namespace ns_util;
-
- struct Question{
- string number;// 题目编号,唯一
- string tile;// 题目标题
- string star;// 难度: 简单 中等 困难
-
- int cpu_limit;// 题目的时间复杂度(S)
- int mem_limit;// 题目的空间复杂度(KB)
-
- string desc;// 题目描述
- string header; // 题目预设给用户在线编辑器的代码
- string tail;// 题目测试用例,需要和header拼接
- };
-
- const string questions_list = "./question/quetions.list";
- const string questions_path = "./question";
-
- class Model
- {
- public:
- Model(){
- // 加载所有题目:底层是用hash表映射的
- assert(LoadQuestionList(questions_list));
- }
- ~Model(){
- ;
- }
-
- // 获取所有题目,这里的out是输出型参数
- bool GetAllQuestions(vector
*out) { - if(questions.size() == 0){
- LOG(ERROR) << "用户获取题库失败" << "\n";
- return false;
- }
- for(const auto&q: questions){
- out->push_back(q.second);
- }
- return true;
- }
-
- // 获取指定题目,这里的q是输出型参数
- bool GetOneQuestion(const string& number,Question* q){
- const auto& iter = questions.find(number);
- if(iter == questions.end()){
- LOG(ERROR) << "用户获取题目失败,题目编号: " << number << "\n";
- return false;
- }
- (*q) = iter->second;
- return true;
- }
-
- // 加载配置文件: questions/questions.list + 题目编号文件
- bool LoadQuestionList(const string&question_list){
- // 加载配置文件: questions/questions.list +题目编号文件
- ifstream in(question_list);
- if(!in.is_open()){
- LOG(FATAL) << "加载题库失败,请检查是否存在题库文件" << "\n";
- return false;
- }
- string line;
- while(getline(in,line)){
- vector
tokens; - StringUtil::SplitString(line,&tokens," ");// 被分割的字符串 缓冲区 分割符
- // eg: 1 判断回文数 简单 1 30000
- if(tokens.size()!=5){
- LOG(WARNING) << "加载部分题目失败,请检查文件格式" << "\n";
- continue;
- }
- Question q;
- q.number = tokens[0];
- q.tile = tokens[1];
- q.star = tokens[2];
- q.cpu_limit = atoi(tokens[3].c_str());
- q.mem_limit = atoi(tokens[4].c_str());
-
- string path = questions_list;
- path += q.number;
- path += "/";
-
- // 第三个参数代表 是否加上 \n
- FileUtil::ReadFile(path+"desc.txt",&(q.desc),true);
- FileUtil::ReadFile(path+"header.cpp",&(q.header),true);
- FileUtil::ReadFile(path+"tail.txt",&(q.tail),true);
-
- questions.insert({q.number,q});// 录题成功
- }
- LOG(INFO) << "加载题库...成功" << "\n";
- in.close();
- }
- private:
- // 题号 : 题目细节
- unordered_map
questions; - };
- }
-
- namespace ns_control
- {
- using namespace std;
- using namespace ns_log;
- using namespace ns_util;
- using namespace ns_model;
- using namespace ns_view;
- using namespace httplib;
-
- // 提供服务的主机
- class Machine
- {
- public:
- std::string ip; //编译服务的ip
- int port; //编译服务的port
- uint64_t load; //编译服务的负载
- std::mutex *mtx; // mutex禁止拷贝的,使用指针
- public:
- Machine() : ip(""), port(0), load(0), mtx(nullptr)
- {
- }
- ~Machine()
- {
- }
-
- public:
- // 提升主机负载
- void IncLoad()
- {
- if (mtx) mtx->lock();
- ++load;
- if (mtx) mtx->unlock();
- }
- // 减少主机负载
- void DecLoad()
- {
- if (mtx) mtx->lock();
- --load;
- if (mtx) mtx->unlock();
- }
- void ResetLoad()
- {
- if(mtx) mtx->lock();
- load = 0;
- if(mtx) mtx->unlock();
- }
- // 获取主机负载,没有太大的意义,只是为了统一接口
- uint64_t Load()
- {
- uint64_t _load = 0;
- if (mtx) mtx->lock();
- _load = load;
- if (mtx) mtx->unlock();
-
- return _load;
- }
- };
-
- const std::string service_machine = "./conf/service_machine.conf";
- class LoadBlance
- {
- private:
- // 可以给我们提供编译服务的所有的主机
- // 每一台主机都有自己的下标,充当当前主机的id
- std::vector
machines; - // 所有在线的主机id
- std::vector<int> online;
- // 所有离线的主机id
- std::vector<int> offline;
- // 保证LoadBlance它的数据安全
- std::mutex mtx;
-
- public:
- LoadBlance()
- {
- assert(LoadConf(service_machine));
- LOG(INFO) << "加载 " << service_machine << " 成功"
- << "\n";
- }
- ~LoadBlance()
- {
- }
-
- public:
- bool LoadConf(const std::string &machine_conf)
- {
- std::ifstream in(machine_conf);
- if (!in.is_open())
- {
- LOG(FATAL) << " 加载: " << machine_conf << " 失败"
- << "\n";
- return false;
- }
- std::string line;
- while (std::getline(in, line))
- {
- std::vector
tokens; - StringUtil::SplitString(line, &tokens, ":");
- if (tokens.size() != 2)
- {
- LOG(WARNING) << " 切分 " << line << " 失败"
- << "\n";
- continue;
- }
- Machine m;
- m.ip = tokens[0];
- m.port = atoi(tokens[1].c_str());
- m.load = 0;
- m.mtx = new std::mutex();
-
- online.push_back(machines.size());
- machines.push_back(m);
- }
-
- in.close();
- return true;
- }
- // id: 输出型参数
- // m : 输出型参数
- bool SmartChoice(int *id, Machine **m)
- {
- // 1. 使用选择好的主机(更新该主机的负载)
- // 2. 我们需要可能离线该主机
- mtx.lock();
- // 负载均衡的算法
- // 1. 随机数+hash
- // 2. 轮询+hash
- int online_num = online.size();
- if (online_num == 0)
- {
- mtx.unlock();
- LOG(FATAL) << " 所有的后端编译主机已经离线, 请运维的同事尽快查看"
- << "\n";
- return false;
- }
- // 通过遍历的方式,找到所有负载最小的机器
- *id = online[0];
- *m = &machines[online[0]];
- uint64_t min_load = machines[online[0]].Load();
- for (int i = 1; i < online_num; i++)
- {
- uint64_t curr_load = machines[online[i]].Load();
- if (min_load > curr_load)
- {
- min_load = curr_load;
- *id = online[i];
- *m = &machines[online[i]];
- }
- }
- mtx.unlock();
- return true;
- }
- void OfflineMachine(int which)
- {
- mtx.lock();
- for(auto iter = online.begin(); iter != online.end(); iter++)
- {
- if(*iter == which)
- {
- machines[which].ResetLoad();
- //要离线的主机已经找到啦
- online.erase(iter);
- offline.push_back(which);
- break; //因为break的存在,所有我们暂时不考虑迭代器失效的问题
- }
- }
- mtx.unlock();
- }
- void OnlineMachine()
- {
- //我们统一上线,后面统一解决
- mtx.lock();
- online.insert(online.end(), offline.begin(), offline.end());
- offline.erase(offline.begin(), offline.end());
- mtx.unlock();
-
- LOG(INFO) << "所有的主机有上线啦!" << "\n";
- }
- //for test
- void ShowMachines()
- {
- mtx.lock();
- std::cout << "当前在线主机列表: ";
- for(auto &id : online)
- {
- std::cout << id << " ";
- }
- std::cout << std::endl;
- std::cout << "当前离线主机列表: ";
- for(auto &id : offline)
- {
- std::cout << id << " ";
- }
- std::cout << std::endl;
- mtx.unlock();
- }
- };
- }
逻辑控制模块
- // 这是我们的核心业务逻辑的控制器
- class Control
- {
- private:
- Model model_; //提供后台数据
- View view_; //提供html渲染功能
- LoadBlance load_blance_; //核心负载均衡器
- public:
- Control()
- {
- }
- ~Control()
- {
- }
-
- public:
- void RecoveryMachine()
- {
- load_blance_.OnlineMachine();
- }
- //根据题目数据构建网页
- // html: 输出型参数
- bool AllQuestions(string *html)
- {
- bool ret = true;
- vector<struct Question> all;
- if (model_.GetAllQuestions(&all))
- {
- sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2){
- return atoi(q1.number.c_str()) < atoi(q2.number.c_str());
- });
- // 获取题目信息成功,将所有的题目数据构建成网页
- // ...
- }
- else
- {
- *html = "获取题目失败, 形成题目列表失败";
- ret = false;
- }
- return ret;
- }
- bool Question(const string &number, string *html)
- {
- bool ret = true;
- struct Question q;
- if (model_.GetOneQuestion(number, &q))
- {
- // 获取指定题目信息成功,将所有的题目数据构建成网页
- // ....
- }
- else
- {
- *html = "指定题目: " + number + " 不存在!";
- ret = false;
- }
- return ret;
- }
- // code: #include...
- // input: ""
- void Judge(const std::string &number, const std::string in_json, std::string *out_json)
- {
-
- }
- };
渲染本质就是key-value之间的替换
test.cpp
- #include
- #include
- #include
- int main()
- {
- std::string html = "./test.html";
- std::string html_info = "测试ctemplate渲染";
-
- // 建立ctemplate参数目录结构
- ctemplate::TemplateDictionary root("test"); // unordered_map
test; -
- // 向结构中添加你要替换的数据,kv的
- root.SetValue("info", html_info); // test.insert({key, value});
-
- // 获取被渲染对象
- // DO_NOT_STRIP:保持html网页原貌
- ctemplate::Template *tpl = ctemplate::Template::GetTemplate(html,ctemplate::DO_NOT_STRIP);
-
- // 开始渲染,返回新的网页结果到out_html
- std::string out_html;
- tpl->Expand(&out_html, &root);
-
- std::cout << "渲染的带参html是:" << std::endl;
- std::cout << out_html << std::endl;
-
- return 0;
- }
test.html
- "en">
-
- "UTF-8">
- "X-UA-Compatible" content="IE=edge">
- "viewport" content="width=device-width, initial-scale=1.0">
-
Document -
-
{{info}}
-
{{info}}
-
{{info}}
-
{{info}}
-
错误原因: error while loading shared libraries: libmpc.so.3: cannot open shared object file
export LD\_LIBRARY\_PATH=$LD\_LIBRARY\_PATH:/usr/local/lib
# cat /etc/ld.so.conf
include ld.so.conf.d/*.conf
# echo "/usr/local/lib" >> /etc/ld.so.conf
# ldconfig
- #pragma once
- #include
- #include
- #include "./oj_model.hpp"
-
- namespace ns_view
- {
- using namespace ns_model;
-
- const std::string template_path = "./template_html/";
-
- class View
- {
- public:
- View(){}
- ~View(){}
-
- // 渲染所有题目
- void ALLExpandHtml(const vector<struct Question>&question,std::string *html){
- // 题目编号 题目标题 题难度
- // 推荐表格实现
- // 1.形成路径
- string src_html = template_path + "all_quetions.html";
-
- // 2.形成数字典
- ctemplate::TemplateDictionary root("all_question");
- for(const auto& q: question){
- ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("question_list");
- sub->SetValue("number",q.number);
- sub->SetValue("title",q.title);
- sub->SetValue("star",q.star);
- }
-
- // 3. 获取被渲染的html
- ctemplate::Template*tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
-
- // 4.开始完成渲染功能
- tpl->Expand(html,&root);
- }
-
- // 渲染一道题目
- void OneExpandHtml(const struct Question &q,string *html){
- // 1.形成路径
- std::string src_html = template_path + "one_question.html";
-
- // 2. 形成数字典
- ctemplate::TemplateDictionary root("one_question");
- root.SetValue("number",q.number);
- root.SetValue("title",q.title);
- root.SetValue("star",q.star);
- root.SetValue("desc",q.desc);
- root.SetValue("header",q.header);
-
- // 3.获取被渲染的html
- ctemplate::Template*tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
- // 4.开始完成渲染功能
- tpl->Expand(html,&root);
- }
- };
- }
oj_server.cc
- #include
- #include "../comm/httplib.h"// 引入
- #include "oj_control.hpp"
-
- using namespace httplib;// 引入
- using namespace ns_control;
-
- int main()
- {
- // 用户请求的服务器路由功能
- Server svr;
- Control ctrl;
- // 获取所有的题目列表
- svr.Get("/all_questions",[&ctrl](const Request&req,Response &resp){
- // 返回一张包含所有题目的html网页
- std::string html;// 待处理
- ctrl.AllQuestions(&html);
- resp.set_content(html,"text/html;charset=utf-8");
- });
-
- // 根据题目编号,获取题目内容
- // \d+ 是正则表达式的特殊符合
- svr.Get(R"(/question/(\d+))",[&ctrl](const Request&req,Response &resp){
- std::string number = req.matches[1];
- std::string html;
- ctrl.Question(number,&html);
- resp.set_content(html,"text/html;charset=utf-8");
- });
-
- // 判断用户提交的代码(1.每道题c测试用例,2.compile_and_run)
- svr.Post(R"(/judge/(\d+))",[&ctrl](const Request&req,Response &resp){
- std::string number = req.matches[1];
- std::string result_json;
- ctrl.Judge(number,req.body,&result_json);
- resp.set_content(result_json,"application/json;charset=utf-8");
- });
- svr.set_base_dir("./wwwroot");
- svr.listen("0.0.0.0",8080);
- return 0;
- }
-
- void Judge(const std::string &number, const std::string in_json, std::string *out_json)
- {
- // LOG(DEBUG) << in_json << " \nnumber:" << number << "\n";
-
- // 0. 根据题目编号,直接拿到对应的题目细节
- struct Question q;
- model_.GetOneQuestion(number, &q);
-
- // 1. in_json进行反序列化,得到题目的id,得到用户提交源代码,input
- Json::Reader reader;
- Json::Value in_value;
- reader.parse(in_json, in_value);
- std::string code = in_value["code"].asString();
-
- // 2. 重新拼接用户代码+测试用例代码,形成新的代码
- Json::Value compile_value;
- compile_value["input"] = in_value["input"].asString();
- compile_value["code"] = code + "\n" + q.tail;
- compile_value["cpu_limit"] = q.cpu_limit;
- compile_value["mem_limit"] = q.mem_limit;
- Json::FastWriter writer;
- std::string compile_string = writer.write(compile_value);
-
- // 3. 选择负载最低的主机(差错处理)
- // 规则: 一直选择,直到主机可用,否则,就是全部挂掉
- while(true)
- {
- int id = 0;
- Machine *m = nullptr;
- if(!load_blance_.SmartChoice(&id, &m))
- {
- break;
- }
-
- // 4. 然后发起http请求,得到结果
- Client cli(m->ip, m->port);
- m->IncLoad();
- LOG(INFO) << " 选择主机成功, 主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 当前主机的负载是: " << m->Load() << "\n";
- if(auto res = cli.Post("/compile_and_run", compile_string, "application/json;charset=utf-8"))
- {
- // 5. 将结果赋值给out_json
- if(res->status == 200)
- {
- *out_json = res->body;
- m->DecLoad();
- LOG(INFO) << "请求编译和运行服务成功..." << "\n";
- break;
- }
- m->DecLoad();
- }
- else
- {
- //请求失败
- LOG(ERROR) << " 当前请求的主机id: " << id << " 详情: " << m->ip << ":" << m->port << " 可能已经离线"<< "\n";
- load_blance_.OfflineMachine(id);
- load_blance_.ShowMachines(); //仅仅是为了用来调试
- }
- }
- 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;
- }
-
- html,
- body {
- width: 100%;
- height: 100%;
- }
-
- .container .navbar {
- width: 100%;
- height: 50px;
- background-color: black;
- /* 给父级标签设置overflow,取消后续float带来的影响 */
- overflow: hidden;
- }
-
- .container .navbar a {
- /* 设置a标签是行内块元素,允许你设置宽度 */
- display: inline-block;
- /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
- width: 80px;
- /* 设置字体颜色 */
- color: white;
- /* 设置字体的大小 */
- font-size: large;
- /* 设置文字的高度和导航栏一样的高度 */
- line-height: 50px;
- /* 去掉a标签的下划线 */
- text-decoration: none;
- /* 设置a标签中的文字居中 */
- text-align: center;
- }
- /* 设置鼠标事件 */
- .container .navbar a:hover {
- background-color: green;
- }
- .container .navbar .login {
- float: right;
- }
-
- .container .content {
- /* 设置标签的宽度 */
- width: 800px;
- /* 用来调试 */
- /* background-color: #ccc; */
- /* 整体居中 */
- margin: 0px auto;
- /* 设置文字居中 */
- text-align: center;
- /* 设置上外边距 */
- margin-top: 200px;
- }
-
- .container .content .font_ {
- /* 设置标签为块级元素,独占一行,可以设置高度宽度等属性 */
- display: block;
- /* 设置每个文字的上外边距 */
- margin-top: 20px;
- /* 去掉a标签的下划线 */
- text-decoration: none;
- /* 设置字体大小
- font-size: larger; */
- }
- 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="content">
- <h1 class="font_">欢迎来到我的OnlineJudge平台h1>
- <p class="font_">这个我个人独立开发的一个在线OJ平台p>
- <a class="font_" href="/all_questions">点击我开始编程啦!a>
- div>
- div>
- body>
-
- html>
- 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;
- }
-
- html,
- body {
- width: 100%;
- height: 100%;
- }
-
- .container .navbar {
- width: 100%;
- height: 50px;
- background-color: black;
- /* 给父级标签设置overflow,取消后续float带来的影响 */
- overflow: hidden;
- }
-
- .container .navbar a {
- /* 设置a标签是行内块元素,允许你设置宽度 */
- display: inline-block;
- /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
- width: 80px;
- /* 设置字体颜色 */
- color: white;
- /* 设置字体的大小 */
- font-size: large;
- /* 设置文字的高度和导航栏一样的高度 */
- line-height: 50px;
- /* 去掉a标签的下划线 */
- text-decoration: none;
- /* 设置a标签中的文字居中 */
- text-align: center;
- }
-
- /* 设置鼠标事件 */
- .container .navbar a:hover {
- background-color: green;
- }
-
- .container .navbar .login {
- float: right;
- }
-
- .container .question_list {
- padding-top: 50px;
- width: 800px;
- height: 100%;
- margin: 0px auto;
- /* background-color: #ccc; */
- text-align: center;
- }
-
- .container .question_list table {
- width: 100%;
- font-size: large;
- font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
- margin-top: 50px;
- background-color: rgb(243, 248, 246);
- }
-
- .container .question_list h1 {
- color: green;
- }
-
- .container .question_list table .item {
- width: 100px;
- height: 40px;
- font-size: large;
- font-family: 'Times New Roman', Times, serif;
- }
-
- .container .question_list table .item a {
- text-decoration: none;
- color: black;
- }
-
- .container .question_list table .item a:hover {
- color: blue;
- text-decoration: underline;
- }
-
- .container .footer {
- width: 100%;
- height: 50px;
- text-align: center;
- line-height: 50px;
- color: #ccc;
- margin-top: 15px;
- }
- 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>OnlineJuge题目列表h1>
- <table>
- <tr>
- <th class="item">编号th>
- <th class="item">标题th>
- <th class="item">难度th>
- tr>
- {{#question_list}}
- <tr>
- <td class="item">{{number}}td>
- <td class="item"><a href="/question/{{number}}">{{title}}a>td>
- <td class="item">{{star}}td>
- tr>
- {{/question_list}}
- table>
- div>
- <div class="footer">
-
- <h4>@lych4>
- div>
- div>
-
- body>
-
- html>
-
-
-
-
-
全部代码
- 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>
-
-
- <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>
-
- <script src="http://code.jquery.com/jquery-2.1.1.min.js">script>
-
- <style>
- * {
- margin: 0;
- padding: 0;
- }
-
- html,
- body {
- width: 100%;
- height: 100%;
- }
-
- .container .navbar {
- width: 100%;
- height: 50px;
- background-color: black;
- /* 给父级标签设置overflow,取消后续float带来的影响 */
- overflow: hidden;
- }
-
- .container .navbar a {
- /* 设置a标签是行内块元素,允许你设置宽度 */
- display: inline-block;
- /* 设置a标签的宽度,a标签默认行内元素,无法设置宽度 */
- width: 80px;
- /* 设置字体颜色 */
- color: white;
- /* 设置字体的大小 */
- font-size: large;
- /* 设置文字的高度和导航栏一样的高度 */
- line-height: 50px;
- /* 去掉a标签的下划线 */
- text-decoration: none;
- /* 设置a标签中的文字居中 */
- text-align: center;
- }
-
- /* 设置鼠标事件 */
- .container .navbar a:hover {
- background-color: green;
- }
-
- .container .navbar .login {
- float: right;
- }
-
- .container .part1 {
- width: 100%;
- height: 600px;
- overflow: hidden;
- }
-
- .container .part1 .left_desc {
- width: 50%;
- height: 600px;
- float: left;
- overflow: scroll;
- }
-
- .container .part1 .left_desc h3 {
- padding-top: 10px;
- padding-left: 10px;
- }
-
- .container .part1 .left_desc pre {
- padding-top: 10px;
- padding-left: 10px;
- font-size: medium;
- font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
- }
-
- .container .part1 .right_code {
- width: 50%;
- float: right;
- }
-
- .container .part1 .right_code .ace_editor {
- height: 600px;
- }
- .container .part2 {
- width: 100%;
- overflow: hidden;
- }
-
- .container .part2 .result {
- width: 300px;
- float: left;
- }
-
- .container .part2 .btn-submit {
- width: 120px;
- height: 50px;
- font-size: large;
- float: right;
- background-color: #26bb9c;
- color: #FFF;
- /* 给按钮带上圆角 */
- /* border-radius: 1ch; */
- border: 0px;
- margin-top: 10px;
- margin-right: 10px;
- }
- .container .part2 button:hover {
- color:green;
- }
-
- .container .part2 .result {
- margin-top: 15px;
- margin-left: 15px;
- }
-
- .container .part2 .result pre {
- font-size: large;
- }
- 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>
- //初始化对象
- editor = ace.edit("code");
-
- //设置风格和语言(更多风格和语言,请到github上相应目录查看)
- // 主题大全:http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.html
- editor.setTheme("ace/theme/monokai");
- editor.session.setMode("ace/mode/c_cpp");
-
- // 字体大小
- editor.setFontSize(16);
- // 设置默认制表符的大小:
- editor.getSession().setTabSize(4);
-
- // 设置只读(true时只读,用于展示代码)
- editor.setReadOnly(false);
-
- // 启用提示菜单
- ace.require("ace/ext/language_tools");
- editor.setOptions({
- enableBasicAutocompletion: true,
- enableSnippets: true,
- enableLiveAutocompletion: true
- });
-
- function submit(){
- // alert("嘿嘿!");
- // 1. 收集当前页面的有关数据, 1. 题号 2.代码
- var code = editor.getSession().getValue();
- // console.log(code);
- var number = $(".container .part1 .left_desc h3 #number").text();
- // console.log(number);
- var judge_url = "/judge/" + number;
- // console.log(judge_url);
- // 2. 构建json,并通过ajax向后台发起基于http的json请求
- $.ajax({
- method: 'Post', // 向后端发起请求的方式
- url: judge_url, // 向后端指定的url发起请求
- dataType: 'json', // 告知server,我需要什么格式
- contentType: 'application/json;charset=utf-8', // 告知server,我给你的是什么格式
- data: JSON.stringify({
- 'code':code,
- 'input': ''
- }),
- success: function(data){
- //成功得到结果
- // console.log(data);
- show_result(data);
- }
- });
- // 3. 得到结果,解析并显示到 result中
- function show_result(data)
- {
- // console.log(data.status);
- // console.log(data.reason);
- // 拿到result结果标签
- var result_div = $(".container .part2 .result");
- // 清空上一次的运行结果
- result_div.empty();
-
- // 首先拿到结果的状态码和原因结果
- var _status = data.status;
- var _reason = data.reason;
-
- var reason_lable = $( "
"
,{ - text: _reason
- });
- reason_lable.appendTo(result_div);
-
- if(status == 0){
- // 请求是成功的,编译运行过程没出问题,但是结果是否通过看测试用例的结果
- var _stdout = data.stdout;
- var _stderr = data.stderr;
-
- var stdout_lable = $("
"
, { - text: _stdout
- });
-
- var stderr_lable = $("
"
, { - text: _stderr
- })
-
- stdout_lable.appendTo(result_div);
- stderr_lable.appendTo(result_div);
- }
- else{
- // 编译运行出错,do nothing
- }
- }
- }
- script>
- body>
-
- html>
- use oj;
- drop table if exists oj_table;
-
- create table if not exists oj_table(
- _number varchar(200) comment '题目编号',
- _titie varchar(200) comment '题目标题',
- _start varchar(200) comment '题目简单中等困难',
- _desc varchar(2000) comment '题目描述',
- _header varchar(2000) comment '题目预设',
- _tail varchar(2000) comment '题目测试用例',
- _cpu_limit int comment '时间要求',
- _mem_limt int comment '空间要求'
- );
-
- insert into oj_table values(
- 1,
- '判断回文数',
- '简单',
- '判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
- 示例 1:
- 输入: 121
- 输出: true
- 示例 2:
- 输入: -121
- 输出: false
- 解释: 从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
- 示例 3:
- 输入: 10
- 输出: false
- 解释: 从右向左读, 为 01 。因此它不是一个回文数。
- 进阶:
- 你能不将整数转为字符串来解决这个问题吗?',
- '#include
- #include
- #include
- #include
- #include
- using namespace std;
- class Solution{
- public:
- bool isPalindrome(int x)
- {
- //将你的代码写在下面
-
- return true;
- }
- };',
- '#ifndef COMPILER_ONLINE
- #include "header.cpp"
- #endif
- void Test1()
- {
- // 通过定义临时对象,来完成方法的调用
- bool ret = Solution().isPalindrome(121);
- if(ret){
- std::cout << "通过用例1, 测试121通过 ... OK!" << std::endl;
- }
- else{
- std::cout << "没有通过用例1, 测试的值是: 121" << std::endl;
- }
- }
- void Test2()
- {
- // 通过定义临时对象,来完成方法的调用
- bool ret = Solution().isPalindrome(-10);
- if(!ret){
- std::cout << "通过用例2, 测试-10通过 ... OK!" << std::endl;
- }
- else{
- std::cout << "没有通过用例2, 测试的值是: -10" << std::endl;
- }
- }
- int main()
- {
- Test1();
- Test2();
- return 0;
- }',
- 1,
- 30000
- );
-
-
- select * from oj_table;
MySQL :: Download MySQL Community Server
要使用C/C++连接MySQL,需要使用MySQL官网提供的库
下载完毕后需要将其上传到云服务器,这里将下载的库文件存放在下面的目录:
然后使用tar命令将压缩包解压到当前目录下:
xz -d mysql-8.0.37-linux-glibc2.28-i686.tar.xz
tar xvf mysql-8.0.37-linux-glibc2.28-i686.tar
进入解压后的目录当中,可以看到有一个include子目录和一个lib子目录,其中,include目录下存放的一堆头文件。而lib64目录下存放的就是动静态库。
然后在我们的项目中建立软连接
如果你当时下载myql把mysql-devel也下载了,不需要进行上面步骤
这种引入第三方库的操作,可能会因为版本不兼容,而导致出错
skipping incompatible ./lib/libmysqlclient.so when searching for -lmysqlclient
因为oj_model模块是管理数据,提供接口的模块,所以要把这个项目变成mysql就需要重新设计
- #pragma once
- // 文件版本
- #include "../comm/util.hpp"
- #include "../comm/log.hpp"
- #include
- #include
- #include
-
- #include "./include/mysql.h"
-
- namespace ns_model
- {
- using namespace std;
- using namespace ns_log;
- using namespace ns_util;
-
- struct Question{
- string number;// 题目编号,唯一
- string title;// 题目标题
- string star;// 难度: 简单 中等 困难
-
- int cpu_limit;// 题目的时间复杂度(S)
- int mem_limit;// 题目的空间复杂度(KB)
-
- string desc;// 题目描述
- string header; // 题目预设给用户在线编辑器的代码
- string tail;// 题目测试用例,需要和header拼接
- };
-
- const std::string oj_questions = "oj_table";
- const std::string host = "127.0.0.1";
- const std::string user = "oj_client";
- const std::string passwd = "123456";
- const std::string db = "oj";
- const int port = 3306;
-
- class Model
- {
- public:
- Model(){
- }
- ~Model(){
- ;
- }
- bool QueryMysql(const std::string &sql,vector
*out) { - // 这里的out是输出型参数
- // 创建mysql句柄
- MYSQL *my = mysql_init(nullptr);
- // 连接数据库
- if(nullptr == mysql_real_connect(my,host.c_str(),user.c_str(),passwd.c_str(),db.c_str(),port,nullptr,0)){
- LOG(FATAL) << "连接数据库失败!" << "\n";
- return false;
- }
-
- // 一定要设置该链接的编码格式,要不然会出现乱码的问题
- mysql_set_character_set(my,"utf8");
- LOG(INFO) << "连接数据库成功!" << "\n";
-
- // 执行sql语句
- if(0 != mysql_query(my,sql.c_str())){
- LOG(WARNING) << sql << " execute error! " << "\n";
- return false;
- }
-
- // 提取结果
- MYSQL_RES *res = mysql_store_result(my);// 本质就是一个2级指针
-
- // 分析结果
- int rows = mysql_num_rows(res);// 获取行的数量
- int cols = mysql_num_fields(res);// 获取列的数量
-
- Question q;
- for(int i = 0;i < rows;i++){
- MYSQL_ROW row = mysql_fetch_row(res);
- q.number = row[0];
- q.title = row[1];
- q.star = row[2];
- q.desc = row[3];
- q.header = row[4];
- q.tail = row[5];
- q.cpu_limit = atoi(row[6]);
- q.mem_limit = atoi(row[7]);
- out->push_back(q);
- }
-
- // 释放控件
- free(res);
- // 关闭mysql连接
- mysql_close(my);
-
- return true;
- }
- // 获取所有题目,这里的out是输出型参数
- bool GetAllQuestions(vector
*out) { - std::string sql = "select * from ";
- sql += oj_questions;
- return QueryMysql(sql,out);
- }
-
- // 获取指定题目,这里的q是输出型参数
- bool GetOneQuestion(const string& number,Question* q){
- bool res = false;
- std::string sql = "select * from ";
- sql += oj_questions;
- sql += " where number=";
- sql += number;
-
- vector
result; - if(QueryMysql(sql,&result)){
- if(result.size() == 1){
- *q = result[0];
- res = true;
- }
- }
-
- return res;
- }
-
-
- private:
- // 题号 : 题目细节
- unordered_map
questions; - };
- }
-
mysql_query: 发起mysql请求
mysql_close: 关闭mysql连接