Telematica: Almost finished client and server.

parent bb7db307
...@@ -77,6 +77,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher): ...@@ -77,6 +77,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
elif chunk == '\n' and buf[-1] == '\r': elif chunk == '\n' and buf[-1] == '\r':
if not multi_line: if not multi_line:
break break
self.debug_log('receiving multi line response...')
if buf[-3:] == '\r\n\r': if buf[-3:] == '\r\n\r':
# Remove trailing '\n\r' from multi line response # Remove trailing '\n\r' from multi line response
buf = buf[:-2] buf = buf[:-2]
...@@ -84,7 +85,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher): ...@@ -84,7 +85,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
buf += chunk buf += chunk
buf = buf[:-1] buf = buf[:-1]
self.debug_log('< %s' % buf) self.debug_log('< %s' % buf.replace('\n','\\n').replace('\r','\\r'))
# Invoke the proper callback function. # Invoke the proper callback function.
if not self.verified: if not self.verified:
...@@ -118,7 +119,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher): ...@@ -118,7 +119,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
if not self.authentification_sent: if not self.authentification_sent:
self.send_queue.put('USER %s' % self.user) self.send_queue.put('USER %s' % self.user)
self.authentification_sent = True self.authentification_sent = True
elif buf[1:9] == 'Username': elif buf[0] == '+':
# TODO: handle 'username is taken'. # TODO: handle 'username is taken'.
self.authenticated = True self.authenticated = True
if 'authenticated' in self.event_list: if 'authenticated' in self.event_list:
...@@ -130,8 +131,9 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher): ...@@ -130,8 +131,9 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
# After authentification, fetch the list of users currently logged in. # After authentification, fetch the list of users currently logged in.
if not self.retrieved_user_list: if not self.retrieved_user_list:
if not self.request_user_list_sent: if not self.request_user_list_sent:
self.request_user_list_sent = True #self.request_user_list_sent = True
self.retrieve_users() #self.retrieve_users()
pass
else: else:
# TODO: process user list # TODO: process user list
self.retrieved_user_list = True self.retrieved_user_list = True
......
...@@ -10,8 +10,10 @@ import sys ...@@ -10,8 +10,10 @@ import sys
from asyncbase import AsyncBase from asyncbase import AsyncBase
# Greeting message sent to a connected client.
GREETING_MSG = 'Hi there!' GREETING_MSG = 'Hi there!'
# Major and minor version of this server.
MAJOR_VERSION = 1 MAJOR_VERSION = 1
MINOR_VERSION = 0 MINOR_VERSION = 0
...@@ -34,14 +36,13 @@ class Server(asyncore.dispatcher): ...@@ -34,14 +36,13 @@ class Server(asyncore.dispatcher):
self.port = port self.port = port
self.handler = handler self.handler = handler
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
logging.config.fileConfig('logging.conf') logging.config.fileConfig('logging.conf')
self.log = logging.getLogger('Server') self.log = logging.getLogger('Server')
# Listen on given ip-address and port
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr() self.set_reuse_addr()
self.bind((ip, port)) self.bind((ip, port))
self.log.info('waiting for incoming connections on port %s.' % port) self.log.info('waiting for incoming connections on port %s.' % port)
self.listen(5) self.listen(5)
...@@ -56,27 +57,54 @@ class Server(asyncore.dispatcher): ...@@ -56,27 +57,54 @@ class Server(asyncore.dispatcher):
try: try:
conn, addr = self.accept() conn, addr = self.accept()
self.log.info('accepted client %s:%d' % addr) self.log.info('accepted client %s:%d' % addr)
client = self.connect_client(addr) client = self.connect_client(conn, addr)
except socket.error: except socket.error:
self.log.warning('warning: server accept() threw an exception.') self.log.warning('warning: server accept() threw an exception.')
return return
except TypeError: except TypeError:
self.log.warning('warning: server accept() threw EWOULDBLOCK.') self.log.warning('warning: server accept() threw EWOULDBLOCK.')
return return
def connect_client(self, conn, addr):
"""
Initialise a client connection after the server accepted an incoming
connection. This will set the connection handler.
"""
client = ClientData()
# creates an instance of the handler class to handle the # creates an instance of the handler class to handle the
# request/response on the incoming connection. # request/response on the incoming connection.
self.handler(conn, addr, client, self) client.handler = self.handler(conn, addr, client, self)
client.username = ''
def connect_client(self, addr): self.clients['%s:%d' % addr] = client
self.clients['%s:%d' % addr] = ClientData() return client
return self.clients['%s:%d' % addr]
def disconnect_client(self, addr): def disconnect_client(self, addr):
"""
Client leaves the chat server. This function is called when the socket
is broken or the client closes the connection gracefully.
"""
# Suppress error if a client is already disconnected.
try:
client = self.clients['%s:%d' % addr]
self.send_all('LEAVE %s' % client.username, True)
del self.clients['%s:%d' % addr] del self.clients['%s:%d' % addr]
except KeyError:
pass
def change_username(self, addr, username): def change_username(self, addr, username):
"""
Update the username of the client.
"""
self.clients['%s:%d' % addr].username = username self.clients['%s:%d' % addr].username = username
# TODO: notify other clients of changed username
def send_all(self, msg, sender=None):
"""
Send a message or notification from a client to all connected clients
(optionally including to the sender).
"""
for c in self.clients:
if self.clients[c].handler != sender:
self.clients[c].handler.send_raw(msg)
class RequestHandler(AsyncBase, asyncore.dispatcher): class RequestHandler(AsyncBase, asyncore.dispatcher):
def __init__(self, conn, address, client, server): def __init__(self, conn, address, client, server):
...@@ -86,18 +114,25 @@ class RequestHandler(AsyncBase, asyncore.dispatcher): ...@@ -86,18 +114,25 @@ class RequestHandler(AsyncBase, asyncore.dispatcher):
self.address = address self.address = address
self.client = client self.client = client
self.server = server self.server = server
self.username = ''
self.log = self.server.log self.log = self.server.log
self.send_welcome_message() self.send_welcome_message()
def send_welcome_message(self): def send_welcome_message(self):
"""
Welcome our new client by sending a welcome message. This message
contains the server version number and a greetings message.
"""
self.send_raw("CHAT/%d.%d/%s" \ self.send_raw("CHAT/%d.%d/%s" \
% (MAJOR_VERSION, MINOR_VERSION, GREETING_MSG)) % (MAJOR_VERSION, MINOR_VERSION, GREETING_MSG))
def handle_read(self): def handle_read(self):
"""
Receive a message from the client. If the connection is somehow broken,
disconnect the client and clean the corresponding data.
"""
buf = '' buf = ''
# Receive a message from the client. If the connection is somehow
# broken, disconnect the client and clean the corresponding data.
try: try:
while True: while True:
chunk = self.recv(1) chunk = self.recv(1)
...@@ -115,10 +150,11 @@ class RequestHandler(AsyncBase, asyncore.dispatcher): ...@@ -115,10 +150,11 @@ class RequestHandler(AsyncBase, asyncore.dispatcher):
# Received a message, so it's time to parse the message. # Received a message, so it's time to parse the message.
buf = buf[:-1] buf = buf[:-1]
self.debug_log('< %s' % buf) self.debug_log('< %s' % buf)
self.parse_response(buf) self.parse_response(buf)
def handle_error(self):
self.server.disconnect_client(self.address)
def parse_response(self, buf): def parse_response(self, buf):
""" """
>>> class DummyServer(object): >>> class DummyServer(object):
...@@ -140,9 +176,24 @@ class RequestHandler(AsyncBase, asyncore.dispatcher): ...@@ -140,9 +176,24 @@ class RequestHandler(AsyncBase, asyncore.dispatcher):
if cmd == 'USER': if cmd == 'USER':
# User changes/sets its username. # User changes/sets its username.
if re.match(ur'^[^\u0000-\u001f\u007f-\u009f/:]+$', buf[5:]): if re.match(ur'^[^\u0000-\u001f\u007f-\u009f/:]+$', buf[5:]):
if not self.username:
self.send_all('JOIN %s' % buf[5:], True)
else:
self.send_all('RENAME %s/%s' \
% (self.username, buf[5:]), True)
self.set_username(buf[5:]) self.set_username(buf[5:])
return self.send_positive('Ok') return self.send_positive('Ok')
return self.send_negative('Invalid username.') return self.send_negative('Invalid username.')
# TODO: user can't send a message if he didn't send his username first.
if cmd == 'SAY':
return self.send_all('SAY %s/%s' % (self.username, buf[4:]))
if cmd == 'NAMES':
self.send_raw('+Ok:')
for c in self.server.clients:
self.send_raw(self.server.clients[c].username)
self.send_raw('')
return True
return self.send_negative('Unsupported command.') return self.send_negative('Unsupported command.')
def send_positive(self, msg): def send_positive(self, msg):
...@@ -153,7 +204,12 @@ class RequestHandler(AsyncBase, asyncore.dispatcher): ...@@ -153,7 +204,12 @@ class RequestHandler(AsyncBase, asyncore.dispatcher):
self.send_raw('-%s' % msg) self.send_raw('-%s' % msg)
return False return False
def send_all(self, msg, except_sender=False):
self.server.send_all(msg, except_sender and self)
return True
def set_username(self, username): def set_username(self, username):
self.username = username
self.server.change_username(self.address, username) self.server.change_username(self.address, username)
if __name__ == '__main__': if __name__ == '__main__':
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment