前言:
該篇講述了實(shí)現(xiàn)基于負(fù)載均衡式的在線oj,即類似在線編程做題網(wǎng)站一樣,文章盡可能詳細(xì)講述細(xì)節(jié)即實(shí)現(xiàn),便于大家了解學(xué)習(xí)。
文章將采用單篇不分段形式(ps:切著麻煩),附圖文,附代碼,代碼部署在云服務(wù)器上
技術(shù)棧
C++ STL標(biāo)準(zhǔn)庫
Boost 標(biāo)準(zhǔn)庫
cpp-httpib 開源庫
ctemplate 第三方開源前端網(wǎng)頁渲染庫
jsoncpp 第三方開源序列化、反序列化庫
負(fù)載均衡的設(shè)計(jì)
多進(jìn)程、多線程
MYSQL C connect
Ace前端在線編輯器
html/cdd/js/jquery/ajax
開發(fā)環(huán)境
- vscode
- mysql workbench
- Centos 7云服務(wù)器
宏觀結(jié)構(gòu)
- comm:公共模塊
- compile_sever:編譯運(yùn)行模塊
- oj_server:獲取題目,負(fù)載均衡等
?文章來源地址http://www.zghlxwxcb.cn/news/detail-682945.html
?文章來源:http://www.zghlxwxcb.cn/news/detail-682945.html
項(xiàng)目演示:?
https://blog.csdn.net/Obto_/article/details/132558642?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22132558642%22%2C%22source%22%3A%22Obto_%22%7Dhttps://blog.csdn.net/Obto_/article/details/132558642?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22132558642%22%2C%22source%22%3A%22Obto_%22%7D
?
項(xiàng)目設(shè)計(jì) -- 編譯服務(wù)
工具類的準(zhǔn)備:
供程序中各個(gè)部分調(diào)用的方法類:
#pragma once
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <vector>
#include <unistd.h>
#include <sys/time.h>
#include <atomic>
#include <boost/algorithm/string.hpp>
#include <fstream>
namespace ns_util
{
class TimeUtil
{
public:
static std::string GetTimeStamp()
{
// 獲取時(shí)間戳 gettimeofday
struct timeval _time;
gettimeofday(&_time, nullptr);
return std::to_string(_time.tv_sec);
}
// 獲得毫秒時(shí)間戳
static std::string GetTimeMs()
{
struct timeval _time;
gettimeofday(&_time, nullptr);
return std::to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);
}
};
const std::string temp_path = "./temp/";
class PathUtil
{
public:
static std::string AddSuffix(const std::string &file_name, const std::string &suffix)
{
std::string path_name = temp_path;
path_name += file_name;
path_name += suffix;
return path_name;
}
// 構(gòu)建源文件路徑+后綴的完整文件名
// 1234 -> ./temp/1234.cpp
static std::string Src(const std::string &file_name)
{
return AddSuffix(file_name, ".cpp");
}
// 構(gòu)建可執(zhí)行程序的完整路徑 + 后綴名
static std::string Exe(const std::string &file_name)
{
return AddSuffix(file_name, ".exe");
}
static std::string CompilerError(const std::string &file_name)
{
return AddSuffix(file_name, ".compile_stderr");
}
//-------------------------------------------------------------------
// 構(gòu)建該程序?qū)?yīng)的標(biāo)準(zhǔn)錯(cuò)誤完整的路徑+后綴名
static std::string Stderr(const std::string &file_name)
{
return AddSuffix(file_name, ".stderr");
}
static std::string Stdin(const std::string &file_name)
{
return AddSuffix(file_name, ".stdin");
}
static std::string Stdout(const std::string &file_name)
{
return AddSuffix(file_name, ".stdout");
}
};
class FileUtil
{
public:
static bool IsFileExists(const std::string &path_name)
{
struct stat st;
if (stat(path_name.c_str(), &st) == 0)
{
// 獲取屬性成功,文件已經(jīng)存在
return true;
}
return false;
}
static std::string UniqFileName()
{
static std::atomic_uint id(0);
id++;
// 毫秒時(shí)間戳
std::string ms = TimeUtil::GetTimeMs();
std::string uniq_id = std::to_string(id);
return ms + "_" + uniq_id;
}
static bool WriteFile(const std::string &target, const std::string &content)
{
// waiting
std::ofstream out(target);
if (!out.is_open())
{
return false;
}
out.write(content.c_str(), content.size());
out.close();
return true;
}
static bool ReadFile(const std::string &target, std::string *content, bool keep = false)
{
(*content).clear();
std::ifstream in(target);
if (!in.is_open())
{
return false;
}
std::string line;
// getline是按行保存的,不保存行分隔符,自動(dòng)去掉\n
// 但是有些時(shí)候,需要保留行分隔符
// getline內(nèi)部重載了強(qiáng)制類型轉(zhuǎn)化
while (std::getline(in, line))
{
(*content) += line;
(*content) += (keep ? "\n" : "");
}
in.close();
return true;
}
};
class StringUtil
{
public:
/*
str:輸入型,目標(biāo)要切分的字符串
target:輸出型,保存切分完畢的結(jié)果
sep:指定的分隔符
*/
static void SplitString(const std::string &str,std::vector<std::string> *target,std::string sep)
{
boost::split(*target,str,boost::is_any_of(sep),boost::algorithm::token_compress_on);
//boost split
}
};
}
- PathUtil:路徑工具
- 形成exe完整路徑
- 形成cpp完整路徑
- 形成compile_stderr完整路徑
- 形成stderr完整路徑
- 形成stdin完整路徑
- 完整路徑指的是當(dāng)前代碼在本地上的保存路徑:即輸入 1234 要形成 ./temp/1234.cpp等這里的相對路徑形成依靠PathUtil工具
- TimeUtil:時(shí)間工具
- 獲取時(shí)間戳 get time of day
- 獲得好面時(shí)間戳,用于形成文件唯一標(biāo)識(shí)(名字)
- FileUtil:文件工具
- IsFileExits:判斷某文件是否存在
- UniqFileName:形成文件唯一名字
- WriteFile:向指定文件寫入指定字符串
- ReadFile:讀取某文件的內(nèi)容
- StringUtil:字符串工具
- 使用boost庫中的切分字符串Split()
?
compiler編譯服務(wù)設(shè)計(jì) :
目的:能夠編譯并運(yùn)行代碼,得到格式化的相關(guān)結(jié)果
?
#pragma once
#include <iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/wait.h>
#include"../comm/util.hpp"
#include"../comm/log.hpp"
//只負(fù)責(zé)進(jìn)行代碼的編譯
namespace ns_compiler{
//引入路徑拼接功能
using namespace ns_util;
using namespace ns_log;
class Compiler{
public:
Compiler()
{}
~Compiler()
{}
//返回值是編譯成功TRUE;else FALSE
//輸入?yún)?shù)是編譯的文件名
//file_name : 1234
//1234 -> ./temp/1234.cpp
//1234 -> ./temp/1234.exe
//1234 -> ./temp/1234.stderr
static bool Compile(const std::string &file_name)
{
pid_t pid = fork();
if(pid < 0)
{
LOG(ERROR) << "內(nèi)部錯(cuò)誤,創(chuàng)建子進(jìn)程失敗"<<"\n";
return false;
}
else if(pid == 0)//子進(jìn)程
{
umask(0);
int _stderr = open(PathUtil::CompilerError(file_name).c_str(),O_CREAT | O_WRONLY,0644);
if(_stderr < 0){
LOG(WARNING)<<"沒有成功行成stderr文件"<<"\n";
exit(1);
}
//重定向標(biāo)準(zhǔn)錯(cuò)誤到_stderr
dup2(_stderr,2);
//程序替婚,并不影響進(jìn)程的文件描述符表
//子進(jìn)程:調(diào)用編譯器
execlp("g++","g++","-o",PathUtil::Exe(file_name).c_str(),PathUtil::Src(file_name).c_str(),"-std=c++11","-D","COMPILER_ONLINE",nullptr);
LOG(ERROR) <<"啟動(dòng)編譯器g++失敗,可能是參數(shù)錯(cuò)誤"<<"\n";
exit(2);
}
else//父進(jìn)程
{
waitpid(pid,nullptr,0);
//編譯是否成功,就看有沒有形成對應(yīng)的可執(zhí)行程序
if(FileUtil::IsFileExists(PathUtil::Exe(file_name).c_str())){
LOG(INFO) <<PathUtil::Src(file_name)<<"編譯成功!"<<"\n";
return true;
}
}
LOG(ERROR) <<"編譯失敗,沒有形成可執(zhí)行程序,return false"<<"\n";
return false;
}
};
};
compiler編譯服務(wù)只管編譯傳過來的代碼,其他一律不管,它只關(guān)心程序是否能夠編譯過
LOG日志的添加:
#pragma once
#include<iostream>
#include<string>
#include"util.hpp"
namespace ns_log
{
using namespace ns_util;
//日志等級
enum{
INFO,
DEBUG,
WARNING,
ERROR ,
FATAL
};
//LOG() << "message"
inline std::ostream &Log(const std::string &level, const std::string &file_name,int line)
{
//添加日志等級
std::string message = "[";
message+=level;
message+="]";
//添加報(bào)錯(cuò)文件名稱
message+="[";
message+=file_name;
message+="]";
//添加報(bào)錯(cuò)行
message+="[";
message+=std::to_string(line);
message+="]";
//添加日志時(shí)間戳
message += "[";
message += TimeUtil::GetTimeStamp();
message += "]";
//cout 本質(zhì)內(nèi)部是包含緩沖區(qū)的
std::cout<<message;//不要endl刷新
return std::cout;
}
//LOG(INFo) << "message"
//開放式日志
#define LOG(level) Log(#level,__FILE__,__LINE__)
}
runner運(yùn)行功能設(shè)計(jì):
#pragma once
#include <iostream>
#include<sys/time.h>
#include<sys/resource.h>
#include<signal.h>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include<sys/wait.h>
#include<sys/time.h>
#include<sys/resource.h>
#include <fcntl.h>
#include "../comm/log.hpp"
#include "../comm/util.hpp"
namespace ns_runner
{
using namespace ns_log;
using namespace ns_util;
class Runner
{
public:
Runner() {}
~Runner() {}
public:
//提供設(shè)置進(jìn)程占用資源大小的接口
static void SerProcLimit(int _cpu_limit,int _mem_limit)
{
//設(shè)置CPU時(shí)長
struct rlimit cpu_rlimit;
cpu_rlimit.rlim_max = RLIM_INFINITY;
cpu_rlimit.rlim_cur = _cpu_limit;
setrlimit(RLIMIT_CPU,&cpu_rlimit);
//設(shè)置內(nèi)存大小
struct rlimit mem_rlimit;
mem_rlimit.rlim_max = RLIM_INFINITY;
mem_rlimit.rlim_cur = _mem_limit * 1024;//轉(zhuǎn)化成kb
setrlimit(RLIMIT_AS,&mem_rlimit);
}
// 指明文件名即可,不需要帶路徑和后綴
/*
返回值如果是大于 0 :程序異常了,退出時(shí)收到了信號,返回值就是對應(yīng)的信號
返回值 == 0 就是正常運(yùn)行完畢,結(jié)果是什么保存到了臨時(shí)文件中,我不清楚
返回值 < 0 屬于內(nèi)部錯(cuò)誤
cpu_limit:該程序運(yùn)行的時(shí)候,可以使用的最大cpu的資源上限
mem_limit:該程序運(yùn)行的時(shí)候,可以使用的最大內(nèi)存大小KB
*/
static int Run(const std::string &file_name,int cpu_limit,int mem_limit)
{
/*程序運(yùn)行:
1.代碼跑完結(jié)果爭取
2.代碼跑完結(jié)果不正確
3.代碼沒跑完,異常了
run不需要考慮運(yùn)行完后正確與否,只管跑
首先我們必須知道可執(zhí)行程序是誰?
標(biāo)準(zhǔn)輸入:不處理
標(biāo)準(zhǔn)輸入:程序運(yùn)行完成,輸出結(jié)果是什么
標(biāo)準(zhǔn)錯(cuò)誤:運(yùn)行時(shí)錯(cuò)誤信息
*/
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)<<"運(yùn)行時(shí)打開標(biāo)準(zhǔn)文件失敗"<<"\n";
return -1; // 代表打開文件失敗
}
pid_t pid = fork();
if (pid < 0)
{
LOG(ERROR)<<"運(yùn)行時(shí)創(chuàng)建子進(jìn)程失敗"<<"\n";
close(_stdin_fd);
close(_stdout_fd);
close(_stderr_fd);
return -2; //代表創(chuàng)建子進(jìn)程失敗
}
else if (pid == 0)
{
dup2(_stdin_fd,0);
dup2(_stdout_fd,1);
dup2(_stderr_fd,2);
SerProcLimit(cpu_limit,mem_limit);
execl(_execute.c_str()/*我要執(zhí)行誰*/,_execute.c_str()/*我想在命令航商如何執(zhí)行*/,nullptr);
exit(1);
}
else
{
int status = 0;
waitpid(pid,&status,0);
//程序運(yùn)行異常,一定是因?yàn)槭盏搅诵盘? LOG(INFO)<<"運(yùn)行完畢,info:"<<(status & 0x7F)<<"\n";
close(_stdin_fd);
close(_stdout_fd);
close(_stderr_fd);
return status&0x7F;
}
}
};
}
compile_run:編譯并運(yùn)行功能:
#pragma once
#include "compiler.hpp"
#include<unistd.h>
#include "runner.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <jsoncpp/json/json.h>
#include <signal.h>
namespace ns_compile_and_run
{
using namespace ns_log;
using namespace ns_util;
using namespace ns_compiler;
using namespace ns_runner;
class CompileAndRun
{
public:
static void RemoveTempFile(const std::string& file_name)
{
//清理文件的個(gè)數(shù)是不確定的,但是有哪些我們是知道的
std::string _src = PathUtil::Src(file_name);
if(FileUtil::IsFileExists(_src))unlink(_src.c_str());
std::string _compiler_error = PathUtil::CompilerError(file_name);
if(FileUtil::IsFileExists(_compiler_error))unlink(_compiler_error.c_str());
std::string _execute = PathUtil::Exe(file_name);
if(FileUtil::IsFileExists(_execute)) unlink(_execute.c_str());
std::string _stdin = PathUtil::Stdin(file_name);
if(FileUtil::IsFileExists(_stdin)) unlink(_stdin.c_str());
std::string _stdout = PathUtil::Stdout(file_name);
if(FileUtil::IsFileExists(_stdout)) unlink(_stdout.c_str());
std::string _stderr = PathUtil::Stderr(file_name);
if(FileUtil::IsFileExists(_stderr)) unlink(_stderr.c_str());
}
static std::string CodeToDesc(int code, std::string file_name) // code >0 <0 ==0
{
std::string desc;
switch (code)
{
case 0:
desc = "編譯運(yùn)行成功";
break;
case -1:
desc = "用戶提交的代碼是空";
break;
case -2:
desc = "未知錯(cuò)誤";
break;
case -3:
// desc = "編譯發(fā)生報(bào)錯(cuò)";
FileUtil::ReadFile(PathUtil::Stderr(file_name), &desc, true);
break;
case -4:
break;
case SIGABRT:
desc = "內(nèi)存超過范圍";
break;
case SIGXCPU:
desc = "CPU信號超時(shí)";
break;
case SIGFPE:
desc = "除零錯(cuò)誤,浮點(diǎn)數(shù)溢出";
break;
default:
desc = "未知:" + std::to_string(code);
break;
}
return desc;
}
/*
輸入:
code:用戶提交的代碼
input:用戶自己提交的代碼,對應(yīng)的輸入-》不做處理
cpu_limit:時(shí)間要求
mem_limit:空間要求
輸出:
必填:
status:狀態(tài)碼
reason:請求結(jié)果
選填:
stdout:我的的程序運(yùn)行完的結(jié)果
stderr:我的程序運(yùn)行完的錯(cuò)誤結(jié)構(gòu)
參數(shù):
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)
{
LOG(INFO)<<"啟動(dòng)compile_and_run"<<"\n";
Json::Value in_value;
Json::Reader reader;
reader.parse(in_json, in_value); // 最后再處理差錯(cuò)問題
std::string code = in_value["code"].asString();
std::string input = in_value["input"].asString();
int cpu_limit = in_value["cpu_limit"].asInt();
int men_limit = in_value["mem_limit"].asInt();
int status_code = 0;
Json::Value out_value;
int run_result = 0;
std::string file_name; // 需要內(nèi)部形成的唯一文件名
if (code.size() == 0)
{
// 說明用戶一行代碼都沒提交
status_code = -1;
goto END;
}
// 形成的文件名只具有唯一性,沒有目錄沒有后綴
// 毫秒計(jì)時(shí)間戳+原子性遞增的唯一值:來保證唯一性
file_name = FileUtil::UniqFileName(); // 形成唯一文件名字
LOG(DEBUG)<<"調(diào)用UniqFileName()形成唯一名字"<<file_name<<"\n";
run_result = Runner::Run(file_name, cpu_limit, men_limit);
if (!FileUtil::WriteFile(PathUtil::Src(file_name), code)) // 形成臨時(shí)src文件.cpp
{
status_code = -2; // 未知錯(cuò)誤
goto END;
}
if (!Compiler::Compile(file_name))
{
// 編譯失敗
status_code = -3;
goto END;
}
run_result = Runner::Run(file_name, cpu_limit, men_limit);
if (run_result < 0)
{
// 服務(wù)器的內(nèi)部錯(cuò)誤,包括不限于文件打開失敗,創(chuàng)建子進(jìn)程失敗等待
status_code = -2; // 未知錯(cuò)誤
goto END;
}
else if (run_result > 0)
{
status_code = run_result;
}
else
{
// 運(yùn)行成功
status_code = 0;
}
END:
std::cout<<"到達(dá)end語句"<<std::endl;
// status_code
out_value["status"] = status_code;
out_value["reason"] = CodeToDesc(status_code, file_name);
LOG(DEBUG)<<CodeToDesc(status_code, file_name);
if (status_code == 0)
{
// 整個(gè)過程全部成功 , 這時(shí)候才需要運(yùn)行結(jié)果
std::string _stdout;
FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout, true);
out_value["stdout"] = _stdout;
}
else
{
std::string _stderr;
FileUtil::ReadFile(PathUtil::CompilerError(file_name), &_stderr, true);
out_value["stderr"] = _stderr;
}
// 序列化
Json::StyledWriter writer;
*out_json = writer.write(out_value);
//清理所有的臨時(shí)文件
RemoveTempFile(file_name);
}
};
}
compile_run:它的功能是接收遠(yuǎn)端傳進(jìn)來的json包,并反序列化得到其中的代碼與輸入,并調(diào)用compile進(jìn)行編譯
- 編譯成功:調(diào)用runner將代碼運(yùn)行起來->將執(zhí)行結(jié)果分別保存到.exe、.stdin、.stdout 、.stderr、.compile_stderr文件中
- 編譯失敗:不調(diào)用runner
最后按對應(yīng)構(gòu)造json 返回給上級調(diào)用,即write進(jìn)out_json中,收尾清除創(chuàng)建的文件
?
compile_server .cc文件編寫:
?
#include"compile_run.hpp"
#include<jsoncpp/json/json.h>
#include"../comm/httplib.h"
using namespace ns_compile_and_run;
using namespace httplib;
//編譯服務(wù)隨時(shí)可能被多個(gè)人請求,必須保證傳遞上來的code,形成源文件名稱的時(shí)候,要具有唯一性,不然影響多個(gè)用戶
void Usage(std::string proc)
{
std::cerr <<"Usage:"<<"\n\t"<<proc<<"port"<<std::endl;
}
// ./compiler_server port
int main(int argc,char *argv[])
{
if(argc!=2){
Usage(argv[0]);
}
Server svr;
svr.Get("/hello",[](const Request &req,Response &resp)
{
resp.set_content("hello httplib,你好httplib","content_type: text/plain");
});
//svr.set_base_dir("./wwwroot");
svr.Post("/compile_and_run",[](const Request &req,Response &resp){
//請求服務(wù)正文是我們想要的json串
LOG(DEBUG)<<"調(diào)用compile_and_run"<<"\n";
std::string out_json;
std::string in_json = req.body;
if(!in_json.empty()){
LOG(DEBUG)<<"當(dāng)前的in_json"<<in_json<<"\n";
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]));//啟動(dòng)http服務(wù)了
// std::string code = "code";
// Compiler::Compile(code);
// Runner::Run(code);
//0-----------------------測試代碼-------------------
//下面的工作,充當(dāng)客戶端請求的json串
// std::string in_json;
// Json::Value in_value;
// //R"()" raw string 凡事在這個(gè)圓括號里面的東西,就是字符串哪怕有一些特殊的字符串
// in_value["code"] =R"(#include<iostream>
// int main(){
// std::cout<<"測試成功"<<std::endl;
// int a = 10;
// a /= 0;
// return 0;
// })";
// in_value["input"] ="";
// in_value["cpu_limit"] = 1;
// in_value["mem_limit"] = 10240 * 3;
// Json::FastWriter writer;
// std::cout<<in_json<<std::endl;
// in_json = writer.write(in_value);
// //這個(gè)是將來給客戶端返回的字符串
// std::string out_json;
// CompileAndRun::Start(in_json,&out_json);
// std::cout<<out_json<<std::endl;
//0-----------------------------------------------------
//提供的編譯服務(wù),打包新城一個(gè)網(wǎng)絡(luò)服務(wù)
//這次直接用第三方庫,cpp-httplib
return 0;
}
直接引入的httplib庫,?設(shè)置好ip和端口就可以直接監(jiān)聽了
- svr.get() :就是當(dāng)對該服務(wù)器發(fā)起/hello 請求的時(shí)候,我就會(huì)接受到該請求,以Get的方式返回resp
makefile:
由于當(dāng)前使用的c++11的新特性,引入了json庫,和多線程
compile_server:compile_server.cc
g++ -o $@ $^ -std=c++11 -ljsoncpp -lpthread
.PHONY:clean
clean:
rm -f compile_server
項(xiàng)目設(shè)計(jì) -- 基于MVC結(jié)構(gòu)的oj服務(wù)?
本質(zhì):建立一個(gè)小型網(wǎng)站
1.獲取首頁
2.編輯區(qū)域頁面
3.提交判題功能(編譯并運(yùn)行)?
?
M:Model,通常是和數(shù)據(jù)交互的模塊,比如對題庫的增刪改查(文件版,mysql版)
V:view,通常是拿到數(shù)據(jù)之后,要進(jìn)行構(gòu)建網(wǎng)頁,渲染網(wǎng)頁內(nèi)容
C:control,控制器,也就是我們核心業(yè)務(wù)邏輯
用戶的請求服務(wù)路由功能:
#include "../comm/httplib.h"
#include "login.hpp"
#include <iostream>
#include <signal.h>
#include"oj_control.hpp"
using namespace httplib;
using namespace ns_control;
const std::string login_path = "../oj_login/wwwroot/";
static Control *ctrl_ptr = nullptr;
void Recovery(int signo)
{
ctrl_ptr->RecoveryMachine();
}
int main() {
signal(SIGQUIT,Recovery);
// 用戶請求的服務(wù)路由功能
Server svr;
Control ctrl;
Login login;
ctrl_ptr = &ctrl;
/*
1獲取所有的題目列表
*/
svr.Get(R"(/all_questions)", [&ctrl](const Request &req, Response &resp) {
std::string html;
ctrl.AllQuestions(&html);
resp.set_content(html, "text/html;charset=utf-8");
});
// 2用戶要根據(jù)題目編號來選擇題目
// 這里的\d是正則表達(dá)式 + 是匹配數(shù)字
// R"()"保持原始字符串不會(huì)被特殊字符影響比如\d \r \n之類的不需要做相關(guān)的轉(zhuǎn)義
svr.Get(R"(/question/(\d+))", [&ctrl](const Request &req, Response &resp) {
std::string number = req.matches[1];
std::string html;
ctrl.OneQuestion(number,&html);
resp.set_content(html,"text/html;charset=utf-8");
});
// 3用戶提交代碼,使用我們的判題功能(1.沒道題目的測試用例 2.compile_and_run)
svr.Post(R"(/judge/(\d+))",[&ctrl](const Request &req, Response &resp){
std::string number = req.matches[1];
// resp.set_content("這是指定的一道題目的判題:" + number,
// "text/plain;charset=utf-8");
std::string result_json;
ctrl.Judge(number,req.body,&result_json);
resp.set_content(result_json,"application/json;charset=utf-8");
});
svr.Post(R"(/dealregister)",[&ctrl](const Request &req, Response &resp){
int status = 1;
std::string in_json = req.body;
std::string out_json;
if(!ctrl.UserRegister(in_json,&out_json)){
status = 0;
}
LOG(INFO)<<"用戶注冊status : "<<status<<"\n";
Json::Value tmp;
tmp["status"] = status;
Json::FastWriter writer;
std::string res = writer.write(tmp);
resp.set_content(res,"application/json;charset=utf-8");
});
svr.Get(R"(/my_login)",[&login,&ctrl](const Request &req,Response &resp){
//直接跳轉(zhuǎn)到靜態(tài)的html
std::string html;
ctrl.Login(req.body,&html);
resp.set_content(html, "text/html;charset=utf-8");
});
svr.Get(R"(/register)",[&login,&ctrl](const Request &req,Response &resp){
//直接跳轉(zhuǎn)到靜態(tài)的html
std::string html;
ctrl.Register(req.body,&html);
resp.set_content(html, "text/html;charset=utf-8");
});
svr.set_base_dir("./wwwroot");
svr.listen("0.0.0.0", 8080);
return 0;
}
這樣當(dāng)用戶通過http請求我們的oj_server服務(wù)器的時(shí)候我們可以正確的路由到合適的功能
model功能:提供對數(shù)據(jù)的操作(文件版)
#pragma once
//文件版本
/*
編號
標(biāo)題
難度
描述
時(shí)間(內(nèi)部),空間(內(nèi)部處理)
兩批文件構(gòu)成
1.question.list:題目列表:不需要出現(xiàn)題目描述
2.需要題目的描述,需要題目的預(yù)設(shè)置代碼(header.cpp),測試用例代碼(tail.cpp)
這兩個(gè)內(nèi)容是通過題目的編號,產(chǎn)生關(guān)聯(lián)的
*/
#pragma once
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <cassert>
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <string>
#include <unordered_map>
#include <vector>
// 根據(jù)題目list文件,加載所有信息到內(nèi)存中
// model:主要用來和數(shù)據(jù)進(jìn)行交互,對外提供訪問數(shù)據(jù)的接口
namespace ns_model {
using namespace std;
using namespace ns_log;
using namespace ns_util;
class Question {
public:
std::string number; // 題目編號
std::string title; // 題目的標(biāo)題
std::string star; // 難度:簡單中等困難
int cpu_limit; // 時(shí)間要求 s
int mem_limit; // 空間要求 kb
std::string desc; // 題目的描述
std::string header; // 題目預(yù)設(shè)給用戶在線編輯器的代碼
std::string tail; // 題目的測試用例,需要和header拼接形成完整代碼
};
const std::string question_list = "./questions/questions.list";
const std::string question_path = "./questions/";
class Model {
private:
// 題號:題目細(xì)節(jié)
unordered_map<string, Question> questions;
public:
Model() { assert(LoadQuestionList(question_list)); }
bool LoadQuestionList(const std::string &question_list) {
// 加載配置文件questions/question.list + 題目編號文件
ifstream in(question_list);
if (!in.is_open()) {
LOG(FATEL) << "加載題庫失敗,請檢查是否存在題庫文件"
<< "\n";
return false;
}
std::string line;
while (getline(in, line)) {
vector<string> tokens;
StringUtil::SplitString(line, &tokens, " ");
if (tokens.size() != 5) {
LOG(WARNING) << "加載部分題目失敗,請檢查文件格式"
<< "\n";
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 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.tail), true);
questions.insert({q.number, q});
}
LOG(INFO) << "加載題庫成功!"
<< "\n";
in.close();
return true;
}
bool GetAllQuestion(vector<Question> *out) {
if (questions.size() == 0) {
LOG(ERROR) << "用戶獲取題庫失敗"
<< "\n";
return false;
}
for (const auto &q : questions) {
out->push_back(q.second); // fir是key' sec是value
}
return true;
}
bool GetOneQuestion(const std::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;
}
~Model() {}
};
} // namespace ns_model
該設(shè)計(jì)中有一個(gè) question的題目清單,像題庫的目錄一樣,填寫每道題目的基本信息:
對應(yīng)的是:
1.題目編號 2.題目名字 3.題目難度 4.時(shí)間限制 5.空間限制
?
?
?model功能:提供對數(shù)據(jù)的操作(數(shù)據(jù)庫版)
#pragma once
//這個(gè)是mysql版本
/*
編號
標(biāo)題
難度
描述
時(shí)間(內(nèi)部),空間(內(nèi)部處理)
兩批文件構(gòu)成
1.question.list:題目列表:不需要出現(xiàn)題目描述
2.需要題目的描述,需要題目的預(yù)設(shè)置代碼(header.cpp),測試用例代碼(tail.cpp)
這兩個(gè)內(nèi)容是通過題目的編號,產(chǎn)生關(guān)聯(lián)的
*/
#pragma once
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <cassert>
#include <stdio.h>
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <string>
#include <unordered_map>
#include <vector>
#include"include/mysql.h"
// 根據(jù)題目list文件,加載所有信息到內(nèi)存中
// model:主要用來和數(shù)據(jù)進(jìn)行交互,對外提供訪問數(shù)據(jù)的接口
namespace ns_model {
using namespace std;
using namespace ns_log;
using namespace ns_util;
class Question {
public:
std::string number; // 題目編號
std::string title; // 題目的標(biāo)題
std::string star; // 難度:簡單中等困難
int cpu_limit; // 時(shí)間要求 s
int mem_limit; // 空間要求 kb
std::string desc; // 題目的描述
std::string header; // 題目預(yù)設(shè)給用戶在線編輯器的代碼
std::string tail; // 題目的測試用例,需要和header拼接形成完整代碼
};
const std::string oj_questions ="oj_questions";
const std::string oj_user = "oj_user";
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 {
private:
// 題號:題目細(xì)節(jié)
unordered_map<string, Question> questions;
public:
Model() { }
bool QueryMySql(const std::string &sql,vector<Question> *out)
{
//創(chuàng)建mysql句柄
MYSQL *my = mysql_init(nullptr);
//連接數(shù)據(jù)庫
if(mysql_real_connect(my,host.c_str(),user.c_str(),passwd.c_str(),db.c_str(),port,nullptr,0) == nullptr){
LOG(FATAL)<<"連接數(shù)據(jù)庫失??!"<<"\n";
return false;
}
//一定要設(shè)置該鏈接的編碼格式默認(rèn)是拉釘?shù)? mysql_set_character_set(my,"utf8mb4");
LOG(INFO)<<"連接數(shù)據(jù)庫成功"<<"\n";
//執(zhí)行sql語句
if(0 != mysql_query(my,sql.c_str()))
{
LOG(WARNING) << sql <<"execute error!"<<"\n";
return false;
}
MYSQL_RES *res = mysql_store_result(my);
//分析結(jié)果
int rows = mysql_num_rows(res); //獲得行數(shù)量
int cols = mysql_num_fields(res);//獲得列數(shù)量
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);
}
//釋放結(jié)果空間
free(res);
//關(guān)閉mysql連接
mysql_close(my);
return true;
}
bool GetAllQuestion(vector<Question> *out) {
std::string sql ="select *from ";
sql += oj_questions;
return QueryMySql(sql,out);
}
bool GetOneQuestion(const std::string &number, Question *q) {
bool res = false;
std::string sql = "select *from ";
sql+=oj_questions;
sql+= " where number=";
sql+=number;
vector<Question> result;
if(QueryMySql(sql,&result))
{
if(result.size() == 1)
{
*q = result[0];
res = true;
}
}
return res;
}
bool UserRegister(const std::string& in_json,std::string* out_json)
{
//這里先對in_json反序列化
Json::Reader reader;
Json::Value in_value;
reader.parse(in_json,in_value);
std::string number = in_value["number"].asString();
std::string name = in_value["name"].asString();
std::string password = in_value["password"].asString();
int limit = in_value["limit"].asInt();
int level = in_value["level"].asInt();
//判斷賬號密碼是否可行
std::string sql = " select *from ";
sql+=oj_user;
sql+=" where number=";
sql+=number;
//創(chuàng)建數(shù)據(jù)庫
MYSQL *my = mysql_init(nullptr);
//連接數(shù)據(jù)庫
if(mysql_real_connect(my,host.c_str(),user.c_str(),passwd.c_str(),db.c_str(),port,nullptr,0) == nullptr)
{
LOG(WARNING)<<"連接到用戶數(shù)據(jù)庫失敗"<<"\n";
return false;
}
//一定要記得設(shè)置該鏈接的編碼格式
mysql_set_character_set(my,"utf8");
LOG(INFO)<<"連接懂啊用戶數(shù)據(jù)庫成功"<<"\n";
if(0 != mysql_query(my,sql.c_str())){
LOG(WARNING)<< sql <<"execute error!"<<"\n";
return false;
}
MYSQL_RES *res = mysql_store_result(my);
if(mysql_num_rows(res) == 0)//獲得行數(shù)量
{
//當(dāng)前輸入的數(shù)據(jù)可以創(chuàng)建用戶
MYSQL_STMT *stmt = mysql_stmt_init(my);
const char* query = "insert into oj_user values (?,?,?,?,?)";
if(mysql_stmt_prepare(stmt,query,strlen(query)) != 0){
LOG(WARNING)<<"stmt出現(xiàn)錯(cuò)誤"<<"\n";
mysql_stmt_close(stmt);
mysql_close(my);
return false;
}
//下面開始綁定
MYSQL_BIND bind_params[5];
memset(bind_params,0,sizeof bind_params);
bind_params[0].buffer_type = MYSQL_TYPE_STRING;
bind_params[0].buffer = (char*)number.c_str();
bind_params[0].buffer_length = number.size();
bind_params[1].buffer_type = MYSQL_TYPE_STRING;
bind_params[1].buffer = (char*)name.c_str();
bind_params[1].buffer_length = name.size();
bind_params[2].buffer_type = MYSQL_TYPE_STRING;
bind_params[2].buffer = (char*)password.c_str();
bind_params[2].buffer_length = password.size();
bind_params[3].buffer_type = MYSQL_TYPE_LONG;
bind_params[3].buffer = &limit;
bind_params[3].is_unsigned = 1;
bind_params[4].buffer_type = MYSQL_TYPE_LONG;
bind_params[4].buffer = &level;
bind_params[4].is_unsigned = 1;
if(mysql_stmt_bind_param(stmt,bind_params) !=0){
LOG(WARNING) <<"綁定stmt參數(shù)出錯(cuò)"<<"\n";
mysql_stmt_close(stmt);
mysql_close(my);
return false;
}
//執(zhí)行插入語句
if(mysql_stmt_execute(stmt)!=0){
LOG(WARNING)<<"執(zhí)行stmt語句的時(shí)候出現(xiàn)錯(cuò)誤..."<<"\n";
mysql_stmt_close(stmt);
mysql_close(my);
return false;
}
mysql_stmt_close(stmt);
mysql_close(my);
return true;
}
else{
//服務(wù)器有重復(fù)的用戶num ,不允許再創(chuàng)建了
return false;
}
//保存到服務(wù)器
//這里out_json暫時(shí)沒有用,沒有要返回的值
return true;
}
~Model() {}
};
} // namespace ns_model
control:邏輯控制模塊
#pragma once
#include<iostream>
#include<string>
#include<cassert>
#include<algorithm>
#include<fstream>
#include<jsoncpp/json/json.h>
#include<vector>
#include<mutex>
#include"oj_view.hpp"
// #include"oj_model.hpp"
#include"oj_model2.hpp"
#include"../comm/log.hpp"
#include"../comm/util.hpp"
#include"../comm/httplib.h"
namespace ns_control
{
using namespace std;
using namespace httplib;
using namespace ns_log;
using namespace ns_util;
using namespace ns_model;
using namespace ns_view;
//提供服務(wù)的主機(jī)的內(nèi)容
class Machine
{
public:
std::string ip; //編譯服務(wù)器的ip
int port; //編譯服務(wù)器的端口
uint64_t load; //編譯服務(wù)器負(fù)載
std::mutex *mtx;//mutex是禁止拷貝的,使用指針來完成
public:
Machine():ip(""),port(0),load(0),mtx(nullptr)
{}
~Machine()
{}
public:
void ResetLoad()
{
if(mtx)mtx->lock();
load = 0;
LOG(DEBUG)<<"當(dāng)前ip:"<<ip<<"端口:"<<port<<"的load已經(jīng)清除load = "<<load<<"\n";
if(mtx)mtx->unlock();
}
//提升主機(jī)負(fù)載
void IncLoad()
{
if(mtx) mtx->lock();
++load;
if(mtx) mtx->unlock();
}
//減少主機(jī)負(fù)載
void DecLoad()
{
if(mtx) mtx->lock();
--load;
if(mtx) mtx->unlock();
}
//獲取主機(jī)負(fù)載,沒有太大的意義,只是為了同一接口
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";
//負(fù)載均衡模塊
class LoadBalance
{
private:
//可以給我們提供編譯服務(wù)的所有的主機(jī)
//每一臺(tái)主機(jī)都有自己的下標(biāo),充當(dāng)當(dāng)前主機(jī)的id
std::vector<Machine> machines;
//所有在線的主機(jī)
std::vector<int> online;
//所有離線主機(jī)的id
std::vector<int> offline;
//保證選擇主機(jī)上的這個(gè)東西要保證數(shù)據(jù)安全
std::mutex mtx;
public:
LoadBalance(){
assert(LoadConf(service_machine));
LOG(INFO)<<"加載"<<service_machine <<"成功"<<"\n";
}
~LoadBalance(){}
public:
bool LoadConf(const std::string &machine_cof)
{
std::ifstream in(machine_cof);
if(!in.is_open())\
{
LOG(FATAL) <<"加載:"<<machine_cof<<"失敗"<<"\n";
return false;
}
std::string line;
while (getline(in,line))
{
std::vector<std::string> tokens;
StringUtil::SplitString(line,&tokens,":");
if(tokens.size()!=2)
{
LOG(WARNING) <<"切分"<<line<<"失敗"<<"\n";
std::cout<<tokens[0]<<":"<<tokens[1]<<std::endl;
continue;
}
//LOG(INFO) <<"切分"<<tokens[0]<<tokens[1]<<"成功"<<"\n";
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:是一個(gè)輸出型參數(shù)
//m:是一個(gè)輸出型參數(shù)
bool SmartChoice(int *id,Machine **m)
{
//1.使用選擇好的主機(jī)(更新該主機(jī)的負(fù)載)
//2.我們需要可能離線該主機(jī)
mtx.lock();
//選擇主機(jī)
//一般的負(fù)載均衡的算法
//1.隨機(jī)數(shù)法 + hash
//2.輪詢 + hash
int online_num = online.size();//在線主機(jī)的個(gè)數(shù)
if(online_num == 0){
mtx.unlock();
LOG(FATAL) << "所有的后端編譯主機(jī)已經(jīng)全部離線,請后端的盡快重啟"<<"\n";
return false;
}
LOG(DEBUG)<<"online:"<<online.size()<<"\n";
//通過編譯,找到負(fù)載最小的機(jī)器
*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){
//要離線的主機(jī)找到了
machines[which].ResetLoad();
LOG(DEBUG)<<"當(dāng)前離線主機(jī)的負(fù)載更改為:"<<machines[which].load;
online.erase(iter);
offline.push_back(which);
break;//因?yàn)閎reak的存在,所以暫時(shí)不考慮迭代器失效的問題
}
}
mtx.unlock();
}
void OnlineMachine()
{
//我們統(tǒng)一上線,后面統(tǒng)一解決
mtx.lock();
online.insert(online.end(),offline.begin(),offline.end());
offline.erase(offline.begin(),offline.end());
mtx.unlock();
LOG(INFO)<<"所有的主機(jī)又上線了"<<"\n";
LOG(INFO)<<"online:"<<online.size()<<"offline"<<offline.size()<<"\n";
}
void ShowMachines()
{
mtx.lock();
LOG(INFO)<<"online:"<<online.size()<<"offline"<<offline.size()<<"\n";
mtx.unlock();
}
};
//這是我們核心業(yè)務(wù)邏輯的控制器
class Control
{
private:
Model model_;//提供后臺(tái)數(shù)據(jù)
View view_; //提供網(wǎng)頁渲染功能
LoadBalance load_blance_; //核心負(fù)載均衡器
public:
void RecoveryMachine()
{
load_blance_.OnlineMachine();
}
//根據(jù)題目數(shù)據(jù)構(gòu)建網(wǎng)頁
//html:輸出型參數(shù)
bool AllQuestions(string *html)
{
bool ret = true;
vector<Question> all;
if(model_.GetAllQuestion(&all))
{
sort(all.begin(),all.end(),[](const Question &q1,const Question &q2){
return atoi(q1.number.c_str()) < atoi(q2.number.c_str());
});
//獲取題目信息 成功,將所有的題目數(shù)據(jù)構(gòu)建成網(wǎng)頁
view_.AllExpandHtml(all,html);
}
else
{
*html="獲取題目失敗,形成題目列表失敗";
ret = false;
}
return ret;
}
bool OneQuestion(const string &number,string *html)
{
Question q;
bool ret = true;
if(model_.GetOneQuestion(number,&q))
{
//獲取指定信息的題目成功,構(gòu)建程網(wǎng)頁
view_.OneExpandHtml(q,html);
}
else
{
*html="獲取指定題目題目失敗,形成題目列表失敗";
ret = false;
}
return ret;
}
void Login(const std::string in_json,std::string *out_json)
{
//in_json是發(fā)送過來的請求數(shù)據(jù),用戶的賬號等待
//返回渲染的登錄界面
view_.LoginExpandHtml(out_json);
}
void Register(const std::string in_json,std::string *out_json)
{
if(view_.RegisterExpandHtml(out_json)){
LOG(INFO)<<"插入成功"<<"\n";
}
else{
LOG(INFO)<<"插入失敗,可能是重復(fù)的用戶"<<"\n";
}
}
bool UserRegister(const std::string in_json,std::string *out_json)
{
return model_.UserRegister(in_json,out_json);
}
//id:: 100
//code:include
//input:
void Judge(const std::string &number,const std::string in_json,std::string *out_json)
{
// LOG(INFO)<<"調(diào)用Judge功能"<<"\n";
// LOG(DEBUG)<<in_json<<"\nnumber:"<<number<<"\n";
//0.根據(jù)題目編號,拿到題目細(xì)節(jié)
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 + 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.選擇負(fù)載最低的主機(jī),然后發(fā)起HTTP請求得到結(jié)果
//規(guī)則:一直選擇,直到主機(jī)可用,否則就是全部掛掉
while(true)
{
int id = 0;
Machine *m = nullptr;
if(!load_blance_.SmartChoice(&id,&m))
{
break;
}
//4.*out_json = 將結(jié)果復(fù)制給out_json
Client cli(m->ip,m->port);
m->IncLoad();
LOG(DEBUG)<<"選擇主機(jī)成功,主機(jī)id:"<<id<<"\n詳情:"<<m->ip<<":"<<m->port<<"當(dāng)前主機(jī)負(fù)載:"<<m->Load()<<"\n";
if(auto res = cli.Post("/compile_and_run",compile_string,"application/json;charset=utf-8"))
{
//將我們的結(jié)果返回給out_json
if(res->status == 200)
{
*out_json = res->body;
m->DecLoad();
LOG(INFO)<<"請求編譯和運(yùn)行服務(wù)成功..."<<"\n";
break;
}
m->DecLoad();
}
else
{
//請求失敗
LOG(ERROR)<<"選擇主機(jī)失敗,主機(jī)id:"<<id<<"詳情:"<<m->ip<<":"<<m->port<<"可能已經(jīng)離線"<<"\n";
load_blance_.OfflineMachine(id);
load_blance_.ShowMachines();//僅僅為了調(diào)試
}
//m->DecLoad();
}
}
Control(){}
~Control(){}
};
}
control模塊實(shí)現(xiàn)了 負(fù)載均衡
- 負(fù)載均衡
- 第一種:隨機(jī)數(shù)+hash
- 第二種:輪詢+hash , 本文是在用輪詢+hash
- 為了實(shí)現(xiàn)負(fù)載均衡所有要把所有主機(jī)管理起來,有了Machine類
- std::string ip :編譯服務(wù)器的ip
- int port:編譯服務(wù)器的端口
- uint64_t load :編譯服務(wù)器的負(fù)載
- std::mutex *mtx:每個(gè)機(jī)器可能會(huì)同時(shí)被多個(gè)用戶訪問,所以要有鎖來保證臨界資源,并且mutex是不允許拷貝的,所以這里直接用指針,這樣在賦值構(gòu)造和拷貝構(gòu)造就沒事了
?
?view渲染功能:將后端的代碼渲染到html返回給前端
這里就要使用到ctemplate庫了:
?
?
#pragma once
#include<iostream>
#include<string>
#include<ctemplate/template.h>
// #include"oj_model.hpp"
#include"oj_model2.hpp"
namespace ns_view
{
using namespace ns_model;
const std::string template_path ="./template_html/";
const std::string login_path = "./login_html/";
class View
{
public:
View(){}
~View(){}
bool RegisterExpandHtml(std::string *html)
{
//新城路徑
std::string src_html = login_path + "register.html";
//形成數(shù)據(jù)字典
ctemplate::TemplateDictionary root("register");
//獲取渲染的網(wǎng)頁
ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
//開始渲染
tpl->Expand(html,&root);
return true;
}
void LoginExpandHtml(std::string *html)
{
//形成路徑
std::string src_html = login_path + "login.html";
//形成數(shù)據(jù)字典
ctemplate::TemplateDictionary root("my_login");
//獲取渲染網(wǎng)頁
ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
//開始渲染
tpl->Expand(html,&root);
}
void AllExpandHtml(const vector<Question> &questions,std::string *html)
{
// 題目的編號 題目的標(biāo)題 題目的難度
// 推薦使用表格顯示
//1。形成路徑
std::string src_html = template_path + "all_questions.html";
LOG(INFO)<<"形成路徑成功:"<< src_html <<"\n";
//2.形成數(shù)據(jù)字典
ctemplate::TemplateDictionary root("all_questions");
for(const auto& q:questions)
{
ctemplate::TemplateDictionary *sub = root.AddSectionDictionary("question_list");
sub->SetValue("number",q.number);
sub->SetValue("title",q.title);
sub->SetValue("star",q.star);
}
//3.獲取被渲染的網(wǎng)頁html
ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
LOG(INFO)<<"獲取渲染網(wǎng)頁的html成功"<<"\n";
//4.開始完成渲染功能
tpl->Expand(html,&root);
LOG(INFO)<<"渲染成功"<<"\n";
}
void OneExpandHtml(const Question &q,std::string *html)
{
//形成路徑
std::string src_html = template_path + "one_question.html";
LOG(DEBUG)<<"one expand html :"<<src_html<<"\n";
//q.desc
//形成數(shù)字典
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("pre_code",q.header);
//獲取被渲染的html
ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html,ctemplate::DO_NOT_STRIP);
//開始渲染功能
tpl->Expand(html,&root);
}
};
}
?
?
總結(jié)?
1.前端的代碼在博客最上端綁定的文件當(dāng)中 ,篇幅太長不展示出來了
2.該項(xiàng)目的技術(shù)棧眾多,是c++后端和前端進(jìn)行交互的一個(gè)項(xiàng)目
3.項(xiàng)目的難點(diǎn)有:負(fù)載均衡的分配到每一臺(tái)編譯服務(wù)器、容錯(cuò)處理,能夠處理多種不同的錯(cuò)誤原因、并發(fā)處理要對臨界資源的管理、以及高并發(fā)訪問的話要對效率有所保證,畢竟在線oj服務(wù)是具有時(shí)效性的
4.debug困難,要在test.cc下測試成功后再進(jìn)行編寫,便于修改bug
?
?
?
?
?
?
?
?
?
?
?
?
?
?
到了這里,關(guān)于基于負(fù)載均衡的在線OJ實(shí)戰(zhàn)項(xiàng)目的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!