Description:This was written for an undergraduate course in Network Programming.
This particular piece of code was written in Python to implement a peer-to-peer instant messenger.
"""
Project 01 - TCP based P2P messaging program
EECE 4275 - Network Programming
@author Jon Olson
@version 0.5.0
@requires Python 2.7, Cerealizer
Cerealizer can be downloaded from http://home.gna.org/oomadness/en/cerealizer/index.html
"""
import SocketServer
import re
import cerealizer
import threading
import sys
import logging
class BaseTransmission:
"""
A generic transmission between any two hosts
"""
def __init__( self, **kwargs ):
"""
A utility method for subclasses to use to quickly set the payload dictionary. These
transmissions are initialized on the sending side.
@param kwargs a dictionary of variable length
"""
self.payload = kwargs
def process( self, HOST = None, PORT = None ):
"""
A dummy method for subclasses to override. This method is called on the receiving side
of the transmission.
@param HOST the address of the person sending the message
@param PORT the port the HOST is transmitting from, but not necessarily listening to. The
payload often contains information regarding the port being listened to.
@return If something is returned from this method, it will be sent back to the address
sending this transmission.
"""
pass
class Acknowledge( BaseTransmission ):
"""
An acknowledgement to send on receipt of various communications.
"""
pass
class PeerMessage( BaseTransmission ):
"""
A message sent between peers
"""
def __init__( self, fromUser, message ):
"""
@param fromUser The peer (name) initiating the transmission.
@param message The content of what the user wishes to say.
"""
BaseTransmission.__init__(self, name = fromUser, message = message)
def process( self, HOST = None, PORT = None ):
"""
This implementation simply calls the receiving client's method for handling this event.
"""
global clientApp
clientApp.peerMessage( self.payload['name'], self.payload['message'] )
return Acknowledge()
class PeerBroadcast( PeerMessage ):
"""
A special kind of message being transmitted to all peers.
"""
def process( self, HOST = None, PORT = None ):
"""
This implementation simply calls the receiving client's method for handling this event.
"""
global clientApp
clientApp.peerBroadcast( self.payload['name'], self.payload['message'] )
return Acknowledge()
class NameConflict( BaseTransmission ):
"""
The connecting client provided a name that is either not distinct or was malformed.
"""
def process( self, HOST = None, PORT = None ):
"""
This implementation simply calls the connecting client's method for handling this event.
"""
global clientApp
clientApp.nameConflict()
return Acknowledge()
class ClientConnect( BaseTransmission ):
"""
Registers a client with the server
"""
def __init__( self, name, ip, port ):
"""
@param name The name of the user connecting.
@param ip The ip of the user connecting.
@param port The port the user will be listening on, not necessarily where this transmission
will come from.
"""
BaseTransmission.__init__( self, name = name, ip = ip, port = port )
def process( self, HOST = None, PORT = None ):
global clientList, args
verbose( "client list:", clientList)
# verify they aren't in the client list already
if self.payload['name'] in clientList:
verbose( "Name conflict detected in client list:", self.payload['name'] )
#Send( HOST, PORT, NameConflict() )
return NameConflict()
# verify the name isn't malformed
elif re.search('\W', self.payload['name']) != None:
verbose( "Malformed name detected:", self.payload['name'] )
return NameConflict()
# build the peer list, and notify all peers of the connection
peers = dict()
verbose( self.payload )
c = PeerConnect( self.payload['name'], self.payload['ip'], self.payload['port'] )
for client in clientList:
peers[client] = {'ip': clientList[client]['ip'], 'port': clientList[client]['port']}
verbose( "notifying", client, clientList[client] )
ack = SendRespond( clientList[client]['ip'], clientList[client]['port'], c )
if not isinstance( ack, Acknowledge ):
verbose( "did not get an acknowledgement from", client )
# add them to the client list
clientList[self.payload['name']] = {
'ip': self.payload['ip'],
'port': self.payload['port'],
'tick': 0,
}
verbose(self.payload['name'], "has logged in.")
# respond with the peer list
return peers
class PeerConnect( ClientConnect ):
"""
Sent from the server to existing peers when a new peer comes online
"""
def process( self, HOST = None, PORT = None ):
"""
This implementation simply calls the connecting client's method for handling this event.
"""
global clientApp
clientApp.peerConnect( self.payload['name'], self.payload['ip'], self.payload['port'] )
return Acknowledge()
class PeerUpdate( BaseTransmission ):
"""
The server detected an IP address change for a user and notifies each of the peers with this
transmission.
"""
def __init__( self, name, ip ):
"""
@param name The name of the user being updated.
@param ip The IP address to now use.
"""
BaseTransmission.__init__( self, name = name, ip = ip )
def process( self ):
"""
This implementation simply calls the connecting client's method for handling this event.
"""
global clientApp
clientApp.peerUpdate( self.payload['name'], self.payload['ip'] )
return Acknowledge()
class ClientMaintain( BaseTransmission ):
"""
Maintain the presence of client information with the server. Used by the server to detect when
a user disconnect's for unknown reasons.
"""
def __init__( self, name ):
"""
@param name The user this transmission pertains to.
"""
self.payload = name
def process( self, HOST = None, PORT = None ):
"""
Reset the client list's stale counter for the user.
"""
global clientList, args
if self.payload in clientList:
verbose( "Found the client" )
clientList[self.payload]['tick'] = 0
#if clientList[self.payload]['ip'] != HOST:
# verbose( "updating peers with new ip address" )
# for x in clientList:
# if x != self.payload:
# Send( clientList[x]['ip'], clientList[x]['port'], PeerUpdate( self.payload, HOST ) )
# clientList[self.payload]['ip'] = HOST
else:
verbose( "Didn't find the client" )
return Acknowledge()
class ClientDisconnect( BaseTransmission ):
"""
This message is sent by a client to the server when they actively disconnect.
"""
def __init__( self, name ):
"""
@param name The name of the user disconnecting
"""
self.payload = name
def process( self, HOST = None, PORT = None ):
"""
Attempt to delete the user from the client list, then update all peers of the disconnect.
"""
global clientList
try:
del clientList[self.payload]
verbose(self.payload, "has been disconnected from the server.")
p = PeerDisconnect( self.payload )
for x in clientList:
verbose( "Notifying", x, "of disconnect." )
ack = SendRespond( clientList[x]['ip'], clientList[x]['port'], p )
if not isinstance( ack, Acknowledge ):
verbose( "did not get an acknowledgement from", x )
except KeyError:
pass
return Acknowledge()
class PeerDisconnect( ClientDisconnect ):
"""
Sent by the server to all existing peers when a client disconnects (actively or not).
"""
def process( self, HOST = None, PORT = None ):
"""
This implementation simply calls the connecting client's method for handling this event.
"""
global clientApp
clientApp.peerDisconnect( self.payload )
return Acknowledge()
def TickClients():
"""
The server uses a tick counting mechanism to automatically disconnect clients after a certain
elapsed time
"""
global clientList, tik
tik = not tik
if tik:
verbose( "tik" )
else:
verbose( "tok" )
for client in clientList:
try:
clientList[client]['tick'] += 1
except KeyError:
pass
def TrimClients():
"""
Trim all clients who are deemed stale
"""
global clientList
verbose( "Trimming", len(clientList), "clients..." )
for client in clientList.keys():
try:
verbose("client:", client, "tick:", clientList[client]['tick'])
# check if the tick count is "stale"
if clientList[client]['tick'] > 1:
verbose( "Disconnecting", client )
c = PeerDisconnect( client )
# only tell other users who are not "stale"
for x in clientList:
if clientList[x]['tick'] <= 1:
ack = SendRespond( clientList[x]['ip'], clientList[x]['port'], c )
if not isinstance( ack, Acknowledge ):
verbose( "did not get an acknowledgement from", x )
del clientList[client]
except AttributeError:
pass
# automatically shut down the server when there are no more clients
if len(clientList) == 0:
verbose( "No more clients detected, shutting down server..." )
global ShutdownEvent
ShutdownEvent.set()
def Send( HOST, PORT, payload ):
"""
Send a given transmission to a given address
@param HOST host address
@param PORT port to send data to
@param payload the data to serialize into a stream for transfer
"""
# check if there's nothing to send, if so, don't send anything.
if payload == None:
return
verbose( "attempting to send something to", HOST, PORT, type(payload) )
import socket
try:
# create a new socket just for this transmission
sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
sock.connect( (HOST, PORT) )
# scramble the transmission, not necessarily for security, but for simplicity of handling
# the transmission of data on the receiving end
sock.send( scramble( payload ) )
sock.close()
except socket.error as e:
errno, string = e.args
# swallow connection refusal errors
if errno != 61:
# this error was not a connection refusal, so let it bubble up to the next level
raise
else:
verbose( "swallowed:", errno, string )
def SendRespond( HOST, PORT, payload ):
"""
Send a given transmission to a given address, expecting a response
@param HOST host address
@param PORT port to send data to
@param payload the data to serialize into a stream for transfer
@return a response from the recipient of the message
"""
if payload == None:
return
verbose( "attempting to send something to", HOST, PORT, type(payload), "and waiting for a response" )
import socket
try:
#t = socket.gettimeout()
#socket.settimeout(None)
# create a new socket just for this transmission
sock = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
sock.connect( (HOST, PORT) )
# scramble the transmission, not necessarily for security, but for simplicity of handling
# the transmission of data on the receiving end
sock.send( scramble( payload ) )
r = unscramble(socketReadline( sock ))
sock.close()
#socket.settimeout(t)
verbose( "got response:", r )
return r
except socket.timeout:
sock.close()
return -1
except socket.error as e:
errno, string = e.args
# swallow connection refusal errors
if errno != 61:
# this error was not a connection refusal, so let it bubble up to the next level
raise
else:
verbose( "swallowed:", errno, string )
return -1
def Respond( payload ):
"""
Serialize the given payload, whatever it is. This may cause an exception to be raised if the
payload's type is not registered with cerealizer. This function was intended to be used for
responding during a transmission handling. It is not being used at this time, however, opting
for SendRespond(), and the specifics of implementation in the process() method of the
transmission object.
@param payload The data to serialize
@deprecated use SendRespond() and a subclass of BaseTransmission's process() method.
"""
return cerealizer.dumps( payload )
def Tick():
"""
Perform periodic maintainence on the client list.
"""
# increase the "staleness" of all clients
TickClients()
# disconnect any clients who are now "stale"
TrimClients()
# restart the timer to call this function again.
global t
t = threading.Timer( 20, Tick )
t.start()
def Alive():
"""
Notify the server that the client is still alive.
@deprecated ClientApp has a method called maintain() to handle this now.
"""
global myName, myIP, myPort, args, tik
tik = not tik
if tik:
verbose( "tik" )
else:
verbose( "tok" )
c = ClientMaintain()
c.payload = Peer()
c.payload.name = myName
c.payload.ip = myIP
c.payload.port = myPort
Send( args.host, args.port, c )
global t
t = threading.Timer( 15, Alive )
t.start()
def verbose( *vararg ):
"""
Used to log verbose debug information
"""
# create a string based list of the arguments to this function
new = list()
for arg in vararg:
new.append(str(arg))
logging.debug( ' '.join(new) )
class BaseServerHandler( SocketServer.BaseRequestHandler ):
"""
General handler for server interactions
"""
def handle( self ):
"""
Handle a server event generically
"""
verbose("beginning handling of server event...")
global args
# extract an entire request and unscramble it
o = unscramble(socketReadline( self.request ).strip())
verbose( self.acceptable )
# only process acceptable transmissions
if isinstance( o, self.acceptable ):
verbose( "Got a", o.__class__.__name__, "transmission" )
HOST, PORT = self.client_address
response = o.process( HOST, PORT )
# if the process returned a response, send it
if response != None:
self.request.send(scramble(response))
else:
try:
verbose( "Got an unacceptable type:", o.__class__.__name__ )
except AttributeError:
verbose( "Got an unknown type.", o )
verbose("finished handling event...")
def setup( self ):
"""
This method is automatically called to setup the instance for the SocketServer being used
"""
self.acceptable = tuple()
def setAcceptable( self, acceptability ):
"""
Set the list of acceptable classes the serialization creates
@param acceptability A tuple of acceptable classes for this server
"""
self.acceptable = acceptability
class ClientHandler( BaseServerHandler ):
"""
Handle events coming into the client
"""
def handle( self ):
verbose( self.__class__.__name__, "handle" )
BaseServerHandler.handle( self )
def setup( self ):
verbose( self.__class__.__name__, "setup" )
BaseServerHandler.setup( self )
self.setAcceptable( tuple( [NameConflict, PeerConnect, PeerUpdate, PeerDisconnect, PeerMessage, PeerBroadcast] ) )
class ServerHandler( BaseServerHandler ):
"""
Handle events coming into the server
"""
def handle( self ):
verbose( self.__class__.__name__ )
BaseServerHandler.handle( self )
def setup( self ):
BaseServerHandler.setup( self )
self.setAcceptable( tuple( [ClientConnect, ClientMaintain, ClientDisconnect] ) )
class ThreadedTCPServer( SocketServer.ThreadingMixIn, SocketServer.TCPServer ):
"""
This class specifically mixes threading support and TCP service, all implementation is handled
inside SocketServer
"""
pass
def scramble( payload ):
"""
Scramble a given payload for transmission. A trailing carriage return is added for the readline
function.
@param payload The data to scramble.
"""
import base64
return base64.urlsafe_b64encode( cerealizer.dumps( payload ) ) + '\n'
def unscramble( payload ):
"""
Reverse the results of a scrambled payload.
@param payload The data to unscramble.
"""
import base64
return cerealizer.loads( base64.urlsafe_b64decode( payload.rstrip() ) )
def socketReadline( sock ):
"""
Attempt to read a complete line from a given socket.
@param sock The socket to read from.
"""
line = ''
# switch time out settings on the socket temporarily
x = sock.gettimeout()
sock.settimeout(1)
try:
verbose( 'reading a line from the socket' )
while '\n' not in line and '\r' not in line:
line += sock.recv(1)
verbose( 'finished reading a line from socket' )
sock.settimeout(x)
return line.strip()
except socket.timeout:
sock.settimeout(x)
verbose( 'socket timed out' )
raise
class ClientApp:
"""
The bulk of code relating to client handling and interaction
"""
def __init__( self, name, ip, host, hostPort ):
"""
Relatively simple initialization of internal variables needed throughout the client
@param name The username to use for this client
@param ip The ip address of the client, the port used for serving is found later
@param host The authoritative server to use
@param hostPort The authoritative server's port
"""
self.name = name
self.host = host
self.hostPort = hostPort
self.width = self.height = None
self.messengerWindow = self.userWindow = None
self.messengerThread = self.userThread = None
self.shutdownEvent = threading.Event()
self.timer = None
self.commands = {
'exit': self.quit,
'quit': self.quit,
'm': self.message,
'msg': self.message,
'users': self.users,
'peers': self.users,
'help': self.help,
}
self.commandReg = re.compile( '^\s*/(?P<command>' + '|'.join(self.commands.keys()) +
')\s*(?P<rest>.*)$', re.I )
self.server = None
self.ip = ip
self.port = None
self.peers = dict()
self.reason = None
self.error = None
def start( self ):
"""
This method starts the curses wrapper and handles internal errors
"""
# initiate curses
# pass off execution to run()
import curses
try:
curses.wrapper( self.run )
if self.error != None:
raise self.error
except self.ClientError as e:
print e.reason
def run( self, win ):
"""
This method handles the primary running of the client
@param win The screen, as passed by curses' wrapper function
"""
import curses
# divide the screen with a horizontal line separating user and message level areas
self.height, self.width = win.getmaxyx()
verbose( self.width, self.height )
win.hline( self.height - 2, 0, curses.ACS_HLINE, self.width )
win.refresh()
# sleep for a short time to allow the window to settle before moving on
curses.napms( 500 )
# create the window for message displays
self.messengerWindow = win.derwin( self.height - 2, self.width, 0, 0 )
self.messengerWindow.scrollok( True )
# create the window for user typing
self.userWindow = win.derwin( 1, self.width, self.height - 1, 0 )
self.userWindow.scrollok( True )
self.userWindow.nodelay( 0 )
self.userWindow.notimeout( 1 )
# create the message handling thread
self.server = ThreadedTCPServer( (self.ip, 0), ClientHandler )
self.ip, self.port = self.server.server_address
verbose( "{}'s client's server info".format(self.name), self.ip, self.port )
self.messengerThread = threading.Thread( target = self.server.serve_forever, name = 'ClientApp.messenger' )
self.messengerThread.start()
# send the initial connection request to the server and get its response
r = SendRespond( self.host, self.hostPort, ClientConnect( self.name, self.ip, self.port ) )
import types
# if there was a name conflict
if type(r) == types.DictType:
self.peers = r
else:
if isinstance( r, NameConflict ):
self.nameConflict()
else:
if type(r) == types.IntType and r == -1:
self.error = self.ClientError( 'server did not respond in time or refused connection' )
elif r != None:
self.error = self.ClientError( 'unknown reason ' + str(r) )
else:
self.error = self.ClientError( 'should have gotten a response' )
self.shutdown()
return
# always display initial help information
self.help()
# always display peers initially
self.users()
# create the thread for user typing
self.userThread = threading.Thread( target = self.userInteract, name = 'ClientApp.user' )
curses.echo()
self.userThread.start()
# initiate the client maintanence timer thread
self.maintain()
# wait for the client events to allow a shut down
self.shutdownEvent.wait()
# depending on what initiated the shut down, the server may or may not be initialized
if self.server != None:
self.server.shutdown()
# if there was a reason given for the shut down, raise an exception to start() for it.
if self.reason != None:
self.error = self.ClientError( self.reason )
def maintain( self ):
"""
A timer thread to maintain presence with the server
"""
verbose( "maintaining server connection" )
ack = SendRespond( self.host, self.hostPort, ClientMaintain( self.name ) )
if not isinstance( ack, Acknowledge ):
verbose( "did not get an acknowledgement from the server" )
self.timer = threading.Timer( 10, self.maintain )
self.timer.start()
def userInteract( self ):
"""
user interaction thread
terminates when a command sets shutdownEvent or an error is created internally
"""
while not self.shutdownEvent.is_set() and self.error == None:
userInput = self.userWindow.getstr(0,0).rstrip()
self.userWindow.erase()
if userInput != "":
verbose( "got input:", userInput )
m = self.commandReg.match( userInput )
if m != None:
verbose( "found a command:", m.group('command') )
# a command was found in the input
self.commands[m.group('command')]( m.group('rest') )
else:
# broadcast to all peers
self.broadcast( userInput )
def quit( self, data = None ):
"""
The quit command.
@param data Any extra data sent to the command (which is ignored.)
"""
verbose( "attempting to quit client..." )
global args
ack = SendRespond( args.host, args.port, ClientDisconnect( self.name ) )
if not isinstance( ack, Acknowledge ):
verbose( "did not get an acknowledgement from the server" )
self.shutdown()
def help( self, data = None ):
"""
The help command.
@param data Any extra data sent to the command (which is ignored.)
"""
self.display( "Available commands:" )
self.display( " /" + ' /'.join(sorted(self.commands.keys())) )
def message( self, data = None ):
"""
The message command.
@param data The remainder of the command given to the client.
"""
# do nothing if no data was sent
if data == None or data == '':
return
verbose( "attempting to send a message to user" )
try:
# extract the user to send the message to from the data
user, data = data.split( None, 1 )
# ensure the user is a registered peer
if user in self.peers:
m = PeerMessage( self.name, data )
ack = SendRespond( self.peers[user]['ip'], self.peers[user]['port'], m )
if not isinstance( ack, Acknowledge ):
verbose( "did not get an acknowledgement from", user )
self.display( self.name + '@' + user + ': ' + data )
else:
self.display( "! NOTE: unknown user" )
except ValueError:
self.display( "! NOTE: unknown user or empty message" )
pass
def users( self, data = None ):
"""
The users command.
@param data Any extra data sent to the command (which is ignored.)
"""
verbose( "attempting to display users..." )
# check if there are any peers to display
if len(self.peers) > 0:
self.display( "Users:" )
copy = dict(self.peers)
copy[self.name] = 1
self.display( ' ' + ' '.join(sorted(copy.keys())) )
else:
self.display( 'there are no other active peers' )
def broadcast( self, data = None ):
"""
The broadcast command. This is chosen by default if no other commands were matched.
@param data The message that will be sent to all peers
"""
# ensure there is something to send, first
if data == None or data == '':
return
verbose( "attempting to broadcast a message to all peers..." )
m = PeerBroadcast( self.name, data )
# iterate over the peers sending the message
for name in self.peers:
verbose( "sending message to", name )
ack = SendRespond( self.peers[name]['ip'], self.peers[name]['port'], m )
if not isinstance( ack, Acknowledge ):
verbose( "did not get an acknowledgement from", name )
# show the message to the user
self.peerBroadcast( self.name, data )
def peerConnect( self, name, ip, port ):
"""
A peer is coming online. Add them to the peer list.
@param name The name of the user.
@param ip Their IP address.
@param port The port they are listening on.
"""
verbose( "peer connecting", name )
self.peers[name] = {'ip': ip, 'port': port}
self.display( name + ' connected.' )
def peerDisconnect( self, name ):
"""
A peer is being disconnected. Remove them from the peer list.
@param name The user's name
"""
verbose( "peer disconnecting", name )
try:
del self.peers[name]
self.display( name + ' disconnected.' )
except AttributeError:
# this should only happen if they weren't actually in the peer list, for some reason
pass
def peerMessage( self, name, message ):
"""
Receiving a direct message from another user
@param name The user sending the message.
@param message Their message to this user.
"""
verbose( "incoming direct message from", name + ":", message )
self.display( name + '@' + self.name + ': ' + message )
def peerUpdate( self, name, ip ):
"""
A peer's IP address has changed, this will update the peer list accordingly.
"""
verbose( "updating peer ip address" )
if name in self.peers:
self.peers[name]['ip'] = ip
def peerBroadcast( self, name, message ):
"""
A message is being broadcast to all peers of a user.
@param name The user sending the broadcast.
@param message The message they sent.
"""
verbose( "incoming message from", name + ":", message )
self.display( name + ': ' + message )
def shutdown( self, reason = None ):
"""
Shut down the client for some reason.
@param reason The reason why the client is being shut down. This will later raise an
exception, so take care in using it.
"""
if self.timer != None:
self.timer.cancel()
self.timer = None
if self.server != None:
self.server.shutdown()
self.server = None
self.reason = reason
self.shutdownEvent.set()
def nameConflict( self ):
"""
There was some kind of conflict with the user's name.
@deprecated This is handled inside the beginning of run() now.
"""
verbose( "name conflict" )
print "There was a conflict with your chosen name. Please choose another."
self.shutdown( "Name Conflict" )
def display( self, message ):
"""
Display a message from peers, the server or client to the user in the message window.
@param message The message to display.
"""
self.messengerWindow.move( 0, 0 )
self.messengerWindow.deleteln()
height, width = self.messengerWindow.getmaxyx()
self.messengerWindow.addstr( height - 1, 0, message.rstrip() )
self.messengerWindow.redrawwin()
self.messengerWindow.refresh()
self.userWindow.refresh()
class ClientError( Exception ):
"""
Used to treat internal exceptions
"""
def __init__( self, reason ):
#Exception.__init__()
self.reason = reason
if __name__ == '__main__':
# Identify this computer's hostname and IP address.
import socket
global myName, myIP, myPort, args, clientList, ShutdownEvent, tik, time
time = 0
clientList = dict()
myName = socket.gethostname()
try:
# this will throw an error if there is no domain controller for the network
myIP = socket.gethostbyname( socket.getfqdn() )
except socket.gaierror:
# if there is no domain controller, use the known hostname to retrieve the IP address
myIP = socket.gethostbyname( myName )
else:
# if we get here for some reason, use the loopback IP.
myIP = '127.0.0.1'
#print '"' + myName + '"', myIP
# Generate a 64-bit salt
#import random, base64, marshal
#random.seed()
#mySalt = base64.urlsafe_b64encode( marshal.dumps( random.randint( 0, 2**64-1 ) ) )
#print mySalt
# Load Python's argument parsing module to provide parsing and support
from argparse import ArgumentParser, ArgumentDefaultsHelpFormatter, FileType
parser = ArgumentParser( description = 'Network Programming - Project 01 - Peer-to-peer messaging',
formatter_class = ArgumentDefaultsHelpFormatter )
# should the script run as a server?
if '-s' in sys.argv or '--server' in sys.argv:
# run as server
myName = 'Server'
else:
# provide the user with more command line information on usage
parser.add_argument( 'host', help = 'Specify the hostname of the server to connect to.' )
# add all the other command line arguments for parsing
parser.add_argument(
'-u', '--user',
help = 'Specify a user or machine name.',
default = myName )
parser.add_argument(
'-p', '--port',
help = 'Specify the port number to use.',
default = 1337,
type = int )
parser.add_argument(
'-s', '--server',
help = 'Run as a server.',
action = 'store_true' )
parser.add_argument(
'-v', '--verbose',
help = 'Display extra information.',
action = 'store_true' )
#parser.add_argument(
# '-t', '--test',
# help = 'Perform self tests.',
# action = 'store_true' )
parser.add_argument(
'-l', '--log',
help = 'Perform self tests.',
type = FileType('w'),
default = sys.stdout )
args = parser.parse_args()
# configure the logging utility
logging.basicConfig( stream = args.log, datefmt = '%Y-%m-%d %H:%M:%S',
format = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' )
logger = logging.getLogger()
if args.verbose:
logger.setLevel( logging.DEBUG )
else:
logger.setLevel( logging.WARNING )
# configure the serialization allowances.
cerealizer.register( NameConflict )
cerealizer.register( PeerConnect )
cerealizer.register( PeerDisconnect )
cerealizer.register( PeerMessage )
cerealizer.register( PeerBroadcast )
cerealizer.register( ClientConnect )
cerealizer.register( ClientMaintain )
cerealizer.register( ClientDisconnect )
cerealizer.register( Acknowledge )
# check to see if the server is being initiated
if args.server:
myPort = args.port
verbose( "Server initiating on port", myPort )
server = ThreadedTCPServer( ('localhost', myPort), ServerHandler )
myIP, myPort = server.server_address
serverThread = threading.Thread( target = server.serve_forever, name = 'ServerThread' )
serverThread.daemon = True
serverThread.start()
verbose( "Server thread started:", serverThread.name )
if False: #args.test:
c = ClientConnect()
c.payload = Peer()
c.payload.name = 'test'
c.payload.ip = '127.0.0.1'
c.payload.port = 31337
Send( 'localhost', args.port, c )
else:
# perform a tick every 20 seconds
t = threading.Timer( 20, Tick )
tik = False
t.start()
verbose( "Timer started..." )
# wait for a shutdown event
ShutdownEvent = threading.Event()
ShutdownEvent.wait()
t.cancel()
server.shutdown()
else:
global clientApp
verbose( "client:", args.user, myIP, args.host, args.port )
clientApp = ClientApp( args.user, myIP, args.host, args.port )
clientApp.start()
# shut down the logging interface
logging.shutdown()
#eof