-
2021-04-07 22:31:22
总结了OJ系统的错误信息及处理方式
Queuing
程序正在等待队列中,等待编译和执行。
Accepted
程序通过了所有的测试,最后的答案也是正确的。
Presentation Error
格式错误。说明输出是正确的,可能在什么地方多输出了一个空行或者空格,需要再修改输出格式。
Time Limit Exceed
时间超出。每道题目都有规定的时间限制,程序没有在规定时间执行完,就会返回这个信息。一般需要修改输入输出的方式或者程序的算法才能通过这道题目的测评。
Memory Limit Exceed
内存超出。每道题目都有规定内存限制,程序申请了过多的内存,就会返回这个错误。
Wrong Answer
错误结果。这是比较常见的错误信息。说明程序的算法有问题,需要修改。如果发现这个信息返回在第二个或者之后的测试案例中,那么有可能没有考虑到一些极端的情况。
Compile Error
编译错误。程序没有通过编译。一般系统会给出编译错误信息,可以查找为什么没有通过编译。应该先在IDE中进行编译测试之后再提交到评测系统中。
Runtime Error
运行时错误。一般有这几种情况:数组越界、除零、空指针、堆栈溢出。出现这个信息,表示程序中存在漏洞,需要仔细查找。
更多相关内容 -
OJ系统汇总-2021-10-5(B).pdf
2021-10-05 13:56:34OJ系统汇总-2021-10-5(B).pdf -
OJ系统汇总-2021-10-6(C)-32页.pdf
2021-10-06 05:55:12OJ系统汇总-2021-10-6(C)-32页.pdf -
OJ系统汇总-2021-10-5(C)-32页.pdf
2021-10-05 17:58:30OJ系统汇总-2021-10-5(C)-32页.pdf -
基于windows的OJ系统判题核心-已编译成功
2017-12-08 11:28:34基于windows的OJ系统判题核心(FreeJudger)-已编译成功,点击debug中的FreeJudger.exe即可正常运行,亲测在win10系统下可以运行,下载之后一定要看说明 -
宁波大学OJ系统240题(解析).pdf
2020-03-26 19:45:56本文件是对<宁波大学OJ系统240题>文件的解析,解析由本人自己编写,可用作参考,里面有一些相对较复杂的题目,都已经上传至本人CSDN博客,有兴趣的可以查看! -
OJ系统
2018-10-12 15:01:31Online Judge系统(简称OJ)是一个在线的判题系统。用户可以在线提交程序源代码,系统对源代码进行编译和执行,并通过预先设计的测试数据来检验程序源代码的正确性。 一个用户提交的程序在Online Judge系统下...什么是OJ
Online Judge系统(简称OJ)是一个在线的判题系统。用户可以在线提交程序源代码,系统对源代码进行编译和执行,并通过预先设计的测试数据来检验程序源代码的正确性。
一个用户提交的程序在Online Judge系统下执行时将受到比较严格的限制,包括运行时间限制,内存使用限制和安全限制等。用户程序执行的结果将被Online Judge系统捕捉并保存,然后再转交给一个裁判程序。该裁判程序或者比较用户程序的输出数据和标准输出样例的差别,或者检验用户程序的输出数据是否满足 一定的逻辑条件。
Online Judge系统最初使用于ACM-ICPC国际大学生程序设计竞赛和OI信息学奥林匹克竞赛中的自动判题和排名。现广泛应用于世界各地高校学生程序设计的训练、参赛队员的训练和选拔、各种程序设计竞赛以及数据结构和算法的学习和作业的自动提交判断中。
Pending &Waiting: 系统忙,你的答案在排队等待.
Pending Rejudge: 因为数据更新或其他原因,系统将重新判你的答案.
Compiling : 正在编译.
Running & Judging: 正在运行和判断.
Presentation Error : 答案基本正确,但是格式不对。
Wrong Answer : 答案不完全正确.
Time Limit Exceeded : 运行超出时间限制.
Memory Limit Exceeded : 超出内存限制.
Output Limit Exceeded: 输出超过限制.
Runtime Error : 运行时错误.Compile Error : 编译错误.
Accepted : 程序通过!
前言
本文对国内外大家常用的几十个在线测评网站做整理。每个测评网站都有一至两张图片,分别是主页和题库的截图。从图片上可以清晰地看出不同测评网 站的风格,甚至是题目难度,方便大家选择适合对口味的测评网站。作者才疏学浅,无法对每个测评网站做出一句话或是更多的客观评价,只是在自己所熟悉的测评 网站下说了几句自己的见解。
关于测评网站的分类,作者曾想过很多的方式,譬如按照中国外国分或是按照面向对象主要是信息竞赛生或是ACM选手分,无论怎样都无法得到一个很 好的效果。网络时代把大家连成一个整体,应算法竞赛之运而生的书籍,网站,辅导机构都是相通的。更何况,许多读书时参加信息学竞赛的学生以后也往往会成为 大学里ACM校队的主力。一番纠结后,作者想了一个还算折中又鄙浅的分类方法,本文中的测评网站主要分为以下几类:
推荐OJ:用户数多,题目质量高,测评机稳定,容易找到高质量题解或与人交流;
学院派OJ:各大学中学自己维护更新的测评网站,部分对外界开放;
自由型OJ:由社会组织,公司,志愿者运行,部分网站的稳定和更新得不到保证;
比赛类OJ/社区型OJ/其他的国外OJ:定期组织比赛;
提高型OJ:专业性较强,大部分题目思维难度较高
学习型OJ:网上提供编程教育的网站附带的试题库和测评机。
OJ的使用方法
if 你的学校里有自己的oj then 就刷那上面的题即可
else
if 你是一个ACMER then
按准POJ或HDU猛刷
else
if 你是一个OIER then
按准CODEVS或洛谷猛刷
else
从推荐OJ里选一个适合自己难度的OJ猛刷
总而言之,一定要有一个固定的刷题OJ,遇上特殊的自己刷的测评网站上没有的题目时,再去别的测评网站看看。
推荐OJ
北京大学POJ http://poj.org/
中国最受欢迎OJ之一,各式各样各种难度的题目都有。
杭州电子科技大学HDUOJ http://acm.hdu.edu.cn/
中国最受欢迎OJ之一,题目数量多,来源广。
UVA https://uva.onlinejudge.org/
全球最大最老牌的OJ之一,题目数量堪称之最。浏览需要FQ。
codeVs http://codevs.com/
当今最大中文OJ之一,它的天梯功能可以让选手按照难易程度练习各种算法。codevs也是我最早接触到的OJ,题库丰富,难度适中,可以无条件 查看当前出错点的答案。据悉网站管理员WPH95以后想把CODEVS向AI等方面扩宽发展。具体怎样不得而知,不过它作为OJ的强大行还是毋庸置疑的。
还请忽视我的刷题记录
OJ上的后起新秀,迅速占有了很大的OJ评测份额,它的试炼场功能和CODEVS的天梯功能相同,可以让选手按阶段做题提高。个人认为题目难度分类 有些不太恰当,为何同是“普及+提高-”的题目难度差异那么大呢?支持在线IDE编程。其代码公开计划可以让提交此题并达到60分且同加入代码公开计划的 人查看他人的代码。
(账号记录也请忽视,忽视哈。)
大视野在线评测BZOJ http://www.lydsy.com/JudgeOnline/
题目难度多为省选级及以上,但有一部分题目需要购买权限才能访问。
UOJ http://uoj.ac/举办NOIP难度的UER和省选难度的UR。赛题质量极高,命题人大多为现役集训队选手。
清橙评测tsinsen http://www.tsinsen.com/
集训队作业网站。近几年的内容还没有公开。
ACdream http://acdream.info/
学院型OJ
湖南师大附中 vijos https://vijos.org/
学军中学OJ
http://www.hzxjhs.com:83/注册完顺手写了一个背包居然过了O(∩_∩)O
河南实验中学OJhttp://cojs.tk/cogs/index.php
南阳理工学院OJhttp://acm.nyist.net/JudgeOnline/problemset.php
北大在线评测http://openjudge.cn/
好像是许多大学创办比赛提交作业的场所。随着2015年NOI网站公布了NOIP考试将从本网站的NOI题库小组抽取题目后,详细这个网站会迎来很多OIERS的访问。
八一中学码酷http://www.marcool.net/home/index.htm
浙江大学ZOJ http://acm.zju.edu.cn/onlinejudge/西南科技大学SWUST OJ http://acm.swust.edu.cn/
只供校内学生注册
建兰中学OJhttps://school.jloj.cn/oj-plus/
天津大学OJhttp://acm.tju.edu.cn/toj/
TIMUS OJ http://acm.timus.ru/
南开大学OJhttp://acm.nankai.edu.cn/
典雅的界面果断好评
华中科技大学OJhttp://acm.hust.edu.cn/
齐齐哈尔大学OJhttp://www.smartoj.com/
浙江工业大学OJ
http://acm.zjut.edu.cn/system/messageInfoAction.do?method=initIndexPage
萨拉托夫州立大学OJhttp://acm.sgu.ru/
电子科技大学http://acm.uestc.edu.cn/#/
上海交大OJhttp://acm.sjtu.edu.cn/OnlineJudge/
中科大OJhttp://acm.ustc.edu.cn/ustcoj/
宁波工程学院https://ac.2333.moe/
大哥你家网站,卡哇伊诶!
北京师范大学http://acm.bnu.edu.cn/v3/
自由型OJ
九度OJhttp://ac.jobdu.com/problemset.php
RQNOJ http://www.rqnoj.cn/
RQNOJ,是国内OJ(信息学在线测评)开放平台开发和运行的先导
tyvj http://tyvj.cn/
主要面向信息学竞赛,09年成立,现在好像在清北学堂的手中。(话说那大犇出没神马的我们去北京清北的时候挂上去的,现在居然还没撤下来)
比赛类OJ/社区型OJ/其他的国外OJ
Codeforces是一家为计算机编程爱好者提供在线评测系统的俄罗斯网站。该网站由萨拉托夫国立大学的一个团体创立并负责运营。
topcoder社区得到了数百万编码者的支持,因此你可以了解到很多挑战性的项目,基于此你还可以为自己赚去额外的报酬。你可以每天或每周参与编码挑战,该社区提供的项目极具有挑战性,对于初学者而言有一定的难度,但却值得一试。
CodeChef由Directi创建的一个开发者社区,为开发者提供实践、竞赛和进步的平台。该社区拥有大量的编码竞赛问题,允许访问此前别人解答编码挑战赛的源码。该社区也设置了难易度,你可以根据自身条件选择挑战。
Bestcoder
http://bestcoder.acmcoder.com/
克罗地亚coci
波兰MAIN
波兰SPOJ
日本JOI
http://www.ioi-jp.org/index.html
LIGHTOJ http://www.lightoj.com/login_main.php?url=index.php
AOJ http://judge.u-aizu.ac.jp/onlinejudge/
挑战型OJ
欧拉计划PE https://projecteuler.net/
Project Euler可能是全球最流行的编程挑战网站,项目推出初期就拥有几十万的用户,足以表明其影响力有多大。Project Euler致力于鼓励、挑战并且发展解题技巧,并为那些对迷人的数学世界有兴趣的人提供乐趣。leetcode 是一个美国的在线编程网站,上面主要收集了各大IT公司的笔试面试题,对于应届毕业生找工作是一个不可多得的好帮手。
这个网站的的好处在于它会告诉你测试数据以及你的输出和正确的输出是什么,方便大家调试学习。目前,只支持C++和Java两种语言。
另一个很好的地方在于提供了2个独立的代码窗口,分别编译运行。一个自己玩的开发窗口,一个提交代码的窗口,可以在第一个窗口里测试各种拿不准的功能点。
题库包括algorithms ,database, shell三部分,以下是algorithms(算法)的题库部分。
HackerRank网站是为编码程序员们提供一个以编码谜题和现实生活中遇到的编码难题为基础的新兴的社交平台。HackerRank公司得到了风险投资公司Y Combinator 的资金支持。
在这个社区中,他们提供了各种编码谜题、游戏病毒和现实中的编码难题及挑战,让黑客们在该社区中进行交流讨论,接受挑战。HackerRank就如这个名字所暗示的一样,它同时还提供了在线排行榜和其他的竞争元素。
更多请移步:
挑战最强大脑——来自全球的14个编码社区
http://www.csdn.net/article/2014-04-21/2819404-coding-challenges/1
学习型OJ
后记
在搜集各大OJ的过程中发现一个很有趣的现象,面向信息学竞赛的OJ主要是汉文的,毕竟比较注意用户体验和网站风格设计,相比之下,面向ACM的OJ更多地采用英文题目,网站风格简练,并且大部分无法查看错误点,或许这就是常说的有需求有市场,一切为了用户吧。
本文是作者用短短几天的时间做出来的,有所错误,纰漏在所难免,希望大家多多包涵,批评,斧正,提供新的OJ资料,在下不胜感激。
最后,祝大家在算法竞赛,在各自的领域中取得傲人的成绩。
转自:https://www.cnblogs.com/sasuke-/p/5516236.html
-
在线OJ系统
2021-08-07 19:50:57在线OJ系统项目开始之前需要准备的第三方库项目分析两大模块在线编译模块整体思路编译模块题目管理模块数据存储页面显示服务器 项目开始之前 需要准备的第三方库 httplib g++版本必须得是4.9以上 ctemplate boost: ...项目开始之前
需要准备的第三方库
httplib
g++版本必须得是4.9以上ctemplate
boost: yum install boost-devel.x86_64
jsoncpp: yum install jsoncpp-devel.x86_64
项目分析
我们可以看到一个在线OJ至少有
题目ID
名字
难易度
描述
测试用例
代码框架
然后我们试做一道题,可以大概看出一个在线OJ的流程,
1 在浏览器中展示题目
2 用户可以选择题目进行作答
3 后台获取用户代码
4 针对用户代码进行编译
5 后台运行代码
6 将运行结果进行打包
7 把结果返回给客户端。
因此我将这个流程分为两大模块题目管理模块、在线编译模块两大模块
在线编译模块
整体思路
用户发来请求
将用户请求进行切分
改成json格式
分为用户代码“code”和用户输入“stdin”
然后调用编译模块进行编译
调用运行模块进行运行
把最终结果进行返回,构造json对象
按照&和=进行切分,Split是将boost库中的split进行封装
static void ParseBody(const std::string& body, std::unordered_map<std::string, std::string>* params){ //将body字符串切分成键值对 //先按照&切分 std::vector<std::string> kvs; Split(body, "&", &kvs); for(size_t i = 0; i < kvs.size(); ++i){ std::vector<std::string> kv; //按照 = 切分 Split(kvs[i], "=", &kv); if(kv.size() != 2){ continue; } //对键值对进行urldecode (*params)[kv[0]] = UrlDecode(kv[1]); } }
//解析body,获取到用户提交的代码
std::unordered_map<std::string, std::string> body_kv; UrlUtil::ParseBody(req.body, &body_kv); const std::string& user_code = body_kv["code"];
//构造json结构的参数
Json::Value req_json; req_json["code"] = user_code + question.tail_cpp; req_json["stdin"] = user_code; Json::Value resp_json;
//调用编译模块进行编译
Compiler::CompileAndRun(req_json, &resp_json);
编译模块
整体思路
根据所传来的请求,生成对应的源代码文件,然后调用g++来编译文件,如果编译出错,重定向到文件中,然后调用可执行程序,同时将标准输出和标准错误也重定向到文件,最后把程序的最终结果进行返回。
根据请求对象生成源码文件//根据请求对象生成源代码文件 if (req["code"].empty()){ (*resp)["error"] = 3; (*resp)["reason"] = "code empty"; LOG(ERROR) << "code empty" <<std::endl; return false; } const std::string& code = req["code"].asString(); const std::string&file_name=WriteTmpFile(code,req["stdin"].asString());
我们需要生成多种文件,有
源代码文件
、编译错误文件
、可执行程序文件
、标准输入文件
、标准输出文件
、标准错误文件
因此将它们用不同的后缀区分开,并放到同一路径下,传入名字,就可以生成对应的临时文件,例如源代码文件//源代码文件, name表示当前请求的名字 static std::string SrcPath(const std::string& name){ return "./temp_files/" + name + ".cpp"; }
把代码写到文件中,并且分配一个唯一的名字
static std::string WriteTmpFile(const std::string& code, const std::string& str_stdin){ static std::atomic_int id(0); ++id; std::string file_name = "tmp_" + std::to_string(TimeUtil::TimeStamp()) + "." + std::to_string(id); FileUtil::Write(SrcPath(file_name), code); FileUtil::Write(StdinPath(file_name), str_stdin); return file_name; }
注意这里的id需要转成原子操作,因为很有可能有多个用户同时发来请求
然后调用g++进行编译,创建子进程,然后重定向标准错误到编译错误文件中,最后用程序替换的方式执行编译命令
//调用g++进行编译 bool ret = Compile(file_name); static bool Compile(const std::string& file_name){ // 构造出编译指令 char* command[20] = {0}; char buf[20][50] = {{0}}; for(int i = 0; i < 20; ++i){ command[i] = buf[i]; } sprintf(command[0], "%s", "g++"); sprintf(command[1], "%s", SrcPath(file_name).c_str()); sprintf(command[2], "%s", "-o"); sprintf(command[3], "%s", ExePath(file_name).c_str()); sprintf(command[4], "%s", "-std=c++11"); command[5] = NULL; //创建子进程 int ret = fork(); if(ret > 0){ //父进程进行进程等待 waitpid(ret, NULL, 0);; } else{ //子进程进行程序替换 int fd = open(CompileErrorPath(file_name).c_str(), O_WRONLY | O_CREAT, 0666); if (fd < 0){ LOG(ERROR) << "open Compile file error" << std::endl; exit(1); } dup2(fd, 2); execvp(command[0], command); //如果子进程执行失败,就直接退出 exit(0); } //判定可执行文件是否存在来确定编译是否成功 struct stat st; ret = stat(ExePath(file_name).c_str(), &st); if(ret < 0){ //说明文件不存在 LOG(INFO) << "Compile failed!" << file_name << std::endl; return false; } LOG(INFO) << "Compile " << file_name << " OK!" << std::endl; return true; }
调用可执行程序,思路和上面几乎一样
//调用可执行程序 int sig = Run(file_name); static int Run(const std::string& file_name){ //创建子进程 int ret = fork(); if(ret > 0){ //父进程进行等待 int status = 0; waitpid(ret, &status, 0); return status & 0x7f; } else{ //进行标准输入,输出,错误的重定向 int fd_stdout = open(StdoutPath(file_name).c_str(), O_WRONLY | O_CREAT, 0666); dup2(fd_stdout, 1); int fd_stderr = open(StderrPath(file_name).c_str(), O_WRONLY | O_CREAT, 0666); dup2(fd_stderr, 2); //子进程进行程序替换 execl(ExePath(file_name).c_str(), ExePath(file_name).c_str(), NULL); exit(0); } }
把最终结果进行返回,构造json对象,执行到这步,那说明没有什么问题,上面编译和运行出错时都会做错误处理, 这部分我没有贴出来
(*resp)["error"] = 0; (*resp)["reason"] = ""; std::string str_stdout; FileUtil::Read(StdoutPath(file_name), &str_stdout); (*resp)["stdout"] = str_stdout; std::string str_stderr; FileUtil::Read(StderrPath(file_name), &str_stderr); (*resp)["stderr"] = str_stderr; LOG(INFO) << "Program " << file_name << " Done" << std::endl; return true;
至此在线编译模块就告一段落了
题目管理模块
总体思路
获取所有的题目列表
指定题目的详细内容
调用在线编译模块完成代码的编译和运行
根据上述思路,那么我们就需要将题目描述组织起来。
首先基于文件的方式来完成题目的组织,创建一个行文本文件作为总的目录与入口文件
,每一个题目对应一个目录,目录的名字就是题目的id
目录里面包含以下几个文件:
1 题目的详细描述
2. 代码框架
3. 代码测试用例
用一个结构体来表示单个的题目struct Question{ std::string id; std::string name; std::string dir; //题目对应的目录,目录包含了题目描述 //题目的代码框架/题目的测试用例 std::string diff; //难度 std::string desc; //题目的描述 std::string header_cpp; //题目的代码框架中的代码 std::string tail_cpp; //题目的测试用例代码 };
数据存储
整体思路
先打开oj_config.cfg文件(这个文件就是上述的总目录与入口文件,每一行都是一个题目,我选择用\t来作为分割符)
然后按行读取oj_config.cfg文件,并且根据\t进行切分与解析
根据解析出来的结果拼装到上述的结构体中,header.cpp文件与tail.cpp文件分别存放着代码框架中的代码与题目的测试用例代码,在编译运行的时候需要将这两个拼接起来。
最后将结构体存入哈希表中将文件中的数据加载到结构体中
bool Load(){ //打开oj_config.cfg文件,按行读取,解析 std::ifstream file("./oj_data/oj_config.cfg"); if(!file.is_open()){ return false; } std::string line; while(std::getline(file, line)){ //根据解析结果填入结构体 std::vector<std::string> tokens; UrlUtil::Split(line, "\t", &tokens); if(tokens.size() != 4){ LOG(ERROR) << "config file format error!\n"; continue; } Question q; q.id = tokens[0]; q.name = tokens[1]; q.diff = tokens[2]; q.dir = tokens[3]; FileUtil::Read(q.dir + "/desc.txt", &q.desc); FileUtil::Read(q.dir + "/header.cpp", &q.header_cpp); FileUtil::Read(q.dir + "/tail.cpp", &q.tail_cpp); //插入到hash表 _model[q.id] = q; } file.close(); LOG(INFO) << "Load" << _model.size() << " questions\n"; return true; }
然后就是读取全部的题目与读取单个题目了
bool GetAllQuestions(std::vector<Question>* questions) const{ //遍历hash表 questions->clear(); for(const auto& kv : _model){ questions->push_back(kv.second); } return true; } bool GetQuestion(const std::string& id, Question* q) const{ const auto pos = _model.find(id); if(pos == _model.end()){ return false; } *q = pos->second; return true; }
页面显示
这里我们根据数据生成对应的html文件,我们这里使用ctemplate来帮助我们完成。
这里我们需要三个html,一个是所有的题目的html,一个是单个题目的html,还有一个是返回的结果的html。
ctemplate可以使我们所要填充的数据和界面分离,相当于把我们需要计算填入的位置挖出一个空,然后在编写代码的时候,我们将其替换。将所有题目页面进行渲染
static void RenderAllQuestions(const std::vector<Question>& all_questions, std::string* html) { //将所有的题目数据转换成题目列表页html //通过网页模板的方式来构造html //创建一个总的ctemplate对象 ctemplate::TemplateDictionary dict("all_question"); for(const auto& question : all_questions){ //循环往这个对象中田间一些子对象 ctemplate::TemplateDictionary* table_dict = dict.AddSectionDictionary("question"); //每个子对象再设置一些键值对和模板中的{{}}对应 table_dict->SetValue("id", question.id); table_dict->SetValue("name", question.name); table_dict->SetValue("diff", question.diff); } //进行数据的替换,生成最终的html ctemplate::Template* tpl; tpl = ctemplate::Template::GetTemplate( "./template/all_questions.html", ctemplate::DO_NOT_STRIP); tpl->Expand(html, &dict); }
另外两个的渲染原理相同。
服务器
这里构建服务器,使用了第三方库httplib,同时用C++11中的正则表达式,来忽略转义字符,并且获取题号。最终完成三种页面的创建。
int main(){ //加载题库数据 OjModel model; model.Load(); //使用第三方库httplib 来搭建服务器 using namespace httplib; Server server; //所有题目页面 server.Get("/",[&model](const Request& req, Response& resp){ (void) req; //通过model获取所有的题目信息 std::vector<Question> all_questions; model.GetAllQuestions(&all_questions); //将all_questions的数据转换为html std::string html; OjView::RenderAllQuestions(all_questions,&html); //将后端处理完的请求返回给客户端 resp.set_content(html,"text/html"); }); //具体题目的页面 server.Get(R"(/question/(\d+))",[&model](const Request& req, Response& resp){ //通过model获取指定题目的信息 Question question; model.GetQuestion(req.matches[1].str(),&question); //将question的数据转换为html std::string html; OjView::RenderQuestion(question,&html); //将后端处理完的请求返回给客户端 resp.set_content(html,"text/html"); }); //代码运行结果界面 server.Post(R"(/compile/(\d+))",[&model](const Request& req, Response& resp){ //1. 通过model获取指定题目的信息 Question question; model.GetQuestion(req.matches[1].str(),&question); //2. 解析body, 获取用户提交的代码 std::unordered_map<std::string,std::string> body_kv; UrlUtil::ParseBody(req.body,&body_kv); const std::string& user_code = body_kv["code"]; //3. 构造json格式的参数 Json::Value req_json; // 编译的代码 = 用户提交的代码 + 测试用例代码 req_json["code"] = user_code + question.tail_cpp; req_json["stdin"] = user_code; //4. 调用编译模块进行编译 Json::Value resp_json; Compiler::CompileAndRun(req_json,&resp_json); //5. 将运行结果构造成HTML std::string html; OjView::RenderResult(resp_json["stdout"].asString(), resp_json["reason"].asString(),&html); //6. 将后端处理完的请求返回给客户端 resp.set_content(html,"text/html"); });
以上就是全部内容啦
GitHub地址
-
基于java开发的在线OJ系统
2022-04-05 09:29:28在线OJ系统实现像LeetCode,牛客网等在线oj网站所提供的的核心功能,可以实现在线做题,在线判题等功能。核心功能有题目列表展示,题目详情展示,代码编辑并提交,返回用户提交结果等。提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
项目背景
在线OJ系统实现像LeetCode,牛客网等在线oj网站所提供的的核心功能,可以实现在线做题,在线判题等功能。
一、项目说明
1.项目平台与技术栈
- 技术栈:Servlet、Mysql、Runtime,多进程
- 平台与环境:Windows、IDEA、Maven、Tomcat
2.项目功能
- 题目列表页:展示题目id,题目标题,题目难易程度
- 题目详情页:展示某个题的详细信息+代码编辑框
- 提交并运行题目
- 查看运行结果
二、项目演示
1.进入题目列表页
2.进入题目详情页
3.代码编辑框
4.提交代码并运行
三、项目模块划分
1.编译运行模块(compile)
此模块主要用来能让用户进行题目的编译和运行的操作。本模块里边有大致划分为四个模块。
1)执行命令模块(CommandUitl):
这个类里边完成的功能就是能进行编译和运行的指令。要进行编译和运行我们就要创建一个新的进程(子进程),用到的技术有单例设计模式、多进程。
-
里面用run方法来实现这个模块,传入三个参数:指令,将标准输入,输出写入到哪个文件中。
想实现编译运行,就首先需要传入一个指令,其次指令编译运行后,有三种状态:编译运行都成功,编译成功运行失败,编译失败。因此需要获取到标准输出,将其写入到一个文件中,表示编译运行都成功;获取标准错误,将其写入到一个文件,表示编译运行时的错误。 -
单例设计模式和多进程:使用Runtime.exec方法,创建子进程。其参数表示一个可执行程序的路径,执行这个方法,就会把指定的路径的可执行程序,创建出进程并执行。同时Runtime在JVM中是一个单例模式。
-
子进程创建后,父进程要结束的话,要等待子进程结束后才能最终结束所有进程。所以要进行等待子进程的结束,用的是 .waitFor()方法。
-
不管是标准输出还是标准错误里边的内容,我们都采用重定向,即将两个里边的内容放在我们重新创建的文件中,方便后边对文件的读写操作。
public class CommandUtil { public static int run(String cmd, String stdoutFile, String stderrFile){ try { Process process=Runtime.getRuntime().exec(cmd); if (stdoutFile!=null){ InputStream stdoutFrom=process.getInputStream(); FileOutputStream stdoutTo=new FileOutputStream(stdoutFile); while (true){ int ch=stdoutFrom.read(); if (ch==-1){ break; } stdoutTo.write(ch); } stdoutFrom.close(); stdoutTo.close(); } if (stderrFile!=null){ InputStream stderrFrom=process.getErrorStream(); FileOutputStream stderrTo=new FileOutputStream(stderrFile); while (true){ int ch=stderrFrom.read(); if (ch==-1){ break; } stderrTo.write(ch); } stderrFrom.close(); stderrTo.close(); } int exitCode= process.waitFor(); return exitCode; } catch (IOException |InterruptedException e){ e.printStackTrace(); } return 1; }
2)用户编译模块(Question):
当点进具体的题目时,用户编辑代码。因此这个模块完成的功能是用户代码的输入。只需要定义code即可。
public class Question { private String code; public String getCode() { return code; } public void setCode(String code) { this.code = code; } }
3)运行结果模块(Answer):
这个模块用来反馈运行结果给用户,结果包含错误码error,出错原因reason,标准输出信息stdout,标准错误信息stderr。
- 约定error为0表示编译运行都ok,为1编译出错,为2运行出错。reason为出错的提示信息。
public class Answer { private int error; public String getReason() { return reason; } public void setReason(String reason) { this.reason = reason; } private String reason; public String getStderr() { return stderr; } public void setStderr(String stderr) { this.stderr = stderr; } public String getStdout() { return stdout; } public void setStdout(String stdout) { this.stdout = stdout; } private String stdout; private String stderr; public int getError() { return error; } public void setError(int error) { this.error = error; } @Override public String toString() { return "Answer{" + "error=" + error + ", reason='" + reason + '\'' + ", stdout='" + stdout + '\'' + ", stderr='" + stderr + '\'' + '}'; } }
4)编译运行整个过程(Task):
每次的编译运行,都用Task类来完成,里面提供的核心方法为compileAndRun。参数为要编译运行的java源代码。返回值表示编译运行的结果。
- 一次编译运行过程中,会产生很多临时文件,要编译运行的临时文件,编译出错的临时文件、运行出错的标准错误文件、标准输出的临时文件等等。我们将这些临时的文件放在一个目录下,方便后边进程间通信。
考虑到会有多个人进行此系统的操作。因此要保证每一个用户生成的临时文件都有唯一的目录。用UUID来生成唯一目录。 - java中类名和文件名要一致,因此code字符串的类名字,就要和写入的文件名一致。约定:类名和文件名都叫做Solution
- 创建子进程,调用javac进行编译,编译时需要有一个.java文件,因此把question中的code写入到一个Solution.java文件中。编译完成后产生一个.class文件。
创建子进程,执行java运行命令,也就是运行刚编译好的.class文件。父进程获取到刚才编译执行的结果,并打包成Answer对象。
父进程获取到刚才编译执行的结果,并打包成Answer对象。 - 写一个FileUtil类,提供两个方法,一个负责读取整个文件内容,返回一个字符串readFile,另一个方法负责写入整个字符串到文件中writeFile
public class Task { //通过一组常量来约定临时文件的名字 //这个表示所有临时文件所在的目录 private String WORK_DIR="./tmp/"; //约定代码的类名 private String CLASS=null; //要编译的代码文件 private String CODE=null; //存放编译错误信息的文件 private String COMPILE_ERROR=null; //存放运行时的标准输出 private String STDOUT=null; //存放运行时标准错误 private String STDERR=null; public Task(){ WORK_DIR="./tmp/"+ UUID.randomUUID().toString()+"/"; CLASS="Solution"; CODE=WORK_DIR+"Solution.java"; COMPILE_ERROR=WORK_DIR+"compileError.txt"; STDOUT=WORK_DIR+"stdout.txt"; STDERR=WORK_DIR+"stderr.txt"; } public Answer compileAndRun(Question question){ Answer answer=new Answer(); File workDir=new File(WORK_DIR); if (!workDir.exists()){ workDir.mkdirs(); } //1.把question中的code写入到一个Solution.java文件中 FileUtil.writeFile(CODE,question.getCode()); //2.创建子进程,调用javac进行编译,编译时候需要有一个.java文件 // 如果编译出错,javac就会把错误信息给写入到stderr里,就可以用一个专门的文件来保存compileError.txt //需要先把编译命令构造出来 String compileCmd=String.format("javac -encoding utf8 %s -d %s",CODE,WORK_DIR); System.out.println("编译命令"+compileCmd); CommandUtil.run(compileCmd,null,COMPILE_ERROR); //如果编译出错了,错误信息就被记录到COMPILE_ERROR这个文件中,没有出错,空文件 String compileError= FileUtil.readFile(COMPILE_ERROR); if (!compileError.equals("")){ //编译出错,返回Answer System.out.println("编译出错"); answer.setError(1); answer.setReason(compileError); return answer; } //编译正确 String runCmd=String.format("java -classpath %s %s",WORK_DIR,CLASS); System.out.println("运行命令"+runCmd); CommandUtil.run(runCmd,STDOUT,STDERR); String runError= FileUtil.readFile(STDERR); if (!runError.equals("")){ System.out.println("运行出错"); answer.setError(2); answer.setReason(runError); return answer; } answer.setError(0); answer.setStdout(FileUtil.readFile(STDOUT)); return answer; }
2.题目管理模块(problem):
这个模块设计了数据库,封装了数据库操作。此模块实现的功能进行题目的插入、删除、题目列表页的显示和题目详情页的显示。
1)与数据库建立连接
写一个类DBUtil:封装和数据库之间的连接操作。
public class DBUtil { private static final String URL=""; private static final String USERNAME=""; private static final String PASSWORD=""; private static volatile DataSource dataSource=null; private static DataSource getDataSource(){ if (dataSource==null){ synchronized (DBUtil.class){ if (dataSource==null){ MysqlDataSource mysqlDataSource=new MysqlDataSource(); mysqlDataSource.setURL(URL); mysqlDataSource.setUser(USERNAME); mysqlDataSource.setPassword(PASSWORD); dataSource=mysqlDataSource; } } } return dataSource; } public static Connection getConnection() throws SQLException{ return getDataSource().getConnection(); } public static void close(Connection connection, PreparedStatement statement, ResultSet resultSet) { if (resultSet!=null){ try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (statement!=null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection!=null){ try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
2)题目实体类Problem:
这个类是一个实体类,表示的是数据库中的一条记录就对应的是网页上边显示的每一个实例。里边的属性有:题目id,标题,难度,描述,模板代码,测试代码。
public class Problem { private int id; @Override public String toString() { return "Problem{" + "id=" + id + ", title='" + title + '\'' + ", level='" + level + '\'' + ", description='" + description + '\'' + ", templateCode='" + templateCode + '\'' + ", testCode='" + testCode + '\'' + '}'; } private String title; private String level; private String description; private String templateCode; private String testCode; }
3)数据访问类PboblemDAO:
一个Problem对象,就对应着表中的一条记录,还需要针对这个表进行“增删查改”,创建一个ProblemDAO来负责进行增删查改。
-
增 (insert):主要用的是JDBC来操作数据库,基本步骤是:和数据库建立连接、拼装sql、执行sql、断开连接。
-
删(delete):同上,还是JDBC操作数据库,基本步骤:和数据库建立连接、拼装sql、执行sql、断开连接。
-
题目列表页的展示(selectAll):展示题目列表页,题目id,标题,难易程度等。其JDBC操作基本步骤是:建立连接、拼装sql、执行sql、遍历结果集、断开连接。
-
题目详情展示页(selectOne):展示一个题目的所有信息(题目序号、标题、难度级别、题目描述、模板代码、测试代码)。还是要给用户打开页面进行结果集的展示JDBC操作步骤:建立连接、拼装sql、执行sql、遍历结果集、断开连接。
public class ProblemDAO { public void insert(Problem problem){ Connection connection= null; PreparedStatement statement=null; try { //与数据库建立连接 connection = DBUtil.getConnection(); //构造SQL语句 String sql="insert into oj_table values(null,?,?,?,?,?)"; statement=connection.prepareStatement(sql); statement.setString(1,problem.getTitle()); statement.setString(2,problem.getLevel()); statement.setString(3,problem.getDescription()); statement.setString(4,problem.getTemplateCode()); statement.setString(5,problem.getTestCode()); //执行SQL int ret=statement.executeUpdate(); if (ret!=1){ System.out.println("题目新增失败"); }else { System.out.println("题目新增成功"); } } catch (SQLException e) { e.printStackTrace(); }finally { DBUtil.close(connection,statement,null); } } public void delete(int id){ Connection connection= null; PreparedStatement statement=null; try { //和数据库建立连接 connection=DBUtil.getConnection(); //拼装sql语句 String sql="delete from oj_table where id = ?"; statement=connection.prepareStatement(sql); statement.setInt(1,id); //执行SQL int ret=statement.executeUpdate(); if (ret!=1){ System.out.println("删除题目失败"); }else { System.out.println("删除题目成功"); } } catch (SQLException throwables) { throwables.printStackTrace(); }finally { DBUtil.close(connection,statement,null); } } public List<Problem> selectAll(){ List<Problem> problems=new ArrayList<>(); Connection connection=null; PreparedStatement statement=null; ResultSet resultSet=null; try { //和数据库建立连接 connection=DBUtil.getConnection(); //拼装SQL String sql="select id,title,level from oj_table"; statement=connection.prepareStatement(sql); //执行SQL resultSet=statement.executeQuery(); //遍历resultSet while (resultSet.next()){ Problem problem=new Problem(); problem.setId(resultSet.getInt("id")); problem.setTitle(resultSet.getString("title")); problem.setLevel(resultSet.getString("level")); problems.add(problem); } return problems; } catch (SQLException throwables) { throwables.printStackTrace(); }finally { DBUtil.close(connection,statement,resultSet); } return null; } public Problem selectOne(int id){ Connection connection=null; PreparedStatement statement=null; ResultSet resultSet=null; try { connection=DBUtil.getConnection(); String sql="select * from oj_table where id = ?"; statement=connection.prepareStatement(sql); statement.setInt(1,id); resultSet=statement.executeQuery(); if (resultSet.next()){ Problem problem=new Problem(); problem.setId(resultSet.getInt("id")); problem.setTitle(resultSet.getString("title")); problem.setLevel(resultSet.getString("level")); problem.setDescription(resultSet.getString("description")); problem.setTemplateCode(resultSet.getString("templateCode")); problem.setTestCode(resultSet.getString("testCode")); return problem; } } catch (SQLException throwables) { throwables.printStackTrace(); }finally { DBUtil.close(connection,statement,resultSet); } return null; }
3. API模块:
此模块用来实现前后端的交互。主要由两个类构成,CompileServlet,ProblemServlet
- 要实现交互的页面:
题目列表页:向服务器请求题目列表
题目详情页:展示题目详细要求,向服务器请求,获取指定题目的详细信息。
题目详情页:向服务器发送用户当前编写的代码,并获取到结果。 - 具体设计前后端交互的API:通过JSON格式来组织,引入第三方库Jackson
1)CompileServlet类(获取用户提交代码后的结果)
- 编译请求CompileRequest:向服务器发送用户编写的代码,服务器只需要获取题目id对应到题目和用户提交的代码code即可。
- 编译响应CompileResponse:在给服务端发送请求之后,返回响应,响应里边包含返回码error(0编译运行都ok,1表示编译出错,2表示运行出错)、错误原因reason、标准输出(测试用例的输出情况,包含了通过几个测试用例这样的信息)。
- 编译Servlet:CompileServlet
这个模块主要是要完成数据格式之间的转换,完成客户端和服务端之间的相互解析响应全过程。
(1)先读取请求的正文,按照JSON格式进行解析
(2)根据id从数据库中查找到题目的详情,得到测试用例代码
(3)把用户提交的代码和测试用例的代码,给拼接为一个完整代码
(4)创建一个Task实例,调用里面的compileAndRun,来进行编译运行。
(5)根据Task运行的结果,包装成一个HTTP响应
@WebServlet("/compile") public class CompileServlet extends HttpServlet { static class CompileRequest { public int id; public String code; } private class CompileResponse { public int error; public String reason; public String stdout; } private ObjectMapper objectMapper=new ObjectMapper(); @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("用户的当前工作目录:"+System.getProperty("use.dir")); CompileRequest compileRequest=null; CompileResponse compileResponse=new CompileResponse(); try { resp.setStatus(200); resp.setContentType("application/json;charset=utf8"); // 1.先读取请求的正文,并按照JSON 格式进行解析 String body = readBody(req); compileRequest=objectMapper.readValue(body,CompileRequest.class); // 2.根据id从数据库中查找到题目的详情=> 得到测试用例代码 ProblemDAO problemDAO=new ProblemDAO(); Problem problem=problemDAO.selectOne(compileRequest.id); if (problem==null){ throw new ProblemNotFoundException(); } String testCode=problem.getTestCode(); String requestCode=compileRequest.code; // 3.把用户提交的代码和测试用例代码,给拼接成个完整的代码. String finalCode=mergeCode(requestCode,testCode); if (finalCode==null){ throw new CodeInValidException(); } //System.out.println(finalCode); // 4.创建一个Task 实例,调用里面的compileAndRun 来进行编译运行 Task task=new Task(); Question question=new Question(); question.setCode(finalCode); Answer answer=task.compileAndRun(question); // 5.根据Task运行的结果,包装成一个HTTP响应 compileResponse.error=answer.getError(); compileResponse.reason=answer.getReason(); compileResponse.stdout=answer.getStdout(); } catch (ProblemNotFoundException e) { compileResponse.error=3; compileResponse.reason="确认没有找到指定题目 id="+compileRequest.id; } catch (CodeInValidException e) { compileResponse.error=3; compileResponse.reason="提交的代码不符合要求"; }finally { String respString=objectMapper.writeValueAsString(compileResponse); resp.getWriter().write(respString); } } private static String mergeCode(String requestCode,String testCode){ int pos=requestCode.lastIndexOf("}"); if (pos==-1){ return null; } String subStr=requestCode.substring(0,pos); return subStr+testCode+"\n}"; } private static String readBody(HttpServletRequest req) throws UnsupportedEncodingException { // 1.先根据请求头里面的ContentLength 获取到body 的长度(单位是字节) int contentLength=req.getContentLength(); // 2.按照这个长度准备好一个byte[]. byte[] buffer=new byte[contentLength]; // 3.通过req 里面的getInputStream 方法,获取到body 的流对象。 try(InputStream inputStream=req.getInputStream()){ // 4.基于这个流对象,读取内容,然后把内容放到byte[] 数组中即可. inputStream.read(buffer); } catch (IOException e) { e.printStackTrace(); } // 5.把这个byte[] 的内容构造成个String return new String(buffer,"utf8"); } }
2)ProblemServlet类
这个模块是用来展示题目列表页和题目详情页的。
- doGet方法:通过创建problemDao请求对象,向服务端发起请求,在数据库中查找结果并转化成Json格式最终返回给客户端界面。完成的功能是展示题目列表页和题目详情页。
@WebServlet("/problem") public class ProblemServlet extends HttpServlet { private ObjectMapper objectMapper=new ObjectMapper(); @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setStatus(200); resp.setContentType("application/json;charset=utf8"); ProblemDAO problemDAO=new ProblemDAO(); String idString=req.getParameter("id"); if (idString==null||"".equals(idString)){ List<Problem> problems=problemDAO.selectAll(); String respString=objectMapper.writeValueAsString(problems); resp.getWriter().write(respString); }else{ Problem problem=problemDAO.selectOne(Integer.parseInt(idString)); String respString=objectMapper.writeValueAsString(problem); resp.getWriter().write(respString); } } }
总结
以上就是今天要讲的内容,本文介绍了在线oj系统的核心功能:展示题目列表页,题目详情页,提交代码和显示结果如何实现。至于前端页面,小伙伴们可以自行实现想要的页面。欢迎大家讨论。
-
燕山大学Linux的OJ系统设计
2018-07-18 18:00:49燕山大学Linux的OJ系统设计,详细可以看讲解: https://blog.csdn.net/LZJSTUDY/article/details/81101071 -
离线OJ系统
2016-01-13 00:35:58离线OJ系统,支持离线盘题!acm无等待! -
XTUOJ:XTU在线评审系统
2021-04-05 13:29:04湘潭大学OJ系统 前言 关于合理的逻辑:湘潭大学OJ系统是基于vue2.0的vue-cli脚手架应用。应用所需要的到用的功能会试图包装为组件,保证在需要的时候能够更加方便的补充。 关于变量名:该应用的数据变量名,方法名... -
OJ系统蓝桥杯题库
2018-12-03 17:47:13OJ系统的蓝桥杯题库,http://oj.xpuca.top/,这里有这些题的栗子。 -
基于Linux的OJ系统的设计与开发(一)
2021-05-18 15:42:41最近在研究基于linux的OJ系统,然后想自己写一系列文章记录自己这段时间的学习成果。首先,从原理上讲,OJ功能实现并不难,最主要解决的是安全性问题。总结一下,而安全性方面问题主要是用户可能提交恶意不友好的... -
OJ系统研究现状
2020-09-25 16:42:31研究现状 参考: [1]杨博,张能,李善平,夏鑫.智能代码补全研究综述[J]....[2]尹青山.基于群组信息改进矩阵分解的群组推荐方法[J]....开源OJ系统:https://github.com/QingdaoU/OnlineJudge 微服务框架Spring C -
基于Java实现简单的在线 OJ 系统
2021-07-12 17:33:56里边的功能深深的吸引了我,所以我尝试去实现一个类似于力扣网的在线OJ系统。 实现功能: 根据我们在力扣网上的参考,想实现让用户在一个网页上进行读题,做题,提交,最终反馈给用户结果、在操作过程中还要实现对... -
在线OJ系统--第二章(业务层编写)
2022-03-01 17:19:14create table oj_table( id int primary key auto_increment, //主键 title varchar(50), //标题 level varchar(50), //难度级别 description text, //题目描述 templateCode text, //题目模板代码 testCode ... -
宁波大学OJ系统240题.pdf
2020-03-24 23:22:48宁波大学OJ系统的240个ACM题目,题目难度呈阶梯级上升,适合编程新手,有编程基础的可挑选里面经典或者相对困难的题目。特别是计算机专业参加考研复试的同学,一定要完成里面的题目。 -
OJ系统题目:数组求能量.docx
2020-10-21 00:10:05试一下上传,趟趟水,题目是自己写的,然后当时自己保存的,下载后可以直接编译运行,至于文件我自己后来也没阅读了 -
搭建OJ系统
2020-07-13 04:30:40搭建个自己的OJ系统玩玩 -
leetcodeoj调试-TestArch:在LeetCode上或者其他Oj系统中刷题时,如何快速判定程序的正确性。本项目对程序的...
2021-07-01 00:22:27在LeetCode上或者其他Oj系统中刷题时,如何快速判定程序的正确性。本项目对程序的正确性进行了一定程度的测试。首先我们可以写出一个枚举的算法,不要求该算法时间复杂度小,但一定要保证该算法的正确性。通常枚举... -
【项目】基于C++搭建的OJ系统
2021-01-27 19:18:41一、功能总览 1.1 项目介绍 实现一个类似牛客、leetcode的在线OJ系统,用户可以选择试题,并提交代码,通过网络传输,上传到服务器后台,后台对代码进行编译运行,并把测试结果反馈给客户端浏览器 1.2 环境搭建 ... -
各种OJ系统
2020-09-23 12:02:18国内Online Judge 非常活跃的hdu ...杭电OJ是国内最为活跃的OJ,每周都会举办bestcoder比赛,...三大老牌OJ 浙江大学 http://acm.zju.edu.cn 超过2000题,支持C/C++/Pascal/Java/Python/Perl/Scheme/PHP 北京大学 http: -
编程:OJ系统Java语言编程技巧
2021-02-26 14:07:28OJ系统Java语言编程技巧常见题型:最常见题型还是依赖于数组和字符串,需要熟练的操作,而java针对数组还有字符串都提供了大量的方法。可以简化编程,同时也是对编程语言的一种掌握。下面罗列一些自己经常遇到很好用... -
基于SSH三层架构的OJ系统研究与设计
2012-06-07 22:53:14基于SSH三层架构的OJ系统研究与设计 -
OJ系统汇总-2021-12-24(d).pdf
2021-12-25 10:05:52OJ系统汇总-2021-12-24(d).pdf -
ACM等OJ系统的各类输入输出
2021-06-18 11:57:12ACM等OJ系统的各类C/C++输入输出说明(ACM等OJ系统的各类输入输出)注意点效率问题关于分隔关于换行关于输入输出重定向各类数据类型循环场景复杂分割 说明(ACM等OJ系统的各类输入输出) 这里用于记录ACM等OJ系统的...