进程间应用system.out和system.err传递数据

本文提供了一个Java中父进程启动子进程并进行简单通信的例子。父进程通过Runtime.getRuntime().exec()方法启动子进程,并读取子进程的标准输出和错误输出。子进程向标准输出和错误输出写入数据。
public class FatherProcess {
    public static void main(String[] args) {
        int i =0;
        String cmd ="D:\\java_tools\\jdk1.6.0\\bin\\java -Didea.launcher.port=7544 \"-Didea.launcher.bin.path=D:\\Program Files\\JetBrains\\IntelliJ IDEA 11.0.1\\bin\" -Dfile.encoding=UTF-8 -classpath \"D:\\workspace\\thinking\\bin;D:\\java_tools\\jdk1.6.0\\jre\\lib\\alt-rt.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\charsets.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\deploy.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\javaws.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\jce.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\jsse.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\management-agent.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\plugin.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\resources.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\rt.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\ext\\dnsns.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\ext\\localedata.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\ext\\sunjce_provider.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\ext\\sunmscapi.jar;D:\\java_tools\\jdk1.6.0\\jre\\lib\\ext\\sunpkcs11.jar;D:\\Program Files\\JetBrains\\IntelliJ IDEA 11.0.1\\lib\\junit.jar;D:\\Program Files\\JetBrains\\IntelliJ IDEA 11.0.1\\lib\\idea_rt.jar\" com.intellij.rt.execution.application.AppMain thinking.SubProcess";
        try {
            Process p = Runtime.getRuntime().exec(cmd);
            InputStream reader = p.getInputStream();
            i = reader.read();
            System.out.println(i);
            InputStream reader2 = p.getErrorStream();
            i = reader2.read();
            System.out.println(i);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
  }

另一进程的代码如下:

public class SubProcess {
    public static void main(String[] args) {
        System.out.write(100);
        System.out.flush();
        System.err.write(50);
        System.err.flush();
    }
}

缺点:可以传递小于256的数字。大于256的数字,溢出。


package com.example.demo.service.impl; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.UUID; import cn.hutool.core.thread.ThreadFactoryBuilder; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.dao.entity.WExecuteHost; import com.example.demo.dao.entity.WHostProcess; import com.example.demo.dao.entity.WPersonalHost; import com.example.demo.dao.mapper.WExecuteHostMapper; import com.example.demo.request.ProcessRequest; import com.example.demo.service.WExecuteHostService; import com.example.demo.service.WHostProcessService; import com.example.demo.service.WPersonalHostService; import lombok.Data; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.messaging.simp.SimpMessagingTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.interceptor.TransactionAspectSupport; import javax.annotation.PreDestroy; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.Date; import java.util.List; import java.util.Map; import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.regex.Matcher; import java.util.regex.Pattern; @Service public class WExecuteHostServiceImpl extends ServiceImpl<WExecuteHostMapper, WExecuteHost> implements WExecuteHostService { @Autowired private WPersonalHostService wPersonalHostService; @Autowired private WHostProcessService wHostProcessService; @Data private static class UserSession { private Process cmdProcess; private volatile boolean isProcessRunning; private String sessionId; private String logFilePath; private final Queue<String> logBuffer = new ConcurrentLinkedQueue<>(); private static final int BATCH_SIZE = 50; // 批量写入阈值 // 命令执行状态锁 private final AtomicBoolean isExecuting = new AtomicBoolean(false); } private final Map<String, UserSession> userSessions = new ConcurrentHashMap<>(); private final SimpMessagingTemplate messagingTemplate; // 异步日志写入线程池 private static final ExecutorService LOG_WRITER_POOL = Executors.newCachedThreadPool( new ThreadFactoryBuilder().setNamePrefix("log-writer-").build()); // 日志刷新调度器 private static final ScheduledExecutorService LOG_FLUSH_SCHEDULER = Executors.newSingleThreadScheduledExecutor(); public WExecuteHostServiceImpl(SimpMessagingTemplate messagingTemplate) { this.messagingTemplate = messagingTemplate; startLogFlushService(); } // 初始化日志刷新服务 private void startLogFlushService() { LOG_FLUSH_SCHEDULER.scheduleAtFixedRate(() -> { userSessions.values().forEach(session -> { if (!session.getLogBuffer().isEmpty()) { List<String> batch = CollUtil.newArrayList(session.getLogBuffer()); session.getLogBuffer().clear(); asyncWriteLog(session.getLogFilePath(), String.join("\n", batch)); } }); }, 0, 1, TimeUnit.SECONDS); } @Override public void executeCommand(String command, String host, String userId) { // 0. ABORT命令特殊处理(优先处理终止请求) if ("ABORT".equalsIgnoreCase(command)) { handleAbort(userId); return; } // 1. 权限校验 if (!validateUserHost(userId, host)) { sendError("无权访问该主机", userId); return; } // 2. 检查用户当前会话状态 UserSession session = userSessions.get(userId); if (session != null && session.isExecuting.get()) { sendError("已有命令执行中,请等待完成或使用ABORT终止", userId); return; } // 3. 创建新会话(带原子状态检查) session = userSessions.computeIfAbsent(userId, key -> { UserSession newSession = createNewSession(userId, host); if (newSession != null) { newSession.isExecuting.set(true); // 标记为执行中 } return newSession; }); if (session == null) return; // 4. 写入日志并执行命令 try { // 确保获得执行锁 if (!session.isExecuting.compareAndSet(true, true)) { sendError("命令执行冲突,请重试", userId); return; } session.getLogBuffer().offer("——————————————— " + DateUtil.now() + " ———————————————"); // 发送命令到进程 IoUtil.write(session.getCmdProcess().getOutputStream(), Charset.forName("gbk"), true, command + "\n"); } catch (Exception e) { session.isExecuting.set(false); // 发生异常时释放锁 sendError("命令发送失败: " + e.getMessage(), userId); } } @Override public Boolean isCommandExecuting(String userId) { UserSession session = userSessions.get(userId); return session != null && session.isExecuting.get(); } @Override public void handleAbort(String userId) { UserSession session = userSessions.get(userId); if (session == null || session.getCmdProcess() == null) { sendError("没有活动的命令进程", userId); return; } try { long pid = session.getCmdProcess().pid(); System.out.println("Attempting to kill process with PID: " + pid); // 使用 taskkill 命令终止进程 ProcessBuilder taskKill = new ProcessBuilder("taskkill", "/F", "/T", "/PID", String.valueOf(pid)); Process killProcess = taskKill.start(); // 等待命令执行完成 int exitCode = killProcess.waitFor(); System.out.println("taskkill exit code: " + exitCode); if (exitCode == 0) { // 进程终止成功 session.isExecuting.set(false); cleanupSession(userId); messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, "✔️" + "进程已通过 taskkill 终止 (PID: " + pid + ")"); messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, ""); messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, System.getProperty("user.dir") + ">"); } else { // 进程终止失败 sendError("终止进程失败,错误码: " + exitCode, userId); } } catch (IOException | InterruptedException e) { System.err.println("Error killing process: " + e.getMessage()); sendError("终止进程失败: " + e.getMessage(), userId); } } @Override public String startProcess(ProcessRequest processRequest) { try { // 数据库表新增数据 String p13 = processRequest.getP13().trim(); String processName = processRequest.getProcessName().trim(); String productNumber = processRequest.getProductNumber().trim(); String executeHost = processRequest.getExecuteHost().trim(); String department = processRequest.getDepartment().trim(); String version = processRequest.getVersion().trim(); if (StrUtil.isEmpty(p13) || StrUtil.isEmpty(processName) || StrUtil.isEmpty(productNumber) || StrUtil.isEmpty(executeHost) || StrUtil.isEmpty(department) || StrUtil.isEmpty(version)) { return "新增进程失败。"; } WHostProcess wHostProcess = new WHostProcess(); wHostProcess.setP13(p13); wHostProcess.setProcessName(processName); wHostProcess.setProductNumber(productNumber); wHostProcess.setHost(executeHost); wHostProcess.setPid(null); wHostProcess.setDepartment(department); wHostProcess.setState("离线"); wHostProcess.setVersion(version); wHostProcess.setBeginTime(new Date()); boolean saveResult = wHostProcessService.save(wHostProcess); if (saveResult) { LambdaQueryWrapper<WPersonalHost> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper .eq(WPersonalHost::getExecuteHost, processRequest.getExecuteHost()) .eq(WPersonalHost::getSharedHost, processRequest.getSharedHost()); WPersonalHost wPersonalHost = wPersonalHostService.getOne(queryWrapper); // 执行py启动命令 //todo 后续动态 String pythonEXEPath = "D:\\miniforge\\envs" + File.separator + p13 + File.separator + "python" + wPersonalHost.getPythonEnv() + File.separator + "python.exe"; String mainPyPath = System.getProperty("user.dir") + File.separator + "python-package" + File.separator + executeHost + File.separator + p13 + File.separator + "test" + File.separator + "main.py"; executeCommand(pythonEXEPath + " " + mainPyPath, processRequest.getExecuteHost(), processRequest.getP13()); return "正在启动项目..."; } } catch (Exception e) { e.printStackTrace(); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } return "新增进程失败。"; } private boolean validateUserHost(String userId, String executeHost) { LambdaQueryWrapper<WPersonalHost> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(WPersonalHost::getP13, userId) .eq(WPersonalHost::getExecuteHost, executeHost) .eq(WPersonalHost::getState, "在线"); return wPersonalHostService.getOne(queryWrapper) != null; } private UserSession createNewSession(String userId, String executeHost) { try { UserSession session = new UserSession(); session.setSessionId(UUID.randomUUID().toString()); session.setLogFilePath(initLogFile(userId, executeHost)); // 启动CMD进程(带唯一标题) ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/k", "title " + generateUniqueTitle(userId)); session.setCmdProcess(pb.redirectErrorStream(true).start()); // 启动输出监听线程 startOutputThread(session, userId); return session; } catch (IOException e) { sendError("进程启动失败: " + e.getMessage(), userId); return null; } } private String initLogFile(String userId, String executeHost) { // 1. 构建基础路径(使用File.separator) String baseDir = FileUtil.normalize( System.getProperty("user.dir") + File.separator + "command-log"); // 2. 构建安全路径(统一使用File.separator) String safePath = FileUtil.normalize( baseDir + File.separator + userId + File.separator + executeHost + File.separator + "项目名称"); // 3. 安全校验(现在路径分隔符一致) if (!safePath.startsWith(baseDir)) { throw new SecurityException("非法日志路径: " + safePath); } // 4. 创建目录(自动处理路径分隔符) FileUtil.mkdir(safePath); // 5. 生成日志文件 String logFileName = DateUtil.today() + ".log"; return FileUtil.touch(safePath + File.separator + logFileName).getAbsolutePath(); } private void startOutputThread(UserSession session, String userId) { new Thread(() -> { try (BufferedReader reader = new BufferedReader( new InputStreamReader(session.getCmdProcess().getInputStream(), "GBK"))) { String line; while ((line = reader.readLine()) != null) { messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, line); session.getLogBuffer().offer(line); } } catch (Exception e) { sendError("输出流异常: " + e.getMessage(), userId); } finally { session.isExecuting.set(false); // 命令结束释放锁 cleanupSession(userId); // 通知前端命令执行结束 messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, "⏹ 该命令执行已结束"); } }).start(); } private void asyncWriteLog(String logFilePath, String content) { CompletableFuture.runAsync(() -> { try { // 替换掉日志文件中没用的信息,如多余的路径信息 String currentDir = System.getProperty("user.dir"); String escapedDir = currentDir.replace("\\", "\\\\"); String regex = "(?m)^\\s*" + escapedDir + ">(?!\\S)\\s*"; // 创建 Pattern Matcher 对象 Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(content); // 检查是否存在匹配的模式 if (matcher.find()) { // 如果存在匹配,则进行替换 String cleaned = content.replaceAll(regex, ""); FileUtil.appendString(cleaned + System.lineSeparator(), logFilePath, "GBK"); } else { FileUtil.appendString(content + System.lineSeparator(), logFilePath, "GBK"); } } catch (Exception e) { System.err.println("日志写入失败: " + e.getMessage()); } }, LOG_WRITER_POOL); } private void cleanupSession(String userId) { UserSession session = userSessions.remove(userId); if (session != null) { try { if (session.getCmdProcess() != null) { session.getCmdProcess().destroyForcibly(); } // 强制将剩余日志写入文件(新增代码) if (!session.getLogBuffer().isEmpty()) { asyncWriteLog(session.getLogFilePath(), String.join("\n", session.getLogBuffer())); session.getLogBuffer().clear(); } } catch (Exception ignored) { } } } @PreDestroy public void cleanup() { LOG_FLUSH_SCHEDULER.shutdown(); LOG_WRITER_POOL.shutdown(); userSessions.forEach((userId, session) -> { try { if (session.getCmdProcess() != null) { session.getCmdProcess().destroyForcibly(); } } catch (Exception ignored) { } }); userSessions.clear(); } /** * 发送错误日志 */ private void sendError(String message, String userId) { try { messagingTemplate.convertAndSend("/topic/commandOutput/" + userId, "❌" + message); } catch (Exception ignored) { } } /** * 生成cmd窗口唯一id */ private String generateUniqueTitle(String userId) { return "CMD_SESSION_" + userId + "_" + System.currentTimeMillis(); } } 仔细阅读我的代码,现在有个bug,我执行下面的main.py文件,会报错请输入你的名字: 发生未知错误: EOF when reading a line import random import sys from datetime import datetime def generate_random_number(min_val=1, max_val=100): """ 生成一个指定范围内的随机整数 参数: min_val (int): 最小值,默认为1 max_val (int): 最大值,默认为100 返回: int: 随机生成的整数 """ return random.randint(min_val, max_val) def check_even_odd(number): """ 检查一个数字是奇数还是偶数 参数: number (int): 要检查的数字 返回: str: "偶数" 或 "奇数" """ return "偶数" if number % 2 == 0 else "奇数" def greet_user(name): """ 向用户问好 参数: name (str): 用户名 """ print(f"你好, {name}! 当前时间是: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") def main(): """ 主函数 """ print("欢迎使用随机数生成器!") try: user_name = input("请输入你的名字: ") greet_user(user_name) count = int(input("你想生成多少个随机数? (1-10): ")) if count < 1 or count > 10: print("请输入1到10之间的数字!") return for i in range(count): num = generate_random_number() print(f"随机数 #{i+1}: {num} ({check_even_odd(num)})") print("感谢使用!") except ValueError: print("错误: 请输入有效的数字!") except KeyboardInterrupt: print("\n程序被用户中断") except Exception as e: print(f"发生未知错误: {e}") if __name__ == "__main__": main()
最新发布
06-18
<think>我们正在解决一个具体问题:Python脚本main.py在Java服务中执行时,input函数报EOF错误。根据引用[1]中提到的EOF错误,这通常是因为输入不足(EndOfFile)。在Java中启动Python进程时,如果Python脚本试图从标准输入读取数据(如input()函数),但Java进程没有提供足够的输入,就会导致EOFError。引用[3]展示了一个使用raw_input(Python2)或input(Python3)的代码片段。在Python3中,input()函数会从标准输入读取一行。当通过Java进程调用时,需要确保Java进程的标准输出流被正确连接到Python进程的标准输入流。解决方案:1.Java中,使用ProcessBuilder或Runtime.exec启动进程时,需要向Python进程的标准输入流写入数据。2.如果Python脚本需要交互式输入,但Java服务无法提供,可能需要修改Python脚本,使其能够从文件、环境变量或命令行参数读取输入,而不是标准输入。具体步骤:方法一:通过Java向Python进程提供输入在Java中,我们可以获取进程的输出流(即Python进程的标准输入流),然后向其中写入数据。示例Java代码:```javaProcessBuilderpb=newProcessBuilder("python","main.py");pb.redirectErrorStream(true);//合并错误流到输入流Processprocess=pb.start();//获取进程的输出流(对于Python进程来说,这是它的标准输入)OutputStreamstdin=process.getOutputStream();BufferedWriterwriter=newBufferedWriter(newOutputStreamWriter(stdin));//向Python脚本写入输入writer.write("inputdata\n");writer.flush();//注意:如果有多行输入,需要多次写入,并在每行末尾加上换行符//最后关闭流,表示输入结束writer.close();//读取Python进程的输出BufferedReaderreader=newBufferedReader(newInputStreamReader(process.getInputStream()));Stringline;while((line=reader.readLine())!=null){System.out.println(line);}intexitCode=process.waitFor();```方法二:修改Python脚本,避免使用标准输入如果不想在Java中处理输入流,可以修改Python脚本,使其从其他来源获取输入,例如:-命令行参数:使用sys.argv-环境变量:使用os.getenv-配置文件例如,将引用[3]中的代码改为从命令行参数读取:```pythonimportsysiflen(sys.argv)>1:input_num_str=sys.argv[1]else:#如果没有提供参数,可以给出默认值或报错print("需要输入一个数字作为参数")sys.exit(1)input_num=int(input_num_str)print("您刚刚输入的数字是:"+str(input_num))```然后在Java中启动时传递参数:```javaProcessBuilderpb=newProcessBuilder("python","main.py","42");```根据引用[1]中提到的错误,错误发生在加载预训练模型时,但用户描述的问题是关于input函数的EOF错误。因此,需要确认问题发生的具体位置。如果错误发生在input函数处,则上述解决方案适用。如果问题不是由input函数引起,而是发生在其他位置(如引用[1]中提到的加载预训练模型时),则可能是由于文件路径问题或文件损坏。需要检查预训练模型文件是否存在且完整。但用户明确提到是“input函数报EOF错误”,所以我们主要针对input函数来解决。总结:1.确保在Java中向Python进程的标准输入提供必要的数据,并在完成后关闭流。2.修改Python脚本,使用其他方式获取输入(如命令行参数),避免使用标准输入。相关问题:1.Java中调用Python脚本时,如何捕获Python脚本的输出?2.如何避免在Java中调用外部进程时的死锁问题?3.在Python脚本中,如何同时支持命令行参数标准输入?</think>### Python脚本在Java服务中执行时`input()`报EOF错误的解决方案 当Python脚本通过Java进程启动时,`input()`函数会因标准输入流异常而触发`EOFError`。这是因为Java进程未正确提供标准输入交互通道。以下是完整解决方案: #### 1. **根本原因分析** - Java执行外部进程时,默认不会为子进程分配交互式输入流 - Python的`input()`需要读取`sys.stdin`,但Java未提供有效输入管道 - 触发条件:`sys.stdin.read()`遇到流结束符(EOF),报错信息为: ```python EOFError: EOF when reading a line ``` #### 2. **解决方案** ##### 方法1:通过Java主动提供输入数据(推荐) 在Java端使用`ProcessBuilder`向Python进程写入输入数据: ```java // Java端代码 ProcessBuilder pb = new ProcessBuilder("python", "main.py"); pb.redirectErrorStream(true); // 合并错误流 Process process = pb.start(); // 获取输出流(Python的stdin) OutputStream stdin = process.getOutputStream(); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(stdin)); // 写入Python需要的输入数据 writer.write("required_input_data\n"); // \n模拟回车 writer.flush(); writer.close(); // 关闭流触发EOF // 读取Python输出 BufferedReader reader = new BufferedReader( new InputStreamReader(process.getInputStream()) ); String line; while ((line = reader.readLine()) != null) { System.out.println("[Python输出] " + line); } ``` ##### 方法2:修改Python脚本绕过交互输入 将输入源改为参数/配置文件(参考引用[3]): ```python # Python端修改 import sys if len(sys.argv) > 1: user_input = sys.argv[1] # 从命令行参数获取 else: # 从配置文件读取 with open("input_config.txt") as f: user_input = f.read().strip() ``` #### 3. **关键注意事项** - **流关闭时机**:Java端必须显式关闭输出流(`writer.close()`),否则Python会持续等待输入[^1] - **输入格式化**:Java写入数据时需包含换行符`\n`,模拟终端回车 - **超时处理**:添加Java端超时控制,避免进程阻塞: ```java if (!process.waitFor(30, TimeUnit.SECONDS)) { process.destroy(); throw new TimeoutException("Python执行超时"); } ``` #### 4. **替代方案** - **使用Jython**:直接在JVM中运行Python代码 ```java PythonInterpreter interpreter = new PythonInterpreter(); interpreter.execfile("main.py"); // 无需进程隔离 ``` - **RPC通信**:改用gRPC或HTTP接口代替进程调用 ### 相关问题 1. 如何在Java中捕获Python进程的异常退出状态码? 2. 多线程环境下调用Python进程需要注意哪些并发问题? 3. 如何实现Java与Python间的双向实时数据流传输? 4. Python的`sys.stdin`重定向有哪些安全风险? [^1]: EOF错误通常由输入流意外终止引起,常见于非交互式环境执行交互式脚本 [^3]: 命令行参数是跨语言调用的通用输入方案 [^4]: 多线程环境需注意进程资源竞争同步机制
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值