Building a Simple Chat Client and Server with Python and Tkinter GUI

Suraj Singh Bisht
8 min readOct 1, 2023

--

My Fedora Screen

Hello, everyone! I’m Suraj, today I’m excited to share a tutorial on building a very simple chat client and server application using Python and Tkinter GUI. I wrote this project during my college time just to polish my python skill. if you are currently in the early stages of your programming journey and are seeking an engaging project, I encourage you to consider this one. This project allows you to exchange text messages between a client and a server in a user-friendly graphical interface.

File Tree Structure

Before diving into the code, let’s take a look at the project’s file tree structure to understand how the components are organized:

├── main.py
└── src
├── ask_ip.py
├── ask_mode.py
├── client_mode.py
├── __init__.py
└── server_mode.py

2 directories, 6 files

The project consists of a main.py script that serves as the entry point, and several modules inside the src directory, including ask_ip.py, ask_mode.py, client_mode.py, and server_mode.py. These modules help in handling different aspects of the chat client and server application.

Here’s a brief overview of each component of the code:

Server Mode (server_mode.py):

  • This script represents the server side of the chat application.
  • It creates a GUI window using Tkinter for the server to interact with.
  • The server’s IP address and default port (5000) are displayed.
  • It initializes a SOCKETS object, which manages the server's socket connections.
  • The server binds to the specified port, listens for incoming connections, and spawns a new thread to handle each connected client.
  • Messages received from clients are displayed in the chat history panel.

Client Mode (client_mode.py):

  • This script represents the client side of the chat application.
  • It creates a GUI window using Tkinter where clients can enter the server’s IP address and port number.
  • It also has input fields for sending text messages and displaying chat history.
  • The client initializes a SOCKETS object to manage socket connections.
  • It connects to the server using the provided IP and port.
  • Messages sent by the client are displayed in blue, and messages received from the server or other clients are displayed in green.

SOCKETS Class:

  • This class encapsulates the socket-related functionality for both the client and server.
  • It manages socket creation, binding (server), connecting (client), sending, and receiving messages.
  • Messages are exchanged in UTF-8 encoding.
  • Received messages are displayed in the chat history panel.

Script: main.py

The main.py script is the entry point of our application. It imports modules for asking the user for the operation mode (ask_mode), client functionality (client_mode), and server functionality (server_mode). Depending on the user's choice, it launches either the client or server interface.

#!/usr/bin/python3
# Author:
# Suraj Singh Bisht
#

from src import ask_mode as ask, client_mode as client, server_mode as server

if __name__ == '__main__':
tmp_obj = ask.ask_ip_dialog()
if tmp_obj == 0:
client.ClientDialogBox(
className='Chatting [Client Window]').mainloop()
else:
server.ServerDialogBox(className='Chatting [Server Window]').mainloop()

Script: src/ask_ip.py

The ask_ip.py module provides a dialog box for the user to enter the IP address and port number. It uses Tkinter for the GUI elements. This dialog is crucial as it allows the user to specify the server's IP address and port to connect to.

#!/usr/bin/python3
# Author:
# Suraj Singh Bisht
#

import tkinter as Tkinter
import tkinter.ttk as ttk

PORT_ = "5000"
text = '''
Server IP Address :
Enter IP Address Of the server you want to connect.
Server PORT Number :
Enter the PORT number to use:
5000 is default port. if you want to change, check configurations settings.
'''


def ask_ip_dialog(var, var1):
mainroot = Tkinter.Toplevel()
mainroot.title("Enter Ip Address")
mainroot.resizable(0, 0)
mainroot.focus_force()
mainroot.transient()
root = ttk.Frame(mainroot)
root.pack(padx=10, pady=10)
var1.set(PORT_)
ttk.Label(root, text='Server IP Address : ',
width=25).grid(row=1, column=1)
ttk.Label(root, text='Server PORT Number : ',
width=25).grid(row=2, column=1)
k = ttk.Entry(root, textvariable=var, width=25)
k.grid(row=1, column=2)
k.focus_force()
Tkinter.Entry(root, text=var1, state='disabled',
width=25).grid(row=2, column=2)
Label = Tkinter.Text(root, width=70, height=4, font=('arial 8 italic'))
Label.insert('1.0', text, 'end')
Label.grid(row=3, column=1, columnspan=2, rowspan=4)
Label.config(state='disabled')
ttk.Button(root, text="Next", command=lambda: mainroot.destroy(),
width=25).grid(row=8, column=2)
mainroot.mainloop()


if __name__ == '__main__':
ask_ip_dialog()

Script: src/ask_mode.py

The ask_mode.py module provides a dialog box for the user to choose between client and server modes. It sets the program's mode (0 for client, 1 for server) based on the user's choice.

#!/usr/bin/python3
# Author:
# Suraj Singh Bisht
#
import tkinter as Tkinter
import tkinter.ttk as ttk

PROGRAM_NAME = "Choose Program Mode"
text = """
Client Mode [Default]: Program will act like a chat client.
Server Mode: Program will wait for the client connection.
"""


def ask_ip_dialog():
root = Tkinter.Tk(className=PROGRAM_NAME)
mode = Tkinter.IntVar()
mode.set(3)
# 0 for Client Mode
# 1 For Server Mode

def out():
root.destroy()
return

def mode_set(value):
mode.set(value)
out()
return

frame = ttk.LabelFrame(root, text="Choose Your Option")
frame.pack(side='top', padx=10, pady=10, ipady=10, ipadx=10)
ttk.Button(frame, text='Client Mode', command=lambda: mode_set(0)).grid(
row=1, column=1, padx=10, pady=10)
ttk.Button(frame, text='Server Mode', command=lambda: mode_set(1)).grid(
row=1, column=2, padx=10, pady=10)
ttk.Button(frame, text="Exit ", command=out).grid(row=1, column=3)
# Description About Modes
Label = Tkinter.Text(frame, width=60, height=4, font=('arial 8 italic'))
Label.insert('1.0', text, 'end')
Label.grid(row=3, column=1, columnspan=4, rowspan=5, padx=10, pady=10)
Label.config(state='disabled')
root.mainloop()
return mode.get()


# Trigger For Script
if __name__ == '__main__':
print(ask_ip_dialog())

Script: src/client_mode.py

The client_mode.py module handles the client-side functionality of the chat application. It creates a GUI for the client, allowing them to enter an IP address and port, send messages, and view the chat history. The SOCKETS class manages the socket connections for the client.

#!/usr/bin/python3
# Author:
# Suraj Singh Bisht
#
import tkinter as Tkinter
import tkinter.ttk as ttk
import src.ask_ip as ask_ip
import socket
import threading

IP_Address = socket.gethostbyname(socket.gethostname())
PORT_ = "5000"


class SOCKETS:
def __init__(self):
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("[+] Socket Is Now Created")

def load(self, ip_address, port, text, status, server_info):
self.ip_address = ip_address
self.port = port
self.history = text
self.status = status
self.server_info = server_info
print("[=] LOading Attributes Is Completed")
return

def bind(self):
print("[=] Trying To Binds")
while True:
try:
self.s.connect((self.ip_address.get(), self.port.get()))
print("[+] Connection Server Found")
self.server_info.config(text="{}:{}".format(
self.ip_address.get(), self.port.get()))
self.status.config(text="Connected", bg='lightgreen')
threading.Thread(target=self.recv).start()
break

except:
pass

def recv(self):
while True:
try:
data = self.s.recv(1024)
if data:
data = data.decode('utf-8')
data = 'Other : '+data+'\n'
start = self.history.index('end')+"-1l"
self.history.insert("end", data)
end = self.history.index('end')+"-1l"
self.history.tag_add("SENDBYOTHER", start, end)
self.history.tag_config("SENDBYOTHER", foreground='green')
except Exception as e:
print(e, 'recv')

def send(self, text:str):
try:
self.s.sendall(text.encode('utf-8'))
except:
print("[=] Not Connected")
pass


class ClientDialogBox(Tkinter.Tk):
def __init__(self, *args, **kwargs):
Tkinter.Tk.__init__(self, *args, **kwargs)
self.resizable(0, 0)
self.ip_address = Tkinter.StringVar()
self.ip_address.trace_variable("w", self.update_status_info)
self.port = Tkinter.IntVar()
self.create_additional_widgets()

def socket_connections_start(self):
if len(self.ip_address.get().split('.')) == 4:
print("Thread Started")
threading.Thread(target=self.socket_connections).start()

def socket_connections(self):
print("[+] creating")
self.s = SOCKETS()
print("[=] Loading Attributes")
self.s.load(self.ip_address, self.port, self.history,
self.status, self.server_info)
print("[=] Bindings")
self.s.bind()

def update_status(self, Connection='Connected', color='lightgreen'):
self.status.config(text=Connection, bg=color)
return

def update_status_info(self, *args, **kwargs):
data = "{}:{}".format(self.ip_address.get(), self.port.get())
self.server_info.config(text=data)
return

def create_additional_widgets(self):
self.create_panel_for_widget()
self.create_panel_for_connections_info()
self.create_panel_for_chat_history()
self.create_panel_for_sending_text()
self.ask_ip_address()

def ask_ip_address(self):
ask_ip.ask_ip_dialog(self.ip_address, self.port)
return

def send_text_message(self):
if self.status.cget('text') == 'Connected':
input_data = self.Sending_data.get('1.0', 'end')
if len(input_data) != 1:
self.s.send(input_data)
input_data = 'me: '+input_data
start = self.history.index('end')+"-1l"
self.history.insert("end", input_data)
end = self.history.index('end')+"-1l"
self.history.tag_add("SENDBYME", start, end)
self.Sending_data.delete('1.0', 'end')
self.history.tag_config("SENDBYME", foreground='Blue')

pass
else:
print("[=] Input Not Provided")

else:
print("[+] Not Connected")

def create_panel_for_sending_text(self):
# Here Creating Sending Panel
self.Sending_data = Tkinter.Text(
self.Sending_panel, font=('arial 12 italic'), width=35, height=5)
self.Sending_data.pack(side='left')
self.Sending_Trigger = Tkinter.Button(self.Sending_panel, text='Send', width=15,
height=5, bg='orange', command=self.send_text_message, activebackground='lightgreen')
self.Sending_Trigger.pack(side='left')
return

def create_panel_for_chat_history(self):
# Here Creating Chat History
self.history = Tkinter.Text(self.history_frame, font=(
'arial 12 bold italic'), width=50, height=15)
self.history.pack()
return

def create_panel_for_widget(self):
# First For Connection Information
self.Connection_info = Tkinter.LabelFrame(
self, text='Connection Informations', fg='green', bg='powderblue')
self.Connection_info.pack(side='top', expand='yes', fill='both')
# Creating Second For Chatting History
self.history_frame = Tkinter.LabelFrame(
self, text='Chatting ', fg='green', bg='powderblue')
self.history_frame.pack(side='top')
# Creating Third For Sending Text Message
self.Sending_panel = Tkinter.LabelFrame(
self, text='Send Text', fg='green', bg='powderblue')
self.Sending_panel.pack(side='top')
return

def create_panel_for_connections_info(self):
self.frame = ttk.Frame(self.Connection_info)
self.frame.pack(side='top', padx=10, pady=10)
# Main Information Panel
ttk.Label(self.frame, text='Your Entered Address : ', relief="groove",
anchor='center', width=25).grid(row=1, column=1, ipadx=10, ipady=5)
ttk.Label(self.frame, textvariable=self.ip_address, relief='sunken',
anchor='center', width=25).grid(row=1, column=2, ipadx=10, ipady=5)
ttk.Label(self.frame, text='Your Entered Port Number : ', relief="groove",
anchor='center', width=25).grid(row=2, column=1, ipadx=10, ipady=5)
ttk.Label(self.frame, textvariable=self.port, relief="sunken",
anchor="center", width=25).grid(row=2, column=2, ipadx=10, ipady=5)
ttk.Label(self.frame, text='Status : ', relief="groove",
anchor="center", width=25).grid(row=3, column=1, ipadx=10, ipady=5)
ttk.Label(self.frame, text='Connected with : ', relief='groove',
anchor='center', width=25).grid(row=4, column=1, ipadx=10, ipady=5)
self.status = Tkinter.Button(self.frame, text="Not Connected",
anchor='center', width=25, bg="red", command=self.socket_connections)
self.status.grid(row=3, column=2, ipadx=10, ipady=5)
self.server_info = Tkinter.Label(self.frame, text="{}:{}".format(
self.ip_address.get(), self.port.get()), relief='sunken', anchor='center', width=25)
self.server_info.grid(row=4, column=2, ipadx=10, ipady=5)
return


if __name__ == '__main__':
ClientDialogBox(className='Python Chatting [Client Mode]').mainloop()

Script: src/server_mode.py

The server_mode.py module handles the server-side functionality of the chat application. It creates a GUI for the server, allowing it to specify the port, receive messages from clients, and view the chat history. The SOCKETS class manages the server's socket connections.

#!/usr/bin/python3
# Author:
# Suraj Singh Bisht
#
import tkinter as Tkinter
import tkinter.ttk as ttk
import socket
import threading


IP_Address = socket.gethostbyname(socket.gethostname())
PORT_ = "5000"

# ========== Socket Programming ================


class SOCKETS:
def __init__(self):
self.s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

def load(self, ip_address, port, text, status, client_info):
self.ip_address = ip_address
self.port = port
self.history = text
self.status = status
self.client_info = client_info
return

def bind(self):
while True:
try:
self.s.bind(('', self.port.get()))
break
except:
pass
self.s.listen(1)
self.conn, addr = self.s.accept()
ip, port = addr
self.status.config(text="Connected", bg='lightgreen')
self.client_info.config(text="{}:{}".format(ip, self.port.get()))
threading.Thread(target=self.recv).start()

def send(self, text:str):
try:
self.conn.sendall(text.encode('utf-8'))
except Exception as e:
print("[=] Server Not Connected Yet ", e)
pass
return

def recv(self):
print(" [+] recv start")
while True:
try:
data = self.conn.recv(1024)
if data:
data = data.decode('utf-8')
data = 'Other : '+data+'\n'
start = self.history.index('end')+"-1l"
self.history.insert("end", data)
end = self.history.index('end')+"-1l"
self.history.tag_add("SENDBYOTHER", start, end)
self.history.tag_config("SENDBYOTHER", foreground='green')
except Exception as e:
print(e, "[=] Closing Connection [recv]")
self.conn.close()
break

def close(self):
pass
# =============================================


class ServerDialogBox(Tkinter.Tk):
def __init__(self, *args, **kwargs):
Tkinter.Tk.__init__(self, *args, **kwargs)
self.ip_address = Tkinter.StringVar()
self.port = Tkinter.IntVar()
self.port.set(PORT_)
self.create_additional_panel()
threading.Thread(target=self.socket_connections).start()

def socket_connections(self):
self.s = SOCKETS()
self.s.load(self.ip_address, self.port, self.history,
self.status, self.client_info)
self.s.bind()

def create_additional_panel(self):
self.create_panel_for_widget()
self.create_panel_for_connections_info()
self.create_panel_for_chat_history()
self.create_panel_for_sending_text()
return

def send_text_message(self):
if self.status.cget('text') == 'Connected':
print(self.status.cget('text'))
input_data = self.Sending_data.get('1.0', 'end')
if len(input_data) != 1:
input_data_ = 'me: '+input_data+'\n'
start = self.history.index('end')+"-1l"
self.history.insert("end", input_data_)
end = self.history.index('end')+"-1l"
self.history.tag_add("SENDBYME", start, end)
self.Sending_data.delete('1.0', 'end')
self.s.send(input_data)
self.history.tag_config("SENDBYME", foreground='Blue')

pass
else:
print("[=] Input Not Provided")

else:
print("[+] Not Connected")

def create_panel_for_sending_text(self):
# Here Creating Sending Panel
self.Sending_data = Tkinter.Text(
self.Sending_panel, font=('arial 12 italic'), width=35, height=5)
self.Sending_data.pack(side='left')
self.Sending_Trigger = Tkinter.Button(self.Sending_panel, text='Send', width=15,
height=5, bg='orange', command=self.send_text_message, activebackground='lightgreen')
self.Sending_Trigger.pack(side='left')
return

def create_panel_for_chat_history(self):
# Here Creating Chat History
self.history = Tkinter.Text(self.history_frame, font=(
'arial 12 bold italic'), width=50, height=15)
self.history.pack()
return

def create_panel_for_widget(self):
# First For Connection Information
self.Connection_info = Tkinter.LabelFrame(
self, text='Connection Informations', fg='green', bg='powderblue')
self.Connection_info.pack(side='top', expand='yes', fill='both')
# Creating Second For Chatting History
self.history_frame = Tkinter.LabelFrame(
self, text='Chatting ', fg='green', bg='powderblue')
self.history_frame.pack(side='top')
# Creating Third For Sending Text Message
self.Sending_panel = Tkinter.LabelFrame(
self, text='Send Text', fg='green', bg='powderblue')
self.Sending_panel.pack(side='top')
return

def create_panel_for_connections_info(self):
self.frame = ttk.Frame(self.Connection_info)
self.frame.pack(side='top', padx=10, pady=10)
# Creating Main Information Panel
ttk.Label(self.frame, text='Your IP Address : ', relief="groove",
anchor='center', width=25).grid(row=1, column=1, ipadx=10, ipady=5)
ttk.Label(self.frame, text=IP_Address, relief='sunken',
anchor='center', width=25).grid(row=1, column=2, ipadx=10, ipady=5)
ttk.Label(self.frame, text='Using Port Number : ', relief="groove",
anchor='center', width=25).grid(row=2, column=1, ipadx=10, ipady=5)
ttk.Label(self.frame, text=PORT_, relief="sunken", anchor="center",
width=25).grid(row=2, column=2, ipadx=10, ipady=5)
ttk.Label(self.frame, text='Status : ', relief="groove",
anchor="center", width=25).grid(row=3, column=1, ipadx=10, ipady=5)
ttk.Label(self.frame, text='Connected with : ', relief='groove',
anchor='center', width=25).grid(row=4, column=1, ipadx=10, ipady=5)
self.status = Tkinter.Label(
self.frame, text="Not Connected", relief="sunken", anchor='center', width=25, bg="red")
self.status.grid(row=3, column=2, ipadx=10, ipady=5)
self.client_info = Tkinter.Label(
self.frame, text="192.168.00.12:5000", relief='sunken', anchor='center', width=25)
self.client_info.grid(row=4, column=2, ipadx=10, ipady=5)
return


if __name__ == '__main__':
ServerDialogBox(className='Python Chatting [Server Mode]').mainloop()

Want to download : https://drive.google.com/file/d/1Dffy9UOvWsuJc5H2-NFAf0drYPj8j987/view?usp=sharing

Happy Coding!

--

--

Responses (1)