Reputation: 25
Edit: I am running this on windows as the servers are administered on my main gaming rig
I am trying to create a minecraft server controller so that I can streamline the process of administering servers. I have been able to use subprocess to start and stop the server with no issues however when I try to get it to issue commands It will not. Unless I use subprocess.communicate()
the problem with this solution however is that it causes the script to wait forever and eventually crash, leaving the server running. Here is the code for what I have.
import subprocess
class Server:
def __init__(self, server_start_bat, dir):
self.server_start_bat = server_start_bat
self.dir = dir
def start_server(self):
self.server = subprocess.Popen(self.server_start_bat, cwd=self.dir, shell=True, stdin=subprocess.PIPE,
universal_newlines=True, text=True)
def stop_server(self):
self.run_command('stop')
self.server.communicate()
def op_me(self):
self.run_command(f'op Username')
def clear_weather(self):
self.run_command(f'weather clear 1000000')
def mob_griefing_on(self):
self.run_command(f'gamerule mobGriefing True')
def mob_griefing_off(self):
self.run_command(f'gamerule mobGriefing True')
def set_time_day(self):
self.run_command(f'time set day')
def set_time_night(self):
self.run_command(f'time set night')
def run_command(self, command_text):
self.server.stdin.write(f'{command_text}\n')
Upvotes: 1
Views: 342
Reputation: 123
Edit: Check out Non-blocking read on a subprocess.PIPE in python
I was battling a very similar issue and it took me 3 days to get it all running. The problem with subprocess.communicate is that you can only call it once, and the output seems to be given only once the subprocess terminates (in the experience i had, could very well be wrong).
The question i had, which your question imho boils down to was: how can I write to a processes stdin and read from its stdout while it is still running?
I ended up using Pipes. Note that they have to me flushed if you write something that is smaller that BUFSIZE, which is 0x1000 in my case. See man pipe
for more info.
Anyway, below is my code.
import time
import pty
import os
import subprocess
PIPE_BUFSIZE = 4096
_control = False
class Pipe:
def __init__(self, flags=None, terminal=True):
"""Creates a Pipe you can easily write to and read from. Default is to open up a pseudo-terminal.
If you supply flags, pipe2 is used."""
if flags or not terminal:
self._readfd, self._writefd = os.pipe2(flags)
else: # default
self._readfd, self._writefd = pty.openpty()
self.readobj = open(self._readfd, "rb", 0)
self.writeobj = open(self._writefd, "wb", 0)
def write(self, text):
if isinstance(text, str):
text = text.encode()
result = self.writeobj.write(text)
self.writeobj.flush()
return result
def read(self, n):
if _control: # im not using this anymore since the flush seems to be reliable
controlstr = b"this_was_flushed"
controllen = len(controlstr)
self.writeobj.write(controlstr)
time.sleep(.001)
self.writeobj.flush()
result = self.readobj.read(n+controllen)
assert result[-controllen:] == controlstr
return result[:-controllen]
else:
self.writeobj.flush()
return self.readobj.read(n)
class ProcessIOWrapper:
"""Provides an easy way to redirect stdout and stderr using pipes. Write to the processes STDIN and read from STDOUT at any time! """
def __init__(self, args, inittext=None, redirect=True):
#create three pseudo terminals
self.in_pipe = Pipe()
self.out_pipe = Pipe()
self.err_pipe = Pipe()
# if we want to redirect, tell the subprocess to write to our pipe, else it will print to normal stdout
if redirect:
stdout_arg= self.out_pipe.writeobj
stderr_arg= self.err_pipe.writeobj
else:
stdout_arg=None
stderr_arg= None
self.process = subprocess.Popen(args, stdin=self.in_pipe.readobj, stdout=stdout_arg, stderr=stderr_arg)
def write(self, text):
return self.in_pipe.write(text)
def read(self, n, start=None):
return self.out_pipe.read(n)
Small C Program i used for testing (used others, too)
#include <stdio.h>
int main(){
puts("start\n");
char buf[100]="bufbufbuf";
while(1){
gets(buf);
for (int i=0; i<strlen(buf); i++)
if (i%2==0) buf[i]+=1;
puts(buf);
}
}
Upvotes: 1