0

我已经开始使用 Python 进行网络编程,并且正在开发一个基本的点对点聊天客户端-服务器应用程序。我让它为控制台工作,但在开发 GUI 时遇到问题。

这是我的客户端脚本的代码。它正在向服务器发送数据,但无法接收/显示从服务器发送的数据,我很茫然。请在我的代码和解决方案中显示错误。

    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 3.3版本,服务器脚本没有问题。

4

3 回答 3

1

您定义xloop,但据我所知,您从未真正调用它。我建议您考虑使用线程 - 标准库中的线程模块将是一种方法。然后,在您的代码中,您将能够创建一个运行该xloop函数的线程,而无需停止其余代码。或者,您可以从中删除循环xloop(或者实际上只是将函数中的代码放入connect函数中)并定期调用它,使用widget.after(milliseconds, a_function)

我还想提一下,这from amodule import *被认为是不好的做法(尽管 tkinter 是该规则的例外之一)。

于 2013-09-09T18:38:26.987 回答
1

您的connect函数定义了一个名为 的函数xloop,但它不调用该函数,也不返回它,也不将其存储在某个地方以供其他人调用。您需要调用该函数才能执行任何操作。

当然,如果你只是直接内联调用它,它将永远运行,这意味着你永远不会回到事件循环,并且 UI 会冻结并停止响应用户。

有两种选择:线程或轮询。


显而易见的方法是使用后台线程。基本思想很简单:

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 和 Threads解释了如何做到这一点。

或者,您可以使用mtTkinter,它在后台线程中拦截 Tkinter 调用并自动将它们传递给主线程,因此您不必担心。


另一种选择是将阻塞xloop函数更改为轮询数据的非阻塞函数。问题是你想等待 Tkinter GUI 事件,但你也想等待套接字。

如果您可以将套接字集成到主事件循环中,那将很容易:传入的新消息将像处理任何其他事件一样处理。一些更强大的 GUI 框架(如 Qt)为您提供了执行此操作的方法,但 Tkinter 没有。像Twisted这样的反应器框架可以将自己绑定到 Tkinter 并为您添加它(或者至少很好地伪造)。但是如果你想坚持你的基本设计,你必须自己做。

所以,有两种选择:

  • 让 Tkinter 完全控制。要求它每隔 1/20 秒调用一次你的函数,并在函数中进行非阻塞检查。或者也许循环非阻塞检查,直到没有什么可读的。
  • 授予套接字控制权。每次有机会时让 Tkinter 调用你的函数,并在返回 Tkinter 之前阻止 1/20 秒检查数据。

当然,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)
于 2013-09-09T19:01:56.827 回答
0

跟随潮流可能会有所帮助。“app=ipbcc(root)”步骤将调用“self.connect()”,并且有一个“def xloop():”,其中包含步骤“data=clientsock.recv”。但是,有人需要调用 xloop()。谁这样做?顺便说一句,为什么在方法中有一个函数?

此外,我没有看到任何人通过 write() 方法调用“clientsock.send(msg.encode())”。我不熟悉 Tinker 部分(以及 mainloop() 的作用),所以请您检查是否有调用者 send() 和 recv() 调用。

于 2013-09-09T18:35:59.273 回答