Linux网络编程,仿照牛客网,搭建在线OJ网站服务器,实现选择题目,并进行对应题目的在线编程提交给服务器,并在服务器中验证编译结果,再返回给浏览器。
由于在线oj的测试用例难以获取,所以目前只有一道题目
cpp-httplib,一个header-only的第三方框架,封装了http协议,使用起来十分方便,只需要包含其头文件即可
https://github.com/yhirose/cpp-httplib
ctemplat是一个进行html渲染,实现视图与配置内容的分离,通过 {{ }} 占位符进行替换,能够根据程序动态变化页面中所要显示的内容。
因为博主只是简单学习过前端知识,HTML,CSS,JS多少会一点,所以所使用的前端技术都是基础。
<html lang="en">
<head>
<meta charset="UTF-8">
<title>在线OJtitle>
<link href="all_q.css" rel="stylesheet" type="text/css">
head>
<body>
<div id="header"><img src="./img/title.png">div>
<div style="position: relative; top:20px; left: 15%;margin-bottom: 20px">首页>在线编程>剑指offerdiv>
<div id="content">
<div id="intro"><h2 style="margin-bottom: 10px">专题训练h2>
<p style="margin: 20px">
该专题为剑指offer专题,题目均来自《剑指offer》,里面每道题带有练习模式和考试模式,可还原考试模式进行模拟,也可通过练习模式进行练习。
p>
div>
<div id="questions">
<table>
<tr>
<th style="width: 50px">题号th>
<th style="width: 160px">考点th>
<th style="width: 320px">题目th>
<th style="width: 100px">难度th>
<th style="width: 80px">通过率th>
tr>
{{#question}}
<tr>
<td>{{id}}td>
<td>{{family}}td>
<td><a href="/question/{{id}}">{{name}}a>td>
<td>{{difficulty}}td>
<td>{{percent}}td>
tr>
{{/question}}
table>
div>
div>
<div id="else">
<div id="t">
<h3 style="display: inline;margin: 10px; width:15px" >
<script type="text/javascript">
var out = document.getElementById("t");
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>
<div id="scult">div>
<h3 style="text-align: center;margin-top: 5px">求职小鱼h3>
div>
body>
html>
这里的{{#question}} {{/question}}是用来定义子区域,通过ctemplate渲染后能够实现循环输出区域内的内容。
css就不多作介绍感兴趣可以去git上查看源码
通过改变页面中各个标签的浮动,实现像图片中各个小区域,类似瀑布流的显示效果
当中的JS代码仅是用来获取当前日期
<html lang="en">
<head>
<meta charset="UTF-8">
<title>在线编程title>
<link rel="stylesheet" href="one_q.css" type="text/css">
head>
<body>
<div class="detail">
<div id="intro"><pre>时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 256M,其他语言512M
热度指数:4692
pre>div>
<h3>题目描述h3>
<p id="desc">{{desc}}p>
<h4>示例h4>
<div id="des">
<p>输入p>
<div class="print"><p>{{inp}}p>div><br>
<p>输出p>
<div class="print"><p>{{outp}}p>div><br>
div>
div>
<div class="pram">
<form action="/question/{{id}}" method="POST">
<textarea style="height: 500px" class="inp" name="code">
{{header}}
textarea>
<hr>
<textarea style="height: 150px" class="inp">
{{comp}}
textarea>
<label><div id="save"><h3 style="color: white;text-align: center;line-height: 10px">保存并调试h3>div><input type="submit" formenctype="appliaction/json" style="display: none">label>
form>
div>
body>
html>
在线编译界面分为左右两个区域,左边区域用于显示当前问题的描述信息,是能够上下滑动的,所以将左边区域设为绝对定位,而右边黑色区域是编辑的输入框,除了textarea内部能上下滚动,整个黑色部分是不能上下移动的,所以右边的定位为fixed
代码体量也较大,所以不再博客中展示,可以在git中对照查看,跳转
整个服务器的入口。并定义/all_questions下的GET请求处理逻辑,已经/question/re(\d+),此处通过正则表达来加载不同题目的地址,并定义其GET和POST请求的处理逻辑。
get请求中获取单个题目文件夹中的所有信息,读取信息后通过模板技术填充到页面当中,GET也请求相对简单
post请求,需要向服务器提交form表单中的信息
牛客网中的效果是在点击保存并调试时,在编程局域下方输出运行结果,而页面不会切换
因为博主完全不懂Ajax这类相关技术,所以想了一个偷鸡的方法,实现类似的效果
每次提交的时候,将用户提交的代码,保存到问题目录下的一个save.cpp文件当中,同样的将编译运行结果也存放在一个result.txt文件中
每次提交时重新渲染一次页面,将save.cpp与result.txt中的内容与{{header}}和{{comp}}替换来实现类似效果
效果如下
加载问题中的所相关内容
定义结构体,用于存放题目的相关信息,名称,路径等
类中通过unordered_map的关联索容器存放题目信息,因为unordered_map底层的哈希结构,查找效率十分高效。
读取conf文件中的信息并存放在map中,并按照id排序,升序显示,该方法主要用来显示所有问题。
出参,通过id在map中查找到对应题目的路径,通过FileOpen::ReadDataFromFile工具类中的方法读取对应文件中的内容,并通过参数传出。
自己封装的读写文件的工具类
通过boost库中的split方法,可让读取的内容按要求分开,并存放到vector
封装的两个静态方法读写文件,不多介绍
这个是在网上找的解析url请求正文中的内容的封装类
模板类技术的封装,主要用于填充all_questions.html和question.html页面中的内容
ctemplate的使用,首先定义一个TemplateDictionary的字典
使用SetValue的方法,将html中的占位符与变量中的内容替换,最后打开Template* 的操作句柄打开html文件,与打开文件类似,使用Expand方法,将替换后的html文件以字符串的形式传出函数。
all_questions的唯一不同是有一个{{/question}}这样的子区域,需要在循环中替换占位符,所以每次循环都要定义一个AddSectionDictionary的指针。
编译运行模块
每一个文件的路径中,都有一个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