2013-09-09 2 views
0

Я начал сетевое программирование с помощью Python и работаю над базовым одноранговым клиентским клиентским приложением чата. Я работал на консоль, но сталкиваюсь с проблемой при разработке графического интерфейса.клиент, не получающий данные через сокет TCP

Это код для моего клиентского скрипта. Он отправляет данные на сервер, но не может получать/отображать данные, отправленные с сервера, я не в порядке. Пожалуйста, покажите ошибку в моем коде и решении.

from socket import * 
    from tkinter import * 
    host="127.0.0.1" 
    port=1420 
    buffer=1024 
    server=(host,port) 
    clientsock=socket(AF_INET,SOCK_STREAM) 
    clientsock.connect(server) 
    class ipbcc(Frame): 
     def __init__(self,master): 
      Frame.__init__(self,master) 
      self.grid() 
      self.create() 
      self.connect() 
     def write(self,event): 
      msg=self.e.get() 
      clientsock.send(msg.encode()) 
     def create(self): 

      self.pic=PhotoImage(file="logo.gif") 
      self.label=Label(self,image=self.pic) 
      self.label.grid(column=0) 
      self.wall=Text(self,width=70,height=20,wrap=WORD) 
      self.wall.grid(row = 0, column = 1, columnspan = 2, sticky = W) 
      self.e=Entry(self,width=50) 
      self.e.grid(row = 1, column = 1, sticky = W) 
      self.e.bind('<Return>',self.write) 
     def add(self,data): 
      self.wall.insert(END,data) 
     def connect(self): 
      def xloop(): 
       while 1: 
        data=clientsock.recv(buffer).decode() 
        print(data) 
        self.add(data) 

    root=Tk() 
    root.title("IPBCC v0.1") 
    app=ipbcc(root) 
    root.mainloop() 

PS: Python Version 3.3, и в серверном скрипте нет проблем.

+0

В качестве побочного примечания есть серьезная проблема с этим кодом - и, вероятно, с сервером. [TCP - поток байтов] (http://stupidpythonideas.blogspot.com/2013/05/sockets-are-byte-streams-not-message.html); буфер, отправленный 'send', всегда может отображаться как два отдельных вызова' recv'. И если кодировка по умолчанию является чем-то многобайтным, например UTF-8, это означает, что вы можете получить половину символа, а затем этот 'decode' будет повышаться. Первая часть редко встречается на локальном хосте, а вторая вы должны получить довольно неудачный ... что означает, что это сломается, когда это действительно нужно работать, но будет больно воспроизводить и отлаживать. – abarnert

+0

Как еще одно примечание, имеющее кучу глобальных переменных, к которым обращается экземпляр 'ipbcc', очень странный дизайн. Он делает 'ipbcc' то, что не является одиночным, но потерпит неудачу, если вы не притворитесь, что это ... Вероятно, лучше сделать' customersock' членом. – abarnert

ответ

1

Функция connect определяет функцию, называемую xloop, но она не вызывает эту функцию или не возвращает ее или не хранит ее где-нибудь, чтобы кто-либо еще ее вызывал. Вам нужно вызвать эту функцию, чтобы она ничего не делала.

Конечно, если вы просто вызываете его напрямую в строке, он будет работать вечно, то есть вы никогда не вернетесь в цикл событий, и пользовательский интерфейс зависнет и перестанет отвечать на пользователя.

Для этого есть два варианта: нарезание резьбы или опрос.


Очевидный способ сделать это с помощью a background thread. Основная идея очень проста:

def connect(self): 
    def xloop(): 
     while 1: 
      data=clientsock.recv(buffer).decode() 
      print(data) 
      self.add(data) 
    self.t = threading.Thread(target=xloop) 
    self.t.start() 

Однако есть две проблемы с этим.


Во-первых, нет способа остановить фоновый поток. Когда вы попытаетесь выйти из программы, он будет ждать остановки фонового потока, что означает, что он будет ждать вечно.

Это простое решение для этого: если вы сделаете его «потоком демона», он будет уничтожен, когда основная программа выйдет. Это явно не подходит для потоков, которые выполняют работу, которая может быть повреждена, если она прерывается посередине, но в вашем случае это не похоже на проблему.Таким образом, просто изменить одну строку:

self.t = threading.Thread(target=xloop, daemon=True) 

Во-вторых, что self.add метод должен изменить Tkinter виджет. Вы не можете сделать это из фонового потока. В зависимости от вашей платформы, он может терпеть неудачу молча, поднимать исключение или даже сбой - или, что еще хуже, он может работать 99% времени и сбой 1%.

Итак, вам нужно как-то отправить сообщение в основной поток, запросив его, чтобы сделать модификацию виджета для вас. Это немного сложно, но Tkinter and Threads объясняет, как это сделать.

В качестве альтернативы вы можете использовать mtTkinter, который перехватывает вызовы Tkinter в фоновом потоке и автоматически передает их в основной поток, поэтому вам не нужно беспокоиться об этом.


Другой вариант заключается в изменении блокировки xloop функцию в функцию неблокируемой, что опросы для данных. Проблема в том, что вы хотите ждать на событиях TKinter GUI, но вы также хотите дождаться сокета.

Если вы могли бы интегрировать сокет в основной цикл событий, это было бы легко: новое входящее сообщение обрабатывалось точно так же, как и любое другое событие. Некоторые из более мощных графических интерфейсов, таких как Qt, дают вам способы сделать это, но Tkinter этого не делает. Рамка реактора, такая как Twisted, может завязать себя в Tkinter и добавить ее для вас (или, по крайней мере, подделать красиво). Но если вы хотите придерживаться своего основного дизайна, вы должны сделать это сами.

Итак, есть два варианта:

  • Дает Tkinter полного контроля. Попросите его называть вашу функцию каждый, скажем, 1/20 секунды, а в функции выполняет неблокирующую проверку. Или, может быть, цикл вокруг неблокирующих проверок, пока нечего читать.
  • Дайте управление гнездом. Попросите Tkinter вызывать вашу функцию каждый раз, когда она получает шанс, и блокировать 1/20 секунд проверки данных перед возвратом в Tkinter.

Конечно 1/20 секунды не может быть правильная длина для многих приложений, не ответа не совсем правильно. Во всяком случае, вот простой пример:

def poll_socket(self): 
    r, w, x = select.select([clientsock], [], [], 0) 
    if r: 
     data=clientsock.recv(buffer).decode() 
     print(data) 
     self.add(data) 
    self.after(50, self.poll_socket) 

def connect(self): 
    self.after(50, self.poll_socket) 
0

Это может помочь следить за потоком. Шаг «app = ipbcc (root)» вызывается «self.connect()», и у него есть «def xloop():», который имеет шаг «data = clientsock.recv». Но тогда кому-то нужно вызывать xloop(). Кто так делает? Кстати, почему есть функция внутри метода?

Кроме того, я не вижу никого, кто ссылается на «clientsock.send (msg.encode()) через метод write(). Я не знаком с частью Tinker (и что делает mainloop()), поэтому можете ли вы проверить, есть ли вызывающие абоненты для отправки() и вызова recv().

0

Вы определяете xloop, однако вы никогда не называете его, насколько я могу видеть. Я бы предложил вам изучить потоки - модуль threading в стандартной библиотеке был бы одним из способов. Затем в вашем коде вы сможете создать поток с функцией xloop, не останавливая остальную часть кода. Кроме того, можно удалить петлю из xloop (или даже просто поставить код в функции в функцию connect) и назовем его периодически, используя widget.after(milliseconds, a_function)

Я хотел бы также отметить, что from amodule import * считается плохой практикой (хотя tkinter является одним из исключений из этого правила).

+0

Зачем использовать 'multiprocessing'? Просто используйте 'threading'. Трудно найти лучший пример кода с привязкой к I/O, чем функция, которая ничего не делает, кроме цикла «recv». – abarnert

+0

Это хороший момент. – rlms