#!
env python
"""Chat client for CST311 Programming Assignment 4"""
__author__ = "Bitwise"
__credits__ = [
"Chris McMichael",
"Jake Kroeker",
"Jeremiah McGrath",
"Miguel Jimenez"
]
# Import statements
import PA4_Server_Team1 as server
import select
import socket as s
import ssl
import sys
import threading as t
import time
# Configure logging
import logging
logging.basicConfig()
log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG)
# Constants
SERVER_NAME = server.SERVER_NAME
SERVER_PORT = server.SERVER_PORT
RETRY_COUNT = 5
RETRY_WAIT = 1
BYE_COMMAND = server.BYE_COMMAND
# Global variables
running = True
# Handles receiving server responses
def receive_handler(socket):
global running
while running:
# Read response from server
server_response = socket.recv(1024).decode()
# Parse "bye" command
if (index := server_response.find(" : ")) != -1:
message = server_response[index + 3:]
if message.lower() == BYE_COMMAND:
running = False
# Prevents invalid output on chat termination
if server_response != "":
# Print output from server
print(server_response)
# Handles sending messages to the server
def send_handler(socket):
global running
while running:
# [IMPORTANT] select.select() is UNIX ONLY, this script doesn't
work on Windows!
# Tests whether the user has typed a line and pressed enter
# Blocks for 0.1 seconds if a line isn't ready; the timeout allows
the thread to react to running = False
if select.select([sys.stdin], [], [], 0.1)[0]:
# Send data across socket to server
# Note: encode() converts the string to UTF-8 for
transmission
user_input = input()
socket.send(user_input.encode())
if user_input.lower() == BYE_COMMAND:
running = False
def main():
# Create socket
ssl_context = ssl.create_default_context()
client_socket = s.socket(s.AF_INET, s.SOCK_STREAM)
secure_socket = ssl_context.wrap_socket(client_socket,
server_hostname=SERVER_NAME)
retry_counter = 0
while True:
try:
# Establish TLS connection
secure_socket.connect((SERVER_NAME, SERVER_PORT))
break
except Exception as e:
if isinstance(e, ConnectionRefusedError) and retry_counter <
RETRY_COUNT:
retry_counter += 1
# Uncomment this line if you want to log connection retry
attempts
# log.warning(f"Retrying connection; attempt
{retry_counter}/{RETRY_COUNT} ...")
time.sleep(RETRY_WAIT)
else:
log.exception(e)
log.error("***Advice:***")
if isinstance(e, s.gaierror):
log.error("\tCheck that server_addr and server_port
are set correctly.")
elif isinstance(e, ConnectionRefusedError):
log.error("\tCheck that server is running and the
address is correct")
else:
log.error("\tNo specific advice, please contact
teaching staff and include text of error and code.")
exit(8)
# Wrap in a try-finally to ensure the socket is properly closed
regardless of errors
try:
# Create receive and send threads
receive_thread: t.Thread = t.Thread(target=receive_handler,
args=(secure_socket,))
send_thread: t.Thread = t.Thread(target=send_handler,
args=(secure_socket,))
# Start receive and send threads
receive_thread.start()
send_thread.start()
# Wait for receive and send threads
receive_thread.join()
send_thread.join()
finally:
# Close socket prior to exit
secure_socket.close()
# This helps shield code from running when we import the module
if __name__ == "__main__":
main()