项目笔记------------C++实现的在线oj系统
项目介绍
本项目是仿照牛客网和力扣网实现的一个简易的在线oj系统,系统包含任意题目展示功能、在线作答功能、在线编译功能,其中在线编译功能也可以独立出来,实现一个简易的在线编译器。三个功能分别是三个页面,下面是项目测试的部分截图。
项目流程
- 当输入地址信息,服务端会收到请求,服务端通过对请求的解码与分析,获取信息,并返回
- 如果输入的地址为题目列表信息,服务端收到GET请求后,会返回所有的题目信息
- 如果收到的是详细题目信息,服务端收到GET请求后,会通过查询题目获取对应题目的信息,再返回
- 作答完成后,点击提交键进行提交,服务端会收到POST请求,通过对提交代码的编译运行,返回测试结果
- 用户在点击提交之后,服务端返回的测试结果会显示在新的页面上
项目实现
从上面就一直在说,项目分为三部分,因此我们也是从三个方面来实现
题目展示
首先将我们存储在本地的题目信息加载到内存中。我们创建一个总的文件夹,里面保存所有的题目。在此文件夹中,有一个配置文件configure
,文件中按行保存着每个题目的信息,格式为题号 题目名 题目等级 题目信息所在文件夹
,每个元素都按照制表符分隔开。题目信息所在文件夹
是我们为每个题目都创建的一个对应的文件夹,文件夹名为题目序号,在文件夹中保存着题目的描述信息
、默认接口
、测试用例
。最后,创建一个结构体来保存题目信息,结构体的成员包括序号 id
题目名 name
题目难度 star
题目信息所在文件夹 dir
题目描述desc
题目默认接口 header
测试用例 tail
。
创建一个类,专门用来管理题目数据的加载,类包括三个静态成员函数加载信息
获取所有题目信息
获取单个题目信息
以及一个成员变量model
题目信息,这是一个STL中map
容器的对象,其中题目号为键,题目信息为值(这里使用map
的原因为,如果点击了所有题目页面的某个题目,就需要根据题号查询题目,而map
存储的结构查询的效率更高)。我们首先循环按行读取配置文件configure
,每一行都是一个题目。在循环中,创建一个题目对象,将从configure
中读到的行信息按照指标符切分,将切分得到的结果存储到题目对象的对应部分。为题目对象的成员变量dir
分别加上对应文件夹中的文件名,再次读取,这样就读取到了具体的题目文件中题目的描述、默认接口和测试用例。每读完一个变量,就将它存储map
中。因为在加载页面时,所有题目页面和单个题目详情页面都会用到题目信息,因此,我们应该在函数一开始就将题目信息加载进来,存储进对象中,等到使用的时候,直接获取即可。
加载完成后,我们再将数据显示到网页中,在前面我们已经获取到了所有题目的信息,在这里,如果我们需要显示所有题目的信息,我们传入一个数组的引用,将所有数据信息返回,如果需要显示单个题目的信息,那么就传入一个对象的引用,获取该题目信息并显示。这里具体的html页面是借助ctemplate
库实现的,创建html页面模板,在函数中将刚才得到的数据传入,即可显示。具体操作来自百度。
在线作答
首先,在线作答的界面在题目的详细界面,也就是从所有题目信息中点进去后跳转到的单个题目详情界面。
在客户端点击提交之后,服务端就会收到数据,这里对数据的处理我们使用的也是网上的库httplib
库,具体的使用方法来自百度。创建一个题目对象,通过调用httplib
库中相应的函数,我们可以得到具体的题号。由于我们已经从前面读取到了所有的题目信息,因此本次我们将题号和创建的题目对象传入,获取到对应的题目信息,我们对http的请求报文进行先切分再解码。这里的http请求报文切割与解码后,我们得到的也是类似于JSON格式的键值对,我们将它存储到STL的容器unordered_map
中(使用它的原因是查询快,使用Key
访问时速度快)。因为这里用到了键值对的方式,因此在html的网页文件中,输入框的名称应该为code
,这样就能通过code
为键值得到我们的代码,代码包括了在输入框中默认显示的默认代码和我们输入的代码,我们将题目对象的测试用例代码与我们的代码拼装到一起,再重新写入一个新的文件中,等待下一步编译。
在线编译
当走到编译这步时,剩余的工作就很简单了,为了保存编译与运行过程中的信息,我们需要创建文件进行输出流重定向,让我们的输出信息输出到文件中。
在编译阶段,需要保存的是标准错误流,运行阶段,需要保存标准输出流以及标准错误流。
我们创建一个子进程,通过调用系统调用接口dup2
将该子进程的标准错误流替换为我们创建的保存错误信息的文件的文件描述符,然后将子进程替换为g++
,进行文件的编译。如果编译失败了,那么返回false。如果编译成功了,就返回true,进行下一步运行。
运行文件同样需要创建一个新的进程,在运行文件之前,先调用系统调用接口dup2
将子进程的标准输入流,标准输出流以及标准错误流与我们打开的对应的文件描述符交换,交换完成后,运行我们的程序,如果程序运行有错误,那么就返回false,否则返回true。
编译和运行被封装为两个不同的函数分别调用,如果返回的是true,那么服务器读取存储标准输出流信息的文件,并将其转换为http回复报文,返回给客户端。如果返回的是false,那么就读取存储标准错误流信息的文件,将其转码,并返回给客户端。
注意事项
- 在获取到客户端的请求后,需要对http报文进行切分和解码,完成后得到的是键值对的形式,为了保证对其查找的效率,这里采用了
unordered_map
来存储。 - 在从配置文件中读取到题目的信息后,得到的也是键值对的数据格式,为了保证题目的输出有序和查询效率,这里采用了
map
来存储题目信息。 - 用到了四个第三方库
josn
httplib
ctemplate
boost
关于它们的使用,需要了解。 - 对于http报文的处理,需要先切割再解码,因为数据中可能含有分隔符
- 在编译运行阶段,为了保证可以存储到运行信息,需要先进行重定向,再进行程序替换。