在线OJ项目铺垫

在线OJ项目铺垫

一.boost库的字符串切割函数(split)

  1. split头文件
#include <boost/algorithm/string.hpp>
  1. 函数原型:
boost::split(type,select_list,boost::is_any_of(""),
                                  boost::token_compress_on/off);
  1. 参数解释:
参数 参数解释
type 类型是std::vector< std::string > ,用来保存切割后的字符串
select_list 待切割的字符串,可直接传入string 类型变量
boost::is_any_of 告诉split函数按照什么字符进行切割,可以是单个字符,可以是字符串
boost::token_compress_on/off 当是boost::token_compress_on时,对于在boost::is_any_of中传入的多个字符当成一个处理。当是boost::token_compress_off时,boost::is_any_of中有多少个字符就按多少个字符来处理
  1. 测试
#include "Header.hpp"
#include <boost/algorithm/string.hpp>

void TestSplit()
{
  vector<string> strout;
  string strstr = "https://www.baidu.com";

  boost::split(strout,strstr,boost::is_any_of("./"),boost::token_compress_on);
  for(auto e : strout)
  {
    cout<<e<<endl;
  }
  cout<<endl;
}

int main()
{
  TestSplit();
  return 0;
}

二.谷歌的html模版库ctemplate

1. ctemplate概述

在进行web开发时,使用传统的CGI方式,在C/C++程序里面既要处理逻辑,也要处理页面显示内容,会比较混乱。可以通过模板引擎,使得逻辑与显示的分离。

Google CTemplate就是其中一个开源的C++模板引擎。使用ctemplate不仅可以产生html,还可以生成xml,json等格式的内容。CTemplate是Google开源的一个使用简单但功能很强大的C++模板库

2. 入门示例

模板库一般用来隔离数据和展现,这样可以减少数据和展现之间的耦合,使其可以相互独立变化,减少耦合,增加代码的复用,下面我们先看一个简单的例子,模板如下:

Hello {{NAME}},
You have just won ${{VALUE}}!
{{#IN_CA}}Well, ${{TAXED_VALUE}}, after taxes.{{/IN_CA}}

对应的C++代码是:

#include <cstdlib>
#include <iostream>  
#include <string>
#include <ctemplate/template.h>  
 
int main() {
    ctemplate::TemplateDictionary dict("example");
    int winnings = rand() % 100000;
    dict["NAME"] = "John Smith";
    dict["VALUE"] = winnings;
    dict.SetFormattedValue("TAXED_VALUE", "%.2f", winnings * 0.83);
    // For now, assume everyone lives in CA.
    // (Try running the program with a 0 here instead!)
    if (1) {
        dict.ShowSection("IN_CA");
    }
    std::string output;
    ctemplate::ExpandTemplate("example.tpl", ctemplate::DO_NOT_STRIP, &dict, &output);
    std::cout << output;
    return 0;
}

模板中{{NAME}}{{VALUE}}对应的是变量,在dict中通过dict[“NAME”]或dict的setValue的相关方法进行赋值{{#IN_CA}}{{/IN_CA}}之间的是一个section,可以通过dict的ShowSection来控制是否要显示这个section

3. 循环

有的数据我们需要循环展示,比如一个table的多个tr,ctemplate本身不支持循环,需要C++来控制循环,核心思想是多次Insert Section,示例模板如下:

{{#TEST_TABLE}}
<table>
  {{#TEST_TABLE_ITEM}}
  <tr>
    <td>{{INDEX}}</td>
    <td>{{NAME}}</td>
    <td>{{AGE}}</td>
  </tr>
  {{/TEST_TABLE_ITEM}}
</table>
{{/TEST_TABLE}}

对于这个模板,我们先显示外面的这个Section,然后再循环多次Insert里面的子Section就可以了,代码如下:

#include <cstdlib>
#include <iostream>  
#include <string>
#include <ctemplate/template.h>  
 
int main() {
    ctemplate::TemplateDictionary dict("loop");
    dict.ShowSection("TEST_TABLE");
 
    for (int i = 0; i != 3; ++i) {
        ctemplate::TemplateDictionary *item = dict.AddSectionDictionary("TEST_TABLE_ITEM");
        item->SetFormattedValue("INDEX", "%d", i);
        item->SetValue("NAME", "阿牛牛牛牛");
        item->SetValue("AGE", "保密");
    }
 
    std::string output;
    ctemplate::ExpandTemplate("loop.tpl", ctemplate::DO_NOT_STRIP, &dict, &output);
    std::cout << output;
    return 0;
}

4. 文件包含

对于一个通常的系统来说,一般都有多个模板文件,这多个模板文件之间,有很多相同的部分,比如有同样的header、footer,为了减少重复的代码,并且以后修改的话,只需修改一个地方,我们可以把公共的部分提取出来,然后其它的模板文件包含这个文件。ctemplate很好的支持了文件包含,对于ctemplate来说,包含的文件也是一个Section,模板代码如下:

{{>HEADER}}

{{#TEST_TABLE}}
<table>
  {{#TEST_TABLE_ITEM}}
  <tr>
    <td>{{INDEX}}</td>
    <td>{{NAME}}</td>
    <td>{{AGE}}</td>
  </tr>
  {{/TEST_TABLE_ITEM}}
</table>
{{/TEST_TABLE}}

注意第一行的{{>HEADER}},这是一个特殊格式的Section,用来表示文件包含。对应的C++代码如下:

#include <cstdlib>
#include <iostream>  
#include <string>
#include <ctemplate/template.h>  
 
int main() {
    ctemplate::TemplateDictionary dict("loop");
 
    ctemplate::TemplateDictionary *example = dict.AddIncludeDictionary("HEADER");
    example->SetFilename("./header.tpl");
    example->SetValue("TITLE", "测试");
    
    dict.ShowSection("TEST_TABLE");
 
    for (int i = 0; i != 3; ++i) {
        ctemplate::TemplateDictionary *item = dict.AddSectionDictionary("TEST_TABLE_ITEM");
        item->SetFormattedValue("INDEX", "%d", i);
        item->SetValue("NAME", "阿牛牛牛牛");
        item->SetValue("AGE", "保密");
    }
 
    std::string output;
    ctemplate::ExpandTemplate("loop.tpl", ctemplate::DO_NOT_STRIP, &dict, &output);
    std::cout << output;
    return 0;
}

三.序列化反序列化之jsoncpp

1. jsoncpp概述

JSON全称为JavaScript ObjectNotation,它是一种轻量级的数据交换格式,易于阅读、编写、解析。jsoncpp是c++解析JSON串常用的解析库之一。

2. JsonCpp基础

jsonCpp主要包含三种类型的class:value、reader、write jsonCpp总所有对象、类名都在namespace json中,使用时只要包含json.h即可。

3. Json::Value

Json::Value时jsonCpp中最基本、最重要的类,用于表示各种类型的对象,jsoncpp 支持的对象类型可见 Json::ValueType 枚举值。
例:

Json::Value root;
root["status"] = 1;			//新建一个key为status,赋予数值1
root["message"] = "OK";		//新建一个key为message,赋予字符串OK
root["array"].append("arr"); //新建一个key为array,类型为数组,对第一个元素赋值为字符串“arr”
root["array"].append(1234);  // 为数组 key_array 赋值,对第二个元素赋值为:1234。
Json::ValueType type = root.type();	//获得root的类型

注:

  • 跟C++ 不同,JavaScript 数组可以为任意类型的值,所以 jsoncpp 也可以。
  • jsoncpp 还有一些其他同能,比如说设置注释、比较 json 大小、交换 json 对象等
  • 在把value插入值后再输出来,输出的值是按字母表的顺序排列。
  • Json::ValueType:value的类型,是一个枚举型
enum ValueType {
  nullValue = 0, ///< 'null' value
  intValue,      ///< signed integer value
  uintValue,     ///< unsigned integer value
  realValue,     ///< double value
  stringValue,   ///< UTF-8 string value
  booleanValue,  ///< bool value
  arrayValue,    ///< array value (ordered list)
  objectValue    ///< object value (collection of name/value pairs).
};

4. Json::Writer

  • Json::Writer负责将内存中的Value对象转换成JSON文档,输出到文件或者是字符串中。
  • Json::Writer是一个纯虚类,不能直接使用,一般使用Json::Writer的子类:Json::FasterWriter, Json::StyledWriter、Json::StyledStreamWriter

注:在新版中Json::FasterWriter, Json::StyledWriter、Json::Reader都被弃用,替代的是Json::StreamWriterBuilder、Json::CharReaderBuilder

  • Json::FasterWriter:速度最快
  • Json::StyledWriter:格式化后的json

5. Josn::Reader

用于读取,准确说是用于将字符串或者文件输入流转换为Json::Value对象的。

parse()函数

  • 使用Json::Reader对json文件进行解析
bool parse(const std::string& document, Value& root, bool 
									collectComments = true);

参数

  • document: 包含要读取的文档的UTF-8编码字符串
  • root:(输出)Json::Value的对象
  • 使用Json::Reader对json输入流(文件)进行解析
    bool parse(std:stream& is, Value& root, bool collectComment = true);
  • 使用Json::Reader对字符串进行解析
bool parse(const char* beginDoc, const char* ednDoc, Value& root, bool 
									collectComment = true);

6. JsonCpp其他操作

  • 判断key是否存在
bool Json::Value::isMember ( const char * key) const;
存在返回1,否则返回0
  • 判断是否为null成员函数

注:Json::Value和C++中的map有一个共同的特点,就是当你尝试访问一个不存在的 key 时会自动生成这样一个key-value默认为null的值对

  • 得到所有成员
typedef std::vectorstd::string Json::Value::Members;
Value::Members Json::Value::getMemberNames ( ) const;

该函数的类型为一个string的vector

  • 删除成员
Value Json::Value::removeMember( const char* key)
返回删除的值,或者null

7. 测试:

#include <iostream>
#include <fstream>
#include <string>
#include "json/json.h"

using namespace std;

int main()
{
	const char* age;
	Json::Value root;
	Json::FastWriter fast_writer;
	Json::StyledWriter style_writer;
	Json::StyledStreamWriter stream_writer;
	Json::Reader reader;
	Json::Value json_object;

	root["null"] = NULL;			//注意此处在输出是显示为0,而非null
	root["message"] = "OK";
	root["age"] = 52;
	root["array"].append("arr");	// 新建一个key为array,类型为数组,对第一个元素赋值为字符串“arr”
	root["array"].append(123);		// 为数组 key_array 赋值,对第二个元素赋值为:1234

	Json::ValueType type = root.type();				//root的类型

	cout << root.toStyledString() << endl;			//格式化输出
	cout << "root_type:" <<type << endl;			//类型为7,即为类型为对象

	// 写到本地文件
	cout << "快速输出:" << endl;
	string str = fast_writer.write(root);
	cout << str << endl;		//快速输出,紧凑型
	ofstream ofs("fast_writer.json");
	ofs << str;
	ofs.close();

	cout << "格式化输出:" << endl;
	str = style_writer.write(root);
	cout << str << endl;		//格式化输出,排版型
	ofs.open("style_writer.json");
	ofs << str;
	ofs.close();


	// 对字符串解析
	//const char* json_document = "{\"age\" : 12, \"name\" : \"weng\"}";
	string json_document = "{\"age\" : 123, \"name\" : \"weng\"}";
	if (!reader.parse(json_document, json_object)){
		cout << "error" << endl;
		return 0;
	}
	else{
		cout <<"age:" <<json_object["age"] << " name" << json_object["name"] << endl;
	}
	
	// 对文件解析
	ifstream ifs("E:\\demo\\JsonTest\\JsonTest\\example.json", ios::binary);
	if (!reader.parse(ifs, json_object)){
		cout << "open error" << endl;
		ifs.close();
	}else{
		cout << "encoding:" << json_object["encoding"].asString() << endl;
		cout << "age:" <<json_object["age"].asInt() << endl;
		int num = json_object["plug"].size();
		for (int i = 0; i < num; i++){
			cout << json_object["plug"][i].asString() << " ";
		}
		ifs.close();
		cout << endl;
	}

	// 判断key是否存在
	bool is_age =  root.isMember("age");
	cout << "isMember:" <<is_age << endl;

	// 判断是否为null
	//bool is_null = root["null"].isNull();	//注意此处被赋值为0,而非null
	bool is_null = json_object["null"].isNull();
	cout << "is_null:" << is_null << endl;

	// 得到所有的key
	for (auto elem : root.getMemberNames()){
		cout << elem << " ";
	}
	cout << endl;

	// 删除成员
	cout << "remove_age:" << root.removeMember("age") << endl;


	system("pause");
	return 0;
}

四.服务器搭建cpp-httplib

cpp-httplib使用详解

1. cpp-httplib:一个header-only的跨平台HTTP/HTTPS服务器和客户端C++库

2. Server Example

#include <httplib.h>

int main(void)
{
    using namespace httplib;

    Server svr;

    svr.Get("/hi", [](const Request& req, Response& res) {
        res.set_content("Hello World!", "text/plain");
    });

    svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
        auto numbers = req.matches[1];
        res.set_content(numbers, "text/plain");
    });

    svr.Get("/stop", [&](const Request& req, Response& res) {
        svr.stop();
    });

    svr.listen("localhost", 1234);
}

Post, Put, Delete and Options methods are also supported.

3. 将套接字绑定到多个接口和任何可用端口

int port = svr.bind_to_any_port("0.0.0.0");
svr.listen_after_bind();

4. 静态文件服务器Static File Server

svr.set_base_dir("./www");

5. Logging

svr.set_logger([](const auto& req, const auto& res) {
    your_logger(req, res);
});

6. 差错处理器Error Handler

svr.set_error_handler([](const auto& req, auto& res) {
    const char* fmt = "<p>Error Status: <span style='color:red;'>%d</span></p>";
    char buf[BUFSIZ];
    snprintf(buf, sizeof(buf), fmt, res.status);
    res.set_content(buf, "text/html");
});

7. ‘multipart/form-data’ POST data

svr.Post("/multipart", [&](const auto& req, auto& res) {
    auto size = req.files.size();
    auto ret = req.has_file("name1"));
    const auto& file = req.get_file_value("name1");
    // file.filename;
    // file.content_type;
    auto body = req.body.substr(file.offset, file.length));
})

Client Example

8. GET

#include <httplib.h>
#include <iostream>

int main(void)
{
    httplib::Client cli("localhost", 1234);

    auto res = cli.Get("/hi");
    if (res && res->status == 200) {
        std::cout << res->body << std::endl;
    }
}

9. 使用HTTP头信息获取GET with HTTP headers

  httplib::Headers headers = {
    { "Accept-Encoding", "gzip, deflate" }
  };
  auto res = cli.Get("/hi", headers);

10. 使用内容接收器获取GET with Content Receiver

std::string body;
  auto res = cli.Get("/large-data", [&](const char *data, size_t len) {
    body.append(data, len);
  });
  assert(res->body.empty());

11. POST

res = cli.Post("/post", "text", "text/plain");
res = cli.Post("/person", "name=john1&note=coder", "application/x-www-form-urlencoded");

12. POST with parameters

httplib::Params params;
params.emplace("name", "john");
params.emplace("note", "coder");

auto res = cli.Post("/post", params);
or

httplib::Params params{
  { "name", "john" },
  { "note", "coder" }
};

auto res = cli.Post("/post", params);

13. POST with Multipart Form Data

 httplib::MultipartFormDataItems items = {
    { "text1", "text default", "", "" },
    { "text2", "aωb", "", "" },
    { "file1", "h\ne\n\nl\nl\no\n", "hello.txt", "text/plain" },
    { "file2", "{\n  \"world\", true\n}\n", "world.json", "application/json" },
    { "file3", "", "", "application/octet-stream" },
  };

  auto res = cli.Post("/multipart", items);

14. PUT

res = cli.Put("/resource/foo", "text", "text/plain");

15. DELETE

res = cli.Delete("/resource/foo");

16. OPTIONS

res = cli.Options("*");
res = cli.Options("/resource/foo");

注意g ++ 4.8无法构建此库,因为<regex>g ++ 4.8中的损坏!!!

五.在线OJ项目整体框架

1. 在线OJ项目需求实现一个在线判题系统,用户通过浏览器编写并提交代码,通过网络传输,将代码上传到服务器,服务器对用户提交对代码进行编译,并把编译结果返回给用户

2. 客户端和服务器端的工作流程

wolf
3. 服务器端的工作模块

  • oj_server模块:专门处理http请求。比如收到客户端请求所有题目列表的请求,服务器就会调用试题模块完成题目列表的加载并返回给客户端。如果收到客户端提交的代码,就需要调用编译运行模块,对提交的代码进行编译并返回运行结果
  • 试题模块:首先服务器需要处理客户端发送过来的获取题目列表的请求,所以服务器端需要对试题列表进行加载
  • 编译运行模块:对用户通过网络提交的代码进行编译运行处理
  • 日志模块:对程序的状态进行跟踪,方便调试
  • 工具模块:避免多次编写重复的代码,提高代码的复用率。如打开关闭文件等

在这里插入图片描述

4. 需求细分

  1. 客户端在浏览器当中展示试题列表(oj_server模块,试题模块)
  2. 客户端可以选择题目进行作答(oj_server模块,试题模块)
  3. 客户端提交代码到服务器(oj_server模块)
  4. 服务器端针对客户端提交的代码进行编译(编译运行模块)
  5. 服务器端运行客户端的程序(编译运行模块)
  6. 服务气端对运行结果进行打包(编译运行模块)
  7. 服务器端将打包数据发送给客户端(oj_server模块)

5. 每个模块的功能简介

oj_server模块:提供http服务,串联试题模块和编译运行模块

  • 获取题目列表
  • 提交选中的题目
  • 根据用户选中的题目,把该题的题目描述和代码模版

试题模块

  • 从文件(配置文件)当中加载题目
    1. 约束配置文件中对题目描述的格式(如果不约束,从文件当中加载文件就没有规则):题目编号(id) 题目名称 题目详情信息的路径 题目的难度信息。如:1 回文数 ./oj_data/1 简单。
    2. 加载每个题目的配置文件,获取完整题目信息并保存在数据结构当中(这个路径一定要对)
    3. 针对每一个题目,还需要根据路径加载相关信息:desc.txt:题目的描述信息。header.cpp:存放的是该题目的头文件及实现类。tail.cpp:存放测试的用例及main函数的入口
  • 提供获取所有题目列表的接口
    给oj_server模块提供一个获取所有题目列表的接口,展示给用户
  • 提供获取单个题目的接口
    给oj_server模块提供一个获取单个题目描述和作答的接口,展示给用户

编译运行模块:将用户提交的代码写入到文件当中编译运行源文件。

  • 编译
    1. 将用户提交的代码写入到文件
    2. 再fork出子进程,进行进程替换为g++程序,编译原码文件。
    3. 获取编译结果,写入到文件当中(标准输出/标准错误)
    在这里插入图片描述

  • 运行

    1. 代码走到运行阶段,说明已经编译出来可执行程序,
    2. fork出子进程,让子进程进行进程程序替换,替换成编译出来的可执行程序
    3. 将可执行程序的运行结果输入到标准输出或标准错误文件当中
      在这里插入图片描述

工具模块

  • 提供时间戳服务:为了区分不同用户提交的代码。当用户将代码写入到文件当中的时候,使用时间戳来进行区分。

  • 提供文件写操作

  • 提供读文件操作:
    我们可以提供一个一次读取一行的函数,但是前面我们对题目进行约束,题目编号(id) 题目名称 题目详情信息的路径 题目的难度信息,我们需要对每个信息保存到数据结构当中。所以需要提供一个数据切割对接口

  • 提供URL解码操作

发布了150 篇原创文章 · 获赞 89 · 访问量 9万+

猜你喜欢

转载自blog.csdn.net/wolfGuiDao/article/details/105196528