5

PythonTwistedを使用して単純なTCPサーバー用のクライアントを作成しようとしています。もちろん、私はPythonにかなり慣れていて、Twistedを見始めたばかりなので、すべて間違っている可能性があります。

サーバーはシンプルで、ncまたはtelnetを使用することを目的としています。認証はありません。接続するだけで、シンプルなコンソールを入手できます。いくつかのreadline機能を追加するクライアントを作成したい(履歴とctrl-a / ctrl-eのようなemacsが私が求めているものです)

以下は私が書いたコードで、このようにコマンドラインからnetcatを使用するのと同じように機能しますnc localhost 4118

from twisted.internet import reactor, protocol, stdio
from twisted.protocols import basic
from sys import stdout

host='localhost'
port=4118
console_delimiter='\n'

class MyConsoleClient(protocol.Protocol):
    def dataReceived(self, data):
        stdout.write(data)
        stdout.flush()

    def sendData(self,data):
        self.transport.write(data+console_delimiter)

class MyConsoleClientFactory(protocol.ClientFactory):
    def startedConnecting(self,connector):
        print 'Starting connection to console.'

    def buildProtocol(self, addr):
        print 'Connected to console!'
        self.client = MyConsoleClient()
        self.client.name = 'console'
        return self.client

    def clientConnectionFailed(self, connector, reason):
        print 'Connection failed with reason:', reason

class Console(basic.LineReceiver):
    factory = None
    delimiter = console_delimiter

    def __init__(self,factory):
        self.factory = factory

    def lineReceived(self,line):
        if line == 'quit':
            self.quit()
        else:
            self.factory.client.sendData(line)

    def quit(self):
        reactor.stop()

def main():
    factory = MyConsoleClientFactory()
    stdio.StandardIO(Console(factory))
    reactor.connectTCP(host,port,factory)
    reactor.run()

if __name__ == '__main__':
    main()

出力:

$ python ./console-console-client.py 
Starting connection to console.
Connected to console!
console> version
d305dfcd8fc23dc6674a1d18567a3b4e8383d70e
console> number-events
338
console> quit

私は見ました

PythonTwistedとCmdモジュールの統合

これは本当にうまくいきませんでした。サンプルコードはうまく機能しますが、ネットワーキングを導入したとき、stdioとの競合状態があるようでした。この古いリンクは同様のアプローチ(別のスレッドでreadlineを実行する)を提唱しているようですが、私はそれをうまく理解していませんでした。

ねじれた巻き貝の侮辱も調べましたが、デモの例以外はうまくいきませんでした。

リードラインサポートを提供する端末ベースのクライアントを作成するための最良の方法は何ですか?

http://twistedmatrix.com/documents/current/api/twisted.conch.stdio.html

有望に見えますが、使い方がわかりません。

http://twistedmatrix.com/documents/current/api/twisted.conch.recvline.HistoricRecvLine.html

また、たとえば上矢印と下矢印の処理をサポートしているようですが、LineReceiverではなくHistoricRecVLineから継承するようにコンソールを切り替えて機能させることができませんでした。

多分ねじれは使用するのに間違ったフレームワークであるか、私はすべての巻き貝のクラスを使用する必要があります。イベント駆動型のスタイルが気に入りました。ツイストクライアントでのサポートのようなreadlineまたはreadlineを持つためのより良い/より簡単なアプローチはありますか?

4

1 に答える 1

0

Twistedフレームワークを使用せずにこれを解決することにしました。これは素晴らしいフレームワークですが、この仕事には間違ったツールだったと思います。代わりに、、およびモジュールを使用しtelnetlibましcmdreadline

私のサーバーは非同期ですが、それは私のクライアントが必要であるという意味ではなかったので、私telnetlibはサーバーへの通信に使用しました。これにより、サブクラス化して履歴やemacsのようなショートカットを取得するConsoleClientクラスを簡単に作成できました。cmd.Cmd

#! /usr/bin/env python

import telnetlib
import readline
import os
import sys
import atexit
import cmd
import string

HOST='127.0.0.1'
PORT='4118'

CONSOLE_PROMPT='console> '

class ConsoleClient(cmd.Cmd):
    """Simple Console Client in Python.  This allows for readline functionality."""

    def connect_to_console(self):
        """Can throw an IOError if telnet connection fails."""
        self.console = telnetlib.Telnet(HOST,PORT)
        sys.stdout.write(self.read_from_console())
        sys.stdout.flush()

    def read_from_console(self):
        """Read from console until prompt is found (no more data to read)
        Will throw EOFError if the console is closed.
        """
        read_data = self.console.read_until(CONSOLE_PROMPT)
        return self.strip_console_prompt(read_data)

    def strip_console_prompt(self,data_received):
        """Strip out the console prompt if present"""
        if data_received.startswith(CONSOLE_PROMPT):
            return data_received.partition(CONSOLE_PROMPT)[2]
        else:
            #The banner case when you first connect
            if data_received.endswith(CONSOLE_PROMPT):
                return data_received.partition(CONSOLE_PROMPT)[0]
            else:
                return data_received

    def run_console_command(self,line):
        self.write_to_console(line + '\n')
        data_recved = self.read_from_console()        
        sys.stdout.write(self.strip_console_prompt(data_recved))        
        sys.stdout.flush()

    def write_to_console(self,line):
        """Write data to the console"""
        self.console.write(line)
        sys.stdout.flush()

    def do_EOF(self, line): 
        try:
            self.console.write("quit\n")
            self.console.close()
        except IOError:
            pass
        return True

    def do_help(self,line):
        """The server already has it's own help command.  Use that"""
        self.run_console_command("help\n")

    def do_quit(self, line):        
        return self.do_EOF(line)

    def default(self, line):
        """Allow a command to be sent to the console."""
        self.run_console_command(line)

    def emptyline(self):
        """Don't send anything to console on empty line."""
        pass


def main():
    histfile = os.path.join(os.environ['HOME'], '.consolehistory') 
    try:
        readline.read_history_file(histfile) 
    except IOError:
        pass
    atexit.register(readline.write_history_file, histfile) 

    try:
        console_client = ConsoleClient()
        console_client.prompt = CONSOLE_PROMPT
        console_client.connect_to_console()
        doQuit = False;
        while doQuit != True:
            try:
                console_client.cmdloop()
                doQuit = True;
            except KeyboardInterrupt:
                #Allow for ^C (Ctrl-c)
                sys.stdout.write('\n')
    except IOError as e:
        print "I/O error({0}): {1}".format(e.errno, e.strerror)
    except EOFError:
        pass

if __name__ == '__main__':
    main()

私が行った変更の1つは、サーバーから返されたプロンプトを削除Cmd.promptし、ユーザーに表示するために使用することでした。私の理由は、Ctrl-cがシェルのように機能することをサポートするためでした。

于 2013-01-22T17:47:41.843 に答える