mirror of
https://github.com/Xevion/tcp-chat.git
synced 2025-12-06 11:16:39 -06:00
reorganizing oserver.py into proper folder, creating client class for organization and clarity
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
HEADER_LENGTH = 10
|
||||||
|
|
||||||
class Requests:
|
class Requests:
|
||||||
REQUEST_NICK = 'REQUEST_NICK'
|
REQUEST_NICK = 'REQUEST_NICK'
|
||||||
REFRESH_CONNECTIONS_LIST = 'REFRESH_CLIENT_LIST'
|
REFRESH_CONNECTIONS_LIST = 'REFRESH_CLIENT_LIST'
|
||||||
|
|||||||
@@ -32,3 +32,8 @@ def prepare_server_message(nickname: str, message: str, color: str, msgtime: int
|
|||||||
'time': msgtime or int(time.time())
|
'time': msgtime or int(time.time())
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_request(request: str) -> bytes:
|
||||||
|
"""Helper function for creating a request message."""
|
||||||
|
return prepare_json({'type': constants.Types.REQUEST, 'request': request})
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
<?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>
|
|
||||||
120
server/handler.py
Normal file
120
server/handler.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import random
|
||||||
|
import socket
|
||||||
|
import time
|
||||||
|
import traceback
|
||||||
|
import uuid
|
||||||
|
from typing import Any, List
|
||||||
|
|
||||||
|
import constants
|
||||||
|
import helpers
|
||||||
|
|
||||||
|
logger = logging.getLogger('handler')
|
||||||
|
|
||||||
|
|
||||||
|
class Client:
|
||||||
|
def __init__(self, conn: socket.socket, address: Any, all_clients: List['Client']):
|
||||||
|
self.conn, self.address = conn, address
|
||||||
|
self.all_clients = all_clients
|
||||||
|
|
||||||
|
self.id = str(uuid.uuid4())
|
||||||
|
self.nickname = self.id[:8]
|
||||||
|
self.color = random.choice(constants.Colors.ALL)
|
||||||
|
|
||||||
|
self.first_seen = time.time()
|
||||||
|
self.last_nickname_change = None
|
||||||
|
self.last_message_sent = None
|
||||||
|
|
||||||
|
def request_nickname(self) -> None:
|
||||||
|
"""Send a request for the client's nickname information."""
|
||||||
|
self.conn.send(helpers.prepare_request(constants.Requests.REQUEST_NICK))
|
||||||
|
|
||||||
|
def receive_message(self) -> Any:
|
||||||
|
length = int(self.conn.recv(constants.HEADER_LENGTH).decode('utf-8'))
|
||||||
|
logger.debug(f'Header received - Length {length}')
|
||||||
|
message = json.loads(self.conn.recv(length).decode('utf-8'))
|
||||||
|
logger.info(f'Data received/parsed, type: {message["type"]}')
|
||||||
|
return message
|
||||||
|
|
||||||
|
def send(self, message: bytes) -> None:
|
||||||
|
"""Sends a pre-encoded message to this client."""
|
||||||
|
self.conn.send(message)
|
||||||
|
|
||||||
|
def send_message(self, message: str) -> None:
|
||||||
|
"""Sends a string message as the server to this client."""
|
||||||
|
self.conn.send(helpers.prepare_server_message(
|
||||||
|
nickname='Server', message=message, color=constants.Colors.BLACK
|
||||||
|
))
|
||||||
|
|
||||||
|
def broadcast_message(self, message: str) -> None:
|
||||||
|
"""Sends a string message to all connected clients as the Server."""
|
||||||
|
prepared = helpers.prepare_server_message(
|
||||||
|
nickname='Server', message=message, color=constants.Colors.BLACK
|
||||||
|
)
|
||||||
|
for client in self.all_clients:
|
||||||
|
client.send(prepared)
|
||||||
|
|
||||||
|
def broadcast(self, message: bytes) -> None:
|
||||||
|
"""Sends a pre-encoded message to all connected clients"""
|
||||||
|
for client in self.all_clients:
|
||||||
|
client.send(message)
|
||||||
|
|
||||||
|
def handle(self) -> None:
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data = self.receive_message()
|
||||||
|
|
||||||
|
if data['type'] == constants.Types.REQUEST:
|
||||||
|
if data['request'] == constants.Requests.REFRESH_CONNECTIONS_LIST:
|
||||||
|
self.conn.send(helpers.prepare_json(
|
||||||
|
{
|
||||||
|
'type': constants.Types.USER_LIST,
|
||||||
|
'users': [
|
||||||
|
{
|
||||||
|
'nickname': other.nickname,
|
||||||
|
'color': other.color
|
||||||
|
} for other in self.all_clients
|
||||||
|
]
|
||||||
|
}
|
||||||
|
))
|
||||||
|
elif data['type'] == constants.Types.NICKNAME:
|
||||||
|
nickname = data['nickname']
|
||||||
|
if self.last_nickname_change is None:
|
||||||
|
logger.info("Nickname is {}".format(nickname))
|
||||||
|
self.broadcast(helpers.prepare_server_message(
|
||||||
|
nickname='Server',
|
||||||
|
message=f'{nickname} joined!',
|
||||||
|
color=constants.Colors.BLACK
|
||||||
|
))
|
||||||
|
self.last_nickname_change = time.time()
|
||||||
|
else:
|
||||||
|
logger.info(f'{self.nickname} changed their name to {nickname}')
|
||||||
|
self.nickname = nickname
|
||||||
|
elif data['type'] == constants.Types.MESSAGE:
|
||||||
|
self.broadcast(helpers.prepare_server_message(
|
||||||
|
nickname=self.nickname,
|
||||||
|
message=data['content'],
|
||||||
|
color=self.color
|
||||||
|
))
|
||||||
|
|
||||||
|
# Basic command processing
|
||||||
|
if data['content'] == '/reroll':
|
||||||
|
color = random.choice(constants.Colors.ALL)
|
||||||
|
colorName = constants.Colors.ALL_NAMES[constants.Colors.ALL.index(color)]
|
||||||
|
self.color = color
|
||||||
|
self.broadcast(helpers.prepare_server_message(
|
||||||
|
nickname='Server',
|
||||||
|
message=f'Changed your color to {colorName} ({color})',
|
||||||
|
color=constants.Colors.BLACK
|
||||||
|
))
|
||||||
|
except:
|
||||||
|
traceback.print_exc()
|
||||||
|
logger.info(f'Client {self.id} closed. ({self.nickname})')
|
||||||
|
self.conn.close()
|
||||||
|
self.broadcast(helpers.prepare_server_message(
|
||||||
|
nickname='Server',
|
||||||
|
message=f'{self.nickname} left!',
|
||||||
|
color=constants.Colors.BLACK
|
||||||
|
))
|
||||||
|
break
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import logging
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
|
||||||
|
from server import handler
|
||||||
|
|
||||||
|
host = '127.0.0.1'
|
||||||
|
port = 55555
|
||||||
|
|
||||||
|
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
server.bind((host, port))
|
||||||
|
server.listen()
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.DEBUG,
|
||||||
|
format='[%(asctime)s] [%(levelname)s] [%(threadName)s] %(message)s')
|
||||||
|
logger = logging.getLogger('server')
|
||||||
|
|
||||||
|
clients = []
|
||||||
|
|
||||||
|
# Receiving / Listening Function
|
||||||
|
def receive():
|
||||||
|
while True:
|
||||||
|
# Accept Connection
|
||||||
|
conn, address = server.accept()
|
||||||
|
logger.info(f"New connection from {address}")
|
||||||
|
|
||||||
|
client = handler.Client(conn, address, clients)
|
||||||
|
client.request_nickname()
|
||||||
|
|
||||||
|
# Start Handling Thread For Client
|
||||||
|
thread = threading.Thread(target=client.handle, name=client.id[:8])
|
||||||
|
thread.start()
|
||||||
|
|||||||
Reference in New Issue
Block a user