commit rest of old client/server fixes

This commit is contained in:
Xevion
2021-01-08 18:52:25 -06:00
parent b792d9cb9c
commit ab1c14adb5
7 changed files with 224 additions and 32 deletions

View File

@@ -100,7 +100,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
self.client.send(helpers.prepare_json( self.client.send(helpers.prepare_json(
{ {
'type': constants.Types.MESSAGE, 'type': constants.Types.MESSAGE,
'content': message 'content': message.strip()
} }
)) ))

10
constants.py Normal file
View File

@@ -0,0 +1,10 @@
class Requests:
REQUEST_NICK = 'REQUEST_NICK'
REFRESH_CONNECTIONS_LIST = 'REFRESH_CLIENT_LIST'
GET_HISTORY = 'GET_HISTORY'
class Types:
REQUEST = 'REQUEST'
NICKNAME = 'NICKNAME'
USER_LIST = 'USER_LIST'
MESSAGE = 'MESSAGE'

View File

@@ -1,7 +1,19 @@
import json
HEADER_LENGTH = 10 HEADER_LENGTH = 10
def prepare(message: str, encoding='ascii') -> bytes: def prepare(message: str, encoding='utf-8') -> bytes:
"""Prepares a message for sending through a socket by adding a proper header and encoding it.""" """Prepares a message for sending through a socket by adding a proper header and encoding it."""
header = f'{len(message):<{HEADER_LENGTH}}' header = f'{len(message):<{HEADER_LENGTH}}'
return (header + message).encode(encoding) return (header + message).encode(encoding)
def prepare_json(object) -> bytes:
"""
Prepares a object for sending as encoded JSON with a header.
:param object: A JSON-encodable object
:return: Encoded JSON
"""
return prepare(json.dumps(object))

View File

@@ -0,0 +1,8 @@
from PyQt5.QtWidgets import QApplication
from client.gui import MainWindow
app = QApplication([])
app.setApplicationName("TCPChat Client")
m = MainWindow()
app.exec_()

View File

@@ -1,6 +1,7 @@
import socket import socket
import threading import threading
import constants
import helpers import helpers
from config import config from config import config
@@ -15,9 +16,9 @@ client.connect(('127.0.0.1', 55555))
def receive(): def receive():
while True: while True:
try: try:
length = int(client.recv(HEADER_LENGTH).decode('ascii')) length = int(client.recv(HEADER_LENGTH).decode('utf-8'))
message = client.recv(length).decode('ascii') message = client.recv(length).decode('utf-8')
if message == 'NICK': if message == constants.Requests.REQUEST_NICK:
client.send(helpers.prepare(nickname)) client.send(helpers.prepare(nickname))
else: else:
print(message) print(message)
@@ -30,7 +31,7 @@ def receive():
# Sending Messages To Server # Sending Messages To Server
def write(): def write():
while True: while True:
message = '{}: {}'.format(nickname, input('')) message = input('> ')
client.send(helpers.prepare(message)) client.send(helpers.prepare(message))

View File

@@ -1,6 +1,11 @@
import json
import socket import socket
import threading import threading
import time
import traceback
import uuid
import constants
import helpers import helpers
from config import config from config import config
@@ -15,35 +20,60 @@ server.bind((host, port))
server.listen() server.listen()
# Lists For Clients and Their Nicknames # Lists For Clients and Their Nicknames
clients = [] clients = {}
nicknames = []
# Sending Messages To All Connected Clients # Sending Messages To All Connected Clients
def broadcast(message): def broadcast(message):
print(f'Broadcasting: "{message}"') print(f'"{message}"')
encoded = helpers.prepare(message) encoded = helpers.prepare_json({
for client in clients: 'type': constants.Types.MESSAGE,
client.send(encoded) 'content': message
})
for client in clients.values():
client['client'].send(encoded)
# Handling Messages From Clients # Handling Messages From Clients
def handle(client): def handle(client_id):
client = clients[client_id]['client']
nickname = clients[client_id]['nickname']
while True: while True:
try: try:
# Broadcasting Messages # Broadcasting Messages
length = int(client.recv(HEADER_LENGTH).decode('ascii')) length = int(client.recv(HEADER_LENGTH).decode('utf-8'))
message = client.recv(length).decode('ascii') message = json.loads(client.recv(length).decode('utf-8'))
broadcast(message)
if message['type'] == constants.Types.REQUEST:
if message['request'] == constants.Requests.REFRESH_CONNECTIONS_LIST:
client.send(helpers.prepare_json(
{
'type': constants.Types.USER_LIST,
'users': [
clients[other]['nickname'] for other in clients.keys() if other != client_id
]
}
))
elif message['type'] == constants.Types.NICKNAME:
nickname = message['nickname']
if not clients[client_id]['has_nickname']:
print("Nickname is {}".format(nickname))
broadcast("{} joined!".format(nickname))
clients[client_id]['has_nickname'] = True
else:
print(f'{clients[client_id]["nickname"]} changed their name to {nickname}')
clients[client_id]['nickname'] = nickname
elif message['type'] == constants.Types.MESSAGE:
broadcast(f'<{nickname}>: {message["content"]}')
except: except:
# Removing And Closing Clients # Removing And Closing Clients
index = clients.index(client)
clients.remove(client)
client.close() client.close()
del clients[client_id]
nickname = nicknames[index]
broadcast('{} left!'.format(nickname)) broadcast('{} left!'.format(nickname))
nicknames.remove(nickname)
break break
@@ -52,22 +82,33 @@ def receive():
while True: while True:
# Accept Connection # Accept Connection
client, address = server.accept() client, address = server.accept()
print("Connected with {}".format(str(address))) print("New Client from {}".format(str(address)))
# Request And Store Nickname # Request And Store Nickname
client.send(helpers.prepare('NICK')) client_id = str(uuid.uuid4())
length = int(client.recv(HEADER_LENGTH).decode('ascii')) client.send(helpers.prepare_json(
nickname = client.recv(length).decode('ascii') {
nicknames.append(nickname) 'type': constants.Types.REQUEST,
clients.append(client) 'request': constants.Requests.REQUEST_NICK
}
))
# Print And Broadcast Nickname clients[client_id] = {
print("Nickname is {}".format(nickname)) 'client': client,
broadcast("{} joined!".format(nickname)) 'nickname': client_id[:10],
client.send(helpers.prepare('Connected to server!')) 'first_seen': int(time.time()),
'has_nickname': False
}
client.send(helpers.prepare_json(
{
'type': constants.Types.MESSAGE,
'content': 'Connected to server!'
}
))
# Start Handling Thread For Client # Start Handling Thread For Client
thread = threading.Thread(target=handle, args=(client,)) thread = threading.Thread(target=handle, args=(client_id,))
thread.start() thread.start()

120
server/MainWindow.ui Normal file
View File

@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>651</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="2">
<widget class="QLabel" name="label">
<property name="text">
<string>Connections</string>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QTextEdit" name="messageBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>80</height>
</size>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QTextBrowser" name="messageHistory">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>5</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QListWidget" name="connectionsList">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Chat History</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionConnect_to"/>
<addaction name="actionSave_chat_to"/>
<addaction name="actionQuit"/>
</widget>
<addaction name="menuFile"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<action name="actionConnect_to">
<property name="text">
<string>Connect to...</string>
</property>
</action>
<action name="actionSave_chat_to">
<property name="text">
<string>Export chat...</string>
</property>
</action>
<action name="actionQuit">
<property name="text">
<string>Quit</string>
</property>
</action>
</widget>
<resources/>
<connections/>
</ui>