数据库网站建设多少钱,个人网站开发教程,锦州网站建设更好,可以做设计兼职的网站有哪些一.目的
负责负载均衡#xff0c;获取网站首页#xff0c;通过网络罗调用编译并运行并提供结果给用户。根据用户的操作提供不同的功能。采用mvc结构。使用 ctemplate文字模板做数据渲染
m在这里指的是和数据的交互#xff0c;可以用文件或者sql交互。在使用文件的情况下获取网站首页通过网络罗调用编译并运行并提供结果给用户。根据用户的操作提供不同的功能。采用mvc结构。使用 ctemplate文字模板做数据渲染
m在这里指的是和数据的交互可以用文件或者sql交互。在使用文件的情况下就必须要有对应的文件目录存放每一道题。提供题目描述和题头还有测试用例。
二.实现model
负责将文件题库抽象成数据结构并提供接口给ojcontrol调用。ojcontrol通过model模块获取全部的题目信息和测试用例用来交给后端服务继续运行
题的数据结构 typedef struct Question // 每一道题对应的基本信息{string number; // 题目编号string title; // 题目名称string diffculty; // 题目难度int cpu_limit; // 运行时间限制int mem_limit; // 内存时间限制string desc; // 题目描述string header; // 题目提供给用户的首部代码string tailer; // 题目的测试用例需要和header拼接形成完整代码}Question;获取题信息的数据结构因为是从文件中读取所以需要一个字符串格分割函数。
加载所有题库信息到数据结构。 bool LoadQuestionList(const string question_path) //根据文件题目列表获取到数据结构内{ifstream in(questionlist_path); // 打开题目列表的文件流if (!in.is_open()) // 打开文件流失败{LOG(FATAL) 加载题库失败,请检查是否存在题库文件 \n;return false;}string line;while (getline(in, line)){vectorstring tokens; // 题的五个信息StringUtil::SplitString(line, tokens, ); // 根据空格分隔出不同元素if (tokens.size() ! 5) // 每道题有五种基本信息编号题目,难度,mem,cpu{LOG(WARNING) 加载部分题目失败, 请检查文件格式 \n;continue; // 获取当前题目信息失败直接跳过}Question q; // 创建题目对象填充信息q.number tokens[0];q.title tokens[1];q.diffculty tokens[2];q.cpu_limit atoi(tokens[3].c_str());q.mem_limit atoi(tokens[4].c_str());// 获取题目详细信息string path question_path; // 需要拼出对应的题目路径path q.number;path /;// 从文件内读出内容并填充进题目结构体里FileUtil::ReadFile(path desc.txt, (q.desc), true);FileUtil::ReadFile(path header.cpp, (q.header), true);FileUtil::ReadFile(path tail.cpp, (q.tailer), true);// 形成哈希映射questions.insert({q.number, q});}LOG(INFO) 题库加载成功 \n;in.close();return true;}获取一道题给客户 bool GetOneQuestion(const string number, Question *ret) // 通过题号获取对应题{const auto iter questions.find(number);if (iter questions.end()){LOG(ERROR) 用户获取题目失败, 题目编号: number \n;return false;}*ret iter-second; // 输出行参数return true;}获取所有题目给客户
bool GetAllQuestion(vectorQuestion *out){if (questions.size() 0){LOG(ERROR) 用户获取题库失败 \n;return false;}for (auto q : questions){out-push_back(q.second); // 遍历哈希映射把题目信息依次录入vector}return true;}
三.control模块
主要实现逻辑控制从网页上拿来各种信息提取有用信息后结合本地数据向后端提交如判题功能或者根据要求获取对应本地信息并通过前段模块返回给用户 // 核心业务逻辑class Control // 控制ojserver的基础功能包括修改题库和前端界面交互整合数据控制和前端交互{private:ns_model::Model _model; // 提供后台数据View _view; // 提供html渲染功能LoadBalance _load; // 提供负载均衡模块public:void RecoveryMachine(){_load.OnlineMachine();}// 获取所有题目并生成html文件bool AllQuestions(string *html) // 输出型参数{vectorQuestion all;if (_model.GetAllQuestion(all)) // 获取所有题目到vector内{// 获取信息成功构建htmlsort(all.begin(),all.end(),[](const Question q1,const Question q2){ return atoi(q1.number.c_str())atoi(q2.number.c_str());});_view.AllExpandHtml(all, html);}else{*html 获取题目失败, 形成题目列表失败;return false;}return true;}// 获取一道题bool OneQuestion(const string number, string *html){Question q;if (_model.GetOneQuestion(number, q)){_view.OneExepandHtml(q, html);return true;}else{*html 指定题目: number 不存在!;return false;}}// 判断用户代码是否正确void Judge(const string in_json, string *outj_son, const string number){// 需要读取上来用户的代码并和测试用例拼接成一份完整代码最后// 0. 根据题目编号直接拿到对应的题目细节Question q;_model.GetOneQuestion(number, q);// 1. in_json进行反序列化得到题目的id得到用户提交源代码inputJson::Reader reader;Json::Value usr_val;reader.parse(in_json, usr_val);string code usr_val[code].asString(); // 用户写的代码// 2. 重新拼接用户代码测试用例代码形成新的代码Json::Value compile_val;compile_val[code] code \n q.tailer;compile_val[input] usr_val[input].asString();compile_val[cpu_limit] q.cpu_limit;compile_val[mem_limit] q.mem_limit;// 序列化准备传输Json::FastWriter writer;std::string compile_string writer.write(compile_val);// 3. 选择负载最低的主机(差错处理)// 规则: 一直选择直到主机可用否则就是全部挂掉while (true){int id;Machine *m;// 跟据负载选择服务器if (!_load.SmartChoice(m, id)){break; // 选择主机失败}// 4. 选择完主机后发起http请求链接到主机Client cli(m-_ip, m-_port);m-IncLoad();LOG(INFO) 选择主机成功, 主机id: id 详情: m-_ip : m-_port 当前主机的负载是: m-Load() \n;// 5.将数据提交给服务主机并进行编译运行if (auto res cli.Post(/Compile_and_run, compile_string, application/json;charsetutf-8)) // 这个请求完成编译运行并返回结果{// 需要判断返回结果有返回结果不一定成功运行if (res-status 200) // 200表示成功运行{// 将结果返回给用户*outj_son res-body;m-DecLoad();LOG(INFO) 主机执行任务成功 \n;break;}// 若提交失败也需要将负载复原并重新执行该流程m-DecLoad();}else{// 请求失败(当前主机不存在)LOG(ERROR) 当前请求的主机id: id 详情: m-_ip : m-_port 可能已经离线 \n;_load.OfflineMachine(id);_load.ShowMachines(); // 仅仅是为了用来调试}}}
四.view模块
在拿到model的题目信息后结合本地html进行渲染给用户提供前端展示页面。采用ctemplate渲染。
通过互取到的所有题目信息使用ctemplate渲染展示给用户。
// 获取所有题目并生成html文件bool AllQuestions(string *html) // 输出型参数{vectorQuestion all;if (_model.GetAllQuestion(all)) // 获取所有题目到vector内{// 获取信息成功构建htmlsort(all.begin(),all.end(),[](const Question q1,const Question q2){ return atoi(q1.number.c_str())atoi(q2.number.c_str());});_view.AllExpandHtml(all, html);}else{*html 获取题目失败, 形成题目列表失败;return false;}return true;}
一道题 bool OneQuestion(const string number, string *html){Question q;if (_model.GetOneQuestion(number, q)){_view.OneExepandHtml(q, html);return true;}else{*html 指定题目: number 不存在!;return false;}}
五.负载均衡
我们需要以一个文件存放所有可用的主机和端口号。作为主机的配置文件。同时主机需要保存自身的负载情况所以需要加锁。
class Machine // 标识提供服务的主机{public:std::string _ip; // 本机的ip端口号和负载程度int _port;uint64_t _load;std::mutex *_mtx; // 重点必须用指针不能用实例容器内有拷贝c里的mutex是禁止拷贝的只能用指针取地址绕过Machine(): _ip(),_port(0),_load(0),_mtx(nullptr){}// 提升负载(有可能多个主机同时运行有竞争问题)void IncLoad(){if (_mtx){_mtx-lock();}_load;if (_mtx){_mtx-unlock();}}// 降低负载void DecLoad(){if (_mtx){_mtx-lock();}_load--;if (_mtx){_mtx-unlock();}}// 获取主机负载,没有太大的意义只是为了统一接口uint64_t Load(){uint64_t load 0;if (_mtx)_mtx-lock();load _load;if (_mtx)_mtx-unlock();return load;}void ResetLoad(){if (_mtx){_mtx-lock();}_load 0;if (_mtx){_mtx-unlock();}}};
负载均衡模块负责所有主机的情况包括是否上线。并提供负载最小的主机也需要加锁因为涉及到对所有主机属性的更改。算临界区
class LoadBalance // 负载均衡模块{// 可以给我们提供编译服务的所有的主机// 每一台主机都有自己的下标充当当前主机的idvectorMachine machines;// 所有在线的主机idstd::vectorint online;// 所有离线的主机idstd::vectorint offline;std::mutex mtx; //需要保证负载均衡的数据安全public:LoadBalance(){assert(LoadConf(service_machine));LOG(INFO) 加载 service_machine 成功 \n;}bool LoadConf(const string machine_conf) // 从conf文件里读取上来所有主机信息{ifstream in(machine_conf); // 打开文件流if (!in.is_open()){LOG(FATAL) 加载: machine_conf 失败 \n;return false;}// 读取数据到linestring line;while (getline(in, line)){vectorstring tokens; // 每一行切分到这里只有两个元素ip和端口号StringUtil::SplitString(line, tokens, :);if (tokens.size() ! 2){LOG(WARNING) 切分 line 失败 \n;continue;}// 读取数据完毕构建对象Machine m;m._ip tokens[0]; // 填充ip和端口号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;}bool SmartChoice(Machine **m, int *id) // 两个输出出行参数返回选择的主机或者看情况离线主机{ // 因为传参的时候不想通过下标访问所以通过地址修改// 1. 选择主机(更新该主机的负载)// 2. 我们需要可能离线该主机// 选择主机有安全问题mtx.lock();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 0; i online_num; i) // 寻找最小负载{uint64_t cur_load machines[online[i]].Load();if (cur_load min_load){min_load cur_load;*id online[i];*m machines[online[i]];}}mtx.unlock();return true;}void OnlineMachine() // 上线服务器(一次直接全部上线){mtx.lock();online.insert(online.end(), offline.begin(), offline.end());offline.erase(offline.begin(), offline.end());LOG(INFO) 重新登陆主机成功 \n;mtx.unlock();}void ShowMachines() // 查看所有服务器状态{mtx.lock();std::cout 当前在线主机列表: \n;for (auto id : online){std::cout id ;}std::cout std::endl;std::cout 当前离线主机列表: \n;for (auto id : offline){std::cout id ;}mtx.unlock();}void OfflineMachine(const int mid) // 下线服务器{mtx.lock();for (auto iter online.begin(); iter ! online.end(); iter) // 用迭代器遍历好一些利于删除{if (*iter mid){machines[mid].ResetLoad(); // 下线前清空负载online.erase(iter);offline.push_back(mid);break; // 因为break所以不用考虑迭代器失效此时循环已经终止了}}mtx.unlock();}};