chat screenshot

Creating Graphical Chat Application Using PyQt and Socket

Hi guys, in this tutorial I will be showing you how to create a graphical chat application using PyQt and
Socket.

For those who don’t know PyQt is the GUI framework for Python from the well known organization or project qt-project.

To follow along this tutorial you need to have basic understanding of GUI programming in Qt Framework not necessarily PyQt as syntax for other languages are overall same.

On the other hand we will be using Python socket module for the connection and transfer of messages.So to follow this tutorial you need to have basic understanding of socket programming in Python.

But before we start I want to tell you something if you did not know already. The application project could me
way more simple if this was purely a command line based application but as it is a GUI application things have become little bit complex. Don’t worry I will be explaining every single line when it will be necessary.

The reason I said so because as you know both GUI and Networking are blocking in behavior. What I meant was
when we show a dialog or a window in PyQt it blocks the main program from running as long as it is kept open.
So when in our code interpreter reaches something that is responsible for showing a dialog or window it will not
interpret the code below that line.When control returns to our last line interpreted everything will start working normal again.

On the other hand networking operations are blocking in nature too. As example when we listening for connections or
receiving or transferring data it blocks the main program so everything except this won’t work.

As you might have guessed already, we have to use multithreading for this. We will run each part of the program in separate thread so that no one blocks no one. So besides PyQt and Socket you also have to have basic understanding of python threading programming.

Before we start let’s have some general concept about what we are gonna soon regarding everything I mentioned above.

We will be having two files-
1) server.py
2) client.py

The GUI code for both will be same more or less except some minor changes here and there.

In server.py there will be

    1) a Window class which will inherit from QDialog.

    2) ServerThread class which will inherit Thread class from
    threading module of Python. It will be responsible for creating the server, waiting for the
    client connections and for each client connection creating a separate client thread.

    3) ClientThread class which will be created by ServerThread for each client connection.


In client.py there will be



    1) a Window class which will inherit from QDialog.

    2) ClientThread which will try to connect to the server. It is created so that connection to
    server operation does not block the GUI from appearing.

The GUI Part

Window for both the Server and Client will be same.There will be two QLineEdit and one QPushButton.
First QLineEdit will be read only where chat texts will be shown. Second QLineEdit will be where user will put text
and the QPushButton will be for sending the chat text.

But there is a interesting thing. Here we will be using something new. We will use QSplitter to split the whole QDialog screen into three parts. With the first QSplitter we will add the chat body and the chat text field. With the second QSplitter we will add the first splitter and the push button. At the last we will add the second splitter to the QVBoxLayout whose parent widget is QDialog. In both cases,for the Server and the Client, GUI will be like this.

Before we dive into more explanation of the code let’s have a look at the code of both Server and Client.

server.py

import sys,time
from PyQt5 import QtGui
from PyQt5 import QtCore
from PyQt5.QtWidgets import QScrollBar,QSplitter,QTableWidgetItem,QTableWidget,QComboBox,QVBoxLayout,QGridLayout,QDialog,QWidget, QPushButton, QApplication, QMainWindow,QAction,QMessageBox,QLabel,QTextEdit,QProgressBar,QLineEdit
from PyQt5.QtCore import QCoreApplication
import socket
from threading import Thread 
from socketserver import ThreadingMixIn 

conn=None
class Window(QDialog):
    def __init__(self):
        super().__init__()
        self.flag=0
        self.chatTextField=QLineEdit(self)
        self.chatTextField.resize(480,100)
        self.chatTextField.move(10,350)
        self.btnSend=QPushButton("Send",self)
        self.btnSend.resize(480,30)
        self.btnSendFont=self.btnSend.font()
        self.btnSendFont.setPointSize(15)
        self.btnSend.setFont(self.btnSendFont)
        self.btnSend.move(10,460)
        self.btnSend.setStyleSheet("background-color: #F7CE16")
        self.btnSend.clicked.connect(self.send)

        self.chatBody=QVBoxLayout(self)
        # self.chatBody.addWidget(self.chatTextField)
        # self.chatBody.addWidget(self.btnSend)
        # self.chatWidget.setLayout(self.chatBody)
        splitter=QSplitter(QtCore.Qt.Vertical)

        self.chat = QTextEdit()
        self.chat.setReadOnly(True)
        # self.chatLayout=QVBoxLayout()
        # self.scrollBar=QScrollBar(self.chat)
        # self.chat.setLayout(self.chatLayout)

        splitter.addWidget(self.chat)
        splitter.addWidget(self.chatTextField)
        splitter.setSizes([400,100])

        splitter2=QSplitter(QtCore.Qt.Vertical)
        splitter2.addWidget(splitter)
        splitter2.addWidget(self.btnSend)
        splitter2.setSizes([200,10])

        self.chatBody.addWidget(splitter2)


        self.setWindowTitle("Chat Application")
        self.resize(500, 500)

    def send(self):
        text=self.chatTextField.text()
        font=self.chat.font()
        font.setPointSize(13)
        self.chat.setFont(font)
        textFormatted='{:>80}'.format(text)
        self.chat.append(textFormatted)
        global conn
        conn.send(text.encode("utf-8"))
        self.chatTextField.setText("")

class ServerThread(Thread):
    def __init__(self,window): 
        Thread.__init__(self) 
        self.window=window

 
    def run(self): 
        TCP_IP = '0.0.0.0' 
        TCP_PORT = 80 
        BUFFER_SIZE = 20  
        tcpServer = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
        tcpServer.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 
        tcpServer.bind((TCP_IP, TCP_PORT)) 
        threads = [] 
        
        tcpServer.listen(4) 
        while True:
            print("Multithreaded Python server : Waiting for connections from TCP clients...") 
            global conn
            (conn, (ip,port)) = tcpServer.accept() 
            newthread = ClientThread(ip,port,window) 
            newthread.start() 
            threads.append(newthread) 
        
 
        for t in threads: 
            t.join() 



class ClientThread(Thread): 
 
    def __init__(self,ip,port,window): 
        Thread.__init__(self) 
        self.window=window
        self.ip = ip 
        self.port = port 
        print("[+] New server socket thread started for " + ip + ":" + str(port)) 
 
    def run(self): 
        while True : 
            
            #(conn, (self.ip,self.port)) = serverThread.tcpServer.accept() 
            global conn
            data = conn.recv(2048) 
            window.chat.append(data.decode("utf-8"))
            print(data)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    
    
    window = Window()
    serverThread=ServerThread(window)
    serverThread.start()
    window.exec()
    
    sys.exit(app.exec_())

client.py

import sys,time
from PyQt5 import QtGui
from PyQt5 import QtCore
from PyQt5.QtWidgets import QScrollBar,QSplitter,QTableWidgetItem,QTableWidget,QComboBox,QVBoxLayout,QGridLayout,QDialog,QWidget, QPushButton, QApplication, QMainWindow,QAction,QMessageBox,QLabel,QTextEdit,QProgressBar,QLineEdit
from PyQt5.QtCore import QCoreApplication
import socket
from threading import Thread 
from socketserver import ThreadingMixIn 

tcpClientA=None

class Window(QDialog):
    def __init__(self):
        super().__init__()
        self.flag=0
        self.chatTextField=QLineEdit(self)
        self.chatTextField.resize(480,100)
        self.chatTextField.move(10,350)
        self.btnSend=QPushButton("Send",self)
        self.btnSend.resize(480,30)
        self.btnSendFont=self.btnSend.font()
        self.btnSendFont.setPointSize(15)
        self.btnSend.setFont(self.btnSendFont)
        self.btnSend.move(10,460)
        self.btnSend.setStyleSheet("background-color: #F7CE16")
        self.btnSend.clicked.connect(self.send)

        self.chatBody=QVBoxLayout(self)
        # self.chatBody.addWidget(self.chatTextField)
        # self.chatBody.addWidget(self.btnSend)
        # self.chatWidget.setLayout(self.chatBody)
        splitter=QSplitter(QtCore.Qt.Vertical)

        self.chat = QTextEdit()
        self.chat.setReadOnly(True)

        splitter.addWidget(self.chat)
        splitter.addWidget(self.chatTextField)
        splitter.setSizes([400,100])

        splitter2=QSplitter(QtCore.Qt.Vertical)
        splitter2.addWidget(splitter)
        splitter2.addWidget(self.btnSend)
        splitter2.setSizes([200,10])

        self.chatBody.addWidget(splitter2)


        self.setWindowTitle("Chat Application")
        self.resize(500, 500)

    def send(self):
        text=self.chatTextField.text()
        font=self.chat.font()
        font.setPointSize(13)
        self.chat.setFont(font)
        textFormatted='{:>80}'.format(text)
        self.chat.append(textFormatted)
        tcpClientA.send(text.encode())
        self.chatTextField.setText("")

class ClientThread(Thread):
    def __init__(self,window): 
        Thread.__init__(self) 
        self.window=window
 
    def run(self): 
       host = socket.gethostname() 
       port = 80
       BUFFER_SIZE = 2000 
       global tcpClientA
       tcpClientA = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
       tcpClientA.connect((host, port))
       
       while True:
           data = tcpClientA.recv(BUFFER_SIZE)
           window.chat.append(data.decode("utf-8"))
       tcpClientA.close() 


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Window()
    clientThread=ClientThread(window)
    clientThread.start()
    window.exec()
    sys.exit(app.exec_())

The Explanation

In server.py I have created a ServerThread class which inherits from Thread class from threading module of Python.
There I have overridden the ‘run’ function. Please have a look at the constructor of the class, I have passed a window object there, that’s necessary as you will see later. In the overridden run function I have created the TCP_IP and TCP_HOST and have bound the tcpServer with that. Then
I am listening to any client connection to my server. Have a look at the while loop there. There
you will see for each new client connection I have created one new ClientThread(will be discussed later) as creating a new thread for each client won’t block the GUI functionality of the Server. At the last I have joined the threads held in a list named threads.

In the ClientThread class I have again overridden the run function where each client waits for data received from the server and displays that in the chat body of the GUI. As I mentioned above a window class object was passed to the ServerThread. In return it passed that object to ClientThread which uses it to access the chat body element of the main GUI.

There you will see a line of code

window.chat.append(data.decode("utf-8"))

There you can see I have decoded the received data because what was passed was in format of bytes. We need to convert the bytes to String using UTF-8 encoding.

Remember, you need to create the ServerThread object first before you actually call the exec() function of the
QDialog. If you don’t it will block the threading and nothing will work.

Now that’s all for Server class. Let’s try to understand what’s going on client.py.

For the GUI it’s same as I mentioned and for threading, it has a ClientThread class which inherits from Thread class and overrides the run function. There it tries to connect to the server with server ip and port and then
tried to receive data from it. When it receives data from the server it simply shows that into it’s chat body of the GUI. There was no need of creating a extra threading class as connecting to server and receiving data from the server are blocking processes so we need to do both of them in separate threads so that our GUI works fine.

send function

In both Server and Client there is a function named send in the Window class. It is responsible for sending the chat text over the connection.It sets the text in chat body as well as sends it over the connection.There you will see a line

textFormatted='{:>80}'.format(text)

It does nothing except adding extra spaces before the text to make it right aligned.So that user can understand
which one he/she sent and which one he/she received.

That’s all guys. I have explained every part of both the file as much as possible. Still if you have any doubt you can ask me in the comment section and if you want to download the sources you can do so from my GitHub Repository.

4 thoughts on “Creating Graphical Chat Application Using PyQt and Socket

  1. Reply
    Nagy Dávid - January 30, 2018

    Dear Anuran,

    I have tried your code, but unfortunately it did not work. I copied everything into 2 py files. I ran the server.py first and then the client.py.
    But something was wrong with the connection. This line: print(“[+] New server socket thread started for ” + ip + “:” + str(port)) was never reached.
    I hope you can help me clarify things because this is a great program.
    Thank you.
    David NAGY

  2. Reply

    Un manga est une bande dessinée japonaise.

  3. Reply
    Helen - June 14, 2018

    Thank you!

Leave a Reply

Your email address will not be published. Required fields are marked *