个人主页:Lei宝啊
愿所有美好如期而遇
目录
一、所用技术与开发环境
所用技术
开发环境
二、准备及库的安装
1. 升级gcc (gcc -v查看gcc版本, 超过7就不用看本条升级gcc)
2. 安装 jsoncpp
3. 安装 cpp-httplib
4. 安装boost库
5. 安装ctemplate
三、项目宏观结构
1. leetcode结构
2. 项目宏观结构
3. 编写思路
四、compiler服务设计
1. 日志模块
2. 公共方法模块
3. 编译模块
4. 运行模块
5. 整合模块
6. 主函数
7. 使用Postman进行调试
五、oj_server服务设计
第一个功能:用户请求的服务器路由功能
第二个功能:完成model,提供对数据的操作
第三个功能:control模块,逻辑控制模块
第四个功能:view模块,进行数据渲染
完成control模块,加入负载均衡
六、效果演示
一、所用技术与开发环境
所用技术
- C++ STL 标准库
- Boost 准标准库(字符串切割)
- cpp-httplib 第三方开源网络库
- ctemplate 第三方开源前端网页渲染库
- jsoncpp 第三方开源序列化、反序列化库
- 负载均衡设计
- 多进程、多线程
开发环境
- ubuntu 5云服务器
- vscode
二、准备及库的安装
1. 升级gcc (gcc -v查看gcc版本, 超过7就不用看本条升级gcc)
对于cpp-httplib库来说,用老的编译器,要么编译不通过,要么运行报错。
安装scl来升级gcc
sudo yum install centos-release-scl scl-utils-build
sudo yum install -y devtoolset-7-gcc devtoolset-7-gcc-c++ (这里的7可以是8或者9)
scl enable devtoolset-7 bash (启动,只在本会话有效)
如果想每次登陆的时候,都是较新的gcc,需要把上面的命令添加到你的~/.bash_profile中
添加:scl enable devtoolset-7 bash
2. 安装 jsoncpp
sudo yum install -y jsoncpp-devel
3. 安装 cpp-httplib
建议:cpp-httplib 0.7.15, 码云上去找。
4. 安装boost库
sudo apt update 更新软件包
sudo apt install libboost-all-dev(博主是unbuntu)
5. 安装ctemplate
这个资源可以在这里找:https://hub.fastgit.xyz/OlafvdSpek/ctemplate
接着将资源放到服务器上,执行如下命令
./autogen.sh
./configure
make //编译
make install //安装到系统中 如果报错,加上sudo
三、项目宏观结构
- comm 模块
- compiler模块
- oj_server模块
1. leetcode结构
本项目只实现了leetcode的题目列表和刷题功能。
2. 项目宏观结构
B/S模式:浏览器-服务器(Browser-Server, B/S)模式
3. 编写思路
先写compiler服务器,接着写oj_server,最后是前端页面设计,直接copy。
四、compiler服务设计
1. 日志模块
其他模块都会引用这个模块,这个模块我们之前的文章有详细代码和讲解,我们这里不多赘述,贴出链接:日志介绍及简单实现
2. 公共方法模块
这个模块可以先直接跳过不看,当后面模块用到这个模块中的方法时返回来看。
这里介绍几个函数:gettimeofday获取时间戳,stat获取文件属性(能获取就能说明文件存在,获取不到就说明文件不存在);C++11及以上版本,支持在atomic头文件中,我们使用atomic_int类型定义的变量,有原子加、原子减、原子比较并交换、原子自增、原子自减等操作。
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <atomic>
#include <fstream>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
using namespace std;namespace ns_commfunc
{const string temp = "./temp/";// 将文件名变为文件路径,并且添加后缀class PathUtil{public: static string AddSuffix(const string filename, const string suffix){string ret = temp + filename + suffix;return ret;}//编译模块static string filepath_src(const string& filename){return AddSuffix(filename, ".cpp");}static string filepath_exe(const string& filename){return AddSuffix(filename, ".exe");}static string filepath_stderr(const string& filename){return AddSuffix(filename, ".compile_err");}//运行模块static string filepath_stdin(const string& filename){return AddSuffix(filename, ".stdin");}static string filepath_stdout(const string& filename){return AddSuffix(filename, ".stdout");}static string filepath_stderr_runner(const string& filename){return AddSuffix(filename, ".stderr");}};class TimeUtil{public:static string Gettimeofms(){struct timeval tv;gettimeofday(&tv, nullptr);string ret = to_string(tv.tv_sec * 1000 + tv.tv_usec / 1000);return ret;}};class FileUtil{public:static bool Isexist_file(const string filename){struct stat temp;int ret = stat(filename.c_str(), &temp);return ret == 0 ? true : false;}static string UniqueFilename(){static atomic_uint id(0);id++;return TimeUtil::Gettimeofms() + "_" + to_string(id);}static bool WritecodeToFile(const string filename, const string& content){ofstream out(filename);if(!out.is_open()) return false;out.write(content.c_str(), content.size());return true;}static bool Readfile(const string& filename, string& content, bool keep){ifstream in(filename);if(!in.is_open()) return false;string line;while(getline(in, line)){content += line;content += keep ? "\n" : "";}return true;}static string CodeTodesc(int status, string filename){string ret = "";switch (status){case 0:ret = "运行正常";break;case -1:ret = "代码为空";break;case -2:ret = "未知错误";break;case -3:// ret = "编译错误";FileUtil::Readfile(filename, ret, true);break;case 24:ret = "超时";default:ret = "待填充";break;}return ret;}};
}
3. 编译模块
1. 创建编译类Compile,封装在命名空间ns_compile中:
namespace ns_compile
{class Compile{private:public:Compile(){}~Compile(){}};
}
2. 写一个方法,完成编译功能,那么这个方法的参数和返回值我们如何设置?我们要明白,外面有一个整合模块来接收请求,并且从请求中提取代码并解析成字符串形成源文件,也就是说,我们的编译方法,参数为这个文件名,也就是字符串类型。返回值可以设置为bool类型,表示是否编译成功。
/*@return val: 编译结果是否成功@pragma : 文件名*/static bool compile(const string& filename){}
接下来就可以创建子进程,按照我们上面的逻辑写代码了:
其中,我们在comm文件夹下,创建了一个公共方法文件,这个文件中我们写不同模块需要使用的公共方法,这些方法封在ns_commfunc中的不同类中,有Path类,这个类中的方法用来形成文件路径;文件类,这个类中的方法用来对文件进行操作。
static bool compile(const string& filename){int id = fork();if(id < 0){Log(ERROR, "子进程创建失败");return false;} else if(id == 0){//子进程//重定向umask(0);i