ACM在线判题系统(OJ)的判题实现(java+python)
学院⼀直是有⼀个⾃⼰的oj的,但是由于最近判题崩了,需要修复⼀下,拿到判题代码,打开却是⼀⼿node.js,让我⼀个搞Java的着实懵逼,因为以前学过点js,摸清判题逻辑,⼀步⼀步console.log来调bug,最后还是太复杂,把⼼态调崩了。最后想了了想判题就是那个流程,还是⾃⼰写⼀个吧,⽽且以前的判题只⽀持python2,现在谁要⽤python2啊。
好吧,直接开始开发:判题需要⼏个步骤:
1.在linux搭建编译器环境:gcc g++ java python2 python3 pascal
2.根据源码的类型创建相应的源⽂件(.c .cpp .java 等)
3.编译对应的源⽂件
4.运⾏程序,使⽤测试⽤例测试得出时间消耗和内存消耗。
这⾥最棘⼿的还是第四步:怎么知道内存和时间消耗?我再⽹上不断的查资料,后来发现⼏乎没有,了很久到⼀个前辈有⼀篇开发oj的博客,纯⽤python写的。由于⾃⼰对于python仅仅是⼊门语法,纯⽤python开发对于我来讲确实有点难度。但是思路是可以借鉴的。
⾥⾯提到过⼀个思路:在判题脚本中运⾏编译之后产⽣的可执⾏⽂件,返回⼀个pid,使⽤过linux的⼩伙伴应该都知道pid(通过这个pid就能到所运⾏的那个程序,在top⾥⾯就可实时知道这个进程的内存消耗),在python调⽤linux操作系统的api就可以持续的知道这个进程的内存消耗情况,那么在judge脚本中写个死循环,不断调⽤操作系统api来获取内存,当时间限制过了之后这个进程还没结束,那么就可以得出结果了(timelimit)。
博客链接:
但是后⾯博主推荐了⼀个包装这些操作的python模块lorun。
项⽬地址:
这个使⽤很简单:
args是⼀个运⾏命令;
fd_in是⼀个输⼊流,也就是我们判题的测试⽂件(.in⽂件);
fd_out是程序的输出流,运⾏结束需要这个内容来判断程序是否正确的。
timelimit、memorylimit就是我们的时间空间限制啦。
调⽤lorun模块的run(runcfg)⽅法就会知道时间空间消耗。
runcfg = {
'args':['./m'],
'fd_in':fin.fileno(),
'fd_out':ftemp.fileno(),
'timelimit':1000, #in MS
'memorylimit':20000, #in KB
}
rst = lorun.run(runcfg)
通过这个模块就解决了我棘⼿的问题。那么我就可以在java中编写代码来实现创建源代码、编译程序,在python中计算内存和空间消耗。
judge.py
#!/usr/bin/python
# ! -*- coding: utf8 -*-
import os
import sys
import sys
import lorun
RESULT_STR = [
'Accepted',
'Presentation Error',
'Time Limit Exceeded',
'Memory Limit Exceeded',
'Wrong Answer',
'Runtime Error',
'Output Limit Exceeded',
'Compile Error',
'System Error'
]
def runone(process, in_path, out_path, user_path, time, memory):
fin = open(in_path)
tmp = os.path.join(user_path, 'temp.out')
ftemp = open(tmp, 'w')
runcfg = {
'args': process,
'fd_in': fin.fileno(),
'fd_out': ftemp.fileno(),
'timelimit': time,  # in MS
'memorylimit': memory,  # in KB
}
rst = lorun.run(runcfg)
fin.close()
ftemp.close()
if rst['result'] == 0:
ftemp = open(tmp)
fout = open(out_path)
crst = lorun.check(fout.fileno(), ftemp.fileno())
fout.close()
ftemp.close()
rst['result'] = crst
return rst
def judge(process, data_path, user_path, time, memory):
result = {
"max_time": 0,
"max_memory": 0,
"status": 8
}
for root, dirs, files in os.walk(data_path):
for in_file in files:
if dswith('.in'):
out_file = place('in', 'out')
fin = os.path.join(data_path, in_file)
fout = os.path.join(data_path, out_file)
if os.path.isfile(fin) and os.path.isfile(fout):
rst = runone(process, fin, fout, user_path, time, memory)
if rst['result'] == 0:
result['status'] = 0
result['max_time'] = max(result['max_time'], rst['timeused'])
result['max_memory'] = max(result['max_memory'], rst['memoryused'])                    else:
result['status'], result['max_time'], result['max_memory'] = rst['result'], 0, 0                        print(result)
return
print result
if __name__ == '__main__':
if len(sys.argv) != 6:
print('Usage:%s srcfile testdata_pth testdata_total'%len(sys.argv))
exit(-1)
judge(sys.argv[1].split("wzy"),sys.argv[2], sys.argv[3], long(sys.argv[4]), long(sys.argv[5]))
这个脚本是需要我在java程序中简单的调⽤这个脚本得出状态和时间空间消耗的;那么在java程序中需要给运⾏的命令,测试⽤例的地址,临时⽂件地址,还有就是时间消耗,空间消耗。这⾥需要说明⼀下,第⼀个参数为什么需要⽤特殊字符(‘wzy’)分割;给出的运⾏程序的命令c和c++就很简单,就是⼀个./a.out之类的,但是对于java、python,则是需要python3 main.py之类的有空格的命令,所以调⽤的时候python脚本是不知道具体的命令的,所以采⽤这个⽅式来统⼀调⽤。
在java中。通过Runtime的exec来调⽤这个命令,再通过这个进程的输出流来得出结果。这⾥我也封装了⼀个调⽤⽅法:package cn.wzy.util;
import cn.wzy.vo.ExecMessage;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class ExecutorUtil {
public static ExecMessage exec(String cmd) {
Runtime runtime = Runtime();
Process exec = null;
try {
exec = (cmd);
} catch (IOException e) {
e.printStackTrace();
return new Message(), null);
}
ExecMessage res = new ExecMessage();
res.setError(ErrorStream()));
res.setStdout(InputStream()));
return res;
}
private static String message(InputStream inputStream) {
BufferedReader reader = null;
try {
reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));
StringBuilder message = new StringBuilder();
String str;
while ((str = adLine()) != null) {
message.append(str);
}
String result = String();
if (result.equals("")) {
return null;
}
return result;
} catch (IOException e) {
Message();
} finally {
try {
inputStream.close();
在线代码运行器
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
ExecMessage类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ExecMessage {
private String error;
private String stdout;
}
这样我调⽤这个⽅法就知道这个进程的错误输出和标准控制台输出啦。
在java中的代码就很简单了:调⽤'python judge.py ./m /×××/×××/×××/ /×××/×××/×××/ 1000 65535'这样的命令给出⼀个结果,然后判题有没有错误输出,如果有则是runtime error,否则解析标准输出的内容(json字符串),转化成java类处理。
java核⼼代码:
三个实体:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class JudgeResult {
private Integer submitId;
private Integer status;
private Integer timeUsed;
private Integer memoryUsed;
private String errorMessage;
}
package cn.wzy.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class JudgeTask {
private String appName;
private int submitId;
private int compilerId;
private int problemId;
private String source;
private int timeLimit;
private int memoryLimit;
private boolean isSpecial;
}
package cn.wzy.vo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class Stdout {
/**
* {'status': 0, 'max_memory': 23328L, 'max_time': 207L}  */
private Integer status;
private Long max_memory;
private Long max_time;
}
判题代码:

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。