Java在线OJ项目(一)、多进程编程实现 做题代码的编译和运行

news2024/11/26 20:23:52

在线OJ项目(一)、多进程编程实现 做题代码的编译和运行

    • 一、回顾线程和进程
    • 二、进程比线程的优势
    • 三、多进程编程样例
    • 四、多进程思想 实现对代码 的编译 以及 运行两个功能
        • CommandUtil 由于我们是在线oj,所以得编译用户的代码
        • 不仅编译 还需要 运行(但这是两种不同的代码,具体如下)
    • 五、字符串写入文件 以及 文件的读取
        • FileUtil
    • 六、完成读取用户代码,并且编译运行,返回结果
        • Question 存储用户输入的代码
        • Answer 返回代码编译运行后的结果
        • Task

一、回顾线程和进程

在这里插入图片描述

二、进程比线程的优势

在这里插入图片描述

三、多进程编程样例

  1. runtime的exec创建进程 用Process接收
  2. InputStream 和 FileOutputStream 输入输出
  3. process.waitFor() 进程等待
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class TestExec {
    public static void main(String[] args) throws IOException, InterruptedException {
        // Runtime 在 JVM 中是一个单例
        Runtime runtime = Runtime.getRuntime();
        // Process 就表示 "进程"
        Process process = runtime.exec("javac");
        // 获取到子进程的标准输出和标准错误, 把这里的内容写入到两个文件中.
        // 获取标准输出, 从这个文件对象中读, 就能把子进程的标准输出给读出来!
        InputStream stdoutFrom = process.getInputStream();
        FileOutputStream stdoutTo = new FileOutputStream("stdout.txt");
        while (true) {
            int ch = stdoutFrom.read();
            if (ch == -1) {
                break;
            }
            stdoutTo.write(ch);
        }
        stdoutFrom.close();
        stdoutTo.close();

        // 获取标准错误, 从这个文件对象中读, 就能把子进程的标准错误给读出来!
        InputStream stderrFrom = process.getErrorStream();
        FileOutputStream stderrTo = new FileOutputStream("stderr.txt");
        while (true) {
            int ch = stderrFrom.read();
            if (ch == -1) {
                break;
            }
            stderrTo.write(ch);
        }
        stderrFrom.close();
        stderrTo.close();

        // 通过 Process 类的 waitFor 方法来实现进程的等待.
        // 父进程执行到 waitFor 的时候, 就会阻塞. 一直阻塞到子进程执行完毕为止.
        // (和 Thread.join 是非常类似的)
        // 这个退出码 就表示子进程的执行结果是否 ok. 如果子进程是代码执行完了正常退出, 此时返回的退出码就是 0.
        // 如果子进程代码执行了一半异常退出(抛异常), 此时返回的退出码就非 0.
        int exitCode = process.waitFor();
        System.out.println(exitCode);
    }
}

四、多进程思想 实现对代码 的编译 以及 运行两个功能

CommandUtil 由于我们是在线oj,所以得编译用户的代码

不仅编译 还需要 运行(但这是两种不同的代码,具体如下)

由于代码可能是正常运行结果,也可能是出现编译错误

所以我们在编译时,要考虑好这两种可能

  1. process接受的是Runtime启动进程的方法,启动时,便指明任务(runtime就类似于cmd窗口)
package compile;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class CommandUtil {
    // 1. 通过 Runtime 类得到 Runtime 实例, 执行 exec 方法
    // 2. 获取到标准输出, 并写入到指定文件中.
    // 3. 获取到标准错误, 并写入到指定文件中.
    // 4. 等待子进程结束, 拿到子进程的状态码, 并返回.
    public static int run(String cmd, String stdoutFile, String stderrFile) {
        try {
            // 1. 通过 Runtime 类得到 Runtime 实例, 执行 exec 方法
            Process process = Runtime.getRuntime().exec(cmd);
            // 2. 获取到标准输出, 并写入到指定文件中.
            if (stdoutFile != null) {
                InputStream stdoutFrom = process.getInputStream();
                FileOutputStream stdoutTo = new FileOutputStream(stdoutFile);
                while (true) {
                    int ch = stdoutFrom.read();
                    if (ch == -1) {
                        break;
                    }
                    stdoutTo.write(ch);
                }
                stdoutFrom.close();
                stdoutTo.close();
            }
            // 3. 获取到标准错误, 并写入到指定文件中.
            if (stderrFile != null) {
                InputStream stderrFrom = process.getErrorStream();
                FileOutputStream stderrTo = new FileOutputStream(stderrFile);
                while (true) {
                    int ch = stderrFrom.read();
                    if (ch == -1) {
                        break;
                    }
                    stderrTo.write(ch);
                }
                stderrFrom.close();
                stderrTo.close();
            }
            // 4. 等待子进程结束, 拿到子进程的状态码, 并返回.
            int exitCode = process.waitFor();
            return exitCode;
        } catch (IOException | InterruptedException e) {
            e.printStackTrace();
        }
        return 1;
    }

    public static void main(String[] args) {
        CommandUtil.run("javac", "stdout.txt", "stderr.txt");
    }
}

五、字符串写入文件 以及 文件的读取

FileUtil

  1. StringBuilder读取字符串 最后toStirng返回
  2. fileWriter.write(content) 直接把字符串写入文件
package common;

import java.io.*;

public class FileUtil {
    // 负责把 filePath 对应的文件的内容读取出来, 放到返回值中.
    public static String readFile(String filePath) {
        StringBuilder result = new StringBuilder();
        try (FileReader fileReader = new FileReader(filePath)) {
            while (true) {
                int ch = fileReader.read();
                if (ch == -1) {
                    break;
                }
                result.append((char)ch);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result.toString();
    }

    // 负责把 content 写入到 filePath 对应的文件中
    public static void writeFile(String filePath, String content) {
        try (FileWriter fileWriter = new FileWriter(filePath)) {
            fileWriter.write(content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        FileUtil.writeFile("d:/test.txt", "hello");
        String content = FileUtil.readFile("d:/test.txt");
        System.out.println(content);
    }
}

六、完成读取用户代码,并且编译运行,返回结果

Question 存储用户输入的代码

package compile;

// 用这个类来表示一个 task 的输入内容
// 会包含要编译的代码
public class Question {
    private String code;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }
}

Answer 返回代码编译运行后的结果

package compile;

// 表示一个 compile.Task 的执行结果
public class Answer {
    // 错误码. 约定 error 为 0 表示编译运行都 ok, 为 1 表示编译出错, 为 2 表示运行出错(抛异常).
    private int error;
    // 出错的提示信息. 如果 error 为 1, 编译出错了, reason 中就放编译的错误信息, 如果 error 为 2, 运行异常了, reason 就放异常信息
    private String reason;
    // 运行程序得到的标准输出的结果.
    private String stdout;
    // 运行程序得到的标准错误的结果.
    private String stderr;

    public int getError() {
        return error;
    }

    public void setError(int error) {
        this.error = error;
    }

    public String getReason() {
        return reason;
    }

    public void setReason(String reason) {
        this.reason = reason;
    }

    public String getStdout() {
        return stdout;
    }

    public void setStdout(String stdout) {
        this.stdout = stdout;
    }

    public String getStderr() {
        return stderr;
    }

    public void setStderr(String stderr) {
        this.stderr = stderr;
    }

    @Override
    public String toString() {
        return "compile.Answer{" +
                "error=" + error +
                ", reason='" + reason + '\'' +
                ", stdout='" + stdout + '\'' +
                ", stderr='" + stderr + '\'' +
                '}';
    }
}

Task

  1. 创建目录,如果存在,创建多级目录
  2. 判断代码是否为恶意代码
  3. 把用户提交的代码Question 存储到Solution.java 文件中.
  4. 创建子进程, 对代码 进行编译
  5. 对编译返回值判断是否 出现错误 出现错误的话 返回错误值
  6. 创建子进程 对代码进行 运行
package compile;


import common.FileUtil;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

// 每次的 "编译+运行" 这个过程, 就称为是一个 compile.Task
public class Task {
    // 通过一组常量来约定临时文件的名字.
    // 之前这里的名字都是静态常量. 但是现在要实现针对每个请求都有不同的临时目录, 就不能使用静态常量了
    
    // 1. 这个表示所有临时文件所在的目录
    private String WORK_DIR = null;
    // 2. 约定代码的类名
    private String CLASS = null;
    // 3. 约定要编译的代码文件名.
    private String CODE = null;
    // 4. 约定存放编译错误信息的文件名
    private String COMPILE_ERROR = null;
    // 5. 约定存放运行时的标准输出的文件名
    private String STDOUT = null;
    // 6. 约定存放运行时的标准错误的文件名
    private String STDERR = null;

    public Task() {
        // 在 Java 中使用 UUID 这个类就能生成一个 UUID 了
        WORK_DIR = "./tmp/" + UUID.randomUUID().toString() + "/";
        CLASS = "Solution";
        CODE = WORK_DIR + "Solution.java";
        COMPILE_ERROR = WORK_DIR + "compileError.txt";
        STDOUT = WORK_DIR + "stdout.txt";
        STDERR = WORK_DIR + "stderr.txt";
    }

    // 这个 compile.Task 类提供的核心方法, 就叫做 compileAndRun, 编译+运行 的意思.
    // 参数: 要编译运行的 java 源代码.
    // 返回值: 表示编译运行的结果. 编译出错/运行出错/运行正确.....
    public Answer compileAndRun(Question question) {
        Answer answer = new Answer();
        // 0. 准备好用来存放临时文件的目录
        File workDir = new File(WORK_DIR);
        if (!workDir.exists()) {
            // 创建多级目录.
            workDir.mkdirs();
        }
        // 进行安全性判定
        if (!checkCodeSafe(question.getCode())) {
            System.out.println("用户提交了不安全的代码!");
            answer.setError(3);
            answer.setReason("您提交的代码可能会危害到服务器, 禁止运行!");
            return answer;
        }
        // 1. 把 question 中的 code 写入到一个 Solution.java 文件中.
        FileUtil.writeFile(CODE, question.getCode());
        // 2. 创建子进程, 调用 javac 进行编译. 注意! 编译的时候, 需要有一个 .java 文件.
        //       如果编译出错, javac 就会把错误信息给写入到 stderr 里. 就可以用一个专门的文件来保存. compileError.txt
        //    需要先把编译命令给构造出来.
        String compileCmd = String.format("javac -encoding utf8 %s -d %s", CODE, WORK_DIR);
        System.out.println("编译命令: " + compileCmd);
        CommandUtil.run(compileCmd, null, COMPILE_ERROR);
        // 如果编译出错了, 错误信息就被记录到 COMPILE_ERROR 这个文件中了. 如果没有编译出错, 这个文件是空文件.
        String compileError = FileUtil.readFile(COMPILE_ERROR);
        if (!compileError.equals("")) {
            // 编译出错!
            // 直接返回 compile.Answer, 让 compile.Answer 里面记录编译的错误信息.
            System.out.println("编译出错!");
            answer.setError(1);
            answer.setReason(compileError);
            return answer;
        }
        // 编译正确! 继续往下执行运行的逻辑
        // 3. 创建子进程, 调用 java 命令并执行
        //       运行程序的时候, 也会把 java 子进程的标准输出和标准错误获取到. stdout.txt, stderr.txt
        String runCmd = String.format("java -classpath %s %s", WORK_DIR, CLASS);
        System.out.println("运行命令: " + runCmd);
        CommandUtil.run(runCmd, STDOUT, STDERR);
        String runError = FileUtil.readFile(STDERR);
        if (!runError.equals("")) {
            System.out.println("运行出错!");
            answer.setError(2);
            answer.setReason(runError);
            return answer;
        }
        // 4. 父进程获取到刚才的编译执行的结果, 并打包成 compile.Answer 对象
        //       编译执行的结果, 就通过刚才约定的这几个文件来进行获取即可.
        answer.setError(0);
        answer.setStdout(FileUtil.readFile(STDOUT));
        return answer;
    }

    private boolean checkCodeSafe(String code) {
        List<String> blackList = new ArrayList<>();
        // 防止提交的代码运行恶意程序
        blackList.add("Runtime");
        blackList.add("exec");
        // 禁止提交的代码读写文件
        blackList.add("java.io");
        // 禁止提交的代码访问网络
        blackList.add("java.net");

        for (String target : blackList) {
            int pos = code.indexOf(target);
            if (pos >= 0) {
                // 找到任意的恶意代码特征, 返回 false 表示不安全
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        Task task = new Task();
        Question question = new Question();
        question.setCode("public class Solution {\n" +
                "    public static void main(String[] args) {\n" +
                "        System.out.println(\"hello world\");\n" +
                "    }\n" +
                "}\n");
        Answer answer = task.compileAndRun(question);
        System.out.println(answer);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/794068.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

TEE GP(Global Platform)安全认证方案

TEE之GP(Global Platform)认证汇总 一、安全认证介绍 GlobalPlatform的安全认证计划通过独立的安全评估&#xff0c;确认安全组件是否符合通用标准认可的Protection Profile。它确保安全组件满足为特定服务定义的所需安全级别&#xff0c;使服务提供商能够自信有效地管理风险并…

技术干货|详解威胁情报在WAF类产品中如何发挥最大价值?

在产业互联网发展的过程中&#xff0c;企业也将面临越来越多的安全风险和挑战&#xff0c;威胁情报在企业安全建设中的参考权重大幅上升。早期安全产品对威胁的鉴定是“一维”的&#xff1a;鉴定文件是黑&#xff08;恶意文件&#xff09;、白&#xff08;正常文件&#xff09;…

数据结构初阶--带头双向循环链表

目录 一.带头双向循环链表的定义 二.带头双向循环链表的功能实现 2.1.带头双向循环链表的定义 2.2.带头双向循环链表的结点创建 2.3.带头双向循环链表的初始化 2.4.带头双向循环链表的打印 2.5.带头双向循环链表的判空 2.6.带头双向循环链表的尾插 2.7.带头双向循环链…

要做接口并发性能测试,总得先学会分析吧!

引言 这篇是我3月份在公司内部做的技术分享内容&#xff0c;由于我在公司内部分享的内容较多以及一些特殊性&#xff0c;我摘取了接口并发测试从设计思路整理→测试方案设计→设计分析→代码编写这套流程&#xff08;我不会承认我把40多页PPT的内容都放在这篇博文里&#xff0…

高压放大器在无线电能传输中的作用和用途

高压放大器是一种电子放大器&#xff0c;主要用于放大输入信号的幅度&#xff0c;通过输出端输出一个较大的电压信号&#xff0c;以达到强化原始信号的功能。在无线电能传输中&#xff0c;高压放大器扮演着非常重要的角色。本文将从无线电能传输的定义、高压放大器的特性以及高…

mysql数据备份与还原进阶操作

创建以下表 创建普通用户backup并给权限 备份数据库school到/backup目录 mysqldump -u backup -p school > /backup/school.sql备份MySQL数据库为带删除表的格式&#xff0c;能够让该备份覆盖已有数据库而不需要手动删除原有数据库 mysqldump -u backup -p --add-drop-d…

mysql mha高可用

目录 1.MHA是什么以及作用 2.工作原理 3.案例 1.MHA是什么以及作用 MHA(Master High Availability) MySQL高可用环境下故障切换和主从复制的软件 MHA 的出现就是解决MySQL单点故障 MySQL故障切换过程中 MHA能做到0-30秒内自动完成故障切换操作 MHA能在故障切换的过程中最大程…

行业追踪,2023-07-26,如果主力不骗人,化工原料和磷化工有第一波机会

自动复盘 2023-07-26 凡所有相&#xff0c;皆是虚妄。若见诸相非相&#xff0c;即见如来。 k 线图是最好的老师&#xff0c;每天持续发布板块的rps排名&#xff0c;追踪板块&#xff0c;板块来开仓&#xff0c;板块去清仓&#xff0c;丢弃自以为是的想法&#xff0c;板块去留让…

1.java入门(基础概念与常识)

文章目录 1.1.java语言有哪些特点&#xff1f;1.2.关于JVM JDK和JRE最详细通俗的解答1.2.1JVM1.2.2 JDK和JRE 1.3. Oracle JDK 和 OpenJDK 的对比1.4. Java 和 C的区别?1.5. import java 和 javax 有什么区别&#xff1f;1.6. 为什么说 Java 语言“编译与解释并存”&#xff1…

Docker仓库

Docker仓库 一、docker Harbor简介 Harbor 是 VMware 公司开源的企业级 Docker Registry 项目&#xff0c;其目标是帮助用户迅速搭建一个企业级的 Docker Registry 服务。Harbor以 Docker 公司开源的 Registry 为基础&#xff0c;提供了图形管理 UI 、基于角色的访问控制(Rol…

Python 基础(十八):异常处理

❤️ 博客主页&#xff1a;水滴技术 &#x1f338; 订阅专栏&#xff1a;Python 入门核心技术 &#x1f680; 支持水滴&#xff1a;点赞&#x1f44d; 收藏⭐ 留言&#x1f4ac; 文章目录 一、异常是什么&#xff1f;二、异常处理的基本语法三、捕获特定的异常类型四、finall…

P3183 [HAOI2016] 食物链

题目描述 如图所示为某生态系统的食物网示意图&#xff0c;据图回答第 1 小题 现在给你 n 个物种和 m 条能量流动关系&#xff0c;求其中的食物链条数。物种的名称为从 1 到 n 编号 M 条能量流动关系形如 1,2,3​,…,am−1​,bm−1​,am​,bm​。其中 ai​ 和 bi​ 表示能量从物…

《零基础入门学习Python》第067讲:GUI的终极选择:Tkinter4

今天我们来学习 Entry 组件&#xff0c;也就是我们平时所说的 输入框。 输入框是跟程序打交道的途径&#xff0c;比如 程序要求你输入 账号 和 密码。那么它就要提供两个输入框&#xff0c;并且接收密码的输入框还会用 星号 * 将实际的内容给隐藏起来。 我们学了还几个 tkint…

uniapp 微信小程序 navigationBarBackgroundColor 标题栏颜色渐变

大体思路&#xff1a; 第一步&#xff1a;“navigationStyle”:“custom” 第二步&#xff1a; template内 重点&#xff1a;给view添加ref“top” 第三步&#xff1a;添加渐变色样式 1、pages.json {"path" : "pages/user/user","style" : …

FAQ文档的重点注意事项!别踩坑了

在很多优秀的大企业中&#xff0c;FAQ文档是企业运营管理中不可或缺的重要部分。但是也仅限大企业&#xff0c;很多企业目前还是没有这个意识的。一方面原因是因为缺乏这个客户服务的意识&#xff0c;另一方面也和技术水平不足有关。但是其实现在有不少的第三方搭建平台可以帮助…

L---泰拉瑞亚---2023河南萌新联赛第(三)场:郑州大学

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 示例1 输入 1 10 3 5 输出 3 说明 只有一把回旋镖&#xff0c;你可以先打两次伤害为3的&#xff0c;再打一次倾尽全力的&#xff0c;造成的伤害为5。总伤害为33511&#xff0c;即可获得胜…

【Java中间件】RocketMQ

RocketMQ 一、MQ概述 Message Queue&#xff0c;是一种提供消息队列服务的中间件。提供了消息生产、存储、消费全过程API的软件系统。 MQ的作用 限流削峰&#xff1a;当用户发送超量请求时&#xff0c;将请求暂存&#xff0c;以便后期慢慢处理。如果不使用MQ暂存直接请求到…

抖音小店开通指南:轻松打造个人电商王国

抖音小店是抖音推出的一个电商平台&#xff0c;为用户提供了开店、上传商品、管理店铺等功能&#xff0c;让用户可以在抖音上直接进行购物。下面不若与众就来谈谈抖音小店的开通的几个步骤&#xff1a; 1. 注册抖音账号&#xff1a;首先&#xff0c;你需要在抖音上注册一个账号…

什么是Koala?

Koala 介绍 koala 是一个前端预处理器语言图形编译工具&#xff0c;支持 Less、Sass、Compass、CoffeeScript&#xff0c;帮助 web 开发者更高效地使用它们进行开发。跨平台运行&#xff0c;完美兼容 windows、linux、mac。 关键特性 多语言支持 支持 Less、Sass、CoffeeSc…

使用PHP实现登录注册功能的完整指南

&#x1f3c6;作者简介&#xff0c;黑夜开发者&#xff0c;全栈领域新星创作者✌&#xff0c;2023年6月csdn上海赛道top4。多年电商行业从业经验&#xff0c;对系统架构&#xff0c;数据分析处理等大规模应用场景有丰富经验。 &#x1f3c6;本文已收录于PHP专栏&#xff1a;PHP…