在线OJ服务器
简介
界面预览
使用框架
httplib
ctemplate
前端
all_questions
question
服务器搭建
oj_server.cpp
在线编程下的GET和POST请求处理逻辑
GET
POST
oj_model.hpp
TestQues结构体
OjModel类
GetAllQuestions
GetOneQuestion
tools.hpp
StringTools类
FileOpen类
UrlUtil类
oj_view
compile
编译处理
源代码
可能出现的问题
简介
Linux网络编程,仿照牛客网,搭建在线OJ网站服务器,实现选择题目,并进行对应题目的在线编程提交给服务器,并在服务器中验证编译结果,再返回给浏览器。
由于在线oj的测试用例难以获取,所以目前只有一道题目
界面预览
图片仅作为页面填充
主页面
在线编译页面
使用框架
httplib
cpp-httplib,一个header-only的第三方框架,封装了http协议,使用起来十分方便,只需要包含其头文件即可
https://github.com/yhirose/cpp-httplib
ctemplate
ctemplat是一个进行html渲染,实现视图与配置内容的分离,通过 {{ }} 占位符进行替换,能够根据程序动态变化页面中所要显示的内容。
前端
因为博主只是简单学习过前端知识,HTML,CSS,JS多少会一点,所以所使用的前端技术都是基础。
all_questions
在线OJtitle>head>
![title.png](./img/title.png)
首页>在线编程>剑指offerdiv>
该专题为剑指offer专题,题目均来自《剑指offer》,里面每道题带有练习模式和考试模式,可还原考试模式进行模拟,也可通过练习模式进行练习。
p>
div>
题号th>
考点th>
题目th>
难度th>
通过率th>
tr>
{{#question}}
{{id}}td>{{family}}td> {{name}}a>td>{{difficulty}}td>{{percent}}td>tr>
{{/question}}
table>
div>
div>
var t = new Date();
var arr = new Array("星期日","星期一","星期二","星期三","星期四","星期五","星期六");
document.write(arr[t.getDay()]);
document.write("
");
document.write(" "+(t.getMonth()+1)+"-"+t.getDate());script>
h3>
div>
求职小鱼h3>
div>
body>
html>
这里的{{#question}} {{/question}}是用来定义子区域,通过ctemplate渲染后能够实现循环输出区域内的内容。
css就不多作介绍感兴趣可以去git上查看源码
通过改变页面中各个标签的浮动,实现像图片中各个小区域,类似瀑布流的显示效果
当中的JS代码仅是用来获取当前日期
question
在线编程title>head>
时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
热度指数:4692
pre>div>
题目描述h3>
{{desc}}p>
示例h4>
输入p>
{{inp}}p>div>
输出p>
{{outp}}p>div>
div>
div>
{{header}}
textarea>
{{comp}}
textarea>
form>
div>
body>
html>
在线编译界面分为左右两个区域,左边区域用于显示当前问题的描述信息,是能够上下滑动的,所以将左边区域设为绝对定位,而右边黑色区域是编辑的输入框,除了textarea内部能上下滚动,整个黑色部分是不能上下移动的,所以右边的定位为fixed
服务器搭建
代码体量也较大,所以不再博客中展示,可以在git中对照查看,跳转
oj_server.cpp
整个服务器的入口。并定义/all_questions下的GET请求处理逻辑,已经/question/re(\d+),此处通过正则表达来加载不同题目的地址,并定义其GET和POST请求的处理逻辑。
在线编程下的GET和POST请求处理逻辑
GET
get请求中获取单个题目文件夹中的所有信息,读取信息后通过模板技术填充到页面当中,GET也请求相对简单
POST
post请求,需要向服务器提交form表单中的信息
牛客网中的效果是在点击保存并调试时,在编程局域下方输出运行结果,而页面不会切换
因为博主完全不懂Ajax这类相关技术,所以想了一个偷鸡的方法,实现类似的效果
每次提交的时候,将用户提交的代码,保存到问题目录下的一个save.cpp文件当中,同样的将编译运行结果也存放在一个result.txt文件中
每次提交时重新渲染一次页面,将save.cpp与result.txt中的内容与{{header}}和{{comp}}替换来实现类似效果
效果如下
oj_model.hpp
加载问题中的所相关内容
TestQues结构体
定义结构体,用于存放题目的相关信息,名称,路径等
OjModel类
类中通过unordered_map的关联索容器存放题目信息,因为unordered_map底层的哈希结构,查找效率十分高效。
GetAllQuestions
读取conf文件中的信息并存放在map中,并按照id排序,升序显示,该方法主要用来显示所有问题。
GetOneQuestion
出参,通过id在map中查找到对应题目的路径,通过FileOpen::ReadDataFromFile工具类中的方法读取对应文件中的内容,并通过参数传出。
tools.hpp
自己封装的读写文件的工具类
StringTools类
通过boost库中的split方法,可让读取的内容按要求分开,并存放到vector中
FileOpen类
封装的两个静态方法读写文件,不多介绍
UrlUtil类
这个是在网上找的解析url请求正文中的内容的封装类
oj_view
模板类技术的封装,主要用于填充all_questions.html和question.html页面中的内容
ctemplate的使用,首先定义一个TemplateDictionary的字典
使用SetValue的方法,将html中的占位符与变量中的内容替换,最后打开Template* 的操作句柄打开html文件,与打开文件类似,使用Expand方法,将替换后的html文件以字符串的形式传出函数。
all_questions的唯一不同是有一个{{/question}}这样的子区域,需要在循环中替换占位符,所以每次循环都要定义一个AddSectionDictionary的指针。
compile
编译运行模块
编译处理
每一个文件的路径中,都有一个tail.cpp的处理程序,浏览器提交的程序,与tail中的main函数合为一个cpp文件,然后fork出一个子进程,子进程通过execv进程程序替换,g++编译处理,生成并打开一个tmp文件存放到tmp路径下,dup2重定向,将编译的标准输出重定向到tmp文件当中,就可以保存编译的错误信息。
源代码
https://github.com/akh5/Linux/tree/master/OJ1.2
可能出现的问题
因为httplib的构造使用了正则表达式,而gcc4.8的正则表达式有bug,所以可能导致gcc编译得到的可执行文件,一旦运行就有可能抛出如图所示的正则表达式异常,而导致无法运行。
所以在编译前需要启动gcc的版本升级
source /opt/rh/devtoolset-4/enable
程序运行后,ip地址无法访问,大概率是linux防火墙导致
所以程序运行前需要先关闭防火墙
systemctl stop firewalld