问题
I am trying to make a chat application using socket library.
I have three files server.py
, client.py
and gui.py
listening process for client and server are provided by infinite loops. because of that server.py
runs in another terminal window. but client and gui are running in one terminal window. the problem is when I call functions containing infinite loop it stuck there and the rest of code won't run. I even tried using multiprocessing
, threading.Thread
, threading.Timer
, QThread
and Queue
, but still no success. I thought that maybe I am not using these libraries correctly, so I decided to ask for some help.
using threading.Timer
problem was it was saying that, new parent is running in another thread, and I can not change gui objects outside main thread
. it's good to mention I somehow solved the problem, and all incoming messages sent by server, eventually appear on GUI. again problem still existed and I got results when I broke the infinite loop of incoming message
function by pressing ctrl+c
. it's taking too much time of me and causing painful headache, I appreciate if anyone could help me get over this problems. thank you
here is a minimal code:(for testing this code, I merged client and gui together)
here is server.py:
import socket, json, select
class Server():
def __init__(self):
self.connected_sockets = []# for saving sockets
self.connected_clients = {}# for saving sockets and related usernames
self.password = '21709'
self.server_name = 'SERVER1'
def start(self, host, port):
# create the socket, AF_INET == ipv4, SOCK_STREAM == TCP
self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.server_socket.bind( (host, port) )
self.connected_sockets.append( self.server_socket )
self.connected_clients[self.server_name] = self.server_socket
self.server_socket.listen()
self.connect_clients()
def connect_clients(self):
next_msg = b''
while True:
read_sockets, _, exception_sockets = select.select(self.connected_sockets, [], self.connected_sockets)
for socket in read_sockets:
if socket == self.server_socket:
#new connecttion
client_socket, address = self.server_socket.accept()
from_client = client_socket
msg, next_msg = self.receive_data( from_client )
data = json.loads(msg)['data']
username = data['username']
password = data['password']
from_user = self.server_name
to_user = username
BCC = self.server_socket
msg_type = "string"
if username in self.connected_clients:
self.transfer_data( f"username {username} is not valid, try again", msg_type, from_user, to_user, BCC )
client_socket.close()
else:
if ( password == self.password ):
self.connected_sockets.append( client_socket )
self.connected_clients[username] = client_socket
self.transfer_data( 'password was correct, wellcome', msg_type, from_user, to_user, BCC )
print(f"Connection from {address} has been established.")
#send welcome phrase to client just joined from the server
self.transfer_data( "Hey there!!! it's a json", msg_type, from_user, to_user, BCC )
self.transfer_data( "Wellcome to this server", msg_type, from_user, to_user, BCC )
self.transfer_data( "here you can", msg_type, from_user, to_user, BCC )
self.transfer_data( "connect to others", msg_type, from_user, to_user, BCC )
else:
self.transfer_data( "password was incorrect, sorry", msg_type, from_user, client_socket, BCC )
client_socket.close()
else:
#old connection and receive_message from them
for user,user_socket in self.connected_clients.items():
if user_socket == socket:
username_related_to_socket = user
break
try:
msg,next_msg = self.receive_data(socket,next_msg)
msg = json.loads(msg)
from_user = msg['from_user']
to_user = msg['to_user']
if to_user != self.server_name:
self.transfer_data(msg['data'], msg['type'], msg['from_user'], msg['to_user'], msg['BCC'] )
else:
print( f"{msg['from_user']}: \x1b[6;30;42m {msg['data']} \x1b[0m" )
except:
print(f'\n client \x1b[6;30;42m {username_related_to_socket} \x1b[0m disconnected \n')
self.connected_sockets.remove( socket )
del self.connected_clients[username_related_to_socket]
def receive_data(self, from_user, next_msg=b""):
from_client = from_user
full_msg = next_msg
while True:
msg = from_client.recv(7)
try:# because the Ӛ has length of 2, so it may happen that, only one of them exist in the msg received
index = msg.decode("utf-8").find('Ӛ')
except:
msg += from_client.recv(1)
index = msg.decode("utf-8").find('Ӛ')
if ( (index != -1 ) or (len(msg) <= 0) ):
full_msg += msg[:index]
next_msg = msg[index+2:]
break
else:
full_msg += msg
full_msg = full_msg.decode("utf-8")
return(full_msg, next_msg)
def transfer_data(self, msg, msg_type, from_user, to_user, BCC):
from_client = self.connected_clients[from_user]
if type(to_user) is str:
try:
to_client = self.connected_clients[to_user]
result = True
except:
msg = f"{to_user} is offline. try later"
to_client = self.connected_clients[from_user]
to_user = from_user
from_user = self.server_name
result = False
else:
to_client = to_user
to_user = from_user
from_user = self.server_name
result = False
if msg_type == 'string':
msg = msg.strip()
msg = {'type':msg_type, 'size':len(msg), 'data':msg, 'from_user':from_user, 'to_user':to_user, 'BCC':str(BCC), 'result':result}
# turn a dictionary into a string to transfere with socket
data_string = json.dumps(msg)
to_client.send( bytes(data_string,"utf-8") )
to_client.send( bytes('Ӛ',"utf-8") )
server = Server()
server.start(host='127.0.0.1', port=1234)
client and gui together.py:
import os, socket, json
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ConnectPage(QWidget):
def __init__(self):
QWidget.__init__(self)
prev_info = ['','','','']
if os.path.isfile("previous_login.txt"):
saved_login_file = open("previous_login.txt",'r')
lines = saved_login_file.readlines()
if len(lines) > 0:
for i in range(0,len(lines)):
prev_info[i] = lines[i]
saved_login_file.close()
self.username = QLineEdit(prev_info[0])
self.password = QLineEdit(prev_info[1])
self.host = QLineEdit(prev_info[2])
self.port = QLineEdit(prev_info[3])
self.port.setValidator( QIntValidator() )#takes only numbers
self.login_button = QPushButton('Login')
self.login_button.clicked.connect(self.login_button_clicked)
self.clear_form = QPushButton('Clear Form')
self.clear_form.clicked.connect(self.clear_form_clicked)
self.status_bar = QLabel()
self.main_layout = QGridLayout()
self.main_layout.addWidget( QLabel('Username:'),0,0 )
self.main_layout.addWidget( self.username,0,1 )
self.main_layout.addWidget( QLabel('Password:'),1,0 )
self.main_layout.addWidget( self.password,1,1 )
self.main_layout.addWidget( QLabel('Host:'),2,0 )
self.main_layout.addWidget( self.host,2,1 )
self.main_layout.addWidget( QLabel('Port:'),3,0 )
self.main_layout.addWidget( self.port,3,1 )
self.main_layout.addWidget( self.clear_form,4,0 )
self.main_layout.addWidget( self.login_button,4,1 )
self.main_layout.addWidget( self.status_bar,5,0,2,1 )
self.setLayout(self.main_layout)
def clear_form_clicked(self):
username = self.username.setText('')
password = self.password.setText('')
host = self.host.setText('')
port = self.port.setText('')
def login_button_clicked(self):
username = self.username.text().strip()
password = self.password.text().strip()
host = self.host.text().strip()
port = self.port.text().strip()
saved_login_file = open("previous_login.txt",'w')
saved_login_file.write( username + '\n' + password + '\n' + host + '\n' + port )
saved_login_file.close()
result,msg = controller.client_connect(host, int(port), username, password)
if result:
controller.connect_page.close()
controller.chat_page.show()
controller.chat_page.start_listening()
else:
controller.connect_page.status_bar.setText(msg['data'])
class ChatPage(QWidget):
def __init__(self):
QWidget.__init__(self)
self.next_msg = b''
#create a scrolled window to put info_grid in it
self.msg_layout = QVBoxLayout()
msg_layout_widget = QWidget()
msg_layout_widget.setLayout(self.msg_layout)
self.scrolled = QScrollArea()
self.scrolled.setWidget(msg_layout_widget)
self.scrolled.setWidgetResizable(True)
self.scrolled.setFixedHeight(400)
scroll_layout = QHBoxLayout()
scroll_layout.addWidget(self.scrolled)
msg_layout_groupbox = QGroupBox()
msg_layout_groupbox.setLayout(scroll_layout)
self.new_msg = QTextEdit()
send_button = QPushButton('Send icon')
#send_button.clicked.connect( self.send_button_clicked )
self.send_layout = QHBoxLayout()
self.send_layout.addWidget(self.new_msg)
self.send_layout.addWidget(send_button)
self.right_layout = QVBoxLayout()
self.right_layout.addWidget(msg_layout_groupbox)
self.right_layout.addLayout(self.send_layout)
self.main_layout = QHBoxLayout()
self.main_layout.addLayout(self.right_layout)
self.setLayout(self.main_layout)
def start_listening(self):
controller.client.listen_to_incoming_messages()
def insert_into_chat_area(self,data):
from_user = data['from_user']
to_user = data['to_user']
msg = data['data']
if msg != '':
if data['type'] == 'string':
self.msg_layout.addWidget( QLabel(msg) )
else:
print( "unknown data recieved (not a dictionary)" )
class Client(QObject):
def __init__(self,host,port,username,password):
QObject.__init__(self)
self.BCC = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.host = host
self.port = port
self.username = username
self.password = password
self.next_msg = b''
def connect(self):
audience = "SERVER1"
self.BCC.connect( (self.host, self.port) )
msg = {'username':self.username,'password':self.password}
msg_type = 'login'
to_user = audience
from_user = self.username
self.transfer_data(msg, msg_type, from_user, to_user, self.BCC)
msg,self.next_msg = self.recieve_data(self.BCC)
msg = json.loads(msg)
print( f"{msg['from_user']}: \x1b[6;30;42m {msg['data']} \x1b[0m" ) #syntax => print(f'\x1b[6;30;42m {colored text} \x1b[0m')
result = msg['result']
return( result,msg )
def listen_to_incoming_messages(self):
while True:
msg,self.next_msg = self.recieve_data(self.BCC,self.next_msg)
msg = json.loads(msg)
print( f"{msg['from_user']}: \x1b[6;30;42m {msg['data']} \x1b[0m" )
controller.chat_page.insert_into_chat_area(msg)
def outgoing_messages(self,from_user,to_user,BCC):
while True:
msg = input(f'{self.username}> ')
self.transfer_data(msg,'string',from_user,to_user,BCC)
def transfer_data(self, msg, msg_type, from_user, to_user, BCC):
if msg_type == 'string':
msg = msg.strip()
result = True
msg = {'type':msg_type, 'size':len(msg), 'data':msg, 'from_user':from_user, 'to_user':to_user, 'BCC':str(BCC), 'result':result}
data_string = json.dumps(msg)
BCC.send( bytes(data_string,"utf-8") )
BCC.send( bytes('Ӛ',"utf-8") )
def recieve_data(self, from_server, next_msg=b""):
full_msg = next_msg
while True:
msg = from_server.recv(7)
try:
index = msg.decode("utf-8").find('Ӛ')
except:
msg += from_server.recv(1)
index = msg.decode("utf-8").find('Ӛ')
if ( (index != -1 ) or (len(msg) <= 0) ):
full_msg += msg[:index]
next_msg = msg[index+2:]
break
else:
full_msg += msg
full_msg = full_msg.decode("utf-8")
return(full_msg, next_msg)
# this class controls moving between all screens
class Controller:
def __init__(self):
self.connect_page = ConnectPage()
self.connect_page.show()
self.chat_page = ChatPage()
def client_connect( self, host, port, username, password ):
self.client = Client(host, port, username, password)
return( self.client.connect() )
app = QApplication([])
controller = Controller()
app.exec_()
回答1:
Finally I could somehow go around situation. I believe we can only edit a widget that is already inside message layout, and we can not adding any widget to any layout, because layouts are thread-safe
and process-safe
. off-course there maybe a solution to that problem too, but right now nothing come to my mind. here is the solution via code:server.py
is as mentioned above and gui+client.py
is as follows:
import threading, time
import os, socket, json
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
class ConnectPage(QWidget):
def __init__(self):
QWidget.__init__(self)
prev_info = ['','','','']
if os.path.isfile("previous_login.txt"):
saved_login_file = open("previous_login.txt",'r')
lines = saved_login_file.readlines()
if len(lines) > 0:
for i in range(0,len(lines)):
prev_info[i] = lines[i]
saved_login_file.close()
self.username = QLineEdit(prev_info[0])
self.password = QLineEdit(prev_info[1])
self.password.setEchoMode(QLineEdit.Password)
self.host = QLineEdit(prev_info[2])
self.port = QLineEdit(prev_info[3])
self.port.setValidator( QIntValidator() )#takes only numbers
self.login_button = QPushButton('Login')
self.login_button.clicked.connect(self.login_button_clicked)
self.clear_form = QPushButton('Clear Form')
self.clear_form.clicked.connect(self.clear_form_clicked)
self.status_bar = QLabel()
self.main_layout = QGridLayout()
self.main_layout.addWidget( QLabel('Username:'),0,0 )
self.main_layout.addWidget( self.username,0,1 )
self.main_layout.addWidget( QLabel('Password:'),1,0 )
self.main_layout.addWidget( self.password,1,1 )
self.main_layout.addWidget( QLabel('Host:'),2,0 )
self.main_layout.addWidget( self.host,2,1 )
self.main_layout.addWidget( QLabel('Port:'),3,0 )
self.main_layout.addWidget( self.port,3,1 )
self.main_layout.addWidget( self.clear_form,4,0 )
self.main_layout.addWidget( self.login_button,4,1 )
self.main_layout.addWidget( self.status_bar,5,0,2,1 )
self.setLayout(self.main_layout)
def clear_form_clicked(self):
username = self.username.setText('')
password = self.password.setText('')
host = self.host.setText('')
port = self.port.setText('')
def login_button_clicked(self):
username = self.username.text().strip()
password = self.password.text().strip()
host = self.host.text().strip()
port = self.port.text().strip()
saved_login_file = open("previous_login.txt",'w')
saved_login_file.write( username + '\n' + password + '\n' + host + '\n' + port )
saved_login_file.close()
result,msg = controller.client_connect(host, int(port), username, password)
if result:
controller.connect_page.close()
controller.chat_page.show()
controller.chat_page.start_listening()
else:
controller.connect_page.status_bar.setText(msg['data'])
class ChatPage(QWidget):
def __init__(self):
QWidget.__init__(self)
self.next_msg = b''
#create a scrolled window to put info_grid in it
self.msg_layout = QVBoxLayout()
self.new_msg_label = QLineEdit()
self.new_msg_label.setHidden(True)
self.new_msg_label.textChanged.connect( self.new_incoming_msg_text_changed )
self.msg_layout.addWidget( self.new_msg_label )
msg_layout_widget = QWidget()
msg_layout_widget.setLayout(self.msg_layout)
self.scrolled = QScrollArea()
self.scrolled.setWidget(msg_layout_widget)
self.scrolled.setWidgetResizable(True)
self.scrolled.setFixedHeight(400)
scroll_layout = QHBoxLayout()
scroll_layout.addWidget(self.scrolled)
msg_layout_groupbox = QGroupBox()
msg_layout_groupbox.setLayout(scroll_layout)
self.new_msg = QTextEdit()
send_button = QPushButton('Send icon')
#send_button.clicked.connect( self.send_button_clicked )
self.send_layout = QHBoxLayout()
self.send_layout.addWidget(self.new_msg)
self.send_layout.addWidget(send_button)
self.right_layout = QVBoxLayout()
self.right_layout.addWidget(msg_layout_groupbox)
self.right_layout.addLayout(self.send_layout)
self.main_layout = QHBoxLayout()
self.main_layout.addLayout(self.right_layout)
self.setLayout(self.main_layout)
def start_listening(self):
t = threading.Timer(1,controller.client.listen_to_incoming_messages,(self.new_msg_label,) )
t.start()
def new_incoming_msg_text_changed(self,text):
if text:
widget = QLabel( text )
self.msg_layout.addWidget( widget )
self.new_msg_label.setText('')
class Client:
def __init__(self,host,port,username,password):
self.BCC = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.host = host
self.port = port
self.username = username
self.password = password
self.next_msg = b''
def connect(self):
audience = "SERVER1"
self.BCC.connect( (self.host, self.port) )
msg = {'username':self.username,'password':self.password}
msg_type = 'login'
to_user = audience
from_user = self.username
self.transfer_data(msg, msg_type, from_user, to_user, self.BCC)
msg,self.next_msg = self.recieve_data(self.BCC)
msg = json.loads(msg)
print( f"{msg['from_user']}: \x1b[6;30;42m {msg['data']} \x1b[0m" ) #syntax => print(f'\x1b[6;30;42m {colored text} \x1b[0m')
result = msg['result']
return( result,msg )
def listen_to_incoming_messages(self, obj):
while True:
data,self.next_msg = self.recieve_data(self.BCC,self.next_msg)
data = json.loads(data)
from_user = data['from_user']
to_user = data['to_user']
msg = data['data']
print( f"{from_user}: \x1b[6;30;42m {msg} \x1b[0m" )
if msg != '':
time.sleep(1) # this line is redundant, I added it to see how it works
obj.setText( f"<font color=red>{from_user}</font> => <font color=blue>{msg}</font>" )
def outgoing_messages(self,from_user,to_user,BCC):
while True:
msg = input(f'{self.username}> ')
self.transfer_data(msg,'string',from_user,to_user,BCC)
def transfer_data(self, msg, msg_type, from_user, to_user, BCC):
if msg_type == 'string':
msg = msg.strip()
result = True
msg = {'type':msg_type, 'size':len(msg), 'data':msg, 'from_user':from_user, 'to_user':to_user, 'BCC':str(BCC), 'result':result}
data_string = json.dumps(msg)
BCC.send( bytes(data_string,"utf-8") )
BCC.send( bytes('Ӛ',"utf-8") )
def recieve_data(self, from_server, next_msg=b""):
full_msg = next_msg
while True:
msg = from_server.recv(7)
try:
index = msg.decode("utf-8").find('Ӛ')
except:
msg += from_server.recv(1)
index = msg.decode("utf-8").find('Ӛ')
if ( (index != -1 ) or (len(msg) <= 0) ):
full_msg += msg[:index]
next_msg = msg[index+2:]
break
else:
full_msg += msg
full_msg = full_msg.decode("utf-8")
return(full_msg, next_msg)
# this class controls moving between all screens
class Controller:
def __init__(self):
self.connect_page = ConnectPage()
self.connect_page.show()
self.chat_page = ChatPage()
def client_connect( self, host, port, username, password ):
self.client = Client(host, port, username, password)
return( self.client.connect() )
app = QApplication([])
controller = Controller()
app.exec_()
来源:https://stackoverflow.com/questions/60995319/how-to-send-incoming-messages-from-socket-client-to-gui-pyqt5