📜 ⬆️ ⬇️

The dream program of a novice pit driver

Virtually any novice Python programmer is pathologically trying to write his own chat. And if also with the GUI, then this program is just the ultimate dream.

Something like an introduction


To begin with, we will introduce in our task how much conventions - we are writing a chat for a local network with allowed UDP broadcast packets. For the simplicity of solving the problem, we also decide that we will use the Tkinter library as a GUI ( usually in Linux distributions it comes out of the box, and it is standard in the official Python build for Windows).

Power up the network


Working with sockets in python does not depend on the platform (by and large, even under PyS60 we get a working network code, according to this example).

For our chat, we decided to use UDP broadcast packets. One of the reasons for their use was the ability to opt out of using the server. It is necessary to accept one more convention in our program - the port number, and let it be equal to 11,719, firstly, this is a prime number (and this is already a lot). And secondly, this port, according to official IANA information, is not busy.
')
Let's create a listening socket that will please the console with messages coming to it:

import socket s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind(('0.0.0.0',11719)) while 1: message = s.recv(128) print (message) 


At the socket, we set the properties of SO_REUSEADDR (allows several applications to “listen” to the socket) and SO_BROADCAST (indicates that the packets will be broadcast) in meaningful truths. In fact, on some systems, the script can work without these lines, but it is still better to explicitly specify these properties.
To wiretap, we connect to the address '0.0.0.0' - this means that in general all interfaces are heard. You can specify in the field of the address and just empty quotes, but still it is better to explicitly specify the address.

We will also create a broadcast messy network (to test the first program):

 import socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) while 1: sock.sendto('broadcast!',('255.255.255.255',11719)) 


With regard to the transmitting part, only the option SO_BROADCAST is necessary (but now it is mandatory on all platforms) and using the sendto () method we send our packets all over the network. Stopping network flooding with CTRL + C, we’ll proceed to the interface description.

Windows, windows, windows


Tkinter is probably one of the simplest libraries for organizing a window interface on python (and, according to the creator of python, it is one of the most reliable and stable). But for a beginner pit-runner the main point is that this library is simple.

And in order to get just the window of the size we need and with the specified header, we need only a few lines of code:

 from Tkinter import * tk=Tk() tk.title('MegaChat') tk.geometry('400x300') tk.mainloop() 


Everything would be fine, but we create, not a gallery of windows of different sizes, but a chat. So, we also need elements on the window - a chat log, a field where we indicate our nickname and the text of the message that we enter. You can do without the send button if our field for the message will automatically respond to pressing the Enter key.

Solving a problem is easier than it seems. For the chat log, you can use the Text widget, and for the other two Entry. In order to place the elements on the form we use the automatic packer pack, which will be known only to him to arrange the elements, adhering only to the explicitly indicated instructions. However, it can specify the side to which to attach the widgets, whether to expand them and in which direction, and some other layout parameters.

 from Tkinter import * tk=Tk() tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk) msg = Entry(tk) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') tk.mainloop() 


And what about the interface connection with the program data? Or how to make a procedure run in the background? Well, Entry can be associated with StingVars (by specifying the textvariable property in the widget constructor). and to run the background procedure, Tkinter has a after method (<time in ms>, <function>). If at the end of the execution of this procedure specify its re-initialization, then the procedure will be performed continuously while the program is running.
A few keystrokes and we get:

 from Tkinter import * tk=Tk() text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): print ('Hello '+ name.get() + '!') tk.after(1000,loopproc) tk.after(1000,loopproc) tk.mainloop() 


A greeting came running in the console. Changing the nickname, we can make sure that the relationship between the Entry field and our variable works perfectly. It's time to implement the ability of the user to send his message to the program by pressing Enter. This is implemented even easier, using the bind method (<action>, <function>) of widgets. The only thing we need to consider is that the function must accept the event parameter. For one move from the console action in the log field. We get:

 from Tkinter import * tk=Tk() text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): log.insert (END,'Hello '+ name.get() + '!\n') tk.after(1000,loopproc) def sendproc(event): log.insert (END,name.get()+':'+text.get()+'\n') text.set('') msg.bind('<Return>',sendproc) tk.after(1000,loopproc) tk.mainloop() 


Almost done...


And it seems that it remains to combine the programs from the first and second parts of the article and we will get a ready-made chat. Well, let's try. However, I will immediately say that we will not specifically display messages that we send - because when we listen to broadcast traffic, we will receive them anyway.

 import socket from Tkinter import * tk=Tk() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind(('0.0.0.0',11719)) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): message = s.recv(128) log.insert(END,message) tk.after(1,loopproc) def sendproc(event): sock.sendto (name.get()+':'+text.get(),('255.255.255.255',11719)) text.set('') msg.bind('<Return>',sendproc) tk.after(1,loopproc) tk.mainloop() 


However, the miracle did not happen. The program tightly stood in anticipation of the incoming package. The background thread was not so background and to continue the rest of the program, it must sometimes be terminated. In order for this to occur, the listening socket must be transferred to the non-blocking mode and so that we do not receive an error message when the socket is empty, we will protect this piece of code with try.

 import socket from Tkinter import * tk=Tk() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind(('0.0.0.0',11719)) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): s.setblocking(False) try: message = s.recv(128) log.insert(END,message+'\n') except: tk.after(1,loopproc) return tk.after(1,loopproc) return def sendproc(event): sock.sendto (name.get()+':'+text.get(),('255.255.255.255',11719)) text.set('') msg.bind('<Return>',sendproc) tk.after(1,loopproc) tk.mainloop() 


“And after finishing with a file ...”


Purely theoretically, the code is operational, but it has significant enough flaws - the message input field is not immediately selected by default, problems with Cyrillic are possible and the log does not scroll down. The solution to all these problems can be seen from the following listing (the place where the Cyrillic question is being addressed is highlighted, I hope the rest is obvious):

 # -*- coding: utf-8 -*- import socket from Tkinter import * #    reload(sys) sys.setdefaultencoding('utf-8') #----------------------------- tk=Tk() s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) s.bind(('0.0.0.0',11719)) sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1) text=StringVar() name=StringVar() name.set('HabrUser') text.set('') tk.title('MegaChat') tk.geometry('400x300') log = Text(tk) nick = Entry(tk, textvariable=name) msg = Entry(tk, textvariable=text) msg.pack(side='bottom', fill='x', expand='true') nick.pack(side='bottom', fill='x', expand='true') log.pack(side='top', fill='both',expand='true') def loopproc(): log.see(END) s.setblocking(False) try: message = s.recv(128) log.insert(END,message+'\n') except: tk.after(1,loopproc) return tk.after(1,loopproc) return def sendproc(event): sock.sendto (name.get()+':'+text.get(),('255.255.255.255',11719)) text.set('') msg.bind('<Return>',sendproc) msg.focus_set() tk.after(1,loopproc) tk.mainloop() 


PS And do not forget - the length of the python can reach 9-10 meters.

Source: https://habr.com/ru/post/151623/


All Articles