Как сделать витой клиент Python с функцией чтения

Я пытаюсь написать клиент для простого TCP-сервера с использованием Python Twisted. Конечно, я новичок в Python и только начал смотреть на Twisted, так что мог все делать неправильно.

Сервер прост, и вы должны использовать nc или telnet. Нет аутентификации. Вы просто подключаетесь и получаете простую консоль. Я хотел бы написать клиент, который добавляет некоторые функции чтения (история и emacs, такие как ctrl-a / ctrl-e, - это то, что мне нужно)

Ниже приведен код, который я написал, который работает так же хорошо, как использование 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

Я смотрел на

Витая интеграция Python с модулем Cmd

Это действительно не сработало для меня. Код примера отлично работает, но когда я представил сеть, у меня возникли условия гонки с stdio. Эта старая ссылка, кажется, поддерживает аналогичный подход (запуск строки чтения в отдельном потоке), но я не продвинулся далеко с этим.

Я также изучал оскорбления в виде витой раковины, но мне не удалось заставить что-либо работать, кроме демонстрационных примеров.

Как лучше всего создать клиент на базе терминала, который бы обеспечивал поддержку чтения в строке?

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

выглядит многообещающе, но я не понимаю, как его использовать.

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

также, похоже, обеспечивает поддержку обработки стрелок вверх и вниз, например, но я не мог заставить консоль переключаться на наследование от HistoricRecVLine вместо LineReceiver для работы.

Может быть, twisted - неправильный фреймворк, или я должен использовать все классы conch. Мне просто понравился его стиль, ориентированный на события. Есть ли лучший / простой подход к поддержке readline или readline в скрученном клиенте?


person Joel    schedule 14.12.2012    source источник


Ответы (1)


Я решил эту проблему, не используя фреймворк Twisted. Это отличный фреймворк, но я думаю, что это не тот инструмент для работы. Вместо этого я использовал модули telnetlib, cmd и readline.

Мой сервер асинхронный, но это не значит, что мой клиент должен быть таким, поэтому я использовал telnetlib для связи с сервером. Это упростило создание ConsoleClient класса, который является подклассом cmd.Cmd и получает историю и ярлыки, подобные emacs.

#! /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()

Одно изменение, которое я сделал, заключалось в удалении приглашения, возвращаемого сервером, и использовании Cmd.prompt для отображения пользователю. Я хотел поддержать Ctrl-c, больше похожее на оболочку.

person Joel    schedule 22.01.2013