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.
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)
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).
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.
Related
I'm making a tkinter program in which it will be needed to make the connection with Arduino via serial (but that's not very important).
Before explaining my problem, here is the code:
def arduino_makeConnection():
global arduino
try:
arduino = serial.Serial('/dev/ttyACM0', 9600, timeout = 0)
except:
print "Failed to connect"
if(time.time()-time_start<20):
root.after(0,arduino_makeConnection())
global time_start
time_start=time.time()
arduino_makeConnection()
So, I want to try to make connection via serial with the arduino only during 20s. After that time, I want it to give it up.
The problem is that my tkinter window doesn't open even though it prints in my console "Failed to connect" many many times until it gets the message (way before the 20 seconds have run off): RuntimeError: maximum recursion depth exceeded in cmp
I have tried to change time from 0 to 10 or 100ms on the root.after, but that doesn't solve the problem.
I think this has something to do with event handler, or something like that. However I thought that as I am not using a While or any other kind of loop, Tkinter would work...
Actually, before using the root.after I was making a While that was only breaking after the 20s or insead if the arduino was plugged in during that time. However when I searched in the internet, I realized that a loop in Tkinter is not a good idea. So, I changed to the root.after method, but now it's not working either!
Any help?
Thanks in advance!
Consider this code:
root.after(0,arduino_makeConnection())
This is exactly the same as this code:
result = arduino_makeConnection()
root.after(0, result)
And, assuming your function doesn't return anything, it's exactly the same as this:
root.after(0, None)
See the problem? The after command must be given a reference to a callable. In short, remove the parenthesis:
root.after(0,arduino_makeConnection)
Also, I highly recommend against using 0 (zero) as the first parameter. At the very least you should use 1 (one). A value of zero can have surprising side effects because you essentially create an infinite event queue that never empties.
If you tried to make an MCVE, you might come up with
import tkinter as tk
root = tk.Tk()
def callback():
print('callback')
root.after(0, callback())
callback()
This might make it more obvious that calling callback() calls callback() calls ..., until you get the recursion error. Remove the () in the root.after call. Also use a non-zero delay. Try the above with, for instance, `root.after(100, callback).
I'm having some trouble getting this to work, i'm trying to append GUI texts to a window that allready exists, i'm using the python interpreter for autodesk maya, i attach here part of the host script where i check for multiple connections and try to add the text for each connection.
The problem comes when calling the interpHostData() function from inside the while loop inside the thread, it dosnt add anything to the window;
But whenever i call the function by itself outside of a thread it works correctly.
If anyone has a clue on how can i fix this i would apreciate it, thanks in advance.
def hostUpdate():
global socketHost
global clientL
clientL=[]
cmds.text('mt1',e=1,l='Update Process Started...')
while 1:
connectionMain,addressMain=socketHost.accept()
cmds.text('mt1',e=1,l='Connected with: %s'%str(addressMain))
#----------Different Thread for each connection
tid=thr.start_new_thread(hostRecieve,(connectionMain,addressMain))
clientL.append([connectionMain,addressMain,tid])
cmds.text('mt1',e=1,l='Thread started with: %s'%str(addressMain))
def hostRecieve(connI,addrI):
global clientL
cmds.text('mt1',e=1,l='Recieve Process Started...')
while 1: #------------------------------------Loop to keep listening for connections
try:
cmData=connI.recv(4096)
interpHostData(cmData) #--------------IF I CALL FROM HERE DOSN'T WORK
except:
for cl in clientL:
if connI==cl[0]:
clientL.remove(cl)
cmds.text('mt1',e=1,l='Disconnected from %s'%str(addrI))
break
def interpHostData(cmDataC):
global cliLayout
tplD=cmDataC.split(',')
if tplD[0]=='0':
cID=tplD[2]
cmds.setParent(cliLayout)
cmds.text(cID+'_1',l=tplD[1])
cmds.text(cID+'_2',l=tplD[3])
cmds.text(cID+'_3',l=tplD[4])
cmds.text(cID+'_4',l='_')
cmds.text(cID+'_5',l='_')
cmds.text(cID+'_6',l='_')
cmds.text(cID+'_7',l='_')
cmds.text(cID+'_8',l='_')
cmds.columnLayout(cID+'_9')
cmds.progressBar(cID+'_10',h=10)
cmds.progressBar(cID+'_11',h=10)
cmds.setParent(cliLayout)
cmds.text(cID+'_12',l='_')
cmds.text(cID+'_13',l='Online')
You can only run Maya ui commands from the main thread. You can use maya.util.ExcecuteDeferred() to get around this to some degree but it takes extra work and won't be as responsive as a true multi-threaded app. Longer explanation in this question
I was wondering if anyone had any good solutions to the pickling error I am having at the moment. I am trying to set my code up to open several different processes in parallel, each with a fitting process to be display on a matplotlib canvas in real time. Within my main application, I have a button which activates this function:
def process_data(self):
process_list = []
for tab in self.tab_list:
process_list.append(mp.Process(target=process_and_fit, args=(tab,)))
process_list[-1].start()
process_list[-1].join()
return
As you may notice, a 'tab' (PyQt4.QtGui.QTabWidget object) is passed to the function process_and_fit, which I have noticed is not able to be pickled readily (link here) .
However, I am not certain how to change the code to get rid of the frame being passed since it needs to be called in the process_and_fit function indirectly. By indirectly I mean something like this: (psuedo code again)
def process_and_fit(tab): # this just sets up and starts the fitting process
result = lmfit.Minimizer(residual, parameters, fcn_args=(tab,))
result.prepare_fit()
result.leastsq()
def residual(params, tab):
residual_array = Y - model
tab.refreshFigure()
return residual_array
class tab(QtGui.QTabWidget):
def __init__(self, parent, spectra):
# stuff to initialize the tab widget and hold all of the matplotlib lines and canvases
# This just refreshes the GUI stuff everytime that the parameters are fit in the least squares method
def refreshFigure(self):
self.line.set_data(self.spectra.X, self.spectra.model)
self.plot.draw_artist(self.line)
self.plot.figure.canvas.blit(self.plot.bbox)
Does anyone know how to get around this pickling error since the tab associated with a process should have only one set of data associated with it? I looked at Steven Bethard's approach but I really didn't understand where to put the code or how to utilize it. (I am a chemical engineer, not a computer scientist so there's a lot that I don't understand)
Any help is greatly appreciated.
EDIT: I added the links in that I forgot about, as requested.
The main issue is that you can't make UI changes from a separate process from the main UI thread (the one that all of your Qt calls are in). You need to use a mp.Pipe or mp.Queue to communicate back to the main process.
def process_data(self):
for tab in self.tab_list:
consumer, producer = mp.Pipe()
process_list.append(mp.Process(target=process_and_fit, args=(producer,)))
process_list[-1].start()
while (true):
message = consumer.recv() # blocks
if message == 'done':
break
# tab.spectra.X, tab.spectra.model = message
tab.refreshFigure()
process_list[-1].join()
return
def process_and_fit(pipe_conn):
...
pipe_conn.send('done')
def residual(params, pipe_conn):
residual_array = Y - model
pipe_conn.send('refresh') # or replace 'refresh' with (X, model)
return residual_array
One more thing to note: blocking for the consumer.recv() will probably hang the GUI thread. There are plenty of resources to mitigate this, the question "subprocess Popen blocking PyQt GUI" will help, since you should probably switch to QThreads. (Qthread: PySide, PyQt)
The advantage of using QThreads instead of Python threads is that with QThreads, since you're already in Qt's main event loop, you can have asynchronous (non-blocking) callbacks to update the UI.
on one of my methods, I have the following code:
def fun():
self.button1.set_sensitive(False)
self.get_time()
However, self.button1 only becomes insensitive after get_time() return !!,use the time.sleep(n) replace the get_time() could get same result
Any clue why?
I think programmic changes to widgets applies in the next lap of event loop (gtk.main()), that is probably after finishing fun function. Does that make a problem for you? How much time self.get_time()
takes? If that takes a sensible time, you can update widgets before that:
def fun():
self.button1.set_sensitive(False)
while gtk.events_pending():
gtk.main_iteration_do(False)
self.get_time()
Uhh are you sure you want to do that?
All GUI programming events are done by message passing and so you really shouldn't block the main thread for long enough you'd ever need some workaround like this. And if you do that, you'll soon have other problems like the window manager killing your window because it's not responding to ping or reentrance problems when you do the iteration. If you have some complicated task like burning a CD or whatever that takes that long, put the actual burning into its own executable and call it by glib.spawn_async (or similar). Use gobject.child_watch_add to ask to be notified about termination.
I am collecting usage stats for my applications which include how much each session lasts. However, I can't seem to be able to save this information because None Of the signals I tried yet actually succeeds to call my report_session function.
This are the signals I have already tried:
lastWindowClosed()
aboutToQuit()
destroyed()
Either these signals never get emitted or the application does not live long enough after that to run anything else. Here is my main:
app = QtGui.QApplication(sys.argv)
ui = MainWindow()
ui.app = app
QtCore.QObject.connect(ui, QtCore.SIGNAL("destroyed()"), ui.report_session)
ui.show()
logger.info('Started!')
splash.finish(ui)
sys.exit(app.exec_())
The method that Mark Byers posted will run after the main widget has been closed, meaning that its controls will no longer be available.
If you need to work with any values from controls on your form, you will want to capture the close event and do your work there:
class MainWidget(QtGui.QWidget):
#...
def closeEvent(self, event):
print "closing PyQtTest"
self.SaveSettings()
# report_session()
Also, see the Message Box example in the ZetCode tutorial First programs in PyQt4 toolkit (near the end of the page). This shows how to accept or cancel the close request.
Put the code between app.exec_ and sys.exit:
ret = app.exec_()
# Your code that must run when the application closes goes here
sys.exit(ret)
To ensure that a Python function gets called at process termination, in general (with or without Qt involved;-), you can use the atexit module of the standard Python library:
import atexit
def whatever(): ...
atexit.register(whatever)
Out of prudence I would recommend against using a bound method instead of a function for this purpose -- it "should" work, but the destruction-phase of a process is always somewhat delicate, and the simpler you keep it, the better.
atexit won't trigger for a sufficiently-hard crash of a process, of course (e.g., if the process is killed with a kill -9, then by definition it's not given a chance to run any termination code) -- the OS sees to that;-). If you need to handle any crash no matter how hard you must do so from a separate "watchdog" process, a substantially subtler issue.
Found this answer which involves overloading closeEvent().
it worked perfectly for me.