负载均衡式的在线OJ项目编写(三)
一.前期内容回顾
对前面的准备不熟悉的,可以看前面的内容,连接如下:
https://blog.csdn.net/weixin_60668256/article/details/152051541?fromshare=blogdetail&sharetype=blogdetail&sharerId=152051541&sharerefer=PC&sharesource=weixin_60668256&sharefrom=from_link
二.引入httplib库
2.1升级g++,引入httplib库
最新的cpp-httplib在使⽤的时候,如果gcc不是特别新的话有可能会有运⾏时错误的问题
建议:cpp-httplib 0.7.15
下载zip安装包,上传到服务器即可
cpp-httplib gitee链接:https://gitee.com/yuanfeng1897/cpp-httplib? _from=gitee_search
v0.7.15版本链接: https://gitee.com/yuanfeng1897/cpp-httplib/tree/v0.7.15
把httplib.h拷⻉到我们的项⽬中即可,就这么简单
使⽤样例: $ cat http_server.cc
接⼊cpp-httplib:header-only,只需要将.h拷⻉到你的项⽬中,即可直接使⽤
cpp-httplib:需要使⽤⾼版本的gcc,建议是gcc 7,8,9 [如果没有升级,cpp-httplib:要么就 是编译报错,要么就是运⾏出错]
cpp-httplib: 阻塞式多线程的⼀个⽹络http库
测试的时候,可以采⽤postman进⾏测试
百度:postman官⽹,下载安装
然后将http-lib库导入项目目录下
下面是gcc更新的方案(可以直接使用大模型)
或者
// ---------------------------------------------------------------------------------------------------------------
2.2测试httplib.h
gcc升级好之后,我们就能直接将httplib.h拷贝到项目目录下
具体的httplib的使用说明,可以去网上其他地方找教程,这里用到的会加注释
在makefile的编译项里面加-lptrhead
尽量不要用vscode终端远程连接进行编译(本人亲测,卡死好几次了,xshell上编译就没问题)
直接./compile_server
这是一个很常见的问题,直接在命令行中进行编译就不会有这个问题
效果图如下:
我们可以看到上面的你好出现了乱码
然后,我们写了一个html文件
并将html文件设置成为根目录的登录界面
2.3Postman进行综合测试
首先先进行下载postman(链接如下)
Download Postman | Get Started for Free
下好之后,就是如下
我们发现,老是不成功(最终发现我们是在命令行进行编译运行的,全是root,改成ltw就行了)
然后我们终于得到了我们想要的结果
compiler.cc代码实现
#include "compile_run.hpp"
#include "../comm/httplib.h"using namespace ns_compile_and_run;
using namespace httplib;int main()
{Server svr;svr.Get("/hello",[](const Request &req, Response &resp){// ⽤来进⾏基本测试 resp.set_content("hello httplib,你好 httplib!", "text/plain;charset=utf-8");});//设置根目录,别人访问根目录,直接就是去访问wwwroot目录下的index.html文件svr.set_base_dir("./wwwroot");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=utf-8");}});svr.listen("0.0.0.0", 8080); //启动http服务 }//编译服务随时可能被多个人请求,必须保证传递上来的code,形成源文件名称的时候
//,要具有唯一性,不然多个用户之间会互相影响// int main()
// {
// //通过http,让client给我们上传一个json串// //in_json: {"code": "#include...", "input": "","cpu_limit":1,"mem_limit":1024*10}
// //out_json: {"status": "0","reason":"","stdout":"","stderr":""}// //下面的工作来充当客户端请求的json串
// std::string in_json,out_json;
// Json::Value in_value;
// //R"()",raw string,c++11的新特性,在这里面的字符全部都保持原貌
// in_value["code"] = R"(#include <iostream>\nint main(){\n std::cout << "你可以看见我了" << std::endl;\nreturn 0;\n})";
// 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;
// // std::cout << in_value << std::endl;// //out_json:是给客户端返回的json串
// CompileAndRun::Start(in_json,&out_json);
// std::cout << out_json << std::endl;// return 0;
// }
但是在我们进行编写的时候端口号一定是由argc,argv传递进来的
void Usage(std::string proc)
{std::cerr << "Usage: " << "\n\t" << proc << " port" << std::endl;
}//./compile_server port
int main(int argc,char *argv[])
{if(argc != 2){Usage(argv[0]);return 1;}Server svr;svr.Get("/hello",[](const Request &req, Response &resp){// ⽤来进⾏基本测试 resp.set_content("hello httplib,你好 httplib!", "text/plain;charset=utf-8");});//设置根目录,别人访问根目录,直接就是去访问wwwroot目录下的index.html文件svr.set_base_dir("./wwwroot");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=utf-8");}});svr.listen("0.0.0.0", atoi(argv[1])); //启动http服务 }
你要部署两个服务的话,./compile_server 8081 ./compile_server 8082即可
2.4get和post的区别
现在将compile打包成了一个网络服务
三.基于 MVC 结构的 oj 服务设计
本质上就是开发一个小型的网址
一.获取首页,用题目列表充当
二.编辑区域页面
三.提交判题功能(编译并运行)
一.MVC的概念
M: Model,通常是和数据交互的模块,比如,对题库进行增删改查(文件版,Mysql版)
V: view,通常是要拿到数据之后,要进行构建网页,渲染网页内容,展示给用户的(浏览器)
C:control,控制器,就是我们的核心业务逻辑
二.编写OJ_server的路由功能
代码实现
//OJ_server 和 编译模块不一样 ,网站肯定要网络服务
#include <iostream>
#include "../comm/httplib.h"using namespace httplib;int main()
{//用户请求的服务路由功能Server svr;//获取所有的题目列表svr.Get("/all_questions",[](const Request& req,Response& resp){resp.set_content("这是所有题目的列表","text/plain;charset=utf-8");});//用户要根据题目编号,获取题目的内容//question/100 为正则匹配,\d表示匹配整数//R"()",原始字符串 raw string ,保持字符串原貌,不用相关的转义svr.Get(R"(/questions/(\d+))",[](const Request& req,Response& resp){std::string number = req.matches[1];//正则匹配到的/100这部分内容resp.set_content("这是指定的一道题: " + number,"text/plain;charset=utf-8");});//用户提交代码,使用我们的判题功能(1.每道题目的测试用例 2.comile_and_run)svr.Get(R"(/judge/(\d+))",[](const Request& req,Response& resp){std::string number = req.matches[1];//正则匹配到的/100这部分内容resp.set_content("这是指定题目的判题: " + number,"text/plain;charset=utf-8");});svr.listen("0.0.0.0",8080);return 0;
}
演示效果:
oj_server是需要首页的,所以我们还得写一个首页
index.html代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>这是我的个人oj系统</title>
</head>
<body><h1>欢迎来到我们的OJ</h1><p>这是我个人独立开发的一个在线OJ平台</p>
</body>
</html>
三.建立文件版题库(version 1)
创建一个文件夹(questions)来存储我们的题目
题目设计包括以下的内容:
1.题目的编号2.题目的标题3.题目的难度4.题目的描述,题面5.时间要求(内部处理)6.空间要求(内部处理)两批文件构成
1.第一个: questions.list : 题目列表(不需要题目的内容)
2.第二个: 题目的描述,题目的预设置代码(header.cpp),测试用例代码(tail.cpp)这两个内容通过题目编号产生关联
最终提交给后端的代码是如下的:
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <algorithm>using 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();
}
但是我们提交代码的时候,是不希望有他的,所以我们只要加上COMPILE_ONLINE定义,就不会包含这个了( g++ -D COMPILE_ONLINE )
// 下⾯的代码,我们不想让编译器编译的时候,保留它,⽽是裁剪掉(g++ -D COMPILER_ONLINE)
// 仅仅是为了让我们设计测试⽤例的时候,不要报错
#ifndef COMPILER_ONLINE
#include "header.cpp"
#endif
如果我们有1000道题:设计OJ,将来最⼤的成本其实是给每⼀道题⽬设计测试⽤例!
四.构建model结构
大致的设计思路就是这样
#pragma once#include "../comm/log.hpp"#include <iostream>
#include <string>
#include <unordered_map>
#include <cassert>
#include <vector>//根据题目list文件,加载所有的题目信息到内存中
//model: 主要是用例和数据进行交互,对外提供访问数据的接口namespace ns_model
{using namespace std;using namespace ns_log;struct Question{std::string number;//题目编号,唯一std::string title; //题目标题std::string star; //难度int cpu_limit; //题目时间要求int mem_limit; //题目空间要求std::string desc; //题目的描述std::string header;//题目预设给用户的在线编辑器的代码std::string tail; //题目的测试用例,需要和header拼接,形成完整代码};const std::string question_list = "./questions/questions.list";class Model{private://题号 : 题目细节unordered_map<string,Question> questions;public:Model(){assert(LoadQuestionList(question_list));}bool LoadQuestionList(const std::string& question_list){//加载配置文件: questions/questions.list + 题目编号文件return true;}void GetAllQUestions(vector<Question>* out){//}void GetOneQUestion(const std::string& number,Question* q){//}~Model(){//}};
}
4.1LoadQuestionList()的实现
bool LoadQuestionList(const std::string& question_list){//加载配置文件: questions/questions.list + 题目编号文件ifstream in(question_list);if(!in.is_open()){return false;}std::string line;while(getline(in,line)){std::vector<string> tokens;StringUtil::SplitString(line,&tokens," ");//1 判断回文数 简单 1 30000if(tokens.size() != 5){continue;}Question q;q.number = tokens[0];q.title = tokens[1];q.star = tokens[2];q.cpu_limit = atoi(tokens[3].c_str());q.mem_limit = atoi(tokens[4].c_str());std::string question_number_path = questions_path;question_number_path += q.number;question_number_path += "/";FileUtil::ReadFile(question_number_path+"desc.txt",&(q.desc),true);FileUtil::ReadFile(question_number_path+"header.cpp",&(q.header),true);FileUtil::ReadFile(question_number_path+"tail.cpp",&(q.tail),true);questions.insert({q.number,q});}in.close();return true;}
4.2GetAllQUestions()的实现
bool GetAllQUestions(vector<Question>* out){if(questions.size() == 0){return false;}for(const auto& q:questions){out->push_back(q.second); // first: key , second: value}return true;}
4.3GetOneQUestion()的实现
bool GetOneQUestion(const std::string& number,Question* q){const auto& iter = questions.find(number);if(iter == questions.end()){return false;}(*q) = iter->second;return true;}
一.添加日志功能
二.字符串分割(boost库测试)
#include <iostream>
#include <vector>
#include <boost/algorithm/string.hpp>//boost库测试
int main()
{std::vector<std::string> tokens;const std::string str = "1:判断回文数:简单:1:30000";const std::string sep = ":";boost::split(tokens,str,boost::is_any_of(sep),boost::algorithm::token_compress_on);for(auto& iter:tokens){std::cout << iter << std::endl;}return 0;
}
class StringUtil{public:/***************************************************************** str: 输入型参数,目标要切分的字符串* target: 输出型,保存切分完毕的结果* sep: 指定的分割符* **************************************************************/static void SplitString(const std::string& str,std::vector<std::string>* target,const std::string& sep){//boost库 spiltboost::split((*target),str,boost::is_any_of(sep),boost::algorithm::token_compress_on);}};
model 模块编写完成
五.构建control结构(通过用户的请求,将数据和前端页面进行返回)
充当一个中转的功能
control代码(未完成),后续还有加入view模块,将读到的数据转成网页
#pragma once#include <iostream>
#include <string>
#include <vector>#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include "oj_model.hpp"namespace ns_control
{using namespace std;using namespace ns_log;using namespace ns_util;using namespace ns_model;class Control{private:Model model_;public:Control(){}~Control(){}//根据题目数据构建网页bool AllQuestions(std::string *html){vector<Question> all;if(model_.GetAllQUestions(&all)){//获取一个题目信息成功,将所有的题目数据构建成网页}else{}}bool OneQuestion(const string& number,string* html){Question q;if(model_.GetOneQUestion(number,&q)){//获取指定题目信息成功,将所有的题目数据构建成网页}else{}}};
}
5.1引入ctemplate网页渲染库
安装ctemplate库
直接命令行输入即可
git clone https://github.com/OlafvdSpek/ctemplate.git
git clone https://hub.fastgit.xyz/OlafvdSpek/ctemplate.git
下载好之后,我们依次命令行输入即可
没有makefile就下载这两个就行了
sudo apt-get install autoconf
sudo apt-get install libtool
5.2测试ctemplate库
需要用数据渲染
就是用存储的字典中的数据对被渲染网页的内容进行替换
这里编译的时候要带 -lctemplate 和 -lpthread
测试的demo代码如下:
#include <iostream>
#include <string>
#include <ctemplate/template.h>//ctemplate的使用
int main()
{std::string in_html = "./test.html";std::string value = "刘天为";//形成数据字典ctemplate::TemplateDictionary root("test"); // unordered_map<> test;root.SetValue("key",value); // test.insert();//获取被渲染网页对象ctemplate::Template* tpl = ctemplate::Template::GetTemplate(in_html,ctemplate::DO_NOT_STRIP);//添加字典数据到网页中std::string out_html;tpl->Expand(&out_html,&root);//完成了渲染std::cout << out_html << std::endl;return 0;
}
这就完成了渲染工作,其他的接口我们后续会进行解释
control的编写还没有结束,可以先看view的编写过程
未完待续