Skip to content

Instantly share code, notes, and snippets.

@alekratz
Created June 3, 2015 14:23
Show Gist options
  • Save alekratz/ee0327173dca16db2a59 to your computer and use it in GitHub Desktop.
Save alekratz/ee0327173dca16db2a59 to your computer and use it in GitHub Desktop.
Python thin client - v2
__author__ = 'Alek Ratzloff <alekratz@gmail.com>'
from socket import socket
class ThinClient:
"""
Python client used for doing stuff... Thinly
"""
def __init__(self, port, host="127.0.0.1", recv_size=1024):
self.port = port
self.host = host
self.recv_size = recv_size
self.sock = None
def connect(self):
"""
Creates a connection from a thin client to a server to send commands back and forth.
"""
if self.sock:
raise Exception("Client is already connected to a server")
# create the socket and connect
self.sock = socket()
self.sock.connect((self.host, self.port))
def close(self):
"""
Closes a connection from a thin client to a server.
"""
self.__verify_connection()
self.sock.close()
self.sock = None
def send(self, message):
"""
Sends a message to the server after a connection is established.
:param message: the message or command to send to the server
"""
self.__verify_connection()
self.sock.send(message)
def wait_receive(self):
"""
Blocks program execution until a message is received from the server.
:return: the string read from the server
"""
self.__verify_connection()
return self.sock.recv(self.recv_size)
def __verify_connection(self):
"""
Ensures that the thin client is connected to a server. If not, it will raise an exception.
"""
if not self.sock:
raise Exception("Client is not connected to the server")
# For now, there is no difference between the BasicThinClient and ThinClient.
BasicThinClient = ThinClient
__author__ = 'Alek Ratzloff <alekratz@gmail.com>'
import abc
from socket import socket
from os.path import exists
from os import fork
class ThinServer:
__metaclass__ = abc.ABCMeta
def __init__(self, port, host='127.0.0.1', recv_size=1024, is_daemon=False, lockfile="/tmp/pythinclient.pid"):
"""
Creates an instance of a server that listens for connections and commands from the thin client.
:param port: the port to listen on
:param host: the hostname to listen on. 127.0.0.1 by default
:param recv_size: the size of the buffer to read. 1024 by default
:param is_daemon: whether this thin server is a daemon. False by default
:param lockfile: the file to use to hold the process ID of the daemon process
"""
self.port = port
self.host = host
self.recv_size = recv_size
self.sock = None
self.is_daemon = is_daemon
self.is_running = False
self.lockfile = lockfile
def start(self):
assert (self.sock is None == (not self.is_running))
if self.is_running:
raise Exception("Server is already running")
# determine if this is a daemonized server, and check to see if the lockfile is already taken
if self.is_daemon:
if exists(self.lockfile):
raise Exception("Daemonized server is already running")
# fork
child = fork()
if child == -1:
raise Exception("Failed to fork for daemon")
elif child == 0:
# child section
self.sock = socket()
self.sock.bind((self.host, self.port))
self.sock.listen(1)
self.__accept_loop()
else:
# parent section
# create the lockfile and put the PID inside of it
with open(self.lockfile, "w") as fp:
fp.write(str(child))
else:
# not a daemon. initialize like normal and run in this thread
self.sock = socket()
self.sock.bind((self.host, self.port))
self.sock.listen(1)
self.__accept_loop()
def __accept_loop(self):
"""
Private helper method that accepts clients
:return:
"""
assert self.sock
assert self.is_running
while self.is_running:
conn, addr = self.sock.accept()
message = conn.recv(self.recv_size)
self.on_receive(message, conn, addr)
@abc.abstractmethod
def on_receive(self, message, conn, addr):
"""
Handles the receiving of a message from a client
:param message: the message that was received
:param conn: the socket connection that sent the message
:param addr: the address of the connection that sent the message
:return:
"""
return
class BasicThinServer(ThinServer):
"""
A basic thin server that can be extended by adding method hooks. Check the add_hook method documentation on how to
do so. This thin server can be used with the BasicThinClient.
"""
def __init__(self, port=65000):
super(BasicThinServer, self).__init__(port)
self.hooks = {}
def add_hook(self, command, method):
"""
Adds a keyword command for the server to invoke a method.
The method must take 3 arguments: the message, the connection, and the address.
:param command: the command, as a string, that is handled
:param method: the function that is called
:return:
"""
self.hooks[command] = method
def on_receive(self, message, conn, addr):
"""
Routes the received message to the correct handler
:param message: the message that was received
:param conn: the socket connection that sent the message
:param addr: the address of the connection that sent the message
"""
# get the first word of the message, and use that to figure out what the command was
command = message.split()[0]
if command in self.hooks:
self.hooks[command](message, conn, addr)
result_message = "OK"
else:
result_message = "Bad command"
conn.send(result_message)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment