how to send incoming messages from socket client to GUI (pyqt5)

喜夏-厌秋 提交于 2021-02-11 13:59:56

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!