关于在线OJ训练营项目的设计思路
Posted 哦哦呵呵
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于在线OJ训练营项目的设计思路相关的知识,希望对你有一定的参考价值。
目录
1. 项目描述
该系统类似于LeetCode的代码练习功能,提供了一个用户可以在线编写代码并且能够进行编译的环境,在编写代码的同时提供了语法纠错、代码高亮、自动补全等基本功能。
用户可以通过域名访问服务器,系统内置了多道编程题,用户点击对应题目就可以进行练习。并且题目内含有大量测试样例,服务器会根据用户代码会进行用例的测试,检测用户代码是否符合题意。并且可以将编译出错的原因返回给用户端。
2. 模块
2.1 在线编译器模块
能够将网页中用户输入的代码内容放入服务器中,并进行编译,运行,比对结果。
实现功能
- 获取需要编译代码的临时文件
- 调用g++进行编译,将编译结果进行存储
- 如果文件编译错误或运行时错误,需要记录到文件中去
- 运行可执行文件,执行测试用例代码,将运行结果进行记录
- 将记录结果发送给客户端
2.2 题目管理模块
管理当前系统中的所有OJ题目及具体题目描述,用例代码。页面以列表形式显示所管理的题目,并且用户点击题目可以跳转到详情页进行代码的编写及提交。
具体功能
- 获取所有的题目列表
/all_question
- 点击指定题目,可以获取题目的详细内容,并且可以书写代码
/quesion
- 调用在线编译模块进行编译和运行
/compile
- 解析运行结果,如果正确返回正确结果,如果错误返回错误情况
result
2.3 工具模块
该模块提供了许多具体工具,可以提高开发效率。
具体工具
- 时间戳工具: 用来生成不重复文件名
文件名: tmp_ + “时间戳” + 序号 + ‘.类型’ (保证了文件名的唯一性)- 日志打印工具: 用来记录代码执行记录
- 文件读写功能: 重新封装文件读写函数,方便使用
- URL解析模块: 使用boost库提供的函数进行解析,解析出有效载荷,分离出用户提交代码与用户输入。
3. 模块实现细节
3.1 在线编译
3.1.1 搭建HTTP服务器
使用第三方库HttpLib
进行搭建,搭建出的服务器支持多连接。通过库中的函数进行请求的接收与响应。
server.Post("/compile", [](const Request& req, Response& resp) {
});
server.listen("0.0.0.0", 9081);
3.1.2 JSON组织请求与响应
使用JSON进行请求与相应的组织,因为http中传来的数据是结构化的数据,将该数据进行序列化,而JSON可以简单且高校的组织结构化数据,并且以键值对的形式进行组织。
网络中传输更加方便,并且可以与HTTP无缝衔接。
使用JSON组织请求和响应的格式:
此处看到的请求参数和响应结果格式为
请求{
"code": "#include....."
"stdin": "hello"
}
响应{
"error": 0结果正常, 1编译错误, 2运行错误, 3其它错误
"reason": "",错误原因
"stdout": "hello",
"stderr": "",
}
3.1.3 处理请求生成响应
用户编写完成代码之后,将代码通过POST
或GET
的方式将内容提交给服务器,服务器就需要进行请求的解析,来处理数据。
但是浏览器在发送请求时,可能会携带多组数据,所以在服务端就需要解析客户端发来的多组数据,提取出存储代码的字段交给编译模块进行处理,并且浏览器会对有效载荷中的字符进行转义,还需要将转义后的字符转义回来。
以上操作执行完之后,才可以调用编译模块进行代码的处理。
3.1.4 编译模块对代码的处理
在这一部分已经拿到了,用户提交的代码,根据代码编译出可执行程序,就需要服务器生成临时文件存储源代码,才能进行编译。
并且在编译或运行过程中,可能会出现错误,所以需要记录过程中可能会出现问题的记录信息。
编译成功调用可执行程序,将标准输入记录到文件中,把文件中的内容重定向给可执行程序,可执行文件的标准输出也重定向到文件中,方便后期使用。
步骤
- 根据json请求对象,生成源代码文件和标准输入文件
- 调用g++进行编译(fork + exec), 生成一个可执行程序,如果编译出错需要记录下来(重定向到文件)
- 调用可执行程序,把标准输入记录到文件中,把文件中的内容重定向给可执行程序,可执行程序的标准输出重定向至文件
- 把程序中的最终结果进行返回,构造resp_json对象
编译过程
- 先构造出编译指令
g++ file_name.cpp -o file_name.exe -std=c++11
- 创建子进程
- 父进程等待子进程退出,防止出现僵尸进程
- 子进程进行程序替换,将标准错误重定向至编译时出错的文件中.
- 编译完成后,通过
stat()
函数判断可执行文件是否生成。生成代表编译成功。
{
// 4.子进程进行程序替换
// 将编译时错误的信息输出到文件中
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);
}
注意:上方程序中先进行了文件描述符的重定向,再进行的进程替换,注意进程替换只会替换程序的代码段和数据段。不会替换PCB,而PCB中存储文件描述符,所以不会影响重定向关系。
执行可执行程序
创建子进程,并且将可执行程序的标准输入、标准输出、标准错误重定向到文件中,记录可执行程序执行的每一个细节。
3.2 题目管理
采用MVC的结构进行管理题目
Model: 题目的描述结构
View: 网页的显示内容
Controller: 负责逻辑处理
3.2.1 如何描述题目(Model)
哪些信息可以描述一个题目
基本结构如上图,在此项目中采用文件的形式进行组织
基于文件的方式完成文件的存储
- 约定每一个题目对应一个目录,目录的名字就是题目的id.
目录包含以下几个文件:
1.header.cpp 代码矿建
2.tail.cpp 代码测试用例
3.desc.txt 题目详细描述- 使用一个oj_config.cfg文件啊,作为一个总的入口文件,这个文件是一个行文本文件.这个文件的每一行对应一个需要被服务器加载起来的题目,一行里面包含以下几个信息: 题目id,题目名字,题目难度,题目对应目录
3.2.2 题目信息加载 (Model)
题库中的题目是很多的,需要将题库中的信息进行组织,通过map
将题目信息全部组织进去,在all_questions
页面中进行题目列表的展示。
- 先打开oj_config.cfg文件
- 按行读取 oj_config.cfg文件,并进行解析,一行包含4个字段,字段间的分隔是 \\t
- 根据解析结果拼装成 Question结构体
- 把结构体加入到map中
上面将题目加载到了内存中,只需要遍历map就可以有序的取出所有题目了。
获取某个具体题目,需要传入题目id,通过在map中检索id,就可以拿到题目详情。
3.2.3 渲染页面(View)
上一步获取到题目列表后,就需要将列表显示出来生成html页面,这一过程叫做渲染。在渲染过程用用到了google的开源库ctemplate
模板库,帮助我们构建页面。
在oj_view.hpp
中,添加好了网页需要填空的数值,需要一个网页来映射这些值。
在C++代码中直接通过字符串拼接的方式构造html太麻烦, 通过网页模板的方式解决问题
模板类似于填空题,实现准备好一个html把其中一些需要动态计算的数据挖个空留下来,处理请求过程中,根据计算结果填空
1.先创建一个 ctemplate对象,借助这个对象完成填空过程,是总的组织的对象
2.循环的往这个对象中添加一些子对象
3.每一个子对象再设置一些键值对(和模板中留下的{{}}对应的)
4.进行数据的替换,生成最终的html
3.2.3 服务程序(Controller)
作为服务器的核心逻辑,需要创建好对应的服务器框架代码,在这个框架中来组织逻辑
- 启动服务器,加载题目信息
- 获取题目信息,使用题目信息生成题目列表对应的HTML页面
- 用户点击某一题后,根据题目id,进入题目详情页并且进行代码编写
- 用户提交代码后,按照编译逻辑进行编译,将结果返回给用户。
1.先根据id获取到题目信息
2.解析body,获取到用户提交的代码,将Http中的body,解析成键值对,存入body_kv
3.构造JSon结构的参数,需要编译的代码,是用户提交代码 + 题目的测试用例代码
4.调用编译模块进行编译
5.根据编译结果构成最终的网页
总结
以上是关于关于在线OJ训练营项目的设计思路的主要内容,如果未能解决你的问题,请参考以下文章