中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

網(wǎng)站建設(shè)常用問題庫黑帽seo是什么

網(wǎng)站建設(shè)常用問題庫,黑帽seo是什么,北京網(wǎng)站建設(shè)第一,交互式網(wǎng)站開發(fā)技術(shù)asp目錄 一、項(xiàng)目的相關(guān)背景 二、所用技術(shù)棧和開發(fā)環(huán)境 三、項(xiàng)目的宏觀結(jié)構(gòu) 四、compile_server模塊設(shè)計(jì) 1. 編譯服務(wù)(compiler模塊) 2. 運(yùn)行服務(wù)(runner模塊) 3. 編譯并運(yùn)行服務(wù)(compile_run模塊) 4…

目錄

一、項(xiàng)目的相關(guān)背景

二、所用技術(shù)棧和開發(fā)環(huán)境

三、項(xiàng)目的宏觀結(jié)構(gòu)

四、compile_server模塊設(shè)計(jì)

1. 編譯服務(wù)(compiler模塊)?

2. 運(yùn)行服務(wù)(runner模塊)

3. 編譯并運(yùn)行服務(wù)(compile_run模塊)

4. 打包成網(wǎng)絡(luò)服務(wù)(compile_server模塊)

五、基于MVC結(jié)構(gòu)的oj_server模塊設(shè)計(jì)

1. 什么是MVC結(jié)構(gòu)

2. oj_model模塊

3. oj_view模塊

4. oj_control模塊

5. 打包成網(wǎng)絡(luò)服務(wù)(oj_server)

六、前端頁面的設(shè)計(jì)

1. indx.html

2. all_questions.html

3. one_question.html

七、項(xiàng)目總結(jié)?


項(xiàng)目源碼:https://gitee.com/lu-code-xiaomiao/load-balancing-online---oj

一、項(xiàng)目的相關(guān)背景

????????學(xué)習(xí)編程的小伙伴,大家對(duì)力扣、??突蚱渌诰€編程的網(wǎng)站一定都不陌生,這些編程網(wǎng)站除了提供了在線編程,還有其他的一些功能。我們這個(gè)項(xiàng)目只是做出能夠在線編程的功能。

二、所用技術(shù)棧和開發(fā)環(huán)境

技術(shù)棧:

  1. C++ STL 標(biāo)準(zhǔn)庫
  2. Boost 準(zhǔn)標(biāo)準(zhǔn)庫(字符串切割)
  3. cpp-httplib 第三方開源網(wǎng)絡(luò)庫
  4. ctemplate 第三方開源前端網(wǎng)頁渲染庫
  5. jsoncpp 第三方開源序列化、反序列化庫
  6. 負(fù)載均衡設(shè)計(jì)
  7. 多進(jìn)程、多線程
  8. MySQL C connect
  9. Ace前端在線編輯器(了解)
  10. html/css/js/jquery/ajax (了解)

開發(fā)環(huán)境:

  1. Centos 7 云服務(wù)器
  2. vscode

三、項(xiàng)目的宏觀結(jié)構(gòu)

我們的項(xiàng)目核心是三個(gè)模塊:

  1. comm : 公共模塊(主要包含:httplib<網(wǎng)絡(luò)服務(wù)>、log<日志信息>、util<項(xiàng)目中都需要使用到的工具類的集合>)
  2. compile_server : 編譯與運(yùn)行模塊(主要包含:編譯服務(wù)、運(yùn)行服務(wù)、編譯和運(yùn)行服務(wù))
  3. oj_server : 獲取題目列表,查看題目編寫題目界面,負(fù)載均衡

????????客戶端向服務(wù)器的oj_server發(fā)起請(qǐng)求,有可能是請(qǐng)求題目的列表、請(qǐng)求特定題目的編寫、請(qǐng)求代碼提交;對(duì)于請(qǐng)求題目列表和編寫,只需要向文件或MySQL獲取數(shù)據(jù),并顯示成網(wǎng)頁即可,但是提交代碼的時(shí)候,我們就要考慮多用戶提交的情況,所以oj_server在收到不同客戶端發(fā)來的提交代碼的請(qǐng)求時(shí),就要需要負(fù)載均衡式的選擇我們后端的complie_server進(jìn)行編譯并運(yùn)行,然后反饋?zhàn)罱K結(jié)果。

四、compile_server模塊設(shè)計(jì)

????????compile_server模塊,主要包括編譯服務(wù)、運(yùn)行服務(wù)和編譯運(yùn)行服務(wù),最后打包成網(wǎng)絡(luò)服務(wù)。

1. 編譯服務(wù)(compiler模塊)?

????????compiler模塊只負(fù)責(zé)代碼的編譯,要對(duì)代碼進(jìn)行編譯,那么我們就需要有file_name(文件名)(如:1234.cpp)

? ? ? ? 對(duì)代碼進(jìn)行編譯,有可能成功,形成.exe文件,后續(xù)可以直接運(yùn)行。也有可能失敗,對(duì)于編譯失敗了的原因,我們也需要保存起來,用于反饋給用戶,否則客戶怎么知道錯(cuò)誤在哪里。

對(duì)于客戶提交過來的文件(如1234),我們需要對(duì)文件進(jìn)行路徑拼接,拼接出(1234.cpp、1234.exe、1234.compiler_error) 所以我們將這個(gè)功能編寫到我們的comm模塊中:

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>namespace ns_util {const std::string temp_path = "./temp/";//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;}// 編譯時(shí)需要的臨時(shí)文件// 構(gòu)建源文件路徑 + 后綴的完整文件名// 1234 -> ./temp/1234.cppstatic 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_error");}};}

????????其中./temp是對(duì)用戶提交過來的文件名進(jìn)行路徑的拼接,形成三個(gè)文件的存放位置,這是編譯時(shí)需要的三個(gè)臨時(shí)文件,有了這三個(gè)臨時(shí)文件后,我們就可以對(duì)用戶的代碼進(jìn)行編譯的操作了。

#pragma once#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.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,否則:false//輸入?yún)?shù):編譯的文件名//file_name: 1234//1234 -> ./temp/1234.cpp//1234 -> ./temp/1234.exe//1234 -> ./temp/1234.compiler_errorstatic bool Compile(const std::string &file_name) {pid_t pid = fork();//創(chuàng)建子進(jìn)程,成功,就給子進(jìn)程返回0,給父進(jìn)程返回pidif (pid < 0) {LOG(ERROR) << "內(nèi)部錯(cuò)誤,創(chuàng)建子進(jìn)程失敗" << "\n";return false;} else if (pid == 0) {umask(0);//將umask設(shè)置為0,以防系統(tǒng)修改我們?cè)O(shè)置的權(quán)限//_stderr文件用來保存編譯出錯(cuò)時(shí),產(chǎn)生的信息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ò)誤到_stderrdup2(_stderr, 2);//程序替換,并不影響進(jìn)程的文件描述符表//子進(jìn)程: 調(diào)用編譯器,完成對(duì)代碼的編譯工作//g++ -o target src -std=c++11execlp("g++", "g++", "-o", PathUtil::Exe(file_name).c_str(), PathUtil::Src(file_name).c_str(), "-std=c++11", nullptr);//這個(gè)函數(shù)替換的時(shí)候不要忘記最后給個(gè)nullptrLOG(ERROR) << "啟動(dòng)編譯器g++失敗,可能是參數(shù)錯(cuò)誤" << "\n";exit(2);} else {waitpid(pid, nullptr, 0);//編譯是否成功,就看有沒有形成對(duì)應(yīng)的可執(zhí)行程序if (FileUtil::IsFileExists(PathUtil::Exe(file_name))) {LOG(INFO) << PathUtil::Src(file_name) << " 編譯成功!" << "\n";return true;}}LOG(ERROR) << "編譯失敗,沒有形成可執(zhí)行程序" << "\n";return false;}};
}

其中關(guān)于LOG,你暫時(shí)可以理解為std::cout; 后面我會(huì)給出完整的代碼;

2. 運(yùn)行服務(wù)(runner模塊)

????????我們已經(jīng)完成的編譯服務(wù),相應(yīng)的會(huì)在temp目錄下形成三個(gè)臨時(shí)文件,當(dāng)然編譯成功會(huì)形成.exe文件,失敗會(huì)形成compiler_error文件不會(huì)形成.exe文件,相應(yīng)的錯(cuò)誤信息回報(bào)存在這個(gè)文件中。有了.exe文件后,我們接下來的工作就是對(duì)可執(zhí)行程序進(jìn)行運(yùn)行了。

????????用戶提交的代碼,雖然經(jīng)過編譯器編譯后,形成了可執(zhí)行程序,但是對(duì)于代碼的運(yùn)行也需要三個(gè)臨時(shí)文件(1234.stdin、1234.stdout、1234.stderr) 這三個(gè)文件分別表示:1234.stdin:用戶外部自測(cè)輸入的參數(shù)(但是我們不考慮,直接使我們提供參數(shù))1234.stdout:代表運(yùn)行成功后的結(jié)果,我們不需要顯示到顯示器上,用文件保存起來,用于反饋給客戶;1234.stderr:代表運(yùn)行失敗后的結(jié)果,我們不需要顯示到顯示器上,用文件保存起來,用于反饋給客戶;所以我們?cè)趗til中再添加三個(gè)運(yùn)行時(shí)需要的臨時(shí)文件:

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>namespace ns_util {const std::string temp_path = "./temp/";//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;}// 編譯時(shí)需要的臨時(shí)文件// 構(gòu)建源文件路徑 + 后綴的完整文件名// 1234 -> ./temp/1234.cppstatic 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_error");}// 運(yùn)行時(shí)需要的臨時(shí)文件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");}// 構(gòu)建該程序?qū)?yīng)的標(biāo)準(zhǔn)錯(cuò)誤完整路徑 + 后綴名static std::string Stderr(const std::string &file_name) {return AddSuffix(file_name, ".stderr");}};}

運(yùn)行時(shí)需要的三個(gè)臨時(shí)文件,我們已經(jīng)可以進(jìn)行路徑拼接了,接下來我們來完成運(yùn)行模塊:

#pragma once#include <iostream>
#include <string>
#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"namespace ns_runner {using namespace ns_log;using namespace ns_util;class Runner {public:Runner() {}~Runner() {}public:// 指明文件名即可,不需要代路徑,不需要帶后綴/** 返回值 >  0:程序異常了,退出時(shí)收到了信號(hào),返回值就是對(duì)應(yīng)的信號(hào)編號(hào)* 返回值 == 0:正常運(yùn)行完畢了,結(jié)果保存到了對(duì)應(yīng)的臨時(shí)文件中* 返回值 <  0:內(nèi)部錯(cuò)誤* */static int Run(const std::string &file_name, int cpu_limit, int mem_limit) {/** 程序運(yùn)行:* 1. 代碼跑完,結(jié)果正確* 2. 代碼跑完,結(jié)果不正確* 3. 代碼沒跑完,異常了* Run需要考慮代碼跑完,結(jié)果正確是否嗎??不考慮* 結(jié)果正確是否:是由我們的測(cè)試用例決定的!* 我們只考慮是否正確運(yùn)行完畢** 我們必須知道可執(zhí)行程序是誰?* 一個(gè)程序在默認(rèn)啟動(dòng)時(shí):* 標(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);   //可執(zhí)行程序的名字std::string _stdin = PathUtil::Stdin(file_name);   //運(yùn)行時(shí)產(chǎn)生的標(biāo)準(zhǔn)輸入文件std::string _stdout = PathUtil::Stdout(file_name); //運(yùn)行時(shí)產(chǎn)生的標(biāo)準(zhǔn)輸出文件std::string _stderr = PathUtil::Stderr(file_name); //運(yùn)行時(shí)產(chǎn)生的標(biāo)準(zhǔn)錯(cuò)誤文件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);/*我要執(zhí)行誰       我想在命令行上如何執(zhí)行該程序*/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);// 程序運(yùn)行異常,一定是因?yàn)槭盏搅诵盘?hào)!LOG(INFO) << "運(yùn)行完畢,info" << (status & 0x7F) << "\n";return status & 0x7F;}}};
}

????????雖然上面已經(jīng)基本完成了運(yùn)行模塊,但是還是有缺陷的,我們常常在力扣或??蜕纤㈩}時(shí),明確標(biāo)注了時(shí)間限制和內(nèi)存限制,或者棧的限制。所以我們對(duì)資源的限制也需要做一些處理,我們這里只處理時(shí)間和內(nèi)存上的限制。在運(yùn)行模塊中添加如下的函數(shù),是一個(gè)系統(tǒng)調(diào)用:

#include <sys/resource.h>//系統(tǒng)調(diào)用接口// 設(shè)置進(jìn)程占用資源大小的接口
static void SetProcLimit(int _cpu_limit, int _mem_limit) {// 設(shè)置cpu時(shí)長struct rlimit cpu_limit;//調(diào)用setrlimit所需的結(jié)構(gòu)體cpu_limit.rlim_max = RLIM_INFINITY;//硬約束——無窮(INFINITY)cpu_limit.rlim_cur = _cpu_limit;   //軟約束——當(dāng)前cpu能跑的時(shí)長setrlimit(RLIMIT_CPU, &cpu_limit);//系統(tǒng)調(diào)用接口// 設(shè)置內(nèi)存大小struct rlimit mem_limit;//調(diào)用setrlimit所需的結(jié)構(gòu)體mem_limit.rlim_max = RLIM_INFINITY;//硬約束——無窮(INFINITY)mem_limit.rlim_cur = _mem_limit * 1024; //(單位是字節(jié)) 轉(zhuǎn)化為KB  //軟約束——當(dāng)前內(nèi)存最大上限setrlimit(RLIMIT_AS, &mem_limit);//系統(tǒng)調(diào)用接口
}

3. 編譯并運(yùn)行服務(wù)(compile_run模塊)

編譯模塊和運(yùn)行模塊有了之后,我們將其整合到一起(編譯并運(yùn)行服務(wù))

  1. 在編譯模塊中,我們是根據(jù)用戶傳過來的文件名,先形成三個(gè)臨時(shí)文件(1234.cpp、1234.exe、1234.compiler_error)然后對(duì)1234.cpp進(jìn)行編譯,形成1234.exe。
  2. 在運(yùn)行模塊中,我們是對(duì)1234.exe進(jìn)行運(yùn)行,形成三個(gè)臨時(shí)文件(1234.stdin、1234.stdout、1234.stderr)
  3. 在編譯并運(yùn)行的模塊中,才是真正的接收用戶傳過來的數(shù)據(jù)信息,通過編譯和運(yùn)行模塊的分別處理,完成用戶的請(qǐng)求編譯運(yùn)行工作,這些數(shù)據(jù)信息是通過網(wǎng)絡(luò)傳輸過來的,我們知道通過網(wǎng)絡(luò)接收用戶傳過來json串的(其原因可以看上一篇項(xiàng)目博客中的介紹),其中json串中應(yīng)該包含如下:
in_json:
{code: “#include <iostream> ....int main(){...}”,input: "用戶的輸入(像牛客哪些)",cpu_limit: "1024",mem_limit: "30"
}

我們提供一個(gè)start函數(shù),用于解析這個(gè)in_json串,將數(shù)據(jù)解析出來;

然后將提取出來的代碼寫入到特定的文件中,但是存在多個(gè)用戶提交代碼,我們就需要保證每個(gè)文件的唯一性。

如何保證每個(gè)文件的唯一性呢?我們采用毫秒級(jí)時(shí)間戳+原子遞增的唯一值,來實(shí)現(xiàn):

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <atomic>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/time.h>namespace ns_util {class TimeUtil {public:/*struct timeval{_time.tv_sec;//秒_time.tv_usec;//微秒};*/// 獲取秒級(jí)別時(shí)間戳(這是為日志模塊提供的函數(shù))static std::string GetTimeStamp() {struct timeval _time;gettimeofday(&_time, nullptr);return std::to_string(_time.tv_sec);}// 獲取毫秒級(jí)別時(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;}/// 編譯時(shí)需要的臨時(shí)文件// 構(gòu)建源文件路徑 + 后綴的完整文件名// 1234 -> ./temp/1234.cppstatic 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_error");}/// 運(yùn)行時(shí)需要的臨時(shí)文件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");}// 構(gòu)建該程序?qū)?yīng)的標(biāo)準(zhǔn)錯(cuò)誤完整路徑 + 后綴名static std::string Stderr(const std::string &file_name) {return AddSuffix(file_name, ".stderr");}};class FileUtil {public://判斷文件是否存在static bool IsFileExists(const std::string &path_name) {// 獲取文件屬性的函數(shù)stat,成功返回0struct stat st;if (stat(path_name.c_str(), &st) == 0) {// 獲取屬性成功,文件已經(jīng)存在return true;}return false;}//形成唯一的臨時(shí)文件static std::string UniqFileName() {static std::atomic_uint id(0);id++;// 毫秒級(jí)時(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) {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:不保存分隔符,但有些時(shí)候需要保留\n// getline:內(nèi)部重載了強(qiáng)制類型轉(zhuǎn)換while (std::getline(in, line)) {(*content) += line;(*content) += (keep ? "\n" : "");}in.close();return true;}};}

????????我們可以獲取到唯一的文件后,我們將獲取到的in_json串進(jìn)行解析,?提供路徑拼接函數(shù),形成唯一的源文件,將in_json中的代碼,寫入到文件中(它保存在我們的temp目錄下),然后進(jìn)行編譯工作,編譯是通過創(chuàng)建子進(jìn)程執(zhí)行函數(shù)替換,其中所需的源文件和可執(zhí)行程序文件都可以通過路徑拼接來完成,最終線程可執(zhí)行程序;緊接著就是去調(diào)用runner模塊進(jìn)行程序的運(yùn)行,也是通過路徑拼接的方式找到文件,它的返回值是int(大于0:程序異常,退出時(shí)收到了信號(hào),返回值就是對(duì)應(yīng)的信號(hào);小于0:內(nèi)部錯(cuò)誤,子進(jìn)程創(chuàng)建失敗;等于0:正常運(yùn)行完畢,結(jié)果保存到對(duì)應(yīng)的臨時(shí)文件中)。我們可以通過這個(gè)返回值來進(jìn)行判斷程序運(yùn)行的結(jié)果,并自行設(shè)置狀態(tài)碼,將狀態(tài)碼對(duì)應(yīng)到不同的信息,我們可以通過實(shí)現(xiàn)一個(gè)CodeToDesc函數(shù)。

? ? ? ? 當(dāng)然,在temp目錄下會(huì)不斷的形成臨時(shí)文件,我們還需要做個(gè)處理,就是清理工作。

#pragma once#include "compiler.hpp"
#include "runner.hpp"
#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <signal.h>
#include <jsoncpp/json/json.h>namespace ns_compile_and_run {using namespace ns_log;using namespace ns_util;using namespace ns_runner;using namespace ns_compiler;class CompileAndRun {public:// code > 0:進(jìn)程收到了信號(hào)導(dǎo)致異常崩潰// code < 0:整個(gè)過程非運(yùn)行報(bào)錯(cuò)(代碼為空,編譯報(bào)錯(cuò)等)// code = 0:整個(gè)過程全部完成// 將錯(cuò)誤代碼轉(zhuǎn)為描述(CodeToDesc())static std::string CodeToDesc(int code, const std::string &file_name) {std::string desc;switch (code) {case 0:desc = "編譯運(yùn)行成功";break;case -1:desc = "提交的代碼為空";break;case -2:desc = "未知錯(cuò)誤";break;case -3:FileUtil::ReadFile(PathUtil::CompilerError(file_name), &desc, true);break;case SIGABRT: //6號(hào)信號(hào)desc = "內(nèi)存超過了范圍";break;case SIGXCPU: //24號(hào)信號(hào)desc = "CPU使用超時(shí)";break;case SIGFPE: //8號(hào)信號(hào)desc = "浮點(diǎn)數(shù)溢出";break;default :desc = "未知" + std::to_string(code);break;}return desc;}//清理temp目錄下的臨時(shí)文件static void RemoveTempFile(const std::string &file_name) {/*清理文件的個(gè)數(shù)是不確定的,但是有哪些我們是知道的*///unlink函數(shù):是Linux下刪除特定文件的一個(gè)函數(shù),參數(shù)是字符串形式//清理源文件std::string _src = PathUtil::Src(file_name);if (FileUtil::IsFileExists(_src)) unlink(_src.c_str());//清理編譯出錯(cuò)文件std::string _compiler_error = PathUtil::CompilerError(file_name);if (FileUtil::IsFileExists(_compiler_error)) unlink(_compiler_error.c_str());//清理可執(zhí)行程序文件std::string _execute = PathUtil::Exe(file_name);if (FileUtil::IsFileExists(_execute)) unlink(_execute.c_str());//清理標(biāo)準(zhǔn)輸入文件std::string _stdin = PathUtil::Stdin(file_name);if (FileUtil::IsFileExists(_stdin)) unlink(_stdin.c_str());//清理標(biāo)準(zhǔn)輸出文件std::string _stdout = PathUtil::Stdout(file_name);if (FileUtil::IsFileExists(_stdout)) unlink(_stdout.c_str());//清理標(biāo)準(zhǔn)錯(cuò)誤文件std::string _stderr = PathUtil::Stderr(file_name);if (FileUtil::IsFileExists(_stderr)) unlink(_stderr.c_str());}/** 輸入:*      code:用戶提交的代碼*      input:用戶給自己提交代碼對(duì)應(yīng)的輸入,不做處理*      cpu_limit:時(shí)間要求*      mem_limit:空間要求** 輸出:* 必填字段:*      status:狀態(tài)碼*      reason:請(qǐng)求結(jié)果* 選填字段:*      stdout:我的程序運(yùn)行完的結(jié)果*      stderr:我的程序運(yùn)行完的錯(cuò)誤結(jié)果* *//** start函數(shù)功能:*      通過網(wǎng)絡(luò)接收用戶傳過來的json串(in_json),其中in_json包含如下:*          in_json:*          {*              code: “#include <iostream> ....int main(){...}”,*              input: "用戶的輸入(像??湍男?#xff09;",*              cpu_limit: "1024",*              mem_limit: "30"*          }*       我們start函數(shù)就需要去解析這個(gè)in_json串,將數(shù)據(jù)取出來;*       然后將提取出來的代碼寫入到特定的文件中,因?yàn)榇嬖诙鄠€(gè)用戶提交代碼,我們就需要保證每個(gè)文件的唯一性;** */static void Start(const std::string &in_json, std::string *out_json) {Json::Value in_value;Json::Reader reader;reader.parse(in_json, in_value);//in_json——你要解析是誰;in_value——解析的結(jié)果放到哪里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(); int status_code = 0;//狀態(tài)碼int run_result = 0;// 在goto之間定義的變量是不允許的,我們提前定義std::string file_name;// 需要內(nèi)部形成的唯一文件名(為后續(xù)編譯和運(yùn)行提供好文件名)Json::Value out_value;if (code.size() == 0) {status_code = -1;// 表示代碼為空goto END;}// 形成的文件名只具有唯一性,沒有目錄沒有后綴// 毫秒級(jí)時(shí)間戳+原子性遞增的唯一值:來保證唯一性file_name = FileUtil::UniqFileName();if (!FileUtil::WriteFile(PathUtil::Src(file_name), code)) // 形成臨時(shí)源文件{status_code = -2; // 表示未知錯(cuò)誤goto END;}if (!Compiler::Compile(file_name)) { //編譯文件// 編譯失敗status_code = -3; // 表示代碼編譯時(shí)發(fā)生了錯(cuò)誤goto END;}run_result = Runner::Run(file_name, cpu_limit, mem_limit);//運(yùn)行可執(zhí)行程序文件if (run_result < 0) {status_code = -2; // 表示未知錯(cuò)誤goto END;} else if (run_result > 0) {// 程序運(yùn)行崩潰了(源于某種信號(hào))status_code = run_result;} else {status_code = 0;// 表示運(yùn)行成功}END:out_value["status"] = status_code;out_value["reason"] = CodeToDesc(status_code, file_name);if (status_code == 0) {// 整個(gè)過程全部成功std::string _stdout;FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout, true);out_value["stdout"] = _stdout;std::string _stderr;FileUtil::ReadFile(PathUtil::Stderr(file_name), &_stderr, true);out_value["stderr"] = _stderr;}Json::StyledWriter writer;*out_json = writer.write(out_value);RemoveTempFile(file_name);}};
}

4. 打包成網(wǎng)絡(luò)服務(wù)(compile_server模塊)

?

#include "compile_run.hpp"
#include "../comm/httplib.h"
using namespace ns_compile_and_run;
using namespace httplib;
// 編譯服務(wù)隨時(shí)可能被多個(gè)人請(qǐng)求,必須保證傳遞上來的code,形成源文件名稱的時(shí)候,要具有
// 唯一性,要不然多個(gè)用戶之間會(huì)相互影響void Usage(std::string proc){std::cerr << "Usage: " << "\n\t" << proc << std::endl;
}// ./compile_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){// 用戶請(qǐng)求的服務(wù)正文是我們想要的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;charset=utf-8");}});svr.listen("0.0.0.0", atoi(argv[1])); // 啟動(dòng)http服務(wù)/** 這里是測(cè)試代碼in_json:{"code": "#include...", "input":" ", "cpu_limit":1, "mem_limit":10240}out_json:{"status":"0", "reason":"", "stdout":"", "stderr":""}通過http 讓client 給我們上傳一個(gè)json string下面的工作,充當(dāng)客戶端請(qǐng)求的json串std::string in_json;Json::Value in_value;in_value["code"] = R"(#include <iostream>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;std::string out_json; // 這個(gè)是將來給客戶返回的json串CompileAndRun::Start(in_json, &out_json);std::cout << out_json << std::endl;*/return 0;
}

?

五、基于MVC結(jié)構(gòu)的oj_server模塊設(shè)計(jì)

1. 什么是MVC結(jié)構(gòu)

????????經(jīng)典MVC模式中,M是指業(yè)務(wù)模型,V是指用戶界面(視圖),C則是控制器,使用MVC的目的是將M和V的實(shí)現(xiàn)代碼分離,從而使同一個(gè)程序可以使用不同的表現(xiàn)形式。其中,View的定義比較清晰,就是用戶界面。?

  • M:model表示的是模型,代表業(yè)務(wù)規(guī)則。在MVC的三個(gè)部件中,模型擁有最多的處理任務(wù)。被模型返回的數(shù)據(jù)時(shí)中立的,模型與數(shù)據(jù)格式無關(guān),這樣一個(gè)模型就能夠?yàn)槎鄠€(gè)視圖提供數(shù)據(jù),由于應(yīng)用于模型的代碼只需要寫一次就可以被多個(gè)視圖重用,所以減少了代碼的重復(fù)性,
  • V:view表示的視圖,代表用戶看到并與之交互的界面。在視圖中沒有真正的處理發(fā)生,它只是作為一種輸出數(shù)據(jù)并允許用戶操作的方式。
  • C:controller表示的是控制器,控制器接收用戶的輸入并調(diào)用模型(M)和視圖(V)去完成用戶需求??刂破鞅旧聿惠敵鋈魏螙|西和任何處理。它只接收請(qǐng)求并決定調(diào)用那個(gè)模型構(gòu)建去處理請(qǐng)求,然后再確定用那個(gè)視圖來顯示返回的數(shù)據(jù)。

2. oj_model模塊

oj_model模塊主要是和數(shù)據(jù)交互的,這里的數(shù)據(jù)就是我們后端文件或者數(shù)據(jù)庫當(dāng)中的題目信息,題目應(yīng)該包含如下的信息:

  1. 題目的編號(hào)(1)
  2. 題目的標(biāo)題(求最大值)
  3. 題目的難度(簡(jiǎn)單、中等、困難)
  4. 題目的時(shí)間要求(1s)
  5. 題目的空間要求(30000KB)
  6. 題目的描述(給定一個(gè)數(shù)組,求最大值)
  7. 題目預(yù)設(shè)給用戶在線編輯的代碼(#include<iostream>...)
  8. 題目的測(cè)試用例

????????到這里我們劇需要有自己對(duì)應(yīng)的題庫啦,我們這個(gè)模塊當(dāng)中新增一個(gè)目錄questions,用來存放我們的題庫,這個(gè)questions目錄下包含題目列表(文件形式)和每個(gè)題目的文件夾(其中又包含題目的描述、題目預(yù)設(shè)給用戶在線編輯的代碼header和題目的測(cè)試用例tail)

?

#pragma once#include "../comm/log.hpp"
#include "../comm/util.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <fstream>
#include <cstdlib>
#include <unordered_map>
// 根據(jù)題目list文件,加載所有的題目信息到內(nèi)存中
// model:主要用來和數(shù)據(jù)進(jìn)行交互,對(duì)外提供訪問數(shù)據(jù)接口namespace ns_model {using namespace std;using namespace ns_log;using namespace ns_util;//題目的相關(guān)信息節(jié)點(diǎn)struct Question {string number; // 題目的編號(hào)string title;  // 題目的標(biāo)題string star;   // 難度:簡(jiǎn)單/中等/困難int cpu_limit; // 題目的時(shí)間要求int mem_limit; // 題目的空間要求string desc;   // 題目的描述string header; // 題目預(yù)設(shè)給用戶在線編輯的代碼string tail;   // 題目的測(cè)試用例,需要和header拼接,形成完整代碼};const string questions_list = "./questions/questions.list"; //題目列表的路徑const string question_path = "./questions/"; //題庫路徑class Model {private:// kv ---> k:題號(hào) v:題目細(xì)節(jié)unordered_map<string, Question> questions;public:Model() {assert(LoadQuestionList(questions_list));}// 加載配置文件:questions/questions.list + 題目編號(hào)文件bool LoadQuestionList(const string &question_list) {ifstream in(question_list);//打開配置文件if (!in.is_open()) {LOG(FATAL) << "加載題庫失敗,請(qǐng)檢查是否存在題庫文件" << "\n";return false;}string line;//按行讀取題目列表的路徑中的內(nèi)容while (getline(in, line)) {//題目列表的路徑的內(nèi)容如:// 1 判斷回文數(shù) 簡(jiǎn)單 1 30000// 2 找出最大值 簡(jiǎn)單 1 30000vector<string> tokens;//保存切分的子串:如:vector{1,判斷回文數(shù),簡(jiǎn)單,1,30000}StringUtil::SplitString(line, &tokens, " ");//按空格進(jìn)行切分if (tokens.size() != 5) {LOG(WARNING) << "加載部分題目失敗,請(qǐng)檢查文件格式" << "\n";continue;}//填充Question:Question q;q.number = tokens[0]; //填寫題目編號(hào):1q.title = tokens[1];  //填寫題目標(biāo)題:判斷回文數(shù)q.star = tokens[2];   //填寫題目難度:簡(jiǎn)單q.cpu_limit = atoi(tokens[3].c_str()); //填寫cpu限制:1q.mem_limit = atoi(tokens[4].c_str()); //填寫內(nèi)存限制:30000//拼接題目路徑//題庫路徑在 questions/ 如:// questions///      1///      2/string _path = question_path; //_path:題目路徑_path += q.number;_path += "/";FileUtil::ReadFile(_path + "desc.txt", &(q.desc), true);    //讀取:題目的描述路徑(true表示需要換行)FileUtil::ReadFile(_path + "header.cpp", &(q.header), true);//讀取:題目預(yù)設(shè)給用戶在線編輯的代碼的路徑FileUtil::ReadFile(_path + "tail.cpp", &(q.tail), true);    //讀取:題目的測(cè)試用例路徑questions.insert({q.number, q});//插入到unordered_map中}LOG(INFO) << "加載題庫......成功" << "\n";in.close();return true;}//獲取所有題目bool GetAllQuestions(vector<Question> *out) {//如果沒有題目if (questions.size() == 0) {LOG(ERROR) << "用戶獲取題庫失敗" << "\n";return false;}//遍歷questions(題目和題目細(xì)節(jié)的映射)放到vector中for (const auto &q: questions) {out->push_back(q.second);}return true;}//獲取一道題目bool GetOneQuestion(const string &number, Question *q) {const auto &iter = questions.find(number);//去映射表中查找對(duì)應(yīng)的題目if (iter == questions.end()) {LOG(ERROR) << "用戶獲取題目失敗,題目編號(hào):" << number << "\n";return false;}(*q) = iter->second;return true;}~Model() {}};
}

3. oj_view模塊

oj_view模塊是將model中的數(shù)據(jù)進(jìn)行渲染構(gòu)建出網(wǎng)頁,所以我們需要引入一個(gè)第三方庫ctemplate;功能如下:

#pragma once#include <iostream>
#include <string>
#include <ctemplate/template.h>
#include "oj_model.hpp"namespace ns_view {using namespace ns_model;const std::string template_path = "./template_html/";class View {public:View() {}~View() {}public://所有題目的網(wǎng)頁void AllExpandHtmml(const std::vector<Question> &questions, std::string *html) {// 題目的編號(hào) 題目的標(biāo)題 題目的難度// 推薦使用表格顯示//1.形成路徑std::string src_html = template_path + "all_questions.html";//被渲染的網(wǎng)頁//2.形成數(shù)據(jù)字典ctemplate::TemplateDictionary root("all_questions.html");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)頁ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);//4.開始完成渲染功能tpl->Expand(html, &root);}//一道題目的網(wǎng)頁void OneExpandHtmml(const Question &q, std::string *html) {//1.形成路徑std::string src_html = template_path + "one_question.html";//被渲染的網(wǎng)頁//2.形成數(shù)據(jù)字典ctemplate::TemplateDictionary root("one_question.html");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);//3.獲取被渲染的網(wǎng)頁ctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);//4.開始完成渲染功能tpl->Expand(html, &root);}};
}

4. oj_control模塊

?oj_control模塊主要是控制,通過獲取用戶的輸入調(diào)用不同的模型構(gòu)建view。但是我們還需要完成一個(gè)負(fù)載均衡的概念,因?yàn)樵诤蠖诉M(jìn)行編譯服務(wù)的時(shí)候,如果只提供一臺(tái)主機(jī),當(dāng)用戶請(qǐng)求比較多或主機(jī)掛了,則會(huì)影響用戶體驗(yàn)。

#pragma once#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <cassert>
#include <fstream>
#include <algorithm>
#include <jsoncpp/json/json.h>#include "../comm/httplib.h"
#include "../comm/util.hpp"
#include "../comm/log.hpp"
#include "oj_model.hpp"
#include "oj_view.hpp"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;//提供服務(wù)的主機(jī)class Machine {public:std::string ip; //編譯服務(wù)的ipint port;       //編譯服務(wù)的portuint64_t load;  //編譯服務(wù)的負(fù)載數(shù)量std::mutex *mtx;//C++中mutex是禁止拷貝的,所有使用指針來完成public:Machine() : ip(""), port(0), load(0), mtx(nullptr) {}~Machine() {}public://遞增負(fù)載void IncLoad() {if (mtx) mtx->lock();++load;if (mtx) mtx->unlock();}//遞減負(fù)載void DecLoad() {if (mtx) mtx->lock();--load;if (mtx) mtx->unlock();}// 清除負(fù)載void ResetLoad(){if (mtx) mtx->lock();load = 0;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 LoadBlance {private:// 可以提供給我們服務(wù)編譯的所有主機(jī),每一臺(tái)主機(jī)都有自己的下標(biāo),充當(dāng)當(dāng)前主機(jī)的idstd::vector<Machine> machines;// 所有在線主機(jī)idstd::vector<int> online;// 所有離線主機(jī)idstd::vector<int> offline;//保證LoadBlance它的數(shù)據(jù)安全std::mutex mtx;public:LoadBlance() {assert(LoadConf(service_machine));LOG(INFO) << "加載 " << service_machine << " 成功" << "\n";}~LoadBlance() {}public:// 加載主機(jī)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<std::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;}// 智能選擇bool SmartChoice(int *id, Machine **m) {//1.使用選擇好的主機(jī)(更新該主機(jī)的負(fù)載)//2.我們需要可能離線該主機(jī)mtx.lock();//負(fù)載均衡的算法://1.隨機(jī)數(shù)算法;2.輪詢+隨機(jī)算法int online_num = online.size();if (online_num == 0) {mtx.unlock();LOG(WARNING) << " 所有的后端編譯主機(jī)已經(jīng)全部離線,請(qǐng)運(yùn)維的同事盡快查看" << "\n";return false;}//通過遍歷的方式,找到所有負(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;}// 離線主機(jī)void OfflineMachine(int which) {mtx.lock();for (auto iter = online.begin(); iter != online.end(); iter++) {if (*iter == which) {machines[which].ResetLoad();//負(fù)載清0//要離線的主機(jī)找到了online.erase(iter);offline.push_back(which);break;//因?yàn)閎reak的存在,所以我們暫時(shí)不考慮迭代器失效的問題}}mtx.unlock();}// 在線主機(jī)void OnlineMachine() {//當(dāng)所有主機(jī)都離線的時(shí)候,我們統(tǒng)一上線mtx.lock();online.insert(online.end(), offline.begin(), offline.end());offline.erase(offline.begin(), offline.end());mtx.unlock();LOG(INFO) << "所有的離線主機(jī)已上線......" << "\n";}//for testvoid ShowMachine() {mtx.lock();std::cout << "當(dāng)前在線主機(jī)列表:";for (auto id: online) {std::cout << id << " ";}std::cout << std::endl;std::cout << "當(dāng)前離線主機(jī)列表:";for (auto id: offline) {std::cout << id << " ";}std::cout << std::endl;mtx.unlock();}};//這是我么核心業(yè)務(wù)邏輯的控制器class Control {private:Model model_;            //model_:主要用來和數(shù)據(jù)進(jìn)行交互,對(duì)外提供訪問數(shù)據(jù)接口View view_;              //提供html渲染功能LoadBlance load_blance_; //核心負(fù)載均衡器public:Control() {}~Control() {}public:// 恢復(fù)離線主機(jī)上線void RecoveryMachine(){load_blance_.OnlineMachine();}// 根據(jù)全部題目數(shù)據(jù)構(gòu)建網(wǎng)頁bool AllQuestions(string *html) {bool ret = true;vector<Question> all;if (model_.GetAllQuestions(&all)) {// 1. 先對(duì)題號(hào)進(jìn)行排序sort(all.begin(), all.end(), [](const Question &q1, const Question &q2){return atoi(q1.number.c_str()) < atoi(q2.number.c_str());//升序排序});// 2. 獲取題目信息成功,將所有的題目數(shù)據(jù)構(gòu)建成網(wǎng)頁view_.AllExpandHtmml(all, html);} else {*html = "獲取題目失敗, 形成題目列表失敗";ret = false;}return ret;}// 根據(jù)一道題目數(shù)據(jù)構(gòu)建網(wǎng)頁bool OneQuestion(const string &number, string *html) {bool ret = true;Question q;if (model_.GetOneQuestion(number, &q)) {// 獲取指定題目信息成功,將指定題目數(shù)據(jù)構(gòu)建成網(wǎng)頁view_.OneExpandHtmml(q, html);} else {*html = "指定題目:" + number + " 不存在!";ret = false;}return ret;}//in_json包含://code:#include...//input:“”void Judge(const std::string &number, const std::string in_json, std::string *out_json) {//LOG(DEBUG) << in_json << "\nnumnber:" << number << "\n";//1.根據(jù)題目編號(hào)拿到題目細(xì)節(jié)Question q;model_.GetOneQuestion(number, &q);//2.對(duì)in_json進(jìn)行反序列化,得到題目id和用戶提交的源代碼Json::Reader reader;Json::Value in_value;reader.parse(in_json, in_value);std::string code = in_value["code"].asString();//3.重新拼接用戶代碼+測(cè)試用例代碼,形成新的代碼Json::Value compile_value;compile_value["input"] = in_value["input"].asString();compile_value["code"] = code + "\n" + q.tail;//這里加\n是為了展示的代碼和測(cè)試用例代碼,在編譯的時(shí)候,不發(fā)生錯(cuò)誤(能夠去掉那個(gè)#ifdefine)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);//4.選擇負(fù)載最低的主機(jī)//規(guī)則:一直選擇,直到主機(jī)可用,否則,就是全部掛掉while (true) {int id = 0;Machine *m = nullptr;if (!load_blance_.SmartChoice(&id, &m)) {break;}//5.然后發(fā)起http請(qǐng)求,得到結(jié)果Client cli(m->ip, m->port);m->IncLoad();LOG(INFO) << " 選擇主機(jī)成功,主機(jī)id:" << id << " 詳情:" << 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")) {//6.將結(jié)果賦值給out_jsonif (res->status == 200) {*out_json = res->body;m->DecLoad();LOG(INFO) << " 請(qǐng)求編譯和運(yùn)行服務(wù)成功......" << "\n";break;}m->DecLoad();} else {//請(qǐng)求失敗LOG(ERROR) << " 選擇當(dāng)前請(qǐng)求的主機(jī)的id:" << id << " 詳情:" << m->ip << ":" << m->port << " 可能已經(jīng)離線"<< "\n";load_blance_.OfflineMachine(id);load_blance_.ShowMachine();//僅僅是為了調(diào)式}}}};
}

5. 打包成網(wǎng)絡(luò)服務(wù)(oj_server)

#include <iostream>
#include <signal.h>
#include "../comm/httplib.h"
#include "oj_control.hpp"using namespace httplib;
using namespace ns_control;static Control *ctrl_ptr = nullptr;
void Recovery(int signo){ctrl_ptr->RecoveryMachine();
}
int main()
{signal(SIGQUIT, Recovery);// 用戶請(qǐng)求的服務(wù)路由功能Server svr;Control ctrl;ctrl_ptr = &ctrl;// 獲取所有的題目列表svr.Get("/all_questions", [&ctrl](const Request& req, Response& resp){// 返回一張包含所有題目的html網(wǎng)頁std::string html;ctrl.AllQuestions(&html);// 用戶看到的是什么?網(wǎng)頁數(shù)據(jù)+拼上了題目相關(guān)的數(shù)據(jù)resp.set_content(html, "text/html; charset=utf-8");});// 用戶要根據(jù)題目編號(hào),獲取題目?jī)?nèi)容// /question/100 -> 正則匹配// R"()" 原始字符串raw string,保持字符串內(nèi)容的原貌,不用做相關(guān)的轉(zhuǎn)義svr.Get(R"(/question/(\d+))", [&ctrl](const Request& req, Response& resp){std::string number = req.matches[1];//獲取題目編號(hào)std::string html;ctrl.OneQuestion(number, &html);resp.set_content(html, "text/html; charset=utf-8");});// 用戶提交代碼,使用我們的判題功能(1.每道題的測(cè)試用例 2.執(zhí)行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;
}

六、前端頁面的設(shè)計(jì)

1. indx.html

當(dāng)用戶訪問根目錄時(shí)顯示的網(wǎng)頁

<!DOCTYPE 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>這是我的個(gè)人oj系統(tǒng)</title><style>/*起手式:100%保證我們的樣式設(shè)置可以不受默認(rèn)影響*/* {margin: 0px;/*消除網(wǎng)頁的默認(rèn)外邊距*/padding: 0px;/*消除網(wǎng)頁的默認(rèn)內(nèi)邊距*/}html,body {width: 100%;height: 100%;}.container .navbar{width: 100%;height: 50px;background-color:black;/* 給父級(jí)標(biāo)簽overflow,取消后續(xù)float帶來的影響 */overflow: hidden;}.container .navbar a{/* 設(shè)置a標(biāo)簽是行內(nèi)塊元素,允許你設(shè)置寬度*/display: inline-block;/* 設(shè)置a標(biāo)簽的寬度,默認(rèn)a標(biāo)簽是行內(nèi)元素,無法設(shè)置寬度*/width: 80px;/* 設(shè)置字體的顏色 */color: white;/* 設(shè)置字體的大小 */font-size: large;/* 設(shè)置文字的高度和導(dǎo)航欄一樣的高度 */line-height: 50px;/* 去掉a標(biāo)簽的下劃線 */text-decoration: none;/* 設(shè)置a標(biāo)簽的文字居中 */text-align: center;}/* 設(shè)置鼠標(biāo)事件 */.container .navbar a:hover{background-color:green;}/* 設(shè)置浮動(dòng) */.container .navbar .login{float:right;}.container .content {/* 設(shè)置標(biāo)簽的寬度 */width: 800px;/* background-color: #ccc; *//* 整體居中 */margin: 0px auto;/* 設(shè)置文字居中 */text-align: center;/* 設(shè)置上外邊距 */margin-top: 200px;}.container .content .front_ {/* 設(shè)置標(biāo)簽為塊級(jí)元素,獨(dú)占一行,可以設(shè)置高度寬度等屬性 */display: block;/* 設(shè)置每個(gè)文字的上外邊距 */margin-top: 20px;/* 去掉a標(biāo)簽的下劃線 */text-decoration: none;}</style>
</head><!-- <body background="C:\Users\MLG\Desktop\壁紙.jpg"> --><body background="./壁紙.jpg">
<div class="container"><!--導(dǎo)航欄--><div class="navbar"><a href="/">首頁</a><a href="/all_questions">題庫</a><a href="#">競(jìng)賽</a><a href="#">討論</a><a href="#">求職</a><a class="login" href="#">登錄</a></div><!--網(wǎng)頁的內(nèi)容--><div class="content"><h1 class="front_">歡迎來到我的Online_Judge平臺(tái)</h1><a class="front_" href="/all_questions">點(diǎn)擊我開始編程啦!</a></div>
</div>
</body></html>

?

2. all_questions.html

當(dāng)用戶獲取題目列表的時(shí)候顯示的網(wǎng)頁?

?

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>在線OJ-題目列表</title><style>/*起手式:100%保證我們的樣式設(shè)置可以不受默認(rèn)影響*/* {margin: 0px;/*消除網(wǎng)頁的默認(rèn)外邊距*/padding: 0px;/*消除網(wǎng)頁的默認(rèn)內(nèi)邊距*/}html,body {width: 100%;height: 100%;}.container .navbar {width: 100%;height: 50px;background-color: black;/* 給父級(jí)標(biāo)簽overflow,取消后續(xù)float帶來的影響 */overflow: hidden;}.container .navbar a {/* 設(shè)置a標(biāo)簽是行內(nèi)塊元素,允許你設(shè)置寬度*/display: inline-block;/* 設(shè)置a標(biāo)簽的寬度,默認(rèn)a標(biāo)簽是行內(nèi)元素,無法設(shè)置寬度*/width: 80px;/* 設(shè)置字體的顏色 */color: white;/* 設(shè)置字體的大小 */font-size: large;/* 設(shè)置文字的高度和導(dǎo)航欄一樣的高度 */line-height: 50px;/* 去掉a標(biāo)簽的下劃線 */text-decoration: none;/* 設(shè)置a標(biāo)簽的文字居中 */text-align: center;}/* 設(shè)置鼠標(biāo)事件 */.container .navbar a:hover {background-color: green;}.container .navbar .login{float: right;}.container .question_list {padding-top: 50px;width: 800px;height: 600px;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: #c6cbcc;}.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;}</style>
</head><body>
<div class="container"><div class="navbar"><!--導(dǎo)航欄--><div class="navbar"><a href="/">首頁</a><a href="/all_questions">題庫</a><a href="#">競(jìng)賽</a><a href="#">討論</a><a href="#">求職</a><a class="login" href="#">登錄</a></div></div><div class="question_list"><h1>Online_Judge題目列表</h1><table><tr><th class="item">編號(hào)</th><th class="item">標(biāo)題</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></body></html>

3. one_question.html

當(dāng)用戶獲取單道題目所顯示的網(wǎng)頁

?

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport"content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>{{number}}.{{title}}</title><!-- 引入ACE CDN --><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;/* 給父級(jí)標(biāo)簽overflow,取消后續(xù)float帶來的影響 */overflow: hidden;}.container .navbar a {/* 設(shè)置a標(biāo)簽是行內(nèi)塊元素,允許你設(shè)置寬度*/display: inline-block;/* 設(shè)置a標(biāo)簽的寬度,默認(rèn)a標(biāo)簽是行內(nèi)元素,無法設(shè)置寬度*/width: 80px;/* 設(shè)置字體的顏色 */color: white;/* 設(shè)置字體的大小 */font-size: large;/* 設(shè)置文字的高度和導(dǎo)航欄一樣的高度 */line-height: 50px;/* 去掉a標(biāo)簽的下劃線 */text-decoration: none;/* 設(shè)置a標(biāo)簽的文字居中 */text-align: center;}/* 設(shè)置鼠標(biāo)事件 */.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;/* 添加滾動(dòng)條*/}.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: 'Franklin Gothic Medium', 'Arial Narrow', Arial, 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: 100px;height: 30px;margin-top: 1px;margin-right: 1px;font-size: large;float: right;background-color: #26bb9c;color: #FFF;border-radius: 1ch;/* 給按鈕帶圓角*/border: 0px;}.container .part2 button:hover {color: green;}.container .part2 .result{margin-top: 15px;margin-left: 15px;}.container .part2 .result pre{font-size: larger;}</style>
</head><body>
<div class="container"><div class="navbar"><a href="/">首頁</a><a href="/all_questions">題庫</a><a href="#">競(jìng)賽</a><a href="#">討論</a><a href="#">求職</a><a class="login" href="#">登錄</a></div><!-- 左右呈現(xiàn),題目描述和預(yù)設(shè)代碼 --><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><!-- 提交結(jié)果并顯示 --><div class="part2"><div class="result"></div><button class="btn-submit" onclick="submit()">提交代碼</button></div>
</div><script>//初始化對(duì)象editor = ace.edit("code");//設(shè)置風(fēng)格和語言(更多風(fēng)格和語言,請(qǐng)到github上相應(yīng)目錄查看)// 主題大全:http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.htmleditor.setTheme("ace/theme/monokai");editor.session.setMode("ace/mode/c_cpp");// 字體大小editor.setFontSize(16);// 設(shè)置默認(rèn)制表符的大小:editor.getSession().setTabSize(4);// 設(shè)置只讀(true時(shí)只讀,用于展示代碼)editor.setReadOnly(false);// 啟用提示菜單ace.require("ace/ext/language_tools");editor.setOptions({enableBasicAutocompletion: true,enableSnippets: true,enableLiveAutocompletion: true});function submit() {// 1. 收集當(dāng)前頁面的有關(guān)數(shù)據(jù):1.題號(hào) 2.代碼我們采用JQuery// console.log("哈哈!");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. 構(gòu)建json,并向后臺(tái)發(fā)起基于http的json請(qǐng)求$.ajax({method: 'Post',    //向后端發(fā)起請(qǐng)求的方式(post、get)url: judge_url,    //向后端指定的url發(fā)起請(qǐng)求dataType: 'json',  //告知server,我們需要什么格式contentType: 'application/json;charset=utf-8', //告知server我給你的是什么格式data: JSON.stringify({'code': code,'input': ''}),success: function (data) {//成功得到結(jié)果//console.log(data);show_result(data);}});// 3. 得到結(jié)果,解析并顯示到result中function show_result(data) {// console.log(data.status);// console.log(data.reason);// 拿到result結(jié)果標(biāo)簽var result_div = $(".container .part2 .result");// 清空上一次的運(yùn)行結(jié)果result_div.empty();// 首先拿到結(jié)果的狀態(tài)碼和原因結(jié)果var _status = data.status;var _reason = data.reason;var reson_lable = $("<p>",{text: _reason});reson_lable.appendTo(result_div);if (status == 0) {// 請(qǐng)求是成功的,編譯運(yùn)行沒出問題,但是結(jié)果是否通過看測(cè)試用例的結(jié)果var _stdout = data.stdout;var _stderr = data.stderr;var reson_lable = $("<p>",{text: _reason});var stdout_lable = $("<pre>",{text: _stdout});var stderr_lable = $("<pre>",{text: _stderr});stdout_lable.appendTo(result_div);stderr_lable.appendTo(result_div);} else {}}}
</script>
</body></html>

七、項(xiàng)目總結(jié)?

  1. 基于注冊(cè)和登陸的錄題功能
  2. 業(yè)務(wù)擴(kuò)展,自己寫一個(gè)論壇,接入到在線OJ中
  3. 即便是編譯服務(wù)在其他機(jī)器上,也其實(shí)是不太安全的,可以將編譯服務(wù)部署在docker
  4. 4. 目前后端compiler的服務(wù)我們使用的是http方式請(qǐng)求(僅僅是因?yàn)楹?jiǎn)單),但是也可以將我們的compiler服務(wù),設(shè)計(jì)成為遠(yuǎn)程過程調(diào)用,推薦:rest_rpc,替換我們的httplib
  5. 功能上更完善一下,判斷一道題目正確之后,自動(dòng)下一道題目
  6. navbar中的功能可以一個(gè)一個(gè)的都實(shí)現(xiàn)一下

http://www.risenshineclean.com/news/36799.html

相關(guān)文章:

  • 門店管理系統(tǒng)有哪些寧波免費(fèi)seo在線優(yōu)化
  • 東莞模板網(wǎng)頁制作惠州seo網(wǎng)站排名
  • 橋梁畢業(yè)設(shè)計(jì)代做網(wǎng)站6個(gè)好用的bt種子搜索引擎
  • 網(wǎng)站建設(shè)的需要分析做網(wǎng)絡(luò)優(yōu)化的公司排名
  • 長沙的企業(yè)網(wǎng)站建設(shè)陜西網(wǎng)站設(shè)計(jì)
  • DW做旅游網(wǎng)站模板正規(guī)拉新推廣平臺(tái)有哪些
  • 網(wǎng)站建設(shè)業(yè)務(wù)文案亞洲長尾關(guān)鍵詞挖掘
  • 湛江論壇建站模板集客營銷軟件官方網(wǎng)站
  • 商業(yè)網(wǎng)站建設(shè)案例課程圖片優(yōu)化是什么意思
  • wordpress網(wǎng)站音樂放不全百度搜索服務(wù)
  • 網(wǎng)站上的洗衣液瓶子做花瓶怎么材質(zhì)互聯(lián)網(wǎng)廣告代理商
  • wordpress云端采集插件廣州新塘網(wǎng)站seo優(yōu)化
  • 商務(wù)網(wǎng)站建設(shè)綜合實(shí)訓(xùn)信陽網(wǎng)絡(luò)推廣公司
  • 網(wǎng)站開發(fā)專業(yè)就業(yè)培訓(xùn)學(xué)校信息流優(yōu)化師沒經(jīng)驗(yàn)可以做嗎
  • 網(wǎng)站圖片有什么要求嗎長春seo排名公司
  • 攀枝花 網(wǎng)站建設(shè)app拉新怎么對(duì)接渠道
  • 最好看免費(fèi)觀看高清大全城中之城上海關(guān)鍵詞優(yōu)化排名哪家好
  • 怎么知道哪家公司網(wǎng)站做的好網(wǎng)站買賣交易平臺(tái)
  • 網(wǎng)絡(luò)組建設(shè)計(jì)與方案網(wǎng)站seo 優(yōu)化
  • jquery 特效 網(wǎng)站網(wǎng)絡(luò)推廣公司是干什么
  • 廣西住房城鄉(xiāng)建設(shè)廳官網(wǎng)站大數(shù)據(jù)培訓(xùn)班出來能就業(yè)嗎
  • 網(wǎng)站頁面數(shù)量開發(fā)網(wǎng)站的公司
  • 做網(wǎng)站圖片怎么弄一個(gè)自己的網(wǎng)站
  • 河北網(wǎng)站開發(fā)費(fèi)用百度電話人工服務(wù)
  • 做門窗接活的網(wǎng)站廣告投放渠道
  • 做我女朋友好不好套路網(wǎng)站seo高級(jí)優(yōu)化技巧
  • 做局域網(wǎng)網(wǎng)站公司網(wǎng)站怎么弄
  • 家居企業(yè)網(wǎng)站建設(shè)如何豬八戒網(wǎng)接單平臺(tái)
  • 廣州 企業(yè)網(wǎng)站建設(shè)百度貼吧怎么做推廣
  • 新興建設(shè)網(wǎng)站頭條指數(shù)