Tkinter notification board with messages from socket - Weird behaviour - python

I'm using this forum for some time, but this is the first time that I ask a question, since I haven't been able to find a good way around my difficulties, and because I hope this question will be useful to other people too.
I am implementing a simple notification board, i.e. a window where messages coming from a socket connection are displayed. This board prints in red the last message received and in blue the old ones, up to ten. When a message sent by the client is 'Q', the connection terminates and the notification board is destroyed.
I am using Tkinter, threading and sockets, but the behaviour is not smooth (it takes a while to refresh the notification board). I can think of a few problems: the thread handling the connection is not closed; the update of the window is performed by destroying and recreating the toplevel. Unfortunately I can't understand if these issues are the source of the problem.
Here is the code for the client, a very simple one:
#!/usr/bin/env python
import socket
HOST = '' # Symbolic name meaning the local host
PORT = 24073 # Arbitrary non-privileged port
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST,PORT))
while True:
message = raw_input('Enter your command (Q=quit): ')
s.send(message)
reply = s.recv(1024)
if reply=='Q':
print 'Request to disconnect was received.'
break
else :
print reply
s.close()
And here is the server. The server has implemented a class that handles the notification board characteristics, a thread for the socket connection and finally the main part with the mainloop().
#!/usr/bin/env python
import socket
import threading
from Tkinter import *
from datetime import datetime
### Class definition
class NoticationsBoard() :
def __init__(self, title):
self.messages = []
self.toplevel = None
self.title = title
self.backgroundColor = 'black'
self.textColor = 'blue'
self.textColorFirst = 'red'
self.boardSize = '200x250+0+0'
self.listsize = 10
def createBoard(self):
self.toplevel = Toplevel()
self.toplevel.title(self.title)
self.toplevel.configure(background='black')
self.toplevel.geometry(self.boardSize)
def publish(self, message):
self.addToList(message)
self.displayList()
def addToList(self, msg):
if len(self.messages) == self.listsize:
self.messages.pop(0)
timestamp = datetime.utcnow().strftime('%H:%M:%S')
newMessage = (msg, timestamp)
self.messages.append(newMessage)
def displayList(self):
# Destroy and create the window (is it really necessary?)
if self.toplevel is not None :
self.toplevel.destroy()
self.createBoard()
# create labels for all the messages in the list
index = 1
for m, t in self.messages :
color = self.textColor
if index == len(self.messages) :
color = self.textColorFirst
label = Label(self.toplevel, text=m, height=0, width=100, fg=color, anchor=W)
label.grid(row=0,column=1)
label.configure(background=self.backgroundColor)
label.pack(side='bottom')
index = index +1
####### Run
def receiveMessages(newsboard) :
print '===== Inside receiveMessages ======'
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
try:
s.bind((HOST, PORT))
except socket.error , msg:
print 'Bind failed. Error code: ' + str(msg[0]) + 'Error message: ' + msg[1]
sys.exit()
print 'Socket bind complete'
s.listen(1)
print 'Socket now listening on port', PORT
# Accept the connection once
(conn, addr) = s.accept()
print 'Connected with ' + addr[0] + ':' + str(addr[1])
stored_data = ''
while True:
# RECEIVE DATA
data = conn.recv(1024)
# PROCESS DATA
if data == 'Q' :
print 'Client wants to disconnect.'
reply = 'Q'
conn.send(reply)
break
else :
print data
newsboard.publish(data)
reply = 'Message received:' + data
conn.send(reply)
print 'Close connection.'
conn.close()
board.destroy()
HOST = '' # Symbolic name meaning the local host
PORT = 24073 # Arbitrary non-privileged port
app = Tk()
app.title("GUI main")
board = NoticationsBoard('Notifications')
t = threading.Thread(target=receiveMessages, args = (board,))
t.start()
app.update() # Not sure what it does and if it is necessary
app.mainloop()
I am using Python 2.7.5.
Finally, but this is something minor, I was trying to display the timestamp of each message on the left of the message itself, in a different color. It seems to me that it is not possible to have text of different colours on the same label, so I had created other labels in the for loop with the timestamps. I tried to display the timestamp and message labels one next to the others using .grid(column=0) and .grid(column=1), but they were not one next to the other but one below the other, and I haven't figured out why.
As you understood, I am not a skilled programmer, and definitely a newbie with Python...
Thanks in advance to whom will give me some advice, and I hope this question will be useful to many people.

Ok, it seems like I found a solution by taking parts of other people's questions, suggestions, and code. There are few differences in the appearance maybe.
In the GUI, the most notable one is that I preload all the Labels and then I just modify the text.
In the threading part, well, that's completely changed. Please see below.
#!/usr/local/bin/python
try:
import Tkinter
except ImportError:
import tkinter as Tkinter
import time
import threading
import random
import Queue
import socket
import sys
from datetime import datetime
class GuiPart:
def __init__(self, master, queue):
self.queue = queue
# GUI stuff
self.labelArray = []
self.messages = []
# Set up the GUI
self.master = master
self.backgroundColor = 'black'
self.listsize = 10
master.config(bg=self.backgroundColor)
self.board = Tkinter.LabelFrame(self.master, text='Notification Board', bg='Black', fg='Yellow', labelanchor='n', width=170)
self.initLabels()
self.board.pack()
def initLabels(self) :
self.textColorTime = 'cyan'
self.textColorMessage = 'orange'
colorTime = 'blue'
colorMessage = 'red'
for i in range(0,self.listsize):
la = Tkinter.Label(self.board, height=0, width=10, bg=self.backgroundColor, fg=colorTime, anchor=Tkinter.W)
lb = Tkinter.Label(self.board, height=0, width=160, bg=self.backgroundColor, fg=colorMessage)
la.grid(row=i,column=0, sticky=Tkinter.W)
lb.grid(row=i,column=1, sticky=Tkinter.W)
self.labelArray.append((la, lb))
colorTime = self.textColorTime
colorMessage = self.textColorMessage
self.initList()
self.displayList()
def initList(self):
for i in range(0, self.listsize):
t = ''
m = ''
self.messages.append((t,m))
def processIncoming(self):
while self.queue.qsize():
try:
msg = self.queue.get(0)
self.processMessage(msg)
except Queue.Empty:
pass
def processMessage(self, message):
timestamp = datetime.utcnow().strftime('%H:%M:%S')
self.publish(timestamp, message)
def publish(self, msg1, msg2):
self.addToList(msg1, msg2)
self.displayList()
def addToList(self, msg1, msg2):
if len(self.messages) == self.listsize:
self.messages.pop(0)
if (msg1 == None):
msg1 = datetime.utcnow().strftime('%H:%M:%S')
newMessage = (msg1, msg2)
self.messages.append(newMessage)
def displayList(self):
index = self.listsize -1
for t, m in self.messages :
la, lb = self.labelArray[index]
la.config(text=t)
lb.config(text=m)
index = index -1
def destroy(self):
self.master.destroy()
class ThreadedClient:
def __init__(self, master):
self.master = master
# Create the queue
self.queue = Queue.Queue()
# Define connection parameters
self.conn = None
self.bindError = False
# Set up the GUI part
self.gui = GuiPart(master, self.queue)
# Set up the thread to do asynchronous I/O
self.running = True
self.commThread = threading.Thread(target=self.workerThreadReceive)
self.commThread.daemon = True
self.commThread.start()
# Start the periodic call in the GUI to check if the queue contains anything
self.periodicCall()
def periodicCall(self):
if not self.running:
self.killApplication()
else :
self.gui.processIncoming()
self.master.after(100, self.periodicCall)
def workerThreadReceive(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try :
s.bind((HOST, PORT))
except socket.error as msg :
print 'Bind failed. Error code: ' + str(msg[0]) + ' Error message: ' + str(msg[1])
self.running = False
self.bindError = True
return
s.listen(1)
(self.conn, self.addr) = s.accept()
while self.running :
data = self.conn.recv(1024)
if data == 'Q' :
self.conn.sendall('Q')
self.running = False
else :
self.queue.put(data)
reply = 'ACK'
self.conn.sendall(reply)
if self.conn is not None:
self.conn.close()
def killApplication(self):
self.running = False
if (self.conn is None) and (not self.bindError) :
sfake = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sfake.connect((HOST,PORT))
sfake.sendall('Q')
sfake.close()
self.gui.destroy()
sys.exit()
HOST = '' # Symbolic name meaning the local host
PORT = 24073 # Arbitrary non-privileged port
root = Tkinter.Tk()
client = ThreadedClient(root)
root.protocol("WM_DELETE_WINDOW", client.killApplication)
root.mainloop()
The client is the same as in the question.
I am not sure my code is the most elegant one (ok, let's say it... it is not!), but it seems to do the job. Nevertheless, I would like to get your feedback about it, because I am sure that I have overlooked many issues and that things could have done in an easier way. :)

Related

How do I refer to a socket as a class variable that I can use later?

In my case I have a texting app that has a receiver class that accepts incoming connections, and it has a pyqt signal connected to the main app class that will add messages to the screen upon being received. I want to be able to have two of these running and connect to eachother on different ports and send messages back and forth. This also means I want it to be able to run and listen for connections before itself connecting back.
I thought I'd create a connecter class, with the socket as a class variable, and then later instantiate that within the main app and link the send button pyqt signal to a send function that sends over that socket. It doesn't seem to like this. I get the error "socket.socket(socket.AF_INET, socket.SOCK_STREAM) as self.s" invalid syntax.
This is my code:
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import socket
import numpy as np
from collections import deque, namedtuple
from threading import Thread
from time import sleep
HOST = '127.0.0.1'
inPORT = int(input('Input port to receive from: '))
outPORT = int(input('Input port to send to: '))
class Receiver(QThread):
received = pyqtSignal(object)
def __init__(self):
# self.outPORT = input('Input port to connect to: ')
super().__init__()
def run(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, inPORT))
s.listen()
print('Listening...')
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)
self.received.emit(data)
class Connecter():
def __init__(self):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as self.c
self.c.connect((HOST, outPORT))
class ChatApp(QMainWindow):
def __init__(self):
super().__init__()
self.text_area = QTextEdit()
self.text_area.setReadOnly(True)
self.message_line = QLineEdit()
self.message_line.setFocus()
self.button = QPushButton('Send')
outerLayout = QVBoxLayout()
outerLayout.addWidget(self.text_area)
bottomLayout = QHBoxLayout()
bottomLayout.addWidget(self.message_line)
bottomLayout.addWidget(self.button)
outerLayout.addLayout(bottomLayout)
widget = QWidget()
widget.setLayout(outerLayout)
self.setWindowTitle("Chat App")
self.setWindowIcon(QIcon('ChatIcon.png'))
self.setCentralWidget(widget)
self.name = 'frd'
receiver = Receiver()
receiver.received.connect(self.addMessages)
self.threads = [receiver]
connecter = Connecter()
self.button.clicked.connect(self.sendMessage)
self.message_line.returnPressed.connect(self.sendMessage)
receiver.start()
# def askName(self):
# name, ask = QInputDialog.getText(self, 'input dialog', 'Enter username: ')
# return name
def sendMessage(self):
message = self.message_line.text()
connecter.c.sendall(message)
# headerArr = np.array([len(message)], dtype=np.uint16)
# header = headerArr.tobytes
def addMessages(self, message):
self.text_area.append(message.decode('utf-8'))
app = QApplication([])
myApp = ChatApp()
myApp.show()
app.exec()
The point of with is it creates something, then closes it again after the code inside the with is done. That doesn't apply here. It's not what you want the computer to do.
You can assign the socket to the variable like so:
self.c = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
and when you are done with the socket, close it like so:
self.c.close()

How do multi-threading and infinite loops work together

I'm trying to learn python, doing exercise watching video online. I'm currently trying to do a multi-threading chat server but I think I have trouble understanding the way threads work.
Here's my code:
<!-- language: lang-py -->
import socket
import threading
class ThreadedServer(threading.Thread):
def __init__(self, host, port, buff_size):
threading.Thread.__init__(self)
self.host = host
self.port = port
self.buff_size = buff_size
self.sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.sock.bind((self.host, self.port))
def listen(self):
self.sock.listen(5)
while True:
print('Waiting for a connexion...')
self.c, self.addr = self.sock.accept()
listening = listenToClient(self.c, self.addr)
class listenToClient(threading.Thread):
def __init__(self, client, address):
threading.Thread.__init__(self)
global socks
socks.append(client)
client.send(("Bienvenue sur le serveur, entre ton pseudo: ").encode('utf-8'))
self.name = client.recv(buff_size).decode('utf-8')
welcome = "Welcome " + self.name + "! If ever you want to quit, type {quit}"
client.send(welcome.encode('utf-8'))
msg = self.name + " has joined the chat"
broadcast(msg)
while True:
msg = client.recv(buff_size).decode('utf-8')
if msg != '{quit}':
broadcast(msg, self.name + ": ")
print('TEST6')
else:
client.close()
leave_msg = self.name + " has left"
broadcast(leave_msg)
break
def broadcast(msg, prefix=''):
for sck in socks:
content = prefix + msg
sck.send(content.encode('utf-8'))
def Main():
ThreadedServer(host,port,buff_size).listen()
host = '127.0.0.1'
port = 10000
buff_size = 1024
socks = []
if __name__ == '__main__':
Main()
The code is working ok for only one user.
I think the problem is in my comprehension of threads.
In my mind, when I type
listening = listenToClient(self.c, self.addr)
a new thread is created running in parallel with the first one.
What I see in reality is that my new thread is indeed executing and ends in the infinite loop in here(managing whatever my first user connected is typing):
while True:
msg = client.recv(buff_size).decode('utf-8')
if msg != '{quit}':
broadcast(msg, self.name + ": ")
print('TEST6')
But I thought as the original thread would have continued looping here:
while True:
print('Waiting for a connexion...')
self.c, self.addr = self.sock.accept()
listening = listenToClient(self.c, self.addr)
Printing 'Waiting for a connection' and waiting for a second connection.
But it is not the case.
I'm pretty sure it's a misunderstanding on my side, I spent hours trying and trying changing everything etc, at some point the listenToClient() thread was just a method in the ThreadedServer(). But now I'm very lost and I don't know what to do, so I came here for help.
Thanks in advance,
Vincent

Python Socketserver client timeout

I have a socketserver in Python which has to handle multiple clients using the select.select method, as seen in the code below:
import socket
import select
class Server:
def __init__(self):
self.server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server.bind(('localhost', 2000))
self.socket_list = [self.server]
self.addresses = ['localhost']
self.commands = [""]
self.running = True
self.server.listen(10)
def listen(self):
while self.running:
read, write, error = select.select(self.socket_list, [], self.socket_list, 0)
for sock in read:
if sock == self.server and self.running:
try:
conn, address = self.server.accept()
conn.settimeout(30)
self.socket_list.append(conn)
self.addresses.append(address[0])
self.commands.append("")
except:
self.shutdown()
break
elif self.running:
try:
packet = sock.recv(60)
if not packet:
self.close_conn(sock)
index = self.socket_list.index(sock)
self.commands[index] += packet
if '\n' in self.commands[index]:
#handle command
except:
self.close_conn(sock)
def close_conn(self, conn):
#close client conn
def shutdown(self):
#shutdown server
if __name__ == "__main__":
Server().listen()
The problem i currently have is that the client's connection should be closed after 30 seconds, but that doesn't happen, even though i declared that by using conn.settimeout(30). I haven't found any explanation yet as to why this happens.
Note: comments were used to replace parts of the code that didn't mater to the problem.

Python - multithreaded sockets

From my understanding python can only run 1 thread at a time so if I were to do something like this
import socket, select
from threading import Thread
import config
class Source(Thread):
def __init__(self):
self._wait = False
self._host = (config.HOST, config.PORT + 1)
self._socket = socket.socket()
self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self._sock = None
self._connections = []
self._mount = "None"
self._writers = []
self._createServer()
Thread.__init__(self)
def _createServer(self):
self._socket.bind(self._host)
self._socket.listen(2)
self._connections.append(self._socket)
self._audioPackets=[]
def _addPacket(self, packet):
self._audioPackets.append(packet)
def _removePacket(self, packet):
self._audioPackets.remove(packet)
def _getPacket(self):
if len(self._audioPackets) > 0:
return self._audioPackets[0]
else:
return None
def _sendOK(self, sock):
sock.send("OK")
def _sendDenied(self, sock):
sock.send("DENIED")
def _sendMount(self, sock):
sock.send("mount:{0}".format(self._mount))
def _sendBufPacket(self, sock, packet):
packet = "buffer:%s" % packet
sock.send(packet)
def recv(self, sock, data):
data = data.split(":", 1)
if data[0] == "WAIT": self._wait = True
elif data[0] == "STOP_WAITING": self._wait = False
elif data[0] == "LOGIN":
if data[1] == config.SOURCE_AUTH:
self._source = sock
self._sendOK(sock)
else:
self._sendClose(sock)
elif data[0] == "MOUNT":
if self._source == sock:
self._mount = data[1]
else:
self._sendClose(sock)
elif data[0] == "CLIENT":
self._sendMount(sock)
self._writers.append(sock)
def _sendCloseAll(self):
for sock in self._connections:
sock.send("CLOSE")
sock.close()
def _sendClose(self, sock):
sock.send("CLOSE")
sock.close()
def main(self):
while True:
rl, wl, xl = select.select(self._connections, self._writers, [], 0.2)
for sock in rl:
if sock == self._socket:
con, ip = sock.accept()
self._connections.append(con)
else:
data = sock.recv(config.BUFFER)
if data:
self.recv(sock, data)
else:
if sock in self._writers:
self._writers.remove(sock)
if sock in self._connections:
self._connections.remove(sock)
for sock in wl:
packet = self._getPacket()
if packet != None:
self._sendBufPacket(sock, packet)
def run(self):
self.main()
class writeThread(Thread):
def __init__(self):
self.running = False
def make(self, client):
self.client = client
self.running = True
def run(self):
host = (config.HOST, config.PORT+1)
sock = socket.socket()
sock.connect(host)
sock.send("CLIENT")
sock.send("MOUNT:mountpoint")
while self.running:
data = sock.recv(config.BUFFER)
if data:
data = data.split(":", 1)
if data[0] == "buffer":
self.client.send(data[1])
elif data[0] == "CLOSE":
self.client.close()
break
if __name__=="__main__":
source = Source()
source.start()
webserver = WebServer()
webserver.runloop()
if I need to build the webserver part I will. But, I'll explain it.
Okay, so basically when someone connects to the websever under the mountpoint that was set, They will get there own personal thread that then grabs the data from Source() and sends it to them. Now say another person connects to the mount point and the last client as well as the source is still going. Wouldn't the new client be blocked from getting the Source data considering there are two active threads?
Your understanding of how Threads work in Python seems to be incorrect, based on the question you are asking. If used correctly, threads will not be blocking: you can instantiate multiple thread with Python. The limitation is that, due to the Global Interpreter Lock (GIL), you cannot get the full parallelism expected in thread programming (e.g. simultaneous execution and thus, reduced runtime).
What is going to happen in your case is that the two threads will take, together, the same amount of time that they would take if they were executed sequentially (although that is not necessarily what happens in practice).
Okay, I have copy and pasted some Python3 code that I have already written for a project that I am currently working on. With modification, you can make this code serve your purposes.
The code uses multiprocessing and multithreading. For my purposes, I am using multiprocessing so that sockets will run on one processor, and I can run a GUI program on another processor. You can remove the multiprocessor part if you prefer. The code below runs a socket message server. The server will listen for clients one at a time. Once a client has connected, a new thread will be initiated to handle all the communications between the server and each client. The server will then continue to search for for clients. At the moment however, the server only listens to data being sent from each client, and then it prints it to the terminal. With a small amount of effort, you can modify my code to send information from the server to each client individually.
import multiprocessing
import threading
from threading import Thread
class ThreadedServer(object):
def __init__(self, host, port):
self.host = host
self.port = port
self.sock = socket(AF_INET, SOCK_STREAM)
self.sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
self.sock.bind((self.host, self.port))
def listen(self):
self.sock.listen(3) #Allow 3 Clients to connect to this server
while True:
#The program will search for one client at a time
print("Searching for Client")
client, address = self.sock.accept()
print(address, " is connected")
#client.settimeout(60)
#Once a client has been found, start a individual client thread
d = threading.Thread(target = self.listenToClient, args=(client, address))
d.daemon = True
d.start()
def listenToClient(self, client, address):
size = 1024
while True:
try:
data = client.recv(size)
if not data:
break
if data:
print(data)
#client.send(response)
else:
raise error('Client disconnected')
except:
client.close()
return False
def dataSharingHost():
#Using Sockets to send information between Processes
#This is the server Function
#ThreadServer(Host_IP, Port_Number), for LocalHost use ''
ThreadedServer('', 8000).listen()
def Main():
commServer = multiprocessing.Process(target=dataSharingHost, args=())
commServer.daemon = True
commServer.start()
if __name__== '__main__':
Main()
And to be fair, my code is modified from https://www.youtube.com/watch?v=qELZAi4yra8 . The client code is covered in those videos. I think the 3rd video covers the multiple client connects.

How can I stop udp server in threading?

I wrote a udp no echo server in my program, I used thread to running and listen some message send by others. But it seems I couldn't stop it use tl.stop(), when I input q or quit. Some of my code is following:
class treadListen(threading.Thread):
def __init__(self):
self.running = True
threading.Thread.__init__(self)
def run(self):
address = ('localhost', 16666)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(address)
while True:
data = sock.recv(65535)
print "MESSAGE:{0}".format(data)
sock.close()
def stop(self):
self.running = False
# end of class thread_clock
if __name__ == "__main__":
tl = treadListen()
tl.start()
while True:
message = raw_input("CMD>")
if not message:
print "Please input command!"
continue
elif (message == 'quit') or (message == 'q'):
tl.stop()
break
else:
print "input is {0}".format(message)
# do something
continue
print "[CONNECTION CLOSED!]"
I was trying to add sock.shutdown(socket.SHUT_RDWR) and sock.close() to def stop of class, but it doesn't work.
How can I stop the thread safety? Thanks!
Your while loop while True: works forever, so I guess your close or shutdown calls to the socket never can gets in way to work.
You should change while True: to while self.running: and that should do the trick.
Thanks ntki,rbp,st.
The problem solved use following code:
class treadListen(threading.Thread):
def __init__(self):
**self.running = True**
threading.Thread.__init__(self)
def run(self):
address = ('localhost', 16666)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
**sock.settimeout(1)**
sock.bind(address)
**while self.running:
try:
data = sock.recv(65535)
print "MESSAGE:{0}".format(data)
except Exception, e:
continue**
sock.close()
def stop(self):
**self.running = False**
# end of class thread_clock

Categories

Resources