python-3.x - 如何将来自套接字客户端的传入消息发送到 GUI(pyqt5)
问题描述
我正在尝试使用套接字库制作聊天应用程序。我有三个文件server.py
,客户端client.py
和gui.py
服务器的侦听过程由无限循环提供。因为server.py
它在另一个终端窗口中运行。但客户端和 gui 正在一个终端窗口中运行。问题是当我调用包含无限循环的函数时,它卡在那里,其余代码将无法运行。我什至尝试使用multiprocessing
, threading.Thread
, threading.Timer
, QThread
and Queue
,但仍然没有成功。我想也许我没有正确使用这些库,所以我决定寻求一些帮助。使用threading.Timer
问题是说,new parent is running in another thread, and I can not change gui objects outside main thread
. 值得一提的是,我以某种方式解决了问题,服务器发送的所有传入消息最终都出现在 GUI 上。incoming message
再次问题仍然存在,当我通过按打破函数的无限循环时我得到了结果ctrl+c
。这花费了我太多时间并导致头痛,如果有人可以帮助我解决这个问题,我将不胜感激。谢谢这里是一个最小的代码:(为了测试这个代码,我将客户端和gui合并在一起)
这是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)
客户端和gui一起.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_()
解决方案
最后我可以以某种方式绕过情况。我相信我们只能编辑一个已经在消息布局中的小部件,我们不能将任何小部件添加到任何布局中,因为布局是thread-safe
和process-safe
. 当然,也许也有解决该问题的方法,但是现在我什么都没有想到。这是通过代码的解决方案:server.py
如上所述,gui+client.py
如下所示:
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_()
推荐阅读
- angularjs - 如何在角度 js 中获取 md 选择值
- python - matplotlib 无法绘制单通道或灰度图像像素
- python - 保存字符串中一个字符与每个其他字符之间的距离
- maven - WAR 中的 Maven Jar 依赖项产生 ClassNotFoundException
- performance - 解释 Keras 损失和准确度图
- python-3.x - 无法在 python 中获取绘图
- flutter - 颤振标签栏。如何更改填充
- sql - 表上的多个触发器
- c++ - 在 IAR 下编译 Arduino 函数
- terraform - 必须重新应用相同的 terraform 配置以满足原始 GKE 配置的网络策略