做的网站有营销效果吗,前山网站建设,万网网站建设方案书 备案,网站做字工具0. 前言 这个项目使用了前后端,实现一个丐版的LeetCode刷题网站,并根据每台主机的实际情况,选择对应的主机,负载均衡的调度 0.1 所用技术与开发环境
所用技术: C STL 标准库 Boost 准标准库 ( 字符串切割 ) cpp- httplib 第三方开源网络库 ctemplate 第三方开源前端网…0. 前言 这个项目使用了前后端,实现一个丐版的LeetCode刷题网站,并根据每台主机的实际情况,选择对应的主机,负载均衡的调度 0.1 所用技术与开发环境
所用技术: C STL 标准库 Boost 准标准库 ( 字符串切割 ) cpp- httplib 第三方开源网络库 ctemplate 第三方开源前端网页渲染库 jsoncpp 第三方开源序列化、反序列化库 负载均衡设计 多进程、多线程 MySQL C connect Ace前端在线编辑器 ( 部分 ) html/css/js/jquery/ajax (部分 ) 开发环境: Centos 7 云服务器, vscode, Mysql Workbench 0.2 建立目录及文件 0.3 项目宏观结构 具体的功能类似 leetcode 的题目列表在线编程功能
1. compile 服务设计 由于compiler这个模块管理的是编译与运行,则可以先直接就创建所需要的文件
1.0 书写makefile文件 随着后续代码的跟进,并不断引入第三方库,这里还会新增编译选项
1.1 compiler_server
1.1.0 编译功能(compiler.hpp) 在编译的时候,无非存在2种情况,a)要么通过,b)要么不通过要确定编译通过: 只需要确定是否生成对应的.exe文件要当编译出错的时候(stderr): 需要将出错信息,重定向到一个临时文件中,保存编译出错的结果 还需要调用fork();子进程完成编译工作 父进程继续执行 由于需要频繁的文件名转换,所以在comm模块中,新建util.hpp文件并将文件名转换的函数放在一起 还有后面判断编译成功生成的可执行程序,虽然可以直接暴力的打开文件判断是否存在,但这里使用stat函数会好一些stat结构体会记录文件的各种信息 注意: 程序替换是不会影响进程的文件符描述符表的
1.1.1 日志模块(log.hpp) 由于一般日志都会带上时间, 这里还需要实现一个得到当前时间的函数,则我又在util.hpp把得到时间函数的类封装成了一个类 由于会频繁的调用日志进行打印信息,也为了更简便的调用,我进行了以下处理 如果在宏定义中使用#那么这个宏就被称为带有字符串化操作的宏。这种宏可以将其参数转换成字符串常量并在预处理阶段进行替换。 由于引入了日志,则就可以把之前所有的输出信息,换成日志输出 1.1.2 测试编译模块 Compile的参数是文件名,它内部会自动拼接我们还需要再./temp中创建一个code.cpp文件 上面我的代码有一个错误,在编译成功的时候,并没有return,导致LOG日志打印有问题 要是我们的源文件有问题,错误信息就会重定向到 文件.compile_error中在测试的时候,还需要把 文件.exe 文件.compile_error文件删除,就是上次生成的文件
1.1.3 运行功能(runner.hpp)
程序运行: 1)代码跑完,结果正确 2)代码跑完,结果不正确, 3)代码没跑完,异常了 程序结果是否正确,是由oj_server中的测试用例决定的,则run模块只考虑是否正确运行完毕 #include sys/types.h
#include sys/stat.h
#include fcntl.h
#include sys/types.h
#include sys/wait.h
#include unistd.h// 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.exeexecl(_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: 内部错误 run.hpp也是一样的,把自己的各种输出信息,输出到一个临时文件中要判断一个程序是否异常,只需要看它是否收到了异常信号 解释waitpid第2个输出型参数 status并不是按照整数来整体使用的,而是按照比特位的方式,将32个比特位进行划分,只需要学习低16位这也是上面为什么会写成status 0x7F的原因 那6个程序替换的系统接口,具体使用那个看实际情况 没有p就需要带路径有l,就是列表式传命令有v就是数组式传命令有e就需要传自己设置的环境变量
1.1.4 测试运行模块 虽然运行模块已经能正常运行了,但是万一code.cpp是恶意程序了,比如死循环,不停消耗CPU资源 , 所以还需要进一步的资源约束
1.1.5 添加资源限制(setrlimit) 资源不足导致OS终止进程是通过信号终止的 为了方便上层调用,我直接在Run函数中增加了cpu_limit和mem_limit形参 这个项目走到这里就需要编写compile_run.hpp,将编译和运行的逻辑连接在一起,且code.cpp需要被处理的源文件,不应该是我们自己添加的,而是需要再客户端中导入的
1.1.6 编译 运行功能 (compile_run.hpp) 这个模块要做的是: a)适配用户请求,引入json定制通信协议字段 b)形成唯一文件名 c)正确调用compile and run 在centos中安装: sudo yum install json-c-devel 头文件 #include jsoncpp/json/json.h 注意: 在编译引入了json的文件,需要加上-ljsoncpp 虽然这个code就是文件名了,但client可能会提交大量的代码,所以内部就会需要形成唯一的文件名(待完善)还有很多个出错问题怎么解决(待完善) complie_run.hpp #pragma once#include compiler.hpp
#include runner.hpp
#include ../comm/log.hpp
#include ../comm/util.hpp
#include jsoncpp/json/json.hnamespace 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);}} 1.1.7 基于compile_run.hpp对util.hpp的补充 注意引入流时需要引入头文件: #include fstream getline:不保存行分割符,有些时候需要保留\n,getline内部重载了强制类型转化
1.1.8 测试编译运行模块 #include compile_run.hpp
using namespace ns_compile_and_run;int main()
{std::string in_json;Json::Value in_value;// R(), raw stringin_value[code] R(#includeiostreamint 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;
} 实际上这里的代码应该是client自动提交给我们的,我们直接使用第三方库就行了待优化: 可以把临时生成的这些文件都清理掉,
1.1.9 清理临时文件 这个函数直接放在compile_server.cc中的start函数的最后,清理临时文件
1.1.10 引入cpp-httplib 网络库 下载地址: cpp-httplib: C http 网络库 - Gitee.com 这个就是别人写好的网络库,我们直接使用就行了
1.1.11 更新gcc 安装scl : sudo yum install centos-release-scl scl-utils-build 安装新版本gcc: sudo yum install - y devtoolset - 9 - gcc devtoolset - 9 - gcc - c 把 scl enable devtoolset-9 bash 放在 ~/.bash_profile中想每次登陆的时候都是较新的gcc 如果不更新在使用cpp-httplib时可能会报错, 用老的编译器要么编译不通过要么直接运行报错
1.1.12 测试cpp-httplib网络库 可能会出现服务器的公网ip无法访问的问题,可以试试把防火墙关闭,并打开端口
1.1.13 将compiler_server打包成网络服务
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 Requestreq,Response resp){// 用户请求的服务正文是我们想要的json stringstd::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;charsetuft-8);}});svr.listen(0.0.0.0,atoi(argv[1]));return 0;
} 由于我这里没有写客户端代码,则这里暂时不好测试,不过可以借助第三方工具进行测试 2. oj_server服务设计 本质 建立一个小型网站 1. 获取首页用题目列表充当 2. 编辑区域页面 3. 提交判题功能(编译并运行) 2.1 书写makefile文件 随着后续代码的跟进,并不断引入第三方库,这里还会新增编译选项
2.2 服务路由功能(oj_server.cc) 为用户实现的路由功能就3个 a. 获取所有的题目列表 b.根据题目编号,获取题目内容 c.判断用户提交的代码
2.3 MVC 结构的oj 服务设计(M) Model , 通常是和数据交互的模块 比如对题库进行增删改查文件版 MySQL 2.3.1 安装boost库 字符切分功能 sudo yum install -y boost-devel //是boost 开发库 第一个参数为缓冲区,第二个参数为被分割的字符串第三个参数为分割符,第四个参数为是否压缩 要压缩: 当sep 空格时,sepsepsep - 空格不压缩: 当sep 空格时,sepsepsep - 空格空格空格 2.3.2 数据结构
header.cpp #include iostream
#include string
#include vector
#include map
#include algorithmusing 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;
} des.txt表示题目信息 header.cpp表示预设代码tail.cpp表示测试用例 真正代码 用户在head.cpp中的代码 header.cpp tail.cpp 并去到COMPILER_ONLINE 这个条件编译只是为了编写tail.cpp时不报错
2.3.3 model功能(oj_model.cpp) 数据交互 提供接口 #pragma once
// 文件版本
#include ../comm/util.hpp
#include ../comm/log.hpp
#include unordered_map
#include cassert
#include vectornamespace 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(vectorQuestion*out){if(questions.size() 0){LOG(ERROR) 用户获取题库失败 \n;return false;}for(const autoq: 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 stringquestion_list){// 加载配置文件: questions/questions.list 题目编号文件ifstream in(question_list);if(!in.is_open()){LOG(FATAL) 加载题库失败,请检查是否存在题库文件 \n;return false;}string line;while(getline(in,line)){vectorstringtokens;StringUtil::SplitString(line,tokens, );// 被分割的字符串 缓冲区 分割符// eg: 1 判断回文数 简单 1 30000if(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 /;// 第三个参数代表 是否加上 \nFileUtil::ReadFile(pathdesc.txt,(q.desc),true);FileUtil::ReadFile(pathheader.cpp,(q.header),true);FileUtil::ReadFile(pathtail.txt,(q.tail),true);questions.insert({q.number,q});// 录题成功}LOG(INFO) 加载题库...成功 \n;in.close();}private:// 题号 : 题目细节unordered_mapstring,Question questions;};
} 2.4 MVC 结构的oj 服务设计(C)
2.4.1 负载均衡模块 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; //编译服务的ipint port; //编译服务的portuint64_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:// 可以给我们提供编译服务的所有的主机// 每一台主机都有自己的下标充当当前主机的idstd::vectorMachine machines;// 所有在线的主机idstd::vectorint online;// 所有离线的主机idstd::vectorint 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::vectorstd::string 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. 轮询hashint 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 testvoid 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();}};
} 2.4.1 control功能(oj_control.hpp) 逻辑控制模块 // 这是我们的核心业务逻辑的控制器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;vectorstruct 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){}}; control模块中的判题功能,我打算最后设计
2.5 MVC 结构的oj 服务设计(V)
2.4 安装与测试 ctemplate(网页渲染) 渲染本质就是key-value之间的替换 安装镜像源: git clone https://gitee.com/mirrors_OlafvdSpek/ctemplate.git . / autogen . sh . / configure make // 编译 如果报错请 更新gcc make install test.cpp #include iostream
#include string
#include ctemplate/template.h
int main()
{std::string html ./test.html;std::string html_info 测试ctemplate渲染;// 建立ctemplate参数目录结构ctemplate::TemplateDictionary root(test); // unordered_mapstring,string 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_htmlstd::string out_html;tpl-Expand(out_html, root);std::cout 渲染的带参html是: std::endl;std::cout out_html std::endl;return 0;
} test.html !DOCTYPE html
html langenheadmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0titleDocument/title
/headbodyp{{info}}/pp{{info}}/pp{{info}}/pp{{info}}/p
/body/html 错误原因: 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 2.5.2 渲染功能(oj_view.hpp) #pragma once
#include iostream
#include ctemplate/template.h
#include ./oj_model.hppnamespace ns_view
{using namespace ns_model;const std::string template_path ./template_html/;class View{public:View(){}~View(){}// 渲染所有题目void ALLExpandHtml(const vectorstruct Questionquestion,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. 获取被渲染的htmlctemplate::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.获取被渲染的htmlctemplate::Template*tpl ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);// 4.开始完成渲染功能tpl-Expand(html,root);}};
}2.6 联动MVC模块并测试 oj_server.cc #include iostream
#include ../comm/httplib.h// 引入
#include oj_control.hppusing namespace httplib;// 引入
using namespace ns_control;int main()
{// 用户请求的服务器路由功能Server svr;Control ctrl;// 获取所有的题目列表svr.Get(/all_questions,[ctrl](const Requestreq,Response resp){// 返回一张包含所有题目的html网页std::string html;// 待处理ctrl.AllQuestions(html);resp.set_content(html,text/html;charsetutf-8);});// 根据题目编号,获取题目内容// \d 是正则表达式的特殊符合svr.Get(R(/question/(\d)),[ctrl](const Requestreq,Response resp){std::string number req.matches[1];std::string html;ctrl.Question(number,html);resp.set_content(html,text/html;charsetutf-8);});// 判断用户提交的代码(1.每道题c测试用例,2.compile_and_run)svr.Post(R(/judge/(\d)),[ctrl](const Requestreq,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;charsetutf-8);});svr.set_base_dir(./wwwroot);svr.listen(0.0.0.0,8080);return 0;
} 这里的前端都是提前做好了的,我们可以不关心前端;control功能还有个判题功能没有实现 2.7 完善oj_control.hpp中的判题功能 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得到用户提交源代码inputJson::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;charsetutf-8)){// 5. 将结果赋值给out_jsonif(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(); //仅仅是为了用来调试}} 2.8 测试oj_server服务 在编译时需要加上-D COMPILER_ONLINE条件编译, 设计到前端网页,下面会有提及
2.9 一个BUG 把tail.txt改成tail.cpp,不然后面无法进行代码拼接 3. 前端页面设计(了解)
3.1 index.html !DOCTYPE html
html langenheadmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0title这是我的个人OJ系统/titlestyle/* 起手式, 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
/headbodydiv classcontainer!-- 导航栏 功能不实现--div classnavbara href/首页/aa href/all_questions题库/aa href#竞赛/aa href#讨论/aa href#求职/aa classlogin href#登录/a/div!-- 网页的内容 --div classcontenth1 classfont_欢迎来到我的OnlineJudge平台/h1p classfont_这个我个人独立开发的一个在线OJ平台/pa classfont_ href/all_questions点击我开始编程啦!/a/div/div
/body/html 3.2 all_questions.html !DOCTYPE html
html langenheadmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0title在线OJ-题目列表/titlestyle/* 起手式, 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
/headbodydiv classcontainer!-- 导航栏 功能不实现--div classnavbara href/首页/aa href/all_questions题库/aa href#竞赛/aa href#讨论/aa href#求职/aa classlogin href#登录/a/divdiv classquestion_listh1OnlineJuge题目列表/h1tabletrth classitem编号/thth classitem标题/thth classitem难度/th/tr{{#question_list}}trtd classitem{{number}}/tdtd classitema href/question/{{number}}{{title}}/a/tdtd classitem{{star}}/td/tr{{/question_list}}/table/divdiv classfooter!-- hr --h4lyc/h4/div/div/body/html 3.3 one_questions.html(ACE插件JQueryajax) !-- 引入ACE插件 --!-- 引入ACE CDN --script srchttps://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js typetext/javascriptcharsetutf-8/scriptscript srchttps://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-language_tools.js typetext/javascriptcharsetutf-8/script!-- 引入jquery CDN --script srchttp://code.jquery.com/jquery-2.1.1.min.js/script ACE插件是一个 编写代码的编译框 收集当前页面的有关数据 , a. 题号 a. 代码 , 我们采用 JQuery 来进行获取 html 中的内容 构建json,并通过 ajax向后台 发起基于http的json请求 全部代码 !DOCTYPE html
html langenheadmeta charsetUTF-8meta http-equivX-UA-Compatible contentIEedgemeta nameviewport contentwidthdevice-width, initial-scale1.0title{{number}}.{{title}}/title!-- 引入ACE插件 --!-- 引入ACE CDN --script srchttps://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ace.js typetext/javascriptcharsetutf-8/scriptscript srchttps://cdnjs.cloudflare.com/ajax/libs/ace/1.2.6/ext-language_tools.js typetext/javascriptcharsetutf-8/script!-- 引入jquery CDN --script srchttp://code.jquery.com/jquery-2.1.1.min.js/scriptstyle* {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
/headbodydiv classcontainer!-- 导航栏 功能不实现--div classnavbara href/首页/aa href/all_questions题库/aa href#竞赛/aa href#讨论/aa href#求职/aa classlogin href#登录/a/div!-- 左右呈现题目描述和预设代码 --div classpart1div classleft_desch3span idnumber{{number}}/span.{{title}}_{{star}}/h3pre{{desc}}/pre/divdiv classright_codepre idcode classace_editortextarea classace_text-input{{pre_code}}/textarea/pre/div/div!-- 提交并且得到结果并显示 --div classpart2div classresult/divbutton classbtn-submit onclicksubmit()提交代码/button/div/divscript//初始化对象editor ace.edit(code);//设置风格和语言更多风格和语言请到github上相应目录查看// 主题大全http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.htmleditor.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;charsetutf-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 $( 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);}else{// 编译运行出错,do nothing}}}/script
/body/html 3.4 相关测试 4. MySQL版题目设计
4.1 注册用户 赋予权限 create user oj_clientlocalhost identified by 123456;create database oj;grant select on oj.* to oj_clientlocalhost;select user,Host from user; 4.2 下载第三方工具-workbench 下载下来之后,就不断的下一步,下一步就行了
4.3 录题到mysql中 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 iostream#include string#include vector#include map#include algorithmusing namespace std;class Solution{public:bool isPalindrome(int x){//将你的代码写在下面return true;}};,#ifndef COMPILER_ONLINE#include header.cpp#endifvoid 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; 这里我只录入了一道题为了测试
4.4 下载并引入mysql库文件
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目录下存放的就是动静态库。
然后在我们的项目中建立软连接
4.5 一个BUG 如果你当时下载myql把mysql-devel也下载了,不需要进行上面步骤 这种引入第三方库的操作,可能会因为版本不兼容,而导致出错skipping incompatible ./lib/libmysqlclient.so when searching for -lmysqlclient 建议直接安装: yum -y install mysql-devel 4.5 重新设计oj_model 因为oj_model模块是管理数据,提供接口的模块,所以要把这个项目变成mysql就需要重新设计 #pragma once
// 文件版本
#include ../comm/util.hpp
#include ../comm/log.hpp
#include unordered_map
#include cassert
#include vector#include ./include/mysql.hnamespace 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,vectorQuestion*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(vectorQuestion*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;vectorQuestion result;if(QueryMysql(sql,result)){if(result.size() 1){*q result[0];res true;}}return res;}private:// 题号 : 题目细节unordered_mapstring,Question questions;};
} mysql_init: 创建mysql句柄mysql_real_connect: 创建mysql连接 mysql_query: 发起mysql请求 mysql_close: 关闭mysql连接 4.6 相关测试
编译期间告诉编译器头文件和库文件在哪里 -I指明搜索的头文件,-L指明搜索的lib并加上-lmysqlclient
5. 扩展 功能上更完善一下判断一道题目正确之后自动下一道题目 基于注册和登陆的录题功能 .....
6. 完整项目链接 projects/负载均衡/OnlineJudge at main · 1LYC/projects · GitHub