- nonblocking socket
- server messages divided by a newline, broken packages concatenated before parsing
- sending messages to server from standard input
- reconnect timer
- logger
Created
January 2, 2022 03:03
-
-
Save mikolasan/7652bcd3ee5498c21541f53bce1c72ce to your computer and use it in GitHub Desktop.
Simple Python TCP client
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import errno | |
import json | |
import logging | |
import select | |
import socket | |
import sys | |
import time | |
from threading import Thread, Timer | |
# create logger | |
logger = logging.getLogger('test_client') | |
logger.setLevel(logging.DEBUG) | |
# create console handler and set level to debug | |
console_handler = logging.StreamHandler() | |
console_handler.setLevel(logging.DEBUG) | |
# create formatter | |
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
console_handler.setFormatter(formatter) | |
logger.addHandler(console_handler) | |
TCP_HOST = 'localhost' | |
TCP_PORT = 32155 | |
client_socket = None | |
server_address = (TCP_HOST, TCP_PORT) | |
connect_timeout = 3 | |
reconnect_delay = 6 | |
BUF_SIZE = 4096 | |
def connect_to_server(): | |
global client_socket | |
try: | |
logger.info(f'Connecting to {server_address}...') | |
client_socket = socket.create_connection(server_address, connect_timeout) | |
client_socket.setblocking(False) | |
client_thread = Thread(target=client_communication, args=(client_socket, )) | |
client_thread.start() | |
except (ConnectionError, socket.timeout) as e: | |
logger.debug(f'Connection error: {e}') | |
logger.debug(f'Reconnect in {reconnect_delay} seconds') | |
client_socket = None | |
reconnect_timer = Timer(reconnect_delay, connect_to_server) | |
reconnect_timer.daemon = True | |
reconnect_timer.start() | |
def client_communication(s): | |
keep_connection = True | |
buffer = bytearray(b'') | |
while keep_connection: | |
read_ready, _, _ = select.select([s, sys.stdin], [], []) | |
if s in read_ready: | |
# The socket has data ready to be received | |
continue_recv = True | |
while continue_recv: | |
try: | |
# Try to receive some data | |
buffer += s.recv(BUF_SIZE) | |
except socket.error as e: | |
# no more data in the socket | |
continue_recv = False | |
if e.errno == errno.EWOULDBLOCK: | |
buffer = process_buffer(s, buffer) | |
else: | |
# Error! Print it and tell main loop to stop | |
logger.error(f'Read error: {e}') | |
keep_connection = False | |
elif sys.stdin in read_ready: | |
text = sys.stdin.readline().strip() | |
message = text + "\n" | |
s.send(message.encode()) | |
s.close() | |
def process_buffer(s, buffer): | |
if len(buffer) > 0: | |
messages = buffer.split(b'\n') | |
if buffer[-1] == b'\n': | |
buffer = bytearray(b'') | |
else: | |
buffer = messages[-1] | |
for m in messages[:-1]: | |
if len(m) == 0: continue | |
process_incoming_message(s, m) | |
return buffer | |
def process_incoming_message(sock, msg): | |
print(msg) | |
if __name__ == '__main__': | |
try: | |
connect_to_server() | |
while True: | |
time.sleep(1) | |
except KeyboardInterrupt as e: | |
logger.info("Program closed by the user") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment