commit 1f6b38c89478ca177bd287540de8b5710910ee57 Author: Yui Date: Sat May 2 20:54:01 2026 -0300 First commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..afed073 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.csv diff --git a/client/SocketClient.py b/client/SocketClient.py new file mode 100644 index 0000000..0af8236 --- /dev/null +++ b/client/SocketClient.py @@ -0,0 +1,29 @@ +import socket as s +from logging import Logger +from abc import ABC, abstractmethod + + +class SocketClient(ABC): + ip: str = "" + port: int = 0 + socket: s.socket + logger: Logger + + def __init__(self, ip: str, port: int, logger: Logger): + if port <= 0 or port > 65535: + raise ValueError("Port out of range.") + self.ip = ip + self.port = port + self.logger = logger + + @abstractmethod + def Connect(self) -> bool: + pass + + @abstractmethod + def Reconnect(self) -> bool: + pass + + @abstractmethod + def Send(self, data: str): + pass diff --git a/client/TCPClient.py b/client/TCPClient.py new file mode 100644 index 0000000..c892261 --- /dev/null +++ b/client/TCPClient.py @@ -0,0 +1,42 @@ +import socket as s +from SocketClient import SocketClient +from select import select + + +class TCPClient(SocketClient): + def Connect(self) -> bool: + self.socket = s.socket(s.AF_INET, s.SOCK_STREAM) + try: + self.socket.connect((self.ip, self.port)) + except ConnectionRefusedError: + self.logger.fatal("TCP_FATAL_CONNECT %s:%d" % (self.ip, self.port)) + return False + + self.socket.setblocking(False) + return True + + def Reconnect(self) -> bool: + self.socket.close() + return self.Connect() + + def Send(self, data: str): + try: + self.socket.sendall(data.encode()) + self.logger.info("TCP_SENT %s" % data) + self.Receive() + except Exception as ex: + self.logger.fatal("TCP_FATAL_SEND %s" % ex) + + def Receive(self): + (incoming, _, _) = select([self.socket], [], [], 0) + if incoming: + try: + data = self.socket.recv(1024) + if not data: + self.socket.close() + self.logger.error("TCP_NO_DATA") + return + self.logger.info("TCP_SUCCESS %s" % data.decode()) + except InterruptedError as ex: + self.logger.error("TCP_FAIL %s" % ex) + self.Reconnect() diff --git a/client/UDPClient.py b/client/UDPClient.py new file mode 100644 index 0000000..ffdb1d2 --- /dev/null +++ b/client/UDPClient.py @@ -0,0 +1,53 @@ +from SocketClient import SocketClient +import time +import socket +import select + + +class UDPClient(SocketClient): + _timestamps: set[float] = set() + _last_check = time.time() + + def Connect(self) -> bool: + self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + self.socket.setblocking(False) + return True + + def Send(self, data: str): + now = time.time() + data = "%s/%f" % (data, now) + self.socket.sendto(data.encode(), (self.ip, self.port)) + self.logger.info("UDP_SENT %s" % data) + self._timestamps.add(now) + print(self._timestamps) + self.Receive() + self.staleCheck() + + def Reconnect(self) -> bool: + return True + + def Receive(self): + (incoming, _, _) = select.select([self.socket], [], [], 0) + if incoming: + data, addr = self.socket.recvfrom(1024) + timestamp = data.split(b"/")[1] + print(timestamp.decode()) + self.logger.info("UDP_SUCCESS %s (%s)" % (data.decode(), addr)) + self._timestamps.discard(float(timestamp.decode())) + print(self._timestamps) + + def staleCheck(self): + if (time.time() - self._last_check) < 1: + return + + timedout_timestamps = set() + for timestamp in self._timestamps: + if (self._last_check - timestamp) < 10: + continue + self.logger.error("UDP_FAIL %f" % timestamp) + print(self._timestamps) + timedout_timestamps.add(timestamp) + + self._last_check = time.time() + for timestamp in timedout_timestamps: + self._timestamps.discard(timestamp) diff --git a/client/__pycache__/SocketClient.cpython-314.pyc b/client/__pycache__/SocketClient.cpython-314.pyc new file mode 100644 index 0000000..650f42e Binary files /dev/null and b/client/__pycache__/SocketClient.cpython-314.pyc differ diff --git a/client/__pycache__/TCPClient.cpython-314.pyc b/client/__pycache__/TCPClient.cpython-314.pyc new file mode 100644 index 0000000..7bf2358 Binary files /dev/null and b/client/__pycache__/TCPClient.cpython-314.pyc differ diff --git a/client/__pycache__/UDPClient.cpython-314.pyc b/client/__pycache__/UDPClient.cpython-314.pyc new file mode 100644 index 0000000..9a6bbd3 Binary files /dev/null and b/client/__pycache__/UDPClient.cpython-314.pyc differ diff --git a/client/main.py b/client/main.py new file mode 100644 index 0000000..5d832fd --- /dev/null +++ b/client/main.py @@ -0,0 +1,63 @@ +import logging as log +import time +import datetime +from TCPClient import TCPClient +from UDPClient import UDPClient + +IP = "10.69.0.2" +TCP_PORT = 6069 +UDP_PORT = 5069 + + +class SuperGreatFormatter(log.Formatter): + def formatTime(self, record, datefmt=None): + if not datefmt: + return super().formatTime(record, datefmt=datefmt) + + return ( + datetime.datetime.fromtimestamp(record.created) + .astimezone() + .strftime(datefmt) + ) + + +def setupLogger() -> log.Logger: + logger = log.getLogger("client") + logger.setLevel(log.DEBUG) + + filehandler = log.FileHandler( + filename="client-%s.csv" % datetime.datetime.now().isoformat(), + encoding="utf-8", + mode="a+", + ) + filehandler.setFormatter( + SuperGreatFormatter( + "{message},{levelname},{asctime}", "%Y-%m-%d %H:%M:%S.%f %z", "{" + ) + ) + logger.addHandler(filehandler) + + streamhandler = log.StreamHandler() + streamhandler.setFormatter( + log.Formatter("[{asctime}]{levelname}: {message}", "%Y-%m-%d %H:%M:%S", "{") + ) + logger.addHandler(streamhandler) + + return logger + + +def main(): + logger = setupLogger() + tcp = TCPClient(IP, TCP_PORT, logger) + udp = UDPClient(IP, UDP_PORT, logger) + tcp.Connect() + udp.Connect() + + while True: + # tcp.Send("meow") + udp.Send("woof") + time.sleep(9) + + +if __name__ == "__main__": + main() diff --git a/server.py b/server.py new file mode 100644 index 0000000..5281d9d --- /dev/null +++ b/server.py @@ -0,0 +1,56 @@ +import socket +import select + +IP = "0.0.0.0" +UDP_PORT = 5069 +TCP_PORT = 6069 + +udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) +tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + +udp.bind((IP, UDP_PORT)) +tcp.bind((IP, TCP_PORT)) +tcp.listen(10) + +tcpconns = {tcp} + + +def udp_data(udp: socket.socket) -> None: + (udp_incoming, _, _) = select.select([udp], [], [], 0) + if udp_incoming: + udp_data, udp_addr = udp.recvfrom(1024) + print("Received UDP data from %s with content %s" % (udp_addr, udp_data)) + udp.sendto(udp_data, udp_addr) + + +def tcp_data(tcp: socket.socket) -> None: + (newsocks, _, _) = select.select(tcpconns, [], tcpconns, 0) + for sock in newsocks: + if sock is tcp: + conn, addr = tcp.accept() + conn.setblocking(False) + tcpconns.add(conn) + print("TCP connection from %s:%d" % addr) + print(len(tcpconns)) + continue + try: + data = sock.recv(1024) + print("TCP Data: %s" % data) + sock.sendall(b"aarf") + except socket.timeout: + print("Connection closed by remote end") + tcpconns.remove(sock) + sock.close() + except ConnectionResetError: + print("Connection reset by peer") + tcpconns.remove(sock) + sock.close() + except BrokenPipeError: + print("Broken pipe") + tcpconns.remove(sock) + sock.close() + + +while True: + udp_data(udp) + tcp_data(tcp)