Skip to content

Commit 4a940d2

Browse files
committed
first commit
1 parent 2f5fc29 commit 4a940d2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+6109
-0
lines changed

OJonline/compile.hpp

+206
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#pragma once
2+
//1. c/c++ 标准库文件
3+
#include <string>
4+
#include <atomic>
5+
//2. 操作系统头文件
6+
#include <unistd.h>
7+
#include <sys/wait.h>
8+
#include <fcntl.h>
9+
#include <sys/stat.h>
10+
//3. 第三方库文件
11+
#include <jsoncpp/json/json.h>
12+
//4. 当前项目其他头文件
13+
#include "util.hpp"
14+
// 此代码完成在线编译模块的功能
15+
// 提供一个Compiler 类, 由 这个类 提供一个核心的
16+
// CompileAndRun 函数, 由这个函数来完成编译 + 运行 的功能;
17+
//
18+
19+
class Compiler
20+
{
21+
public:
22+
Compiler() {}
23+
~Compiler() {}
24+
//此处格式为笔记中的格式
25+
//Json::Value 类 是 jsoncpp中的核心类 借助这个类就可以完成序列化和反序列化的动作
26+
//这个类的使用方法和map非常像
27+
28+
29+
//本质上此处使用文件来完成进程间通信
30+
//1. 源代码文件 , name 表示当前请求的名字
31+
// 请求和请求之间,name是不相同的
32+
// 形如:
33+
// tmp_(时间戳).计数器.cpp tmp_1550976161.1.cpp
34+
static std::string SrcPath(const std::string& name) {
35+
return "./temp_files/" + name + ".cpp";
36+
}
37+
//2. 编译错误文件
38+
static std::string CompileErrorPath(const std::string& name) {
39+
return "./temp_files/" + name + ".compile_error";
40+
}
41+
//3. 可执行程序文件
42+
static std::string ExePath(const std::string& name) {
43+
return "./temp_files/" + name + ".exe";
44+
}
45+
//4. 标准输入文件
46+
static std::string StdinPath(const std::string& name) {
47+
return "./temp_files/" + name + ".stdin";
48+
}
49+
//5. 标准输出文件
50+
static std::string StdoutPath(const std::string& name) {
51+
return "./temp_files/" + name + ".stdout";
52+
}
53+
//6. 标准错误文件
54+
static std::string StderrorPath(const std::string& name) {
55+
return "./temp_files/" + name + ".stderror";
56+
}
57+
static bool CompileAndRun(const Json::Value& req_json, Json::Value& resp_json)
58+
{
59+
//1. 根据请求对象生成源代码文件
60+
if (req_json["code"].empty()) {
61+
resp_json["error"] = 3;
62+
resp_json["reason"] = "Code Empty";
63+
//如果在这里退出了,就不存在编译和运行了
64+
//所以 直接返回原因,不用返回运行时的文件描述符
65+
LOG(ERROR) << "Code Empty" << std::endl;
66+
return false;
67+
}
68+
//req["code"] 根据key取出 value value类型也是
69+
//Json::Value 这个类型通过 asString() 转成字符串
70+
const std::string& code = req_json["code"].asString();
71+
//通过这个函数完成把代码写到代码文件的过程
72+
std::string file_name = WriteTmpFile(code, req_json["stdin"].asString());
73+
74+
//2. 调用g++进行编译(fork + exec / system)
75+
// 会生成一个可执行程序, 如果编译出错,需要把编译错误记录下来(重定向到文件)
76+
bool ret = Compile(file_name);
77+
if (!ret) {
78+
//错误处理
79+
resp_json["error"] = 1;
80+
std::string reason;
81+
FileUtil::Read(CompileErrorPath(file_name), reason);
82+
resp_json["reason"] = reason;
83+
//虽然是编译出错,但是这样的错误是用户自己的错误
84+
//不是服务器的错误,因此对于服务器来说
85+
//这样的错误不是错误
86+
LOG(INFO) << "Compile Failed" << std::endl;
87+
return false;
88+
}
89+
//3. 如果编译成功,调用可执行程序运行。把标准输入内容记录到文件中,
90+
// 然后把文件中的内容重定向给可执行程序,
91+
// 可执行程序的标准输出和标准错误内容也重定向到记录文件中
92+
int signo = Run(file_name);
93+
if (signo) {
94+
//错误处理
95+
resp_json["error"] = 2;
96+
resp_json["reason"] = "Program Exit By Signo: " + std::to_string(signo);
97+
LOG(INFO) << "Program Exit By Signo: "
98+
<< std::to_string(signo) << std::endl;
99+
return false;
100+
}
101+
//4. 把程序的最终结果进行返回,构造Json对象
102+
resp_json["error"] = 0;
103+
resp_json["reason"] = "";
104+
std::string str_stdout;
105+
FileUtil::Read(StdoutPath(file_name), str_stdout);
106+
resp_json["stdout"] = str_stdout;
107+
108+
std::string str_stderror;
109+
FileUtil::Read(StderrorPath(file_name), str_stderror);
110+
resp_json["stderror"] = str_stderror;
111+
LOG(INFO) << "Program: "
112+
<< "Done" << std::endl;
113+
return true;
114+
}
115+
private:
116+
//1. 将代码写到文件中,
117+
//2. 给这次请求分配一个文件名
118+
// tmp_(时间戳).计数器.cpp tmp_1550976161.1.cpp
119+
static std::string WriteTmpFile(const std::string& code, const std::string& stdininfo)
120+
{
121+
//定义一个原子的id变量 使用方法与普通的相同 但是使用算数操作时是原子的
122+
//原子操作依赖cpu支持
123+
static std::atomic_int id(0);
124+
++id;
125+
//为了保证文件名的唯一性
126+
std::string file_name = "tmp_" + std::to_string(TimeUtil::TimeStamp())
127+
+ "." + std::to_string(id);
128+
FileUtil::Write(SrcPath(file_name), code);
129+
FileUtil::Write(StdinPath(file_name), stdininfo);
130+
return file_name;
131+
}
132+
133+
134+
static bool Compile(const std::string file_name)
135+
{
136+
//1. 先构造出编译指令
137+
// g++ file_name.cpp -o file_name.exe -std=c++11
138+
// execl execlp execle
139+
// execv execvp execve
140+
char *command[20] = {0};
141+
char buf[20][50] = {{0}};
142+
for (int i = 0; i < 20; i++) {
143+
//command 的每个指针都指向对应的二维数组的横行
144+
command[i] = buf[i];
145+
}
146+
//必须保证command的指针都是指向有效内存
147+
sprintf(command[0], "%s", "g++");
148+
sprintf(command[1], "%s", SrcPath(file_name).c_str());
149+
sprintf(command[2], "%s", "-o");
150+
sprintf(command[3], "%s", ExePath(file_name).c_str());
151+
sprintf(command[4], "%s", "-std=c++11");
152+
command[5] = NULL;
153+
//2. 创建子进程
154+
int ret = fork();
155+
//3. 父进程进行进程等待
156+
if (ret > 0) {
157+
waitpid(ret, NULL, 0);
158+
}else {
159+
//4. 子进程进行程序替换
160+
int fd = open(CompileErrorPath(file_name).c_str(), O_WRONLY | O_CREAT, 0666);
161+
if (fd < 0) {
162+
LOG(ERROR) << "Open Compile File Error" << std::endl;
163+
exit(1);
164+
}
165+
dup2(fd, 2); //期望得到的效果是 写 2 能把数据写入到文件中
166+
execvp(command[0], command);
167+
//此处如果子进程执行失败就直接退出
168+
exit(0);
169+
}
170+
//代码执行到这里 是否知道编译成功与否?
171+
//判定可执行文件是否存在
172+
// stat 函数 功能与ls类似 ls 就是借助它实现的
173+
struct stat st;
174+
ret = stat(ExePath(file_name).c_str(), &st);
175+
if (ret < 0) {
176+
//说明可执行文件不存在
177+
LOG(INFO) << "Compile Failed: " << file_name << std::endl;
178+
return false;
179+
}
180+
LOG(INFO) << "Compile: " << file_name << " OK!" << std::endl;
181+
return true;
182+
}
183+
184+
static int Run(const std::string& file_name)
185+
{
186+
//1. 创建子进程
187+
int ret = fork();
188+
if (ret > 0) {
189+
//2. 父进程进行等待
190+
int status = 0;
191+
waitpid(ret, &status, 0);
192+
return status & 0x7f;
193+
}else {
194+
//3. 进行重定向 (标准输入 标准输出 标准错误)
195+
int fd_stdin = open(StdinPath(file_name).c_str(), O_RDONLY);
196+
dup2(fd_stdin, 0);
197+
int fd_stdout = open(StdoutPath(file_name).c_str(), O_WRONLY | O_CREAT, 0666);
198+
dup2(fd_stdout, 1);
199+
int fd_stderror = open(StderrorPath(file_name).c_str(), O_WRONLY | O_CREAT, 0666);
200+
dup2(fd_stderror, 2);
201+
//4. 子进程进行程序替换
202+
execl(ExePath(file_name).c_str(), ExePath(file_name).c_str(), NULL);
203+
exit(0);
204+
}
205+
}
206+
};

OJonline/compile_server.cc

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#include <iostream>
2+
#include <unordered_map>
3+
#include <string>
4+
#include "httplib.h"
5+
#include "compile.hpp"
6+
#include <jsoncpp/json/json.h>
7+
8+
9+
/*
10+
在线编译oj
11+
*/
12+
int main()
13+
{
14+
//写在里面好
15+
using namespace httplib;
16+
Server server;
17+
//.Get 注册了一个回调函数,这个函数的调用时机
18+
//处理 Get 方法的时候
19+
//lambda 表达式?匿名函数
20+
server.Post("/compile", [] (const Request& req, Response& resp) {
21+
//根据问题的具体场景 根据请求 计算出响应结果
22+
//如何从req中获取到JSON的请求
23+
//JSONcpp 第三方库
24+
//以及JSON如何和HTTP协议结合
25+
//需要的请求格式是JSON格式,而HTTP能够提供的格式是另外一种键值对的格式
26+
//因此在这需要进行格式的转换
27+
//这里有浏览器提供的一些特殊符号,这些特殊符号需要转义,浏览器帮我们完成了
28+
//整理成为我们需要的JSON格式
29+
//帮我们将body解析成为键值对
30+
//键值对用哪个数据结构标识? 用unordered_map;
31+
//vector 数组
32+
//list 链表
33+
34+
//map(key-value)/set(key) 二叉搜索树
35+
//unordered_mapi(key-value)/unordered_set(key) 哈希表
36+
//unordered 无序
37+
std::unordered_map<std::string, std::string> body_kv;
38+
UrlUtil::ParseBody(req.body, body_kv);
39+
//在这里调用设定的compileAndRun
40+
Json::Value req_json; //从req对象中获取到
41+
for (auto e : body_kv) {
42+
//键为first
43+
//值为second
44+
req_json[e.first] = e.second;
45+
}
46+
Json::Value resp_json; //从resp_json 放到响应中
47+
Compiler::CompileAndRun(req_json, resp_json);
48+
//需要把 Json::value 对象序列化成一个字符串 才能返回
49+
Json::FastWriter writer;
50+
resp.set_content(writer.write(resp_json), "text/plain");
51+
});
52+
//加上这个目录是为了让浏览器访问到一个静态页面
53+
//静态页面:index.html 不会发生变化
54+
//动态页面:编译结果会随着参数的不同发生变化
55+
server.set_base_dir("./m");
56+
server.listen("0.0.0.0", 9092);
57+
return 0;
58+
}
59+

0 commit comments

Comments
 (0)