2017-01-02 177 views
0

online compiler這是我的網站,用戶可以在其中運行控制檯程序。實時流stdout和stdin與websocket

目前,用戶在運行程序之前必須輸入程序輸入。我正在嘗試爲程序構建實時用戶輸入(希望在他們的筆記本電腦上運行程序時提供相同的體驗)。

在研究實現這一目標,我碰到一個解決流標準輸出和標準輸入的WebSocket

我實現

# 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" 

此碼流標準輸出未直到shell.sh碼達到閱讀聲明。

請指導我我在做什麼錯。爲什麼它不會等待stdin並在完成程序執行之前打印出'沒有更多數據'?

的源代碼來測試它https://github.com/mryogesh/streamconsole.git

+0

您是否嘗試調試您的代碼? :) – 2017-01-03 13:54:39

+0

是的,。它進入on_message方法,在thread.start_new_thread(self.soutput,())之後,我添加了print語句,這意味着線程不被阻塞。 –

+0

當你(在輸入之後)擊中「what name」語句時發生了什麼?它是不是流式傳輸?如果你經歷了整個'shell.sh',甚至最後一個'echo'沒有流式傳輸,會發生什麼?這個問題讓我想起了一些事情(我在一年前完成了這一切,通過websocket進行syso流式處理,但目前我無法訪問該代碼;但我記得過早終止了流)。 –

回答

1

readline()方法超時,除非你在100ms內發送的輸入,然後打破循環。您看不到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 
+0

很好地解釋。感謝您的回答:) –

-2

也許你應該看看websocketd(homepage)。
我有一種感覺,它會簡化你想要做的事情。
它將充當websocket服務器並啓動每次客戶端連接時將其作爲參數提供的程序。所有通過websocket連接的數據將被轉發到該程序的STDIN。程序輸出到STDOUT的所有內容都將通過websocket發送。

+0

歡迎來到Where Developers學習,分享和建立職業機會!雖然這可能會在理論上回答這個問題,[這將是更可取的](// meta.stackoverflow.com/q/8259)在這裏包括答案的基本部分,並提供參考鏈接。 – moggi

+0

雖然這個鏈接可能回答這個問題,但最好在這裏包含答案的重要部分,並提供供參考的鏈接。如果鏈接頁面更改,則僅鏈接答案可能會失效。 - [來自評論](/ review/low-quality-posts/17330724) –