Published:

Table of contents

Features

  • nonblocking socket
  • server messages divided by a newline, broken packages concatenated before parsing
  • sending messages to server from standard input
  • reconnect timer
  • logger

gist link: https://gist.github.com/mikolasan/7652bcd3ee5498c21541f53bce1c72ce

Code

#!/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")

Rate this page