简体   繁体   中英

client not receiving data via TCP socket

I have started network programming using Python and am working on a basic peer-to-peer chat client-server application. I got it working for console, but am facing problem while developing a GUI.

This is the code for my client script. It is sending data to the server but is unable to receive /display the data sent from server, I am at a loss. Please show the error in my code and the solution.

    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 and there is no problem in the server script.

You define xloop , however you never actually call it as far as I can see. I would suggest you look into using threads - the threading module in the standard library would be one way to go. Then, in your code you will be able to create a thread running the xloop function, without stopping the rest of your code. Alternatively, you could remove the loop from xloop (or indeed just put the code in the function into the connect function) and call it periodically, using widget.after(milliseconds, a_function)

I'd also like to mention that from amodule import * is considered bad practice (although tkinter is one of the exceptions to this rule).

Your connect function defines a function called xloop , but it doesn't call that function, or return it, or store it somewhere for anyone else to call it. You need to call that function for it to do anything.

Of course if you just call it directly inline, it will run forever, meaning you never get back to the event loop, and the UI freezes up and stops responding to the user.

There are two options for this: threading, or polling.


The obvious way to do this is with a background thread . The basic idea is very simple:

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()

However, there are two problems with this.


First, there's no way to stop the background thread. When you try to exit the program, it will wait for the background thread to stop—which means it will wait forever.

There's an easy solution to that one: if you make it a "daemon thread", it will be summarily killed when the main program exits. This is obviously no good for threads that are doing work that could be corrupted if interrupted in the middle, but in your case that doesn't seem to be a problem. So, just change one line:

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

Second, that self.add method needs to modify a Tkinter widget. You can't do that from a background thread. Depending on your platform, it may fail silently, raise an exception, or even crash—or, worse, it may work 99% of the time and fail 1%.

So, you need some way to send a message to the main thread, asking it to do the widget modification for you. This is a bit complicated, but Tkinter and Threads explains how to do it.

Alternatively, you could use mtTkinter , which intercepts Tkinter calls in background threads and passes them to the main thread automatically, so you don't have to worry about it.


The other option is to change the blocking xloop function into a nonblocking function that polls for data. The problem is that you want to wait on Tkinter GUI events, but you also want to wait on the socket.

If you could integrate the socket into the main event loop, that would be easy: a new message coming in would be handled just like any other event. Some of the more powerful GUI frameworks like Qt give you ways to do this, but Tkinter does not. A reactor framework like Twisted can tie itself into Tkinter and add it for you (or at least fake nicely). But if you want to stick with your basic design, you have to do it yourself.

So, there are two options:

  • Give Tkinter full control. Ask it to call your function every, say, 1/20th of a second, and in the function do a non-blocking check. Or maybe loop around non-blocking checks until there's nothing left to read.
  • Give the socket control. Ask Tkinter to call your function every time it gets a chance, and block for 1/20th of a second checking for data before returning to Tkinter.

Of course 1/20th of a second may not be the right length—for many applications, no answer is really correct. Anyway, here's a simple example:

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)

It might help to follow the flow. The "app=ipbcc(root)" step would call "self.connect()" and that has a "def xloop():" that has the step "data=clientsock.recv". But, then somebody needs to invoke xloop(). Who does that? Btw, why do have a function inside a method?

Also, I don't see anybody invoking the "clientsock.send(msg.encode())" via the write() method. I am not familiar with the Tinker part (and what the mainloop() does), so can you please check if there are callers to send() and the recv() call.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM