🐶博主主頁:@??. 一懷明月??
???🔥專欄系列:線性代數,C初學者入門訓練,題解C,C的使用文章,「初學」C++,linux
🔥座右銘:“不要等到什么都沒有了,才下定決心去做”
🚀🚀🚀大家覺不錯的話,就懇求大家點點關注,點點小愛心,指點指點🚀🚀🚀
目錄
一、項目介紹
二、項目技術與開發環境?
1.項目技術
2.開發環境?
三、項目的宏觀結構?
1.項?核?是三個模塊
2.項目的框架
3.項目實現流程
四、comm公共模塊設計
1.日志模塊開發
2.UTIL工具模塊開發
1)TimeUtil模塊
2)PathUtil模塊
3)FileUtil模塊
4)StringUtil模塊
3.httplib.h模塊
五、compile_server模塊
1.compile編譯模塊
2.runneryu運行模塊
3.compile_run模塊
4.compile_server.cc
5.temp緩沖區
6.Makefile
六、oj_server模塊
1.oj_model數據交互模塊
2.oj_view視圖模塊
3.oj_control控制模塊
4.oj_server.cc
5.questions文件版題庫
1)1
[1]desc
[2]header.cpp
[3]tail.cpp
2)questions.list
6.wwwroot主頁
7.template_html
1)all_questions.html
2)one_question.html
8.conf
9.Makefile
七、MySQL版本的實現
1.oj_model_m
2.include
3.lib
4.linux下載mysql服務
5.在Ubuntu上設置MySQL可以遠程登錄用戶
6.創建表結構???????
?八、綜合
1.安裝 jsoncpp
2.安裝 cpp-httplib
3.安裝boost庫
4.安裝ctemplate
5.結果展示
一、項目介紹
隨著計算機技術的快速發展和互聯網的普及,在線評測系統(Online Judge,簡稱 OJ 系統)在教育和競賽領域中得到了廣泛應用。OJ 系統是一個用于批量評測編程作業和競賽題目的平臺,通過自動化評測和實時反饋,提供了高效、公正和具有即時性的編程評測服務。
該項目旨在構建一個功能強大的 OJ 系統,為學生、教師和競賽參與者提供一個便捷的在線學習和評測環境。下面將介紹該項目的歷史、項目實例、市場需求和未來發展。
1)項目歷史: 過去幾年中,計算機編程的重要性逐漸被廣大學生和教育機構所認識到。傳統的教學方法已經不再能夠滿足快速發展的計算機科學領域的需求。為了更好地培養學生的編程能力和解決實際問題的能力,開發一個功能全面的 OJ 系統成為了迫切的需求。
2)項目實例:
LeetCode(https://leetcode.com/):LeetCode 是一個非常受歡迎的面向算法和數據結構練習的在線評測平臺。它提供了一系列編程題目,并為用戶提供了在線編碼環境和自動評測功能。
Codeforces(https://codeforces.com/):Codeforces 是一個面向競賽編程的 OJ 系統,其主要目標是提供高質量的編程競賽題目和評測服務。它支持多種編程語言,并為用戶提供實時排名和評測結果等功能。
HackerRank(https://www.hackerrank.com/):HackerRank 是一個面向編程技能測試和面試準備的 OJ 系統。它提供了豐富的編程題目和技術挑戰,并為用戶提供實時評測、學習資源和招聘服務等功能。
UVa Online Judge(https://onlinejudge.org/):UVa Online Judge 是一個老牌的 OJ 系統,擁有大量的編程題目,并為用戶提供了在線編程環境和自動評測功能。它旨在提供一個開放的學習和競賽平臺。
3)市場需求: 在當今的教育和競賽環境中,對于計算機編程的需求越來越高。越來越多的教育機構和競賽組織需要一個可靠的 OJ 系統來支持他們的教學和競賽活動。正因如此,開發一個功能完善且易于使用的 OJ 系統成為了市場的迫切需求。該系統不僅能夠滿足學習者的自主學習需求,還能夠提供教師管理和評估的功能,能夠適應不同層次和不同類型的編程題目。
4)未來發展: 隨著人工智能、大數據、云計算等技術的不斷發展,未來 OJ 系統還可以進一步提升和完善。例如,可以引入自動評測算法的優化和智能化,增加更多的編程題庫和實例,支持多種編程語言的評測,提供個性化學習推薦等功能。此外,該系統還可以擴展到更廣泛的應用領域,如軟件工程測評、面試準備等。與此同時,與其他教育平臺和在線學習資源的集成將是未來 OJ 系統的重要發展方向。通過不斷創新和拓展,該項目將為學生和教師提供更加優質的編程學習和評測體驗,并為教育領域的發展做出積極的貢獻。
二、項目技術與開發環境?
1.項目技術
- C++ STL 標準庫
- Boost 準標準庫(字符串切割)
- ?cpp-httplib 第三?開源?絡庫
- ?ctemplate 第三?開源前端??渲染庫
- ?jsoncpp 第三?開源序列化、反序列化庫
- ?負載均衡設計
- ?多進程、多線程
- ?MySQL ?
- ?Ace前端在線編輯器
- ?html/css/js/jquery/ajax?
2.開發環境?
- Ubuntu 22.04 64位 服務器
- vscode
- Navicat Premium
三、項目的宏觀結構?
1.項?核?是三個模塊
- comm :公共模塊
- compile_server:編譯與運?模塊
- oj_server:獲取題?列表,查看題?編寫題?界?,負載均衡,其他功能
2.項目的框架
圖1
3.項目實現流程
- 第一步:compile_server
- 第二步:oj_server
- 第三步:?件版的在線OJ
- 第四步:?件版的在線OJ
- 第五步:MySQL 版的在線OJ
四、comm公共模塊設計
文件處理、日志、網絡服務
1.日志模塊開發
- 日志等級
- 打印日志的文件名稱
- 報錯行
- 添加日志的時間
- 開放性輸出
開放性輸出就是說我們可以在后面輸出自己想輸出的東西,比如
LOG(INFO)<<"功能正常運行"<<endl;
#pragma once #include <iostream> #include <string> #include "util.hpp"using namespace std;namespace ns_log {using namespace ns_util;//日志等級enum{INFO = 0, //正常運行DEBUG, //調試信息WARNING, //警告信息,但不影響程序運行ERROR, //錯誤信息,程序運行錯誤FATAL //嚴重錯誤,系統崩潰};//我們通過Log函數,其返回值是標準輸出流對象,從而實現開放性日志inline std::ostream &Log(const string &level, const string &file_name, int line){// 添加日志等級string message = "[";message += level;message += "]";// 添加報錯文件名稱message += "[";message += file_name;message += "]";// 添加報錯行號message += "[";message += to_string(line);message += "]";// 添加報錯時間message += "[";message += TimeUtil::GetTimeStamp();//TimeUtil::GetTimeStamp(),將日志轉化為字符串,XXX(秒)message += "]";// cout本質 內部是包含緩沖區cout << message; // 不要endl進行刷新return cout;}// LOG(INFO)<<"messge"<<"\n";// 開放式接口 #define LOG(level) Log(#level, __FILE__, __LINE__)//LOG是函數log的宏替換函數,這樣提供了,兩個默認的參數,__FILE__(自動獲取文件名稱), __LINE__(自動獲取文件行數) }
2.UTIL工具模塊開發
1)TimeUtil模塊
將時間戳轉化為字符串形式
- 秒
- 毫秒
class TimeUtil{public:static string GetTimeStamp(){struct timeval _time;gettimeofday(&_time, nullptr);return to_string(_time.tv_sec);//秒}static string GetTimeMs(){struct timeval _time;gettimeofday(&_time, nullptr);return to_string(_time.tv_sec * 1000 + _time.tv_usec / 1000);//毫秒}};
- to_string:是C++中用于將數字或其他可轉換為字符串的類型轉換為字符串
gettimeofday
是一個用于獲取當前時間和日期的系統調用函數。它以微秒精度返回當前的UTC時間和日期。2)PathUtil模塊
形成一些文件,源文件,可執行文件,編譯錯誤文件。
const string temp_path = "./temp/"; class PathUtil{public://添加后綴static string AddSuffix(const string &file_name, const string &Suffix)//需要提供文件名和文件名后綴{string path_name = temp_path; //./temp/path_name += file_name; //./temp/codepath_name += Suffix; //./temp/code.stderrreturn path_name;}// 編譯時需要有的臨時文件// 構建一個源文件+路徑+后綴的完整文件名// 1234 -> ./temp/1234.cppstatic string Src(const string &file_name){return AddSuffix(file_name, ".cpp");}// 構建一個可執行程序的源文件+路徑+后綴的完整文件名static string Exe(const string &file_name){return AddSuffix(file_name, ".exe");}// 構建一個可執行程序的標準錯誤完整的路徑+后綴名static string Err(const string &file_name){return AddSuffix(file_name, ".stderr");}// 構建一個可執行程序的標準錯誤完整的路徑+后綴名-編譯該源文件時運行的錯誤static string CompilerError(const string &file_name){return AddSuffix(file_name, ".compile_error");}// 運行時所需要的臨時文件//輸入緩沖區文件static string Stdin(const string &file_name){return AddSuffix(file_name, ".stdin");}//輸出緩沖區文件static string Stdout(const string &file_name){return AddSuffix(file_name, ".stdout");}//錯誤緩沖區文件static string Stderr(const string &file_name){return AddSuffix(file_name, ".stderr");}};
3)FileUtil模塊
判斷文件是否存在、生成唯一的文件名、讀寫文件
class FileUtil{public:static bool IsFileExists(const string &path_name){struct stat st;if (stat(path_name.c_str(), &st) == 0){// 獲取屬性成功,文件已經存在return true;}return false;}// 毫秒級時間戳+源子性遞增唯一值:來保證唯一性static string UniqFileName(){static atomic_uint id(0);string ms = TimeUtil::GetTimeMs();string uniq_id = to_string(id++);return ms + "_" + uniq_id;}static bool WriteFile(const string &target, const string &code){ofstream out(target, ios::out);if (!out.is_open()){return false;}out.write(code.c_str(), code.size());out.close();return true;}static bool ReadFile(const string &target, string *content, bool keep = false){(*content).clear();ifstream in(target, ios::in);if (!in.is_open()){return false;}string line;// getline:不保存行分隔符// getline內部重載了強制類型轉化while (getline(in, line)){(*content) += line;(*content) += (keep ? "\n" : "");}in.close();return true;}};
std::atomic_uint
是C++標準庫中的一個原子類型,用于支持并發編程中的原子操作。它提供了原子無鎖訪問的能力,用于保證多個線程之間對數據的操作不會發生競爭條件4)StringUtil模塊
class StringUtil{public:/************************************************************************ str:輸入型,目標要切分的字符串* target:輸出型,保存切分完畢的結果* sep:輸入型,切分符***********************************************************************/static void SplitString(const string &str, vector<string> *target, string sep){// boost庫boost::split(*target, str, boost::is_any_of(sep), boost::token_compress_on);//用于將字符串str按照指定的分隔符sep進行分割,并將分割后的子字符串存儲在target指針所指向的容器中}};
boost::split
是 Boost 庫中的一個函數,用于將一個字符串拆分成多個子串,并將結果存儲在目標容器中split函數的第一個參數是一個迭代器,指向目標容器的起始位置。第二個參數是要分割的字符串str。第三個參數是分隔符sep,可以是一個字符串,也可以是一個字符數組。第四個參數是一個標志,用于指定是否壓縮分割后的子字符串,默認為boost::token_compress_on,表示壓縮分割后的子字符串
3.httplib.h模塊
cpp-httplib第三方網絡庫,我們自己寫網絡套接字來進行通信也是可以的,不過太麻煩了,我們直接使用開源第三方庫cpp-httplib
進行cpp-httplib的安裝后,cpp-httplib是header獨立分開的,只需要將它里面的httplib.h文件拷貝到項目中。如果你想的話,也可以拷貝到系統目錄下/usr/include/,但是不推薦
需要注意的是
cpp-httplib的使用需要使用高版本的gcc/g++
cpp-httplib是阻塞式的多線程http網絡庫,因為里面使用了原生線程庫,所以在編譯的時候,需要帶上選項-lpthread
五、compile_server模塊
1.compile編譯模塊
負責將用戶提交的源代碼轉換為目標代碼的過程。其主要功能包括詞法分析、語法分析(構建語法樹)、語義分析(檢查語義正確性)、代碼優化(提高程序性能)、目標代碼生成(生成目標機器代碼或中間代碼)、符號表管理(記錄變量、函數等信息)以及錯誤處理(收集和報告錯誤信息
#pragma once#include <iostream> #include <string> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <cstdio> #include <fcntl.h>#include "../comm/util.hpp" #include "../comm/log.hpp" // 只進行代碼的編譯namespace ns_compiler {// 引入路徑拼接功能using namespace ns_util;// 引入日志功能using namespace ns_log;class Compiler{public:Compiler() {}~Compiler() {}// 返回值:// true:編譯成功// false:編譯失敗// file_name:編譯的文件名// 如果編譯的文件是1234 我們需要處理成./temp/1234.cpp (temp專門用來存儲生成的臨時文件)// 我們還需要形成 ./temp/1234.exe文件// 我們還需要形成 ./temp/1234.stderr文件static bool Compile(string &file_name){pid_t pid = fork();if (pid < 0){LOG(ERROR) << "內部錯誤,創建子進程失敗" << endl;return false;}else if (pid == 0){umask(0);int _stderr = open(PathUtil::CompilerError(file_name).c_str(), O_WRONLY | O_CREAT, 0644); //./temp/code.stderrif (_stderr < 0){LOG(WARNING) << "沒有成功形成stderr文件" << endl;exit(1);}// 重定向:標準錯誤到_stderr中dup2(_stderr, 2);// 子進程:調用編譯器,完成對代碼的編譯工作// g++ -o target src -std=c++11execlp("g++", "g++", "-o", PathUtil::Exe(file_name).c_str(), PathUtil::Src(file_name).c_str(), "-D", "COMPILER_ONLINE", "-std=c++11", nullptr);LOG(ERROR) << "啟動編譯器g++失敗,可能是參數錯誤" << endl;exit(2);}else{waitpid(pid, nullptr, 0);// 編譯是否成功,就看有沒有形成對應的可執行程序if (FileUtil::IsFileExists(PathUtil::Exe(file_name))){LOG(INFO) << PathUtil::Src(file_name) << "編譯成功" << endl;return true;}}//如果程序(g++)替換失敗,才會輸出下面的日志信息LOG(ERROR) << "編譯失敗,沒有形成可執行程序" << endl;return false; //}private:}; }
2.runneryu運行模塊
編譯成功,我們需要運行可執行文件,其中會成功,也會失敗,成功我們需要向用戶返回運行結果,運行錯誤,我們需要向用戶提供運行錯誤的信息
#pragma once#include <iostream> #include <string>#include <sys/types.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/wait.h> #include <sys/resource.h> #include <sys/time.h>#include "../comm/util.hpp" #include "../comm/log.hpp" using namespace std;namespace ns_runner{using namespace ns_util;using namespace ns_log;class Runner{public:Runner() {}~Runner() {}// 設置進程占用資源大小的接口static void SetProcLimit(int _cpu_limit, int _mem_limit){struct rlimit cpu_limit, mem_limit;cpu_limit.rlim_cur = _cpu_limit;cpu_limit.rlim_max = RLIM_INFINITY;mem_limit.rlim_cur = _mem_limit * 1024; // 設置最小內存資源占用的空間為以kb為單位mem_limit.rlim_max = RLIM_INFINITY;setrlimit(RLIMIT_CPU, &cpu_limit);setrlimit(RLIMIT_AS, &mem_limit);}public:// 指明路徑即可,不需要代碼路徑,不需要帶后綴/************************************************************************ 返回值>0:程序異常了,退出時收到信號,返回值就是信號編號* 返回值<0:程序異常了,fork失敗(內部失敗)* 返回值=0:程序正常運行完畢,結果保存在對應的臨時文件中** cpu_limit:cpu時間限制,單位是秒* mem_limit:內存限制,單位是kb************************************************************************/static int Run(const string &file_name, int cpu_limit, int mem_limit){// 程序運行:// 1.代碼跑完,結果正確// 2.代碼跑完,結果不正確// 3.代碼沒跑完,異常了// Run需要考慮代碼跑完,結果正確與否??不考慮// 結果正確與否,是由我們測試用例決定的!// 我們只考慮:是否正確運行完畢// 我們必須知道可執行程序是誰?// 一個程序在默認啟動的時候// 標準輸入:暫時不處理// 標準輸出:程序運行完成,輸出結果是什么// 標準錯誤:運行時錯誤的信息string _execute = PathUtil::Exe(file_name);string _stdin = PathUtil::Stdin(file_name);string _stdout = PathUtil::Stdout(file_name);string _stderr = PathUtil::Stderr(file_name);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) << "運行時打開標準文件失敗" << endl;return -1; // 代表打開文件失敗}pid_t pid = fork();if (pid < 0){LOG(ERROR) << "創建子進程失敗" << endl;close(_stdin_fd);close(_stdout_fd);close(_stderr_fd);return -2; // 代表fork失敗}else if (pid == 0){// 子進程dup2(_stdin_fd, 0);dup2(_stdout_fd, 1);dup2(_stderr_fd, 2);// 限制資源——SetProcLimit(cpu_limit, mem_limit);// 執行程序execl(_execute.c_str() /*我要執行誰*/, _execute.c_str() /*我想要在命令行上如何執行*/, NULL);exit(1);}else{close(_stdin_fd);close(_stdout_fd);close(_stderr_fd);int status;waitpid(pid, &status, 0);// 程序運行異常,一定是因為收到了信號LOG(INFO) << "運行完畢,info:" << (status & 0x7f) << endl;return status & 0x7f;}}}; }
3.compile_run模塊
compile_run模塊主要完成compile模塊和runner模塊的調度,首先編譯程序,然后運行程序,最后返回運行后的結果。編譯錯誤和運行錯誤都會將錯誤原因返回用戶
#pragma once// 適配用戶請求#include "compiler.hpp" #include "runner.hpp"#include "../comm/util.hpp" #include "../comm/log.hpp"#include <signal.h> #include <unistd.h> #include <jsoncpp/json/json.h>using namespace std; namespace ns_compile_and_run {using namespace ns_compiler;using namespace ns_runner;using namespace ns_util;using namespace ns_log;class CompileAndRun{public:// code>0:進程收到了信號導致導致崩潰// code<0:整個過程非運行出錯(代碼為空,編譯報錯)// code=0:整個過程全部完成static string CodeToDesc(int code, const string &file_name){string desc;switch (code){case 0:desc = "編譯運行成功";break;case -1:desc = "代碼為空";break;case -2:desc = "未知錯誤";break;case -3:FileUtil::ReadFile(PathUtil::CompilerError(file_name), &desc, true);break;case SIGABRT: // 6desc = "內存超限";break;case SIGXCPU: // 24desc = "time out";break;case SIGFPE: // 8desc = "浮點數溢出";break;default:desc = "未知" + to_string(code);break;}return desc;}// 刪除臨時文件static void RemoveTempFile(const string &file_name){// 清理文件的個數是不確定的// 刪除源文件string _src = PathUtil::Src(file_name);if (FileUtil::IsFileExists(_src))unlink(_src.c_str());// 刪除編譯報錯文件string _compiler_error = PathUtil::CompilerError(file_name);if (FileUtil::IsFileExists(_compiler_error))unlink(_compiler_error.c_str());// 刪除可執行文件string _execute = PathUtil::Exe(file_name);if (FileUtil::IsFileExists(_execute))unlink(_execute.c_str());// 刪除標準輸入文件string _stdin = PathUtil::Stdin(file_name);if (FileUtil::IsFileExists(_stdin))unlink(_stdin.c_str());// 刪除標準輸出文件string _stdout = PathUtil::Stdout(file_name);if (FileUtil::IsFileExists(_stdout))unlink(_stdout.c_str());// 刪除標準錯誤文件string _stderr = PathUtil::Stderr(file_name);if (FileUtil::IsFileExists(_stderr))unlink(_stderr.c_str());}/***************************************************************** 輸入:* code:用戶提交的代碼* input:用戶給自己提交的代碼對應的輸入* cpu_limit:時間要求* mem_limit:空間要求* 選填:* time_limit:* ***************************************************************** 輸出:* status:狀態碼* reason:請求的原因* 選填:* stdout:我的程序運行完的結果* stderr:我的程序運行完的錯誤結果* in_json:{"code","#include...","cpu_limit:1","mem_limit:1024"}* out_json:{"status":0,"reason":"ok","stdout":"","stderr":"error"}*****************************************************************/static void Start(const string &in_json, string *out_json){Json::Value in_value;Json::Reader reader;// 反序列化reader.parse(in_json, in_value); // 最后處理差錯問題string code = in_value["code"].asString();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;Json::Value out_value;int run_result;string file_name; // 需要內部形成的臨時文件名if (code.size() == 0){status_code = -1; // 代碼為空goto END;}// 形成一個唯一的文件名// 形成的文件名只具有唯一性,沒有目錄和后綴// 毫秒級時間戳+源子性遞增唯一值:來保證唯一性file_name = FileUtil::UniqFileName();// 形成臨時src源文件if (!FileUtil::WriteFile(PathUtil::Src(file_name), code)){status_code = -2; // 未知錯誤goto END;}if (!Compiler::Compile(file_name)){// 編譯失敗status_code = -3; // 編譯時發生錯誤goto END;}run_result = Runner::Run(file_name, cpu_limit, mem_limit);if (run_result < 0){status_code = -2; // 未知錯誤goto END;}else if (run_result > 0){// 程序運行崩潰status_code = run_result; // 程序運行崩潰}else{// 運行成功status_code = 0;}END:out_value["status"] = status_code;out_value["reason"] = CodeToDesc(status_code, file_name);cout << out_value["reason"] << endl;if (status_code == 0){// 整個過程全部成功string _stdout;FileUtil::ReadFile(PathUtil::Stdout(file_name), &_stdout, true);out_value["stdout"] = _stdout;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);return;}}; }
在Linux系統中,JsonCpp 是一個流行的 C++ 庫,用于處理 JSON(JavaScript Object Notation)數據格式。JsonCpp 提供了一組易于使用的 API,使得在 C++ 程序中解析、構建和操作 JSON 數據變得簡單。
4.compile_server.cc
形成編譯運行網絡服務
#include "compile_run.hpp" #include"../comm/httplib.h"using namespace std;using namespace ns_compile_and_run;using namespace httplib;void Usage(string proc) {cerr<<"Usage: "<<"\n\t"<<proc<<" <port>"<<endl; }// 編譯服務隨時可能被多個人請求,必須保證傳遞上來的code,形成源文件名稱的時候,要具有唯一性 int main(int argc,char* argv[]) {if(argc!=2){Usage(argv[0]);return -1;}//提供的編譯服務,打包形成一個網絡服務//cpp-httplibServer svr;svr.Get("/hello",[](const Request &req,Response &resp){resp.set_content("hello world,hello 你好", "text/plain;charset=utf-8");});svr.Post("/compile_and_run",[](const Request &req,Response &resp){//用戶請求的服務正文是我們想要的json stringstring in_json=req.body; string out_json;if(!in_json.empty()){CompileAndRun::Start(in_json,&out_json);resp.set_content(out_json,"application/json;charset=utf-8");}});//svr.set_base_dir("./wwwroot");//指明了根目錄svr.listen("0.0.0.0",atoi(argv[1]));//啟動http服務return 0; }
5.temp緩沖區
主要用存放編譯運行產生的臨時文件
- 編譯錯誤文件
- 編譯源文件
- 可執行文件
- 運行錯誤文件
- 交互輸入文件
- 輸出運行結果文件
這些都是臨時文件,在向用戶響應之后,這些文件將會自動被清理,可以手動設置保留這些臨時文件,在compile_run模塊中取消RemoveTempFile方法的調用
6.Makefile
compile_server:compile_server.ccg++ -o $@ $^ -std=c++11 -ljsoncpp -lpthread.PHONY:clean clean:rm -f compile_server
因為使用了jsconcpp庫需要在編譯的時候進行鏈接,還有使用了多線程需要鏈接pthread庫
六、oj_server模塊
oj_server主要是用于面向客戶交互的部分,編譯運行是compile_sever負責
oj_server的功能
- 獲取首頁
- 獲取題目列表
- 獲取指定題目,并提供編輯功能
- 提交判題功能(編譯功能)
MVC模式
- 這種模式可以減少各個模塊的耦合性,保證整個系統運行的穩定性
- M: model,通常是用和數據交互的模塊,比如,對題庫進行增刪改查
- V: view,通常是拿到數據之后,要構建網頁,渲染網頁內容,展示給用戶(瀏覽器)
- C: control,控制器,就是我們業務核心
1.oj_model數據交互模塊
給用戶提供題目題庫,讓用戶可以自主決定在線解答指定的題
#pragma once // 文件版 #include "../comm/log.hpp" #include <iostream> #include <string> #include <unordered_map> #include <cassert> #include <vector> #include <fstream> #include "../comm/util.hpp" #include <cstdlib>// 根據題目list文件,加載所有的題目信息到內存中 // model: 主要用來和數據進行交互,對外提供訪問數據的接口 namespace ns_model {using namespace std;using namespace ns_log;using namespace ns_util;struct Question{string number; // 題號string title; // 題目標題string star; // 難度int cpu_limit; // 時間限制int mem_limit; // 內存限制string desc; // 題目描述string header; // 題目預設給用戶在線編輯器的代碼string tail; // 題目的測試用例,需要和header拼接,形成完整的代碼};const string questlines_list = "./questions/questions.list";const string question_path = "./questions/";class Model{private:// 題號: 題目細節unordered_map<string, Question> questions;public:Model(){assert(LoadquestionList(questlines_list));}bool LoadquestionList(const string &question_list){// 加載配置文件:questions/questions.list+題目編號文件ifstream in(question_list);if (!in.is_open()){LOG(FATAL) << "加載題庫失敗,請檢查是否存在題庫文件" << endl;return false; // 打開文件失敗}string line;while (getline(in, line)){vector<string> tokens; // 保留好切分的字符串StringUtil::SplitString(line, &tokens, " ");// 1 判斷回文數 簡單 1 30000if (tokens.size() != 5){LOG(WARNING) << "加載部分題庫失敗,請檢查題庫文件格式" << endl;continue;}struct 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());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(make_pair(q.number, q));}LOG(INFO) << "加載題庫...成功" << endl;in.close();return true;}bool GetAllQuestions(vector<struct Question> *out){if (questions.size() == 0){return false; // 題庫沒有題目}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);if (iter == questions.end()){LOG(ERROR) << "用戶獲取題目失敗" << endl;return false; // 題目不存在}(*q) = iter->second;return true;}~Model(){}}; }
Boost 是一個廣泛使用的 C++ 庫集合,為 C++ 開發人員提供了豐富的工具和功能增強。Boost 庫通過提供高質量、可移植、可重用的開源組件,擴展了 C++ 標準庫,涵蓋了各個領域,包括算法、容器、字符串處理、多線程、網絡編程、數學和并發編程等。
以下是 Boost 庫的一些核心模塊和功能:
- 智能指針(Smart Pointers):包括
shared_ptr
、unique_ptr
和weak_ptr
,提供了更安全和方便的動態內存管理。- 容器(Containers):包括
vector
、list
、map
、set
等常用容器的增強版本,提供了更多功能和效率。- 字符串處理(String Handling):包括字符串分割、替代、查找、格式化等操作,提供了正則表達式庫和較強大的字符串處理工具。
- 算法(Algorithms):提供了很多算法模板,例如排序、查找、合并、計數等,擴展了 C++ 標準庫中的算法。
- 文件系統(Filesystem):提供了用于文件和目錄操作的跨平臺接口,比標準庫的
std::filesystem
更早支持。- 多線程(Multithreading):提供了線程和互斥量、條件變量等同步和并發編程工具。
- 正則表達式(Regex):提供了正則表達式庫,支持 Perl 風格的正則表達式,方便進行文本處理。
- 數學(Math):提供了數值計算、隨機數生成、矩陣操作等數學相關的庫。
- 網絡編程(Networking):提供了 TCP/IP 網絡編程和網絡通信的庫,如套接字、HTTP 請求等。
- 序列化(Serialization):提供了對象序列化和反序列化的支持,使對象可以在不同平臺和語言間進行通信和持久化。
我們在這里使用boost庫字符串處理功能中的字符串分割
2.oj_view視圖模塊
主要是完成網頁的渲染功能,負責對題庫和單個題目的渲染
#pragma onece#include <iostream> #include <string> #include <ctemplate/template.h>//#include "oj_model.hpp" #include "oj_model_m.hpp"namespace ns_view {using namespace std;using namespace ns_model;// string number;//題號// string title;//題目標題// string star;//難度// int cpu_limit;//時間限制// int mem_limit;//內存限制// string desc;//題目描述// string header;//題目預設給用戶在線編輯器的代碼// string tail;//題目的測試用例,需要和header拼接,形成完整的代碼const string template_path = "./template_html/";class View{public:View() {}~View() {}public:void AllExpandHtml(const vector<struct Question> &questions, string *html){// 題目編號 題目的難度 題目的難度// 推薦使用表格顯示// 1.形成路徑string src_html = template_path + "all_questions.html";// 2.形成字典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.獲取被渲染的htmlctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);// 4.開始完成渲染功能tpl->Expand(html, &root);}void OneExpandHtml(struct Question &q, string *html){// 1.形成路徑string src_html = template_path + "one_question.html";// 2.形成字典ctemplate::TemplateDictionary root("one_question");root.SetValue("number", q.number);root.SetValue("title", q.title);root.SetValue("star", q.star);// root.SetValue("cpu_limit",to_string(q.cpu_limit));// root.SetValue("mem_limit",to_string(q.mem_limit));root.SetValue("desc", q.desc);root.SetValue("pre_code", q.header);// 3.獲取被渲染的htmlctemplate::Template *tpl = ctemplate::Template::GetTemplate(src_html, ctemplate::DO_NOT_STRIP);// 4.開始完成渲染功能tpl->Expand(html, &root);}}; }
ctemplate 是一個基于 C++ 的模板庫,用于生成動態內容或靜態文本。它提供了一種簡單且強大的方式來將數據與模板進行結合,生成輸出結果。ctemplate 的設計目標是快速且易于使用,尤其適用于 Web 開發中的動態頁面生成。
3.oj_control控制模塊
完成對oj_model和oj_view模塊調度控制
#pragma once #include <iostream> #include <string> #include <vector> #include <mutex> #include <cassert> #include <fstream> #include <algorithm> #include <jsoncpp/json/json.h>#include "../comm/log.hpp" #include "../comm/util.hpp" //#include "oj_model.hpp" #include "oj_model_m.hpp" #include "oj_view.hpp" #include "../comm/httplib.h"namespace ns_control {using namespace ns_model;using namespace ns_view;using namespace ns_log;using namespace ns_util;using namespace std;using namespace httplib;const string service_machine = "./conf/service_machine.conf";// 負載均衡模塊class Machine{public:string ip; // 編譯服務的ipint port; // 編譯服務的portuint64_t load; // 編譯服務的負載mutex *mtx; // mutex禁止拷貝的,使用指針public:Machine() : ip(""), port(0), load(0), mtx(nullptr) {}~Machine() {}public:// 提升主機負載void IncLoad(){if (mtx)mtx->lock();++load;if (mtx)mtx->unlock();}// 減少主機負載void DecLoad(){if (mtx)mtx->lock();--load;if (mtx)mtx->unlock();}void ResetLoad(){if (mtx)mtx->lock();load = 0;if (mtx)mtx->unlock();}// 獲取主機負載uint64_t GetLoad(){if (mtx)mtx->lock();uint64_t _load = load;if (mtx)mtx->unlock();return _load;}};class LoadBlance{private:vector<Machine> _machines; // 可以給我們提供編譯服務的所有的主機// 每一臺主機都有自己的下標,充當當前主機的id// 所有在線的主機idvector<int> online;// 所有離線的主機idvector<int> offline;// 保證LoadBlance的線程安全mutex mtx;public:LoadBlance(){assert(LoadConf(service_machine));LOG(INFO) << "加載" << service_machine << "成功" << endl;}~LoadBlance() {}public:bool LoadConf(const string &machine_conf){ifstream in(machine_conf, ios::in);if (!in.is_open()){LOG(FATAL) << "加載:" << machine_conf << "失敗" << endl;return false;}string line;while (getline(in, line)){vector<string> tokens;StringUtil::SplitString(line, &tokens, ":");if (tokens.size() != 2){LOG(WARNING) << "切分" << line << "失敗" << endl;continue;}Machine m;m.ip = tokens[0];m.port = atoi(tokens[1].c_str());m.load = 0;m.mtx = new mutex();online.push_back(_machines.size()); // 默認初始化主機的時候都在online_machines.push_back(m);}in.close();return true;}// id:輸出型參數// m:輸出型參數bool SmartChoice(int *id, Machine **m){// 1。使用選擇好的主機(更新該主機的負載)// 2.我們需要可能離線該主機mtx.lock();// 負載均衡的方法// 1.隨機數+hash// 2.輪詢+hashint online_num = online.size();if (online_num == 0){mtx.unlock();LOG(FATAL) << "所有的后端編譯主機已經離線,請運維的同事查看" << endl;return false;}// 通過遍歷的方式,找到所有負載最小的機器*id = online[0];*m = &_machines[online[0]]; // 拿到負載最小主機的地址uint64_t min_load = _machines[online[0]].GetLoad();for (int i = 0; i < online_num; ++i){uint64_t curload = _machines[online[i]].GetLoad();if (min_load > curload){min_load = curload;*id = online[i];*m = &_machines[online[i]]; // 拿到負載最小主機的地址}}mtx.unlock();return true;}void OfflineMachine(int which){mtx.lock();for (auto iter = online.begin(); iter != online.end(); iter++){if (*iter == which){_machines[which].ResetLoad();// 要離線的主機已經找到啦online.erase(iter);offline.push_back(which);break; // 因為break存在,所以我們不暫時考慮迭代器失效的問題}}mtx.unlock();}void OnlineMachine(){// 我們統一上線,后面統一解決mtx.lock();online.insert(online.end(), offline.begin(), offline.end());offline.erase(offline.begin(), offline.end());mtx.unlock();LOG(INFO) << "所有的主機上線啦" << endl;}// for testvoid ShowMachinees(){mtx.lock();cout << "當前主機列表: ";for (auto &id : online){cout << id << " ";}cout << endl;cout << "當前離線主機列表:";for (auto &id : offline){cout << id << " ";}cout << endl;mtx.unlock();}};// 這是我們的核心業務邏輯的控制器class Control{private:Model _model;View _view;LoadBlance _load_balance; // 核心負載均衡public:Control() {}~Control() {}public:void Recovery(){_load_balance.OnlineMachine();}// 根據題目數據構建網頁// html:輸出型參數bool AllQuestions(string *html){bool ret = true;vector<struct Question> all;if (_model.GetAllQuestions(&all)){sort(all.begin(), all.end(), [](const struct Question &q1, const struct Question &q2){ return atoi(q1.number.c_str()) < atoi(q2.number.c_str()); });// 獲取題目信息成功,將所有的題目數據構建成網頁_view.AllExpandHtml(all, html);}else{ret = false;*html = "獲取題目失敗,形成題目列表失敗";}return ret;}bool Question(const string &number, string *html){bool ret = true;struct Question q;if (_model.GetOneQuestion(number, &q)){// 獲取指定題目信息成功,將所有的題目數據構建成網頁_view.OneExpandHtml(q, html);}else{ret = false;*html = "指定題目" + number + "不存在";}return ret;}// code:"#include"//void Judge(const string &number, const string in_json, string *out_json){// LOG(DEBUG)<<in_json<<"\nnumber:"<<number<<endl;// 0.根據題目編號,直接拿到對應的題目細節struct Question q;_model.GetOneQuestion(number, &q);// 1.in_json進行反序列化,得到題目id,得到用戶提交的源代碼,inputJson::Reader reader;Json::Value in_value;reader.parse(in_json, in_value);string code = in_value["code"].asString();// 2.重新拼接用戶代碼+測試代碼用例,形成新代碼Json::Value compile_value;compile_value["input"] = in_value["input"].asString();compile_value["code"] = code + "\n" + q.tail; // 用戶代碼+測試用例compile_value["cpu_limit"] = q.cpu_limit;compile_value["mem_limit"] = q.mem_limit;Json::FastWriter writer;string compile_string = writer.write(compile_value);// 3.選擇負載最低的主機// 規則:// 一直選擇,直到主機可用,否則,就是全部掛掉while (true){int id = 0;Machine *m = nullptr;if (!_load_balance.SmartChoice(&id, &m)){break;}// 4.然后發起http請求,得到結果m->IncLoad();Client client(m->ip, m->port);LOG(INFO) << "選擇主機成功,主機id:" << id << " 詳情" << m->ip << ":" << m->port << "當前主機的負載為:" << m->GetLoad() << endl;if (auto resp = client.Post("/compile_and_run", compile_string, "application/json;charset=utf-8")){// 5.將結果賦值給out_jsonif (resp->status == 200){*out_json = resp->body;m->DecLoad();LOG(INFO) << "請求編譯運行服務成功...." << endl;break;}m->DecLoad(); // 請求失敗(狀態碼不等于200),負載}else{// 請求失敗LOG(ERROR) << "當前請求的主機id:" << id << " 詳情:" << m->ip << ":" << m->port << "可能已經離線" << endl;// m->IncLoad();//可以沒有必要,因為離線后,我們會將負載清零_load_balance.OfflineMachine(id);_load_balance.ShowMachinees(); // 僅僅是用來調試的}}}}; }
4.oj_server.cc
形成在線OJ的網絡服務
#include <iostream>#include <signal.h>#include "../comm/httplib.h" #include "oj_control.hpp"using namespace httplib; using namespace ns_control; using namespace std;static Control *ctrl_ptr = nullptr;void Recovery(int signno) {ctrl_ptr->Recovery(); }int main() {//這個信號用于將所有的主機上線signal(SIGQUIT, Recovery);// 用戶請求的服務路由功能Server svr;Control ctrl;ctrl_ptr = &ctrl;// 獲取所有的題目列表svr.Get("/all_questions", [&ctrl](const Request &req, Response &resp){//resp.set_content("這是所有的題目列表", "text/plain;charset=utf-8");//返回一張含有所有題目的html網頁string html;ctrl.AllQuestions(&html);resp.set_content(html, "text/html;charset=utf-8"); });// 用戶要根據題目編號,獲取題目類容/// questions/100->正則匹配(/d+是正則表達式)// R"()",原始字符串,保持字符串內容的原貌,不用做相關的轉義svr.Get(R"(/question/(\d+))", [&ctrl](const Request &req, Response &resp){//resp.set_content("這是指定的一道題"+number, "text/plain;charset=utf-8");string number=req.matches[1];//???string html;ctrl.Question(number,&html);resp.set_content(html, "text/html;charset=utf-8"); });// 用戶提交代碼,使我們的判題功能(1.每道題的測試用例 2.compile_and_run)svr.Post(R"(/judge/(\d+))", [&ctrl](const Request &req, Response &resp){string number = req.matches[1]; //???string result_json;ctrl.Judge(number, req.body, &result_json);resp.set_content(result_json, "application/json;charset=utf-8");// resp.set_content("指定題目判題"+number, "text/plain;charset=utf-8");});// 設置首頁的內容svr.set_base_dir("./wwwroot"); // 設置靜態資源的根目錄svr.listen("0.0.0.0", 8080);return 0; }
R"()"作用就是保持字符串的原貌,在?C++11?中引入了一種新的字符串字面量語法,稱為原始字符串字面量(raw?string?literal)。原始字符串字面量使用?R"()?和?)"?作為界定符來定義字符串,其中?)?和?(?之間的任何字符都作為字符串內容。
5.questions文件版題庫
1)1
存儲1號題詳細描述,頭部代碼,測試代碼
[1]desc
輸入:x = -121 輸出:false 解釋:從左向右讀, 為 -121 。 從右向左讀, 為 121- 。因此它不是一個回文數。 示例 3:輸入:x = 10 輸出:false 解釋:從右向左讀, 為 01 。因此它不是一個回文數。
[2]header.cpp
#include<iostream> #include<string> #include<vector> #include<map> #include<algorithm> #include <unordered_map>using namespace std; class Solution{public:bool isPalindrome(int x){//將你的代碼寫在下面return true;} };
[3]tail.cpp
#ifndef COMPILER_ONLINE #include"header.cpp" #endifvoid Test1() {//通過定義臨時對象,來完成方法的調用bool ret=Solution().isPalindrome(121);if(ret){cout<<"通過了用例1,測試121通過....OK!"<<endl;}else{cout<<"沒有通過用例1,測試121失敗....ERROR!"<<endl;} } void Test2() {//通過定義臨時對象,來完成方法的調用bool ret=Solution().isPalindrome(-10);if(!ret){cout<<"通過了用例2,測試-10通過....OK!"<<endl;}else{cout<<"沒有通過用例2,測試-10失敗....ERROR!"<<endl;} } int main() {Test1();Test2();return 0; }
2)questions.list
用于存儲題目詳情,包含
- 題號
- 難度
- 題目簡單描述
- 時間限制
- 空間限制
1 判斷回文數 簡單 1 30000 2 兩數之和 簡單 1 30000
6.wwwroot主頁
在線OJ系統的網頁首頁,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><style>/* 保證我們的樣式設置可以不受默認影響 *//* "*"是選中所有的標簽 */* {/* 消除網頁的默認外邊距 */margin: 0px;/*消除網頁的默認內邊距 */padding: 0;}html,body {/* 設置網頁的寬高 */width: 100%;height: 100%;}.container .navbar {width: 100%;height: 50px;background-color: black;/* 給父級標簽設置overflow屬性,可以防止子級標簽溢出 */overflow: hidden;}.container .navbar a {/* 設置a標簽為行內塊元素,可以設置寬度和高度等屬性 */display: inline-block;/* 設置a標簽的寬度 a標簽是一個行內元素,無法設置寬度*/width: 100px;/* 設置顏色 */color: white;/* 設置字體大小 */font-size: large;/* 設置文字的高度和導航欄一樣的高度 */line-height: 50px;/* 去掉a標簽的下劃線 */text-decoration: none;/* 設置a標簽的文字居中 */text-align: center;}/* 設置鼠標事件 */.container .navbar a:hover{background-color: green;}.container .navbar .login{float: right;}.container .content {/* 設置內容區域的寬度 */width: 800px;/* 設置內容區域的顏色 *//* background-color: #ccc; *//* 整體居中 */margin: 0 auto;/* 設置文字居中 */text-align: center;/* 設置上外邊距 */margin-top: 200px;}.container .content .font_ {/* 設置標簽為塊級元素,獨占一行,可以設置高度寬度等屬性 */display: block;/* 設置每個文字的上外邊距 */margin: 20px;/* 去掉a標簽的下劃線 */text-decoration: none;/* 設置字體大小font-size:larger; */}</style> </head><body><div class="container"><!-- 導航欄 功能不實現(我們沒有這些功能) --><div class="navbar"><a href="/">首頁</a><a href="/all_questions">題庫</a><a href="#">討論</a><a href="#">競賽</a><a href="#">求職</a><a class="login" href="/">登陸</a></div><!-- 網頁內容 --><div class="content"><h1 class="font_ ">歡迎來到我們的OnlineJudge平臺</h1><p class="font_">這個是我個人獨立開發的一個在線OJ平臺</p><a class="font_" href="/all_questions">點擊我開始編程啦!</a></div></div></body></html>
7.template_html
1)all_questions.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><style>/* 保證我們的樣式設置可以不受默認影響 *//* "*"是選中所有的標簽 */* {/* 消除網頁的默認外邊距 */margin: 0px;/*消除網頁的默認內邊距 */padding: 0;}html,body {/* 設置網頁的寬高 */width: 100%;height: 100%;}.container .navbar {width: 100%;height: 50px;background-color: black;/* 給父級標簽設置overflow屬性,可以防止子級標簽溢出 */overflow: hidden;}.container .navbar a {/* 設置a標簽為行內塊元素,可以設置寬度和高度等屬性 */display: inline-block;/* 設置a標簽的寬度 a標簽是一個行內元素,無法設置寬度*/width: 80px;/* 設置顏色 */color: white;/* 設置字體大小 */font-size: large;/* 設置文字的高度和導航欄一樣的高度 */line-height: 50px;/* 去掉a標簽的下劃線 */text-decoration: none;/* 設置a標簽的文字居中 */text-align: center;}/* 設置鼠標事件 */.container .navbar a:hover {background-color: green;}.container .navbar .login {float: right;}.container .question_list{padding: 50px;width: 800px;height: 100%;margin: 0px auto;/* background-color: #ccc; */text-align: center;}.container .question_list table{width: 100%;font-size: large;font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;margin-top: 50px;background-color: rgb(243, 248, 246);}.container .question_list h1{color: green;}.container .question_list table .item{width:100px;height: 40px;font-size: large;font-family:'Times New Roman', Times, serif;}.container .question_list table .item a{text-decoration: none;color: black;}.container .question_list table .item a:hover{color: rgb(31, 31, 190);font-size: larger;text-decoration: underline;}.container .footer{width: 100%;height: 50px;text-align: center;line-height: 50px;color: #ccc;margin-top: 15px;}</style> </head><body><div class="container"><!-- 導航欄 功能不實現(我們沒有這些功能) --><div class="navbar"><a href="/">首頁</a><a href="/all_questions">題庫</a><a href="#">討論</a><a href="#">競賽</a><a href="#">求職</a><a class="login" href="/">登陸</a></div><div class="question_list"><h1>OnlineJudge題目列表</h1><table><tr><th class="item">題目編號</th><th class="item">題目名稱</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 class="footer"><h4>@畢財華</h4></div></div> </body></html>
2)one_question.html
指定題目的網絡頁面
<!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>{{number}}.{{title}}</title><!-- 引入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><!-- 引?jquery CDN --><script src="http://code.jquery.com/jquery-2.1.1.min.js"></script><style>* {margin: 0;padding: 0;}html,body {width: 100%;height: 100%;}/* div .ace_editor {width: 100%;} */.container .navbar {width: 100%;height: 50px;background-color: black;/* 給父級標簽設置overflow屬性,可以防止子級標簽溢出 */overflow: hidden;}.container .navbar a {/* 設置a標簽為行內塊元素,可以設置寬度和高度等屬性 */display: inline-block;/* 設置a標簽的寬度 a標簽是一個行內元素,無法設置寬度*/width: 100px;/* 設置顏色 */color: white;/* 設置字體大小 */font-size: large;/* 設置文字的高度和導航欄一樣的高度 */line-height: 50px;/* 去掉a標簽的下劃線 */text-decoration: none;/* 設置a標簽的文字居中 */text-align: center;}/* 設置鼠標事件 */.container .navbar a:hover {background-color: green;}.container .navbar .login {float: right;}.container .part1 {width: 100%;height: 600px;overflow: hidden;}.container .part1 .left_desc {width: 50%;height: 600px;float: left;overflow: scroll;}.container .part1 .left_desc h3 {padding-top: 10px;padding-left: 10px;}.container .part1 pre {padding-top: 10px;padding-left: 10px;font-size: medium;font-family:'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;}.container .part1 .right_code {width: 50%;float: right;}.container .part1 .right_code .ace_editor {height: 600px;}.container .part2 {width: 100%;overflow: hidden;}.container .part2 .result {width: 300px;float:left;}.container .part2 .btn-submit {width: 120px;height: 50px;font-size: large;float: right;background: #26bb9c;color: white;/* 給按鈕帶上圓角 *//* border-radius:1ch; */border: 0px;margin-top: 10px;margin-right: 10px;}.container .part2 button:hover {color: red;}.container .part2 .result{margin-top: 15px;margin-left: 15px;}.container .part2 .result pre{font-size: large;}</style></head><body><div class="container"><div class="navbar"><!-- 導航欄 功能不實現(我們沒有這些功能) --><a href="/">首頁</a><a href="/all_questions">題庫</a><a href="#">討論</a><a href="#">競賽</a><a href="#">求職</a><a class="login" href="/">登陸</a></div><!-- 左右呈現,題目描述和預設代碼 --><div class="part1"><!-- 1.題目描述2.代碼編輯區 --><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><!-- 提交且得到結果 --><div class="part2"><div class="result"><p>運行結果</p></div><button class="btn-submit" onclick="submit()">提交代碼</button></div></div><script>//初始化對象editor = ace.edit("code");//設置?格和語?(更多?格和語?,請到github上相應?錄查看)// 主題?全:http://www.manongjc.com/detail/25-cfpdrwkkivkikmk.htmleditor.setTheme("ace/theme/monokai");editor.session.setMode("ace/mode/c_cpp");// 字體??editor.setFontSize(16);// 設置默認制表符的??:editor.getSession().setTabSize(4);// 設置只讀(true時只讀,?于展?代碼)editor.setReadOnly(false);// 啟?提?菜單ace.require("ace/ext/language_tools");editor.setOptions({enableBasicAutocompletion: true,enableSnippets: true,enableLiveAutocompletion: true});function submit() {//alert("嘿嘿!");//1.收集當前頁面的有關數據,1.題號 2.代碼var code = editor.getSession().getValue();// console.log(code);var number = $(".container .part1 .left_desc h3 #number").text();var judge_url = "/judge/" + number;//2.構建json 并通過ajax向后臺發送請求$.ajax({method: "Post",//向后端發起請求的方式url: judge_url,//向后端指定的url發起請求dataType: "json",//告知server,我需要什么格式contentType: "application/json;charset=utf-8",//告知server,我給你的是什么格式data: JSON.stringify({"code": code,"input": ""}),success: function (data) {//成功的結果放在data中//console.log(data);show_result(data);}});//3.得到結果,解析并顯示到result中function show_result(data) {// console.log(data.status);// console.log(data.reason);//拿到result結果標簽var result_div = $(".container .part2 .result");//清空上一次的結果result_div.empty();//首先拿到結果的狀態碼和結果原因var _status = data.status;var _reason = data.reason;var reason_lable = $("<p>", {text: _reason});reason_lable.appendTo(result_div);if (status == 0) {//請求是成功的,編譯運行過程沒有出問題,但是結果得看是否通過測試用例的結果var _stdout = data.stdout;var _stderr = data.stderrvar 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>
8.conf
service_machine.conf中用于存儲端口號信息,決定有幾個編譯服務
127.0.0.1:8081 127.0.0.1:8082 127.0.0.1:8083
這里只有三個編譯服務,且都在本地,根據自己的需求進行更改
9.Makefile
oj_server:oj_server.ccg++ -o $@ $^ -std=c++11 -lpthread -lctemplate -ljsoncpp .PHONY:clean clean:rm -f oj_server
因為使用了jsconcpp庫需要在編譯的時候進行鏈接,還有httplib庫中使用了多線程需要鏈接pthread庫,使用渲染模塊需要鏈接ctemplate
七、MySQL版本的實現
文件版的在線OJ系統,日常維護難度較高,我們建議使用數據庫的版本,由文件版本轉化為數據庫版本,只需用oj_model_m.hpp要替換掉oj_model.hpp,更改一下oj_view.hpp中的頭文件,刪除#include"oj_model.hpp",添加#include"oj_model_m.hpp",更改一下oj_control.hpp中的頭文件,和oj_view.hpp的操作即可實現MySQL的切換
注意:還需要添加mysql的庫和頭文件
1.oj_model_m
#pragma once // mysql版 #include "../comm/log.hpp" #include <iostream> #include <string> #include <unordered_map> #include <cassert> #include <vector> #include <fstream> #include "../comm/util.hpp" #include <cstdlib>#include "include/mysql.h" //連接mysql庫// 根據題目list文件,加載所有的題目信息到內存中 // model: 主要用來和數據進行交互,對外提供訪問數據的接口 namespace ns_model {using namespace std;using namespace ns_log;using namespace ns_util;struct Question{string number; // 題號string title; // 題目標題string star; // 難度string desc; // 題目描述string header; // 題目預設給用戶在線編輯器的代碼string tail; // 題目的測試用例,需要和header拼接,形成完整的代碼int cpu_limit; // 時間限制int mem_limit; // 內存限制};const string oj_questions = "oj_questions";const string host = "127.0.0.1";const string user = "oj_client";const string passwd = "123456";const string db = "oj";const int port = 3306;class Model{public:Model(){}bool QueryMysql(const string &sql, vector<Question> *out){// 創建mysql句柄MYSQL *my = mysql_init(nullptr);// 連接數據庫if (nullptr == mysql_real_connect(my, host.c_str(), user.c_str(), passwd.c_str(), db.c_str(), port, nullptr, 0)){LOG(FATAL) << " " << mysql_error(my) << endl;return false;}LOG(INFO) << "連接數據庫成功" << endl;// 一定要設置連接編碼格式mysql_set_character_set(my, "utf8");// 執行sql語句if (0 != mysql_query(my, sql.c_str())){LOG(WARNING) << sql << "execute error!" << endl;return false;}// 提取結果MYSQL_RES *res = mysql_store_result(my);// 分析結果int rows = mysql_num_rows(res); // 獲取行數量int cols = mysql_num_fields(res); // 獲取列數量for (int i = 0; i < rows; i++){MYSQL_ROW row = mysql_fetch_row(res);struct Question q;q.number = row[0];q.title = row[1];q.star = row[2];q.desc = row[3];q.header = row[4];q.tail = row[5];q.cpu_limit = atoi(row[6]);q.mem_limit = atoi(row[7]);out->push_back(q);}// 釋放結果空間free(res);// 關閉mysql連接mysql_close(my);return true;}bool GetAllQuestions(vector<struct Question> *out){string sql = "select * from ";sql += oj_questions;return QueryMysql(sql, out);}bool GetOneQuestion(const string &number, Question *q){bool ret = false;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];ret = true;}}return ret;}~Model(){}}; }
2.include
用于存放mysql所需要使用的頭文件,需要在MySQL官網下載mysql安裝包
3.lib
用于存放mysql所需要使用的庫,需要在MySQL官網下載mysql安裝包
4.linux下載mysql服務
linux服務器下載的mysql操作和上面在官網下載mysql不一樣,上面下載的是mysql庫,實現C/C++語言和sql進行交互,而現在下載的mysql服務是對一個mysql數據庫進行操作
1)安裝MySQL:使用 apt 包管理器安裝MySQL服務器。如果您還沒有安裝MySQL,請使用以下命令進行安裝:
???????sudo apt update sudo apt install mysql-server
2)啟動MySQL服務:安裝完成后,MySQL服務通常會自動啟動。您可以使用以下命令檢查其狀態:
sudo systemctl status mysql
3)如果MySQL未運行,您可以使用以下命令啟動它:
sudo systemctl start mysql
4)安全設置:MySQL安裝后,執行以下命令以提高安全性并設置root用戶的密碼:
?sudo mysql_secure_installation
這個命令將引導您完成一系列安全設置步驟,包括設置root密碼、刪除匿名用戶、禁止root遠程登錄等。按照提示操作即可。
更改root密碼:登錄后,您可以使用以下命令來更改root用戶的密碼:
?ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';
將 new_password 替換為您想要設置的新密碼。
刷新權限:密碼更改后,您需要刷新MySQL的權限以使更改生效:
FLUSH PRIVILEGES;
5)第一步:
登錄MySQL:安裝完成并設置好密碼后,您可以使用以下命令登錄到MySQL服務器:
?sudo mysql -u root -p
然后輸入您設置的root密碼。(第一次登錄的時候沒有密碼)
第二步:
進入MySQL后,對root的密碼進行修改更改root密碼:連接到MySQL后,使用以下命令更改root密碼。替換 new_password 為您要設置的新密碼:
ALTER USER 'root'@'localhost' IDENTIFIED BY 'new_password';
刷新權限:密碼更改后,刷新MySQL的權限:
FLUSH PRIVILEGES;
6)創建新用戶(可選):出于安全考慮,建議不要總是使用root用戶來管理數據庫。您可以創建一個新的MySQL用戶,并為其分配適當的權限。例如,您可以使用以下命令創建一個新用戶,并允許他從本地主機登錄:
CREATE USER 'newuser'@'localhost' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON *.* TO 'newuser'@'localhost' WITH GRANT OPTION;
將 newuser 替換為您要創建的用戶名,password 替換為密碼。
退出MySQL:完成操作后,您可以鍵入 exit 或 \q 來退出MySQL命令行界面。
5.在Ubuntu上設置MySQL可以遠程登錄用戶
1)編輯mydqld.cnf文件
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf 找到bind-address字段,將其設置:bind-address = 0.0.0.0
2)創建用戶
create user oj_client@'%' identified by '123456'; 數據庫:oj
3)給用戶賦權(只讓用戶操作oj這個數據庫)
grant all on oj.* to oj_client@'%';
4)完成權限更改后,必須刷新MySQL以使更改生效:
FLUSH PRIVILEGES;
6.創建表結構???????
CREATE TABLE if not EXISTS `oj_questions`( `number` int PRIMARY key auto_increment COMMENT '題目編號', `title` varchar(128) NOT NULL COMMENT '題目的標題', `star` VARCHAR(8) NOT NULL COMMENT'題目難度', `desc` text NOT NULL COMMENT'題目的描述', `header` text NOT NULL COMMENT'預設給用戶的代碼', `tail` text NOT NULL COMMENT'測試用例的代碼', `cpu_limit` int default 1 COMMENT '題目的cpu超時時間', `mem_limit` int default 50000 COMMENT '題目的memery超限空間' )engine=INNODB default charset=utf8;
?八、綜合
1.安裝 jsoncpp
sudo apt update sudo apt install libjsoncpp-dev
2.安裝 cpp-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拷?到我們的項?中即可,就這么簡單3.安裝boost庫
sudo apt update sudo apt install libboost-all-dev
4.安裝ctemplate
https://gitee.com/src-oepkgs/ctemplate $ git clone https:https://gitee.com/src-oepkgs/ctemplate $ ./autogen.sh $ ./configure $ make //編譯 $ make install //安裝到系統中
5.結果展示
???????
??🌸🌸🌸如果大家還有不懂或者建議都可以發在評論區,我們共同探討,共同學習,共同進步。謝謝大家! 🌸🌸🌸