Dream program for a beginner python guide

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

Something like an introduction


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

Network Name


Working with sockets in python is platform independent (by and large, even under PyS60 we get a working network code, for this example).

For our chat, we decided to use broadcast UDP packets. One of the reasons for their use was the ability to refuse to use the server. It is necessary to accept one more convention in our program - the port number, and let it be equal to 11719, firstly, this is a prime number (and this is already a lot). And, secondly, this port, according to official IANA information, is not busy.

Create a listening socket that will delight 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 SO_REUSEADDR (allows several applications to "listen" to the socket) and SO_BROADCAST (indicates that the packets will be broadcast) to significant truths. Actually, on some systems the script can work without these lines, but it’s better to explicitly specify these properties.
For wiretapping, we connect to the address '0.0.0.0' - this means that all interfaces are listened to in general. You can also specify empty quotation marks in the address field, but it’s still better to explicitly specify the address.

We also create a broadcast “sniff” of the network (for checking 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))


As regards the transmitting part, only the SO_BROADCAST option is needed (but now it is required on all platforms) and using the sendto () method we send our packets across the network. Having stopped the network flood using CTRL + C, we proceed to the description of the interface.

Windows, windows, windows


Tkinter is probably one of the simplest libraries for organizing the window interface in python (and according to the creator of the python, it is one of the most reliable and stable). But for a novice piton guide, the main point is that this library is simple.

And in order to get just a window of the size we need and with the specified title, 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 are creating, not a gallery of windows of different sizes, but a chat. So, we also need elements on the window - the chat log, the field where we indicate our nickname and the message text that we enter. You can dispense with the send button if our message field will itself respond to pressing the Enter key.

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

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 get some procedure to be executed in the background? Well, Entry can be associated with StingVars (by specifying the textvariable property in the widget designer). and to start the background procedure, Tkinter has an after method (<time in ms>, <function>). If at the end of the execution of this procedure you specify its initialization, 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 ran in the console. By changing the nickname, we can make sure that the relationship between the Entry field and our variable works perfectly. It's time to realize the possibility of the user transferring his message to the program by pressing Enter. This is implemented even easier using the bind (<action>, <function>) method of widgets. The only thing we need to consider is that the function must accept the event parameter. For one, we transfer the action from the console to 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('',sendproc)
tk.after(1000,loopproc)
tk.mainloop()


Almost ready...


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 chat. Well, let's try. However, I’ll say right away that we won’t specifically display messages that we send - since when we listen to broadcast traffic, we will receive them.

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('',sendproc)
tk.after(1,loopproc)
tk.mainloop()


However, a miracle did not happen. The program tightly stood up waiting for an incoming package. The background thread turned out to be not so background and for the continuation of the rest of the program it should sometimes end. In order for this to happen, the listening socket must be switched to non-blocking mode and so that we do not receive an error message when we empty the socket, enclose this piece of code with a 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('',sendproc)
tk.after(1,loopproc)
tk.mainloop()


"And then modify the file ..."


Purely theoretically obtained code is workable, but has quite significant drawbacks - the message input field is not immediately selected by default, there may be problems with the Cyrillic alphabet and the log itself does not scroll down. The solution to all these problems can be seen from the following listing (I highlighted the place where the Cyrillic issue is being addressed, 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('',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.

Also popular now: