1

I am monitoring and Minecraft server and I am making a setup file in Python. I need to be able to run two threads, one running the minecraft_server.jar in the console window, while a second thread is constantly checking the output of the minecraft_server. Also, how would I input into the console from Python after starting the Java process?

Example:

thread1 = threading.Thread(target=listener)
thread2 = minecraft_server.jar

def listener():
    if minecraft_server.jarOutput == "Server can't keep up!":
        sendToTheJavaProccessAsUserInputSomeCommandsToRestartTheServer
2
  • 2
    What do you mean by "input into the console from Python"? You want to feed input into minecraft_server.jar as if your program were its console? Or share the real console's stdin with the server and somehow multiplex between the two so you can get user input? Or…? Commented Nov 21, 2014 at 0:19
  • I want to feed input into minecraft_server.jar as if my program where its console. @abarnert Commented Nov 21, 2014 at 0:34

1 Answer 1

2

It's pretty hard to tell here, but I think what you're asking is how to:

  • Launch a program in the background.
  • Send it input, as if it came from a user on the console.
  • Read its output that it tries to display to a user on the console.
  • At the same time, run another thread that does other stuff.

The last one is pretty easy; in fact, you've mostly written it, you just need to add a thread1.start() somewhere.

The subprocess module lets you launch a program and control its input and output. It's easiest if you want to just feed in all the input at once, wait until it's done, then process all the output, but obviously that's not your case here, so it's a bit more involved:

minecraft = subprocess.Popen(['java', 'path/to/minecraft_server.jar', '-other', 'args],
                             stdin=subprocess.PIPE, 
                             stdout=subprocess.PIPE, stderr=subprocess.STDOUT)

I'm merging stdout and stderr together into one pipe; if you want to read them separately, or send stderr to /dev/null, or whatever, see the docs; it's all pretty simple. While we're making assumptions here, I'm going to assume that minecraft_server uses a simple line-based protocol, where every command, every response, and every info message is exactly one line (that is, under 1K of text ending in a \n).

Now, to send it input, you just do this:

minecraft.stdin.write('Make me a sandwich\n')

Or, in Python 3.x:

minecraft.stdin.write(b'Make me a sandwich\n')

To read its output, you do this:

response = minecraft.stdout.readline()

That works just like a regular file. But note that it works like a binary file. In Python 2.x, the only difference is that newlines don't get automatically converted, but in Python 3.x, it means you can only write bytes (and compatible objects), not strs, and you will receive bytes back. There are good reasons for that, but if you want to get pipes that act like text files instead, see the universal_newlines (and possibly bufsize) arguments under Frequently Used Arguments and Popen Constructor.


Also, it works like a blocking file. With a regular file, this rarely matters, but with a pipe, it's quite possible that there will be data later, but there isn't data yet (because the server hasn't written it yet). So, if there is no output yet (or not a complete line's worth, since I used readline()), your thread just blocks, waiting until there is.

If you don't want that, you probably want to create another thread to service stdout. And its function can actually look pretty similar to what you've got:

def listener():
    for line in minecraft.stdout:
        if line.strip() == "Server can't keep up!":
            minecraft.stdin.write("Restart Universe\n")

Now that thread can block all day and there's no problem, because your other threads are still going.


Well, not quite no problem.

First it's going to be hard to cleanly shut down your program.

More seriously, the pipes between processes have a fixed size; if you don't service stdout fast enough, or the child doesn't service stdin fast enough, the pipe can block. And, the way I've written things, if the stdin pipe blocks, we'll be blocked forever in that stdin.write and won't get to the next read off stdout, so that can block too, and suddenly we're both waiting on each other forever.

You can solve this by having another thread to service stdout. The subprocess module itself includes an example, in the Popen._communicate function used by all the higher-level functions. (Make sure to look at Python 3.3 or later, because earlier versions had bugs.)

If you're in Python 3.4+ (or 3.3 with a backport off PyPI), you can instead use asyncio to rewrite your program around an event loop and handle the input and output the same way you'd write a reactor-based network server. That's what all the cool kids are doing in 2017, but back in late 2014 many people still thought it looked new and scary.


If all of this is sounding like a lot more work than you signed on for, you may want to consider using pexpect, which wraps up a lot of the tedious details, and makes some simplifying assumptions that are probably true in your case.

Sign up to request clarification or add additional context in comments.

6 Comments

Exactly what I was looking for, thanks for such a thorough runthrough of how everything should work.
I am trying to use minecraft.stdin.write("say hello world\n") but I keep getting a type error saying that str is not supported by the buffer type? Any idea why? Specs: Windows 7 Python 3.4.2 @abarnert
@TheMountainFurnaceGabriel: Do you not know about the difference between str and bytes, and text and binary file objects? Let me edit the answer.
Should I just convert the string to bytes and that will fix the problem? bytes("say hello world\n", 'utf-8')
@TheMountainFurnaceGabriel: Of course if you're really just sending literals like that, and they're always pure ASCII, there's no reason to write them as Unicode str literals and then encode them; just write them as bytes literals: b'say hello world\n'.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.