0

I'm looking for help for my project with threading and sockets. I have to make a communication between a server and few clients, and I have to do a GUI. The issue is that when I use QRunnable, the GUI (line edit, buttons) does not do anything. For example, my function sender in the AcceptThread does not run at all, same isse with update_reply. (so my SenderThread does not launch) I did the same script for my server with QThread and that works perfectly, but only for one user.

So I want my GUI update with the clients message, and that I can send messages with my Sever too. Thank you for your help.

import sys
import time
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import socket


flag = False
arret = False  

# Création d'une classe qui hérite de QThread pour gérer la réception des messages
class ReceiverThread(QThread):
    # Signal émis lorsque des messages sont reçus
    message_received = pyqtSignal(str)
    def __init__(self, connexion, server_socket):
        super().__init__()
        self.conn = connexion
        self.server_socket = server_socket

    # La méthode run est appelée lorsque le thread démarre
    def run(self):
        print("ReceiverThread Up")
        global flag, arret
        try:
            while not flag:
                recep = self.conn.recv(1024).decode()
                
                if recep == "arret" or recep == "bye":
                    print("Un client se déconnecte")
                    flag = True
                    if recep == "arret":
                        print("Arrêt du serveur")
                        arret = True

                elif not recep:
                    self.conn.close()
                    flag = False

                else:
                    print(f'User : {recep}\n')
                    # Émission du signal avec le message reçu
                    self.message_received.emit(recep)
            
        except Exception as err:
            print(err)

        print("ReceiverThread ends\n")
            
    def quitter(self):
        QCoreApplication.instance().quit()


class SenderThread(QThread):
    def __init__(self, reply, connexion):
        super().__init__()
        self.reply = reply
        self.conn = connexion

    def run(self):
        print("SenderThread Up")
        print(self.reply)
        try:
            try:
                self.conn.send(self.reply.encode())

            except ConnectionRefusedError as error:
                print(error)

            except ConnectionResetError as error:
                print(error)
        except Exception as err:
            print(err)
        
        print("SenderThread ends")

    def quitter(self):
        QCoreApplication.quit()


class AcceptThread(QRunnable):
    def __init__(self, log, send, connect, server_socket, host, i):
        super().__init__()
        self.log = log
        self.send = send
        self.connect = connect
        self.server_socket = server_socket
        self.host = host
        self.i = i
    
    
    def run(self):
        print(f"AcceptThread Up {self.i}")
        global flag, arret
        try:
            while not arret:
                print("En attente d'une nouvelle connexion")
                self.conn, self.address = self.server_socket.accept()
                self.connect.connect(self.sender)
                print(f"Nouvelle connexion de {self.host} !")

                self.receiver_thread = ReceiverThread(self.conn, self.server_socket)
                self.receiver_thread.message_received.connect(self.update_reply)      
                self.receiver_thread.start()
                self.receiver_thread.wait()
                #on attend la fin du thread receive avant de close la connexion
                self.conn.close()

                flag = False 
                #on remet la variable globale flag en False pour que la boucle du ReceiverThread fonctionne
                
                #nécessaire sinon à chaque itération de la boucle il y a une nouvelle connexion du bouton à la fonction sender
            self.server_socket.close()
            print("Socket closed")
            self.quitter()


        except Exception as err:
            print(err)
        
        print(f"AcceptThread ends {self.i}")
    
    # Méthode appelée pour mettre à jour l'interface utilisateur avec le message reçu
    def update_reply(self, message):
        self.log.setText(message)
    
    def listen(self):
        self.server_socket.listen(100)
    
    def sender(self):
        reply = self.send.text()
        self.sender_thread = SenderThread(reply, self.conn)
        self.sender_thread.start()
        self.sender_thread.wait()
    
    def quitter(self):
        QCoreApplication.instance().quit()

# Classe de la fenêtre principale
class Window(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi()

    def setupUi(self):
        self.setWindowTitle("Serveur")
        self.resize(250, 150)
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        # Création et connexion des widgets
        self.label = QLabel("Logs")
        self.label2 = QLabel("Message Serveur")
        self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.label2.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

        self.line_edit = QLineEdit()
        self.line_edit2= QLineEdit()

        self.countBtn = QPushButton("Envoyer")
        self.btn_quit = QPushButton("Quitter")
        self.dialog = QPushButton("?")

        self.line_edit.setEnabled(False)
        self.dialog.clicked.connect(self.button_clicked)
        self.btn_quit.clicked.connect(self.quitter)

        # Configuration du layout
        layout = QGridLayout()
        layout.addWidget(self.label, 0, 0)
        layout.addWidget(self.label2, 2, 0)
        layout.addWidget(self.line_edit, 1, 0)
        layout.addWidget(self.line_edit2, 3, 0)
        layout.addWidget(self.countBtn, 4, 0)
        layout.addWidget(self.btn_quit, 5, 0)
        layout.addWidget(self.dialog, 5, 1)

        self.centralWidget.setLayout(layout)

        self.label.setText(f"Log du serveur")
        self.line_edit.setText(f"")

        self.main_thread()

    def main_thread(self):
        log = self.line_edit
        send = self.line_edit2
        connect = self.countBtn.clicked

        host, port = ('0.0.0.0', 11111)
        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.host = socket.gethostname()
        self.listen()

        #threadCount = QThreadPool.globalInstance().maxThreadCount()
        threadCount = 2
        pool = QThreadPool.globalInstance()
        
        for i in range(threadCount):
            runnable = AcceptThread(log, send, connect, self.server_socket, self.host, i)
            pool.start(runnable)


    def button_clicked(self, s):
        dlg = QMessageBox(self)
        dlg.setWindowTitle("Aide")
        dlg.setText("Centrale du Serveur.")
        dlg.setStandardButtons(QMessageBox.Ok)
        dlg.setIcon(QMessageBox.Question)
        dlg.exec()

    # Méthode appelée lorsqu'on clique sur le bouton Quitter
    def quitter(self):
        global flag, arret
        flag = True
        arret = True
        QCoreApplication.instance().quit()

    def listen(self):
        self.server_socket.listen(100)


# Configuration de l'application PyQt
app = QApplication(sys.argv)
window = Window()
window.show()

sys.exit(app.exec())

Client

import socket 
import threading
import time, sys

flag = False
arret = False

def envoi(client_socket):
    global flag, arret
    while flag == False:
        try:
            message = str(input(">"))
       
            client_socket.send(message.encode())

            if message == "arret" or message == "bye":
                print("Arret du serveur")
                flag = True
                arret = True

        except ConnectionRefusedError as error:
            print(error)
            main()

        except ConnectionResetError as error:
            print(error)
            main()
    print("Arret de la Thread envoi")
                

def reception(client_socket):
    global flag, arret
    while not flag:
        try:
            reply = client_socket.recv(1024).decode("utf-8")

            if not reply:
                print("Le serveur n'est plus accessible...")
                flag = True
                arret = True
    
            else:
                print(f'Serveur : {reply}')
                
        except ConnectionRefusedError as error:
            print(error)
            main()

        except ConnectionResetError as error:
            print(error)
            main()

        except BrokenPipeError as error:
            print(f'{error} : Wait a few seconds')
            main()
    print("Arret de la Thread reception")


def main():
    try :
        host, port = ('127.0.0.1', 11111)
        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        client_socket.connect((host,port))

        while not arret:
            t1 = threading.Thread (target=reception, args=[client_socket])
            t2 = threading.Thread (target=envoi, args=[client_socket])
            t1.start()
            t2.start()

            t1.join()
            t2.join()

            client_socket.close()

    except ConnectionRefusedError:
        print("Impossible de se connecter")
        time.sleep(5)
        main()


if __name__ == '__main__':
    main()
3
  • Creating a QThread inside a QRunnable makes absolutely no sense, and doing it like in your attempt is even more wrong. First of all, QRunnable already runs in a separate thread, so trying to create another makes little or no sense; then you're calling wait() right after starting it, making the usage of the further thread even more pointless; finally, you're trying to access the UI from QRunnable (self.log.setText(message)), which is terribly wrong, as UI elements are not thread-safe and shall never be accessed from external threads. Commented Nov 24, 2023 at 15:31
  • If you want to use QRunnable to take advantage of the threadpool and also communicate with the UI, then implement a "signal proxy" (a QObject subclass that is used to emit signals). Otherwise, use QThread and eventually "queue" worker objects moved into them, starting each one as soon as the previous worker has finished, in case you really need an amount of possibly running/queued thread workers that is above the thread count "limit" (the actual limit may depend on the OS and configuration). Commented Nov 24, 2023 at 15:37
  • Hello, but can you help me ? I'm a beginner. Thank you. Commented Nov 24, 2023 at 15:40

1 Answer 1

0

I forgot QRunnable thing and just did a list "all_thread" for the client connections. Then I just append new connections in the list or remove when they disconnect. I used QThread instead of QRunnable and do not try to make a Thread for each connections now.

import sys
import time
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import socket


flag = False
arret = False  

# Création d'une classe qui hérite de QThread pour gérer la réception des messages
class ReceiverThread(QThread):
    # Signal émis lorsque des messages sont reçus
    message_received = pyqtSignal(str)
    def __init__(self, connexion, server_socket, all_threads):
        super().__init__()
        self.conn = connexion
        self.server_socket = server_socket
        self.all_threads = all_threads

    # La méthode run est appelée lorsque le thread démarre
    def run(self):
        print("ReceiverThread Up")
        global flag, arret
        try:
            while not flag:
                recep = self.conn.recv(1024).decode()

                **if recep == "arret" or recep == "bye":
                    print("Un client se déconnecte")
                    for conn in self.all_threads:
                        if conn != self.conn:
                            continue
                        else:
                            # Fermer uniquement la connexion qui a dit "bye"
                            conn.close()
                            self.all_threads.remove(conn)**

                    if recep == "arret":
                        print("Arrêt du serveur")
                        arret = True
                        self.quitter()

                elif not recep:
                    for conn in self.all_threads:
                        if conn != self.conn:
                            continue
                        else:
                            # Fermer uniquement la connexion qui a dit "bye"
                            conn.close()
                            self.all_threads.remove(conn)

                else:
                    print(f'User : {recep}\n')
                    # Émission du signal avec le message reçu
                    self.message_received.emit(recep)
            
        except Exception as err:
            print(f"{err}")

        print("ReceiverThread ends\n")
            
    def quitter(self):
        for conn in self.all_threads:
            conn.close()
        self.server_socket.close()
        QCoreApplication.instance().quit()


class SenderThread(QThread):
    def __init__(self, reply, all_threads):
        super().__init__()
        self.reply = reply
        self.all_threads = all_threads

    def run(self):
        print("SenderThread Up")
        print(self.reply)
        try:
            try:
                **for conn in self.all_threads:
                    conn.send(self.reply.encode())**
            except ConnectionRefusedError as error:
                print(error)

            except ConnectionResetError as error:
                print(error)
        except Exception as err:
            print(err)
        
        print("SenderThread ends")

    def quitter(self):
        QCoreApplication.instance().quit()

class AcceptThread(QThread):
    def __init__(self, server_socket, log, send, connect):
        super().__init__()
        self.server_socket = server_socket
        self.log = log
        self.send = send
        self.connect = connect
    
    def run(self):
        print("AcceptThread Up")
        global flag, arret
        **self.all_threads = []**
        try:
            host, port = ('0.0.0.0', 11111)
            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.host = socket.gethostname()
            self.listen()
            self.connect.connect(self.sender)
            while not arret:
                print("En attente d'une nouvelle connexion")
                self.conn, self.address = self.server_socket.accept()
                print(f"Nouvelle connexion de {self.host} !")

                self.receiver_thread = ReceiverThread(self.conn, self.server_socket, self.all_threads)
                self.receiver_thread.message_received.connect(self.update_reply)
                self.receiver_thread.message_received.connect(self.send_everyone)      
                self.receiver_thread.start()
                **self.all_threads.append(self.conn)**
   
                #nécessaire sinon à chaque itération de la boucle il y a une nouvelle connexion du bouton à la fonction sender
            
            else:
                for conn in self.all_threads:
                    conn.close()

                self.server_socket.close()
                print("Socket closed")
                self.quitter()


        except Exception as err:
            print(err)
        
        print("AcceptThread ends")
    
    # Méthode appelée pour mettre à jour l'interface utilisateur avec le message reçu
    def update_reply(self, message):
        self.log.append(f'{self.host}: {message}') 
    
    def listen(self):
        self.server_socket.listen(100)
    
    def sender(self):
        reply = self.send.text()
        self.sender_thread = SenderThread(f'Serveur : {reply}', self.all_threads)
        self.sender_thread.start()
        self.sender_thread.wait()

    def send_everyone(self, message):
        print("message")
        self.sender_thread = SenderThread(message, self.all_threads)
        self.sender_thread.start()
        self.sender_thread.wait()
    
    def quitter(self):
        QCoreApplication.instance().quit()

# Classe de la fenêtre principale
class Window(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setupUi()

    def setupUi(self):
        self.setWindowTitle("Serveur")
        self.resize(250, 150)
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        # Création et connexion des widgets
        self.label = QLabel("Logs")
        self.label2 = QLabel("Message Serveur")
        self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.label2.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)

        self.text_edit = QTextEdit()
        self.text_edit.setReadOnly(True)
        self.line_edit2= QLineEdit()

        self.countBtn = QPushButton("Envoyer")
        self.btn_quit = QPushButton("Quitter")
        self.dialog = QPushButton("?")

        self.dialog.clicked.connect(self.button_clicked)
        self.btn_quit.clicked.connect(self.quitter)

        # Configuration du layout
        layout = QGridLayout()
        layout.addWidget(self.label, 0, 0)
        layout.addWidget(self.label2, 2, 0)
        layout.addWidget(self.text_edit, 1, 0, 1, 2) 
        layout.addWidget(self.line_edit2, 3, 0)
        layout.addWidget(self.countBtn, 4, 0)
        layout.addWidget(self.btn_quit, 5, 0)
        layout.addWidget(self.dialog, 5, 1)

        self.centralWidget.setLayout(layout)

        self.label.setText(f"Log du serveur")
        self.text_edit.setText(f"")

        self.main_thread()

    def main_thread(self):
        log = self.text_edit
        send = self.line_edit2
        connect = self.countBtn.clicked

        self.accept_thread = AcceptThread(self, log, send, connect)
        self.accept_thread.start()


    def button_clicked(self, s):
        dlg = QMessageBox(self)
        dlg.setWindowTitle("Aide")
        dlg.setText("Centrale du Serveur.")
        dlg.setStandardButtons(QMessageBox.Ok)
        dlg.setIcon(QMessageBox.Question)
        dlg.exec()

    # Méthode appelée lorsqu'on clique sur le bouton Quitter
    def quitter(self):
        global flag, arret
        flag = True
        arret = True
        QCoreApplication.instance().quit()


if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = Window()
    window.show()
    sys.exit(app.exec())
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.