项目-在线OJ(Linux)

在线OJ
一.需求
二.前后台流程梳理
三.模块划分
四.需求细分
五.模块需求划分
六.技术支持
七.项目扩展

源代码链接:https://github.com/uniquefairty/Linux-Study/tree/master/oj_Item/src
一.需求:实现一个在线判题系统,用户通过浏览器编写和提交代码,通过网络传输,将代码上传到后台,后台需要对提交的代码进行编译运行,将结果反馈给用户。
二.前后台流程梳理
在这里插入图片描述
三.模块划分
在这里插入图片描述
四.需求细分:
1.在浏览器当中展示题目(oj_server,试题模块)
2.客户可以选择题目进行作答(oj_server,试题模块)
3.提交代码到后台(oj_server)
4.针对客户端提交代码进行编译(编译运行模块)
5.后台运行可执行程序(编译运行模块)
6.对结果进行打包(编译运行模块)
7.将结果返回给客户端(oj_server)

1.编译正常过,运行正常—OK
2.编译的时候出错—编译运行的错误
3.运行的时候出错—运行错误的原因
4.输入参数有问题—(内部错误)stdout stderr 内部错误

五.模块需求划分
oj_server模块

1.提供http服务,串联试题模块和运行模块

1.1获取题目列表
1.2提交选中题目
1.3提交题目代码和题目表述,代码编辑框
从正文当中提取出来的内容,主要提取code字段所对应的内容,提交的内容当中有url编码(需要进行解码)

2.编译和运行

需要给提交的代码增加头文件,测试用例,main’函数

3.构造响应

#include <stdio.h>
#include <string>
#include <string.h>

#include "httplib.h"
#include "oj_model.hpp"
#include "oj_view.hpp"
#include "oj_log.hpp"
#include "compile.hpp"

int main()
{
    //httlib的时候,需要使用httplib提供的命名空间
    using namespace httplib;
    Server svr;
    OjModel ojmodel;
    //1.要获取试题的信息
    //试题的信息来源于文件当中
    svr.Get("/all_questions",[&ojmodel](const Request& req,Response& resp){
            std::vector<Question> ques;
            ojmodel.GetAllQuestions(&ques);
            std::string html;
            OjView::ExpandAllQuestionshtml(&html,ques);
            //LOG(INFO,html);
            resp.set_content(html,"text/html;charset=UTF-8");
            });
    svr.Get(R"(/question/(\d+))",[&ojmodel](const Request& req,Response& resp){
            //question/1
            //1.去试题模块去查找对应的题号的具体信息
            //    map (序号 名称 题目的地址 难度)
            std::string desc;
            std::string header;
            
            //从querystr获取id
            printf("path:%s\n",req.path.c_str());
            LOG(INFO,"req.matches")<<req.matches[0]<<":"<<req.matches[1]<<std::endl;
            //2.在题目路径下去加载单个题目描述信息,
            struct Question ques;
            ojmodel.GetOneQuestion(req.matches[1].str(),&desc,&header,&ques);

            //3.进行组织 返回浏览器
            std::string html;
            OjView::ExpandOneQuestion(ques,desc,header,&html);
            resp.set_content(html,"text/html;charset=UTF-8");
            });
    svr.Post(R"(/question/(\d+))",[&ojmodel](const Request& req,Response& resp){
            //key:value
            //1.从正文当中提取出来提交的内容,主要是提取code字段所对应的内容
            //提交的内容当中有url编码-->提交内容进行解码
            //提取完成后的数据放到unordered_map<std::string,std::string >
            std::unordered_map<std::string,std::string> pram;
            UrlUtil::PraseBody(req.body,&pram);
            for(const auto& pr:pram)
            {
               LOG(INFO,"code ")<<pr.second<<std::endl;
            }
            //2.编译&运行
            //2.1 需要给提交的代码增加头文件,测试用例,main函数
            std::string code;
           ojmodel.SplicingCode(pram["code"],req.matches[1].str(),&code);
           //LOG(INFO,"code")<<code<<std::endl;
           Json::Value req_json;
           req_json["code"]=code;
           //req_json["stdin"]=""
           Json::Value Resp_json;
           Compiler::CompilerAndRun(req_json,&Resp_json);
            //3.构造响应
            const std::string errorno=Resp_json["errorno"].asString();
            const std::string reason=Resp_json["reason"].asString();
            const std::string stdout_reason=Resp_json["stdout"].asString();
            std::string html;
            OjView::ExpandReason(errorno,reason,stdout_reason,&html);
            resp.set_content(html,"text/html;charset=   utf-8");
            });
    LOG(INFO,"listen in 0.0.0.0:19999")<<std::endl;
    LOG(INFO,"Server ready")<<std::endl;
    //listen会阻塞
    svr.listen("0.0.0.0",19999);
    return 0;
}

试题模块

1.从配置文件当中加载题目

1.1配置文件的格式–约定配置文件当中对题目的描述

题目的编号id,题目的名称,题目所在的目录(在目录当中存放对题目描述),题目难度
eg: 1 回文数 ./oj_data/1 简单

1.2加载题目的配置文件,使用数据结构保存加载出来的题目的介绍信息, (最重要的是:题目所在的路径一定要正确)
1.3针对每一道题目而言,需要根据给出的路径进行加载

desc.txt:题目的描述信息
header.cpp:存放的是该题目所包含的头文件以及实现类
tail.cpp:存放测试用例以及main函数的入口

2.提供获取整体题目的接口
给oj_server模块提供一个可以获得所有表述题目的接口,展示给用户
3.提供获取单个题目的接口
给oj_server模块提供一个可以获取单个题目描述和作答接口,展示给用户

#include "tools.hpp"
#include "oj_log.hpp"
//试题id 试题名称 试题路径 试题难度
typedef struct Question
{
    std::string id_;
    std::string name_;
    std::string path_;
    std::string star_;
}QUES;
class OjModel
{
 public:
   //加载试题
   OjModel(){LoagQuestions("./config_oj.cfg");
   //获取题目列表
   bool GetAllQuestions(std::vector<Question>* ques);
   //获取单个题目列表
   bool GetOneQuestion(const std::string& id,std::string *desc,std::string *header,Question* ques);
   //合并题目信息和用户代码
   bool SplicingCode(std::string user_code,const std::string& ques_id,std::string* code);
   
 private:
   //加载试题
   bool LoadQuestions(const std::string& configfile_path);
   //获取题目的描述信息路径
   std::string DescPath(const std::string& ques_path);
   //获取该题目所包含的头文件以及实现类路径
   std::string HeaderPath(const std::string& ques_path);
   //测试用例以及main函数的入口路径
   std::string TailPath(const std::string& ques_path);
 private:
   std::unordered_map<std::string ,Question> model_map_;
 };

编译运行模块

将用户提交的code写到文件当中去,编译源码文件,编译成为可执行程序,并且运行
1.编译

1.1将用户提交的代码写到文件当中去
1.2fork子进程进行进程程序替换为g++程序,进行编译源码文件
1.3获取编译结果写到标准输出文件当中或者写入标准错误文件当中去

2.运行

2.1如果代码走到运行阶段,说明一定编译出来了可执行程序,fork子进程,让子进程进行程序替换,替换成为编译出来的可执行程序
2.2将程序的运行结果,保存到标准输出和标准错误文件当中去

#include "oj_model.hpp"
class OjView
{
    public:
        //渲染html页面,并且将该页面返回给调用
        static void ExpandAllQuestionshtml(std::string* html,std::vector<Question>& ques);
        static void ExpandOneQuestion(const Question& ques,std::string& desc,std::string& header,std::string* html);
        static void ExpandReason(const std::string& errorno,const std::string& reason,const std::string& stdout_reason,std::string* html);
};
        

工具模块

1.提供时间戳服务
时间戳计算是针对1970年1月1日,0:0:0 来计算的
为了区分不同的用户提交的代码,当代码写到文件当中去的时候,使用时间戳进行区分
2.提供写文件操作
3.提供读文件操作
需要提供对一行数据进行切割的接口
4.提供URL解码操作

#include "oj_log.hpp"
//实现一个切割字符串的工具函数
class StringTools
{
    public:
        static void Split(const std::string input,const std::string& split_char,std::vector<std::string>* output);
};

/实现文件操作类
class FileOper
{
    public:
        static int ReadDataFromFile(std::string& filename,std::string* content);
        static int WriteDataToFile(const std::string&filename,const std::string& Data);
};

//url解码
class UrlUtil
{
    public:
        static void PraseBody(const std::string& body,std::unordered_map<std::string,std::string>* pram);
 private:
        static unsigned char ToHex(unsigned char x) ;
        static unsigned char FromHex(unsigned char x) ;
        static std::string UrlEncode(const std::string& str);
        static std::string UrlDecode(const std::string& str)    
};

日志模块
//当前实现的log服务也是在控制台进行输出
//输出的格式
//[时间 日志等级 文件:行号] 具体的日志信息
日志等级
INFO WARNING ERROR FATAL DEBUG

class LogTime
{
    public:
        static int64_t GetTimeStamp()
        {
            struct timeval tv;
            gettimeofday(&tv,NULL);
            return tv.tv_sec;
        }

        //返回 年-月-日 时:分:秒
        static void GetTimeStamp(std::string* TimeStamp)
        {
            time_t SysTime;
            time(&SysTime);
            struct tm* st=localtime(&SysTime);
            char buf[30]={'\0'};
            snprintf(buf,sizeof(buf)-1,"%04d-%02d-%02d %02d:%02d:%02d",st->tm_year+1900,st->tm_mon+1,st->tm_mday,st->tm_hour,st->tm_min,st->tm_sec);
        TimeStamp->assign(buf,strlen(buf));
        }

};

//日志等级
//INFO WARNING ERROR FATAL DEBUG
const char* Level[]=
{
    "INFO",
    "WARNING",
    "ERROR",
    "FATAL",
    "DEBUG"
};

enum LogLevel
{
    INFO=0,
    WARNING,
    ERROR,
    FATAL,
    DEBUG
};

inline std::ostream&  Log(LogLevel lev,const char* file,int line,const std::string& logmsg)
{
    std::string level_info=Level[lev];
    std::string TimeStamp;
    LogTime::GetTimeStamp(&TimeStamp);
    //[时间 日志等级 文件:行号] 具体的日志信息
    std::cout<<"["<<TimeStamp<<" "<<level_info<<" "<<file<<":"<<line<<"]"<<logmsg;
    return std::cout;
}

#define LOG(lev,msg) Log(lev,__FILE__,__LINE__,msg)

六.技术支持
代码实现:
1.搭建http服务器
http:应用层协议 采用github上提供的开源httplib.h库,在搭建http服务的时候,只需要包含头文件即可。
问题1:获取请求头部,http协议底层是使用TCP来接收数据的,如何判断接收多少数据就能接收到请求头部;
tcp–>recv(sockfd,buf,size,MSG_PEEK)
buf–>\r\n 存在:就接收回来 不存在:待会再探测接收

2.C++11 gcc -v 4.8.2

3.试题模块中存储试题的数据结构是哪个?
map or undordered_map?选择unordered_map
map–>红黑数,有序的树形结构,查询的效率不高
undordered_map<ket,value>–>哈希表,无序,查询效率高,基本上查询的时候就是常数完成的

4.使用C++当中的文件流来加载文件,并获取文件当中的内容
iostream 处理控制台IO
fstream 处理命名文件的
stringstream 完成内存当中string对象的IO
ofstream:output文件流,文件当中写
ifstream:input文件流,从文件当中读
在定义ifstream对象的时候,就可以指定打开哪一个文件,和文件进行绑定

ifstream(const char* filename,ios_base::openmode mode=ios_base::in)
//in 以读的方式打开
//out 以写的方式打开
void open(const char* filename,ios_base::openmode mode=ios_base::in)

5.html相关标签

head标签:描述html页面的各种属性信息

title标签:定义浏览器标题栏的内容信息
meta标签:提供html页面的元数据,这些元数据可以被浏览器所识别,网页描述,作者

key value

body标签:定义文档主体

<div>标签:可以进行换行,在html页面当中定义一个分区
<h2>标签:字体放大
<a>标签:定义超链接

herf:指定链接的地址

form:浏览器解析到form这个标签时,当前这个页面可以触发给服务端提交数据 用户写完代码之后提交数据
action:提交表单的时候向哪一个服务器提交
method:用什么方法提交数据GET/POST
enctype:提交数据的格式–>json

代码编辑框:
<textarea 名称 行 列>预定于代码编辑框当中还有什么内容

名称:name=”xx"
行:rows=xxx
列:cols=xxx

提交标签

<input>
数据类型(提交submit),数据值:展示页面当中
formenctype 数据格式

<form action="https:www.baidu.com" method="POST">
  <textarea name="name" rows=20 cols=100>{{header}}</textarea>
  <input type="submit" formenctype="appliaction/json" value="Submit">
</form>

6.使用模板技术填充html页面–google ctemplate
可以是逻辑和界面分离,后台负责计算,在使用模板技术将计算后的值填充到预定义的html页面当中
下载路径:https://gitee.com/HGtz2222/ThirdPartLibForCpp/tree/master/

两个部分

1.模板:定义界面真是的形式,预定义的html页面
2.数据字典:填充模板的数据

数据字典
片段(子字典) id name star
片段(子字典) id name star

四种标记

1.变量 {{变量名}}
2.片段 {{#片段名}}
3.包含 {{>模板名称}} 一个模板当中可以包含另外一个模板,对应的就是一个数据字典
4.注释 {{!}} 定义html页面模板的时候的注释

//数据字典的类
explicit TemplateDictionary(const TemplateString& name,UnsafeArena* arena=NULL);
//name:没有特殊意义,为了调试存在
//explicit:声明构造函数为显示声明 implict隐式声明

//获取模板类指针
static Template* GetTemplate(const TemplateString& filename,Strip strip);
//filename:传入预定义html文件的路径,底层是用char *进行保存
//strip:DO_NOT_STRIP:逐字输出到模板文件当中去
//STRIP_BLANK_LINES:删除空行之后输出到模板文件当中去
//STRIP_WHITESPACE:删除空行和每一行的首尾空白字符

...
}

7.正则表达式

\b:单词的分界
*:匹配人一字符串
\d:匹配一个数字
():分组应用 (\d+)-(\d+)-(\d+)

8.源码转义:特殊字符就按照特殊字面源码来编译
R"(str)"

9.json数据格式–使用开源库JsonCpp
yum -y install epel-release
yum -y install jsoncpp-devel

JsonCpp:json.h
三个类:Value,reader,write
Json::Value root

定义对象:使用一对花括号{}
var={"linux":"60","projectname":"oj"}
定义对象:使用一对花括号{}
var={
"people":[
        {
          "firstname":"huang",
          "secondname":"san"
         },
        {
         "firstname":"zhang",
          "secondname":"san"
        }
       ]
}

10.项目性能:
如何限制子进程运行时间?
1)闹钟信号(如果是ms级的限制就不好弄了)
2)非阻塞轮询的waitpid,手动计时,如果时间到,就kill掉子进程
如何限制子进程运行占用的内存? 编程资源限制:
API: 能够通过函数getrlimit()、setrlimit()分别获得、设置每个进程能够创建的各种系统资源的限制使用量。
命令行:ulimit -a

时间和内存限制
七.项目扩展

  1. 界面美化. 从网上获取更美观的 html 模板, 让界面更好看, 让代码编辑更友好(支持语法高亮等功能, 可以基于
    ace.js https://ace.c9.io/).
  2. 支持用户管理. 注册, 登陆, 做题记录统计等
  3. 支持用户提交上传 OJ 题目
  4. 服务安全性. 用户可以通过提交一些恶意代码攻击编译服务器. (例如 system(“rm -rf *”); 此时可以把 system 定义成无实际功能的宏来解决这个问题. )
  5. 数据存储扩展. 使用数据库存储数据.

源代码链接:https://github.com/uniquefairty/Linux-Study/tree/master/oj_Item/src

猜你喜欢

转载自blog.csdn.net/unique_IT/article/details/106849273