Skip to main content
 首页 » 编程设计

python之使用 websocket 直播标准输出和标准输入

2024年10月17日41lovecherry

online compiler这是我的网站,用户可以在其中运行控制台程序。

目前,用户在运行程序之前必须输入程序输入。我正在尝试为该程序构建实时用户输入(希望提供与他们在笔记本电脑上运行程序相同的体验)。

在实现这一目标的研究中,我遇到了使用 websocket 来stream stdout and stdin 的解决方案。

我的实现

# coding: utf-8 
import subprocess 
import thread 
 
from tornado.websocket import WebSocketHandler 
 
from nbstreamreader import NonBlockingStreamReader as NBSR 
 
 
class WSHandler(WebSocketHandler): 
    def open(self): 
        self.write_message("connected") 
        self.app = subprocess.Popen(['sh', 'app/shell.sh'], stdout=subprocess.PIPE, stdin=subprocess.PIPE, 
                                    shell=False) 
        self.nbsr = NBSR(self.app.stdout) 
        thread.start_new_thread(self.soutput, ()) 
 
    def on_message(self, incoming): 
        self.app.stdin.write(incoming) 
 
    def on_close(self): 
        self.write_message("disconnected") 
 
    def soutput(self): 
        while True: 
            output = self.nbsr.readline(0.1) 
            # 0.1 secs to let the shell output the result 
            if not output: 
                print 'No more data' 
                break 
            self.write_message(output) 

nbstreamreader.py

from threading import Thread 
from Queue import Queue, Empty 
 
 
class NonBlockingStreamReader: 
    def __init__(self, stream): 
        ''' 
        stream: the stream to read from. 
                Usually a process' stdout or stderr. 
        ''' 
 
        self._s = stream 
        self._q = Queue() 
 
        def _populateQueue(stream, queue): 
            ''' 
            Collect lines from 'stream' and put them in 'quque'. 
            ''' 
 
            while True: 
                line = stream.readline() 
                if line: 
                    queue.put(line) 
                else: 
                    raise UnexpectedEndOfStream 
 
        self._t = Thread(target=_populateQueue, 
                         args=(self._s, self._q)) 
        self._t.daemon = True 
        self._t.start()  # start collecting lines from the stream 
 
    def readline(self, timeout=None): 
        try: 
            return self._q.get(block=timeout is not None, 
                               timeout=timeout) 
        except Empty: 
            return None 
 
 
class UnexpectedEndOfStream(Exception): pass 

shell.sh

#!/usr/bin/env bash 
echo "hello world" 
echo "hello world" 
read -p "Your first name: " fname 
read -p "Your last name: " lname 
echo "Hello $fname $lname ! I am learning how to create shell scripts" 

此代码流式传输 stdout 直到 shell.sh 代码到达读取语句。

请指导我做错了什么。为什么它不等待 stdin 并在程序执行完成之前打印“No more data”?

测试它的源代码https://github.com/mryogesh/streamconsole.git

请您参考如下方法:

您的 readline() 方法超时,除非您在 100 毫秒内发送输入,然后中断循环。您看不到 read -p 提示的原因是缓冲(因为 readline 和管道缓冲)。最后,您的示例 javascript 不会发送尾随换行符,因此 read 不会返回。

如果增加超时,包括一个换行符和find a way to work around buffering issues ,您的示例基本上应该有效。

我也会使用 tornado.process和协程而不是子进程和线程:

from tornado import gen 
from tornado.process import Subprocess 
from tornado.ioloop import IOLoop 
from tornado.iostream import StreamClosedError 
from tornado.websocket import WebSocketHandler 
 
 
class WSHandler(WebSocketHandler): 
    def open(self): 
        self.app = Subprocess(['script', '-q', 'sh', 'app/shell.sh'], stdout=Subprocess.STREAM, stdin=Subprocess.STREAM) 
        IOLoop.current().spawn_callback(self.stream_output) 
 
    def on_message(self, incoming): 
        self.app.stdin.write(incoming.encode('utf-8')) 
 
    @gen.coroutine 
    def stream_output(self): 
        try: 
            while True: 
                line = yield self.app.stdout.read_bytes(1000, partial=True) 
                self.write_message(line.decode('utf-8')) 
        except StreamClosedError: 
            pass