Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
U
uva
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Taddeüs Kroes
uva
Commits
10ebfad0
Commit
10ebfad0
authored
Mar 11, 2011
by
Sander Mathijs van Veen
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Telematica: finished client side section of report.
parent
1da87927
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
113 additions
and
16 deletions
+113
-16
telematica/ass1/async.py
telematica/ass1/async.py
+41
-10
telematica/ass1/asyncbase.py
telematica/ass1/asyncbase.py
+4
-0
telematica/ass1/doc/implementation.rst
telematica/ass1/doc/implementation.rst
+67
-1
telematica/ass1/server.py
telematica/ass1/server.py
+1
-5
No files found.
telematica/ass1/async.py
View file @
10ebfad0
...
@@ -2,14 +2,15 @@ import asyncore, socket
...
@@ -2,14 +2,15 @@ import asyncore, socket
from
Queue
import
Queue
from
Queue
import
Queue
import
re
import
re
import
random
import
random
from
asyncbase
import
AsyncBase
from
asyncbase
import
AsyncBase
,
MAJOR_VERSION
,
MINOR_VERSION
# Socket error "Resource temporarily unavailable": try again ;)
# Socket error "Resource temporarily unavailable": try again ;)
EAGAIN
=
11
EAGAIN
=
11
class
ClientConnection
(
object
,
AsyncBase
,
asyncore
.
dispatcher
):
class
ClientConnection
(
object
,
AsyncBase
,
asyncore
.
dispatcher
):
"""
"""
Client connection implementing the asynchronous chat connection according to
the provided chat protocol.
"""
"""
def
__init__
(
self
):
def
__init__
(
self
):
...
@@ -28,6 +29,10 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -28,6 +29,10 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
self
.
user
=
'Anonymous'
+
str
(
int
(
random
.
random
()
*
10000
))
self
.
user
=
'Anonymous'
+
str
(
int
(
random
.
random
()
*
10000
))
def
connect
(
self
,
addr
):
def
connect
(
self
,
addr
):
"""
Connect to a chat server, specified by the address tuple (hostname,
port).
"""
self
.
host
=
addr
[
0
]
self
.
host
=
addr
[
0
]
self
.
port
=
addr
[
1
]
self
.
port
=
addr
[
1
]
super
(
ClientConnection
,
self
).
connect
(
addr
)
super
(
ClientConnection
,
self
).
connect
(
addr
)
...
@@ -36,12 +41,14 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -36,12 +41,14 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
"""
"""
Retreive list of users active at the chat server.
Retreive list of users active at the chat server.
"""
"""
self
.
request_user_list_sent
=
True
self
.
request_user_list_sent
=
True
self
.
retrieved_user_list
=
False
self
.
retrieved_user_list
=
False
self
.
send_queue
.
put
(
'NAMES'
)
self
.
send_queue
.
put
(
'NAMES'
)
def
username
(
self
,
name
=
''
):
def
username
(
self
,
name
=
''
):
"""
Get or set the user name of the client.
"""
if
not
name
:
if
not
name
:
return
self
.
user
return
self
.
user
self
.
user
=
name
self
.
user
=
name
...
@@ -51,13 +58,19 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -51,13 +58,19 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
def
handle_connect
(
self
):
def
handle_connect
(
self
):
"""
"""
C
alled when a connection is established.
Event callback, which is c
alled when a connection is established.
"""
"""
if
'connect'
in
self
.
event_list
:
if
'connect'
in
self
.
event_list
:
self
.
event_list
[
'connect'
](
self
)
self
.
event_list
[
'connect'
](
self
)
def
handle_error
(
self
):
def
handle_error
(
self
):
"""
Event callback, which is triggered when an error occured. This callback
function will transform the exception's traceback into a condensed
string and will append that string to the debug log. Also the info bar
is changed to notify the user of the error occured.
"""
if
hasattr
(
self
,
'main'
):
if
hasattr
(
self
,
'main'
):
import
sys
import
sys
t
,
v
,
tb
=
sys
.
exc_info
()
t
,
v
,
tb
=
sys
.
exc_info
()
...
@@ -76,14 +89,18 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -76,14 +89,18 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
%
(
function
,
file
,
line
))
%
(
function
,
file
,
line
))
self
.
debug_log
(
info
)
self
.
debug_log
(
info
)
self
.
main
.
display_info
(
str
(
v
))
self
.
main
.
display_info
(
str
(
v
))
#self.main.execute('disconnect')
#self.main.display_info('Error raised due to a broken or refused'\
# + ' connection')
def
handle_close
(
self
):
def
handle_close
(
self
):
"""
Event callback, which will be called when the connection is about to
be closed.
"""
self
.
close
()
self
.
close
()
def
handle_read
(
self
):
def
handle_read
(
self
):
"""
Event callback, which is triggered when data is available to read.
"""
buf
=
''
buf
=
''
multi_line
=
False
multi_line
=
False
response_format
=
False
response_format
=
False
...
@@ -129,11 +146,18 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -129,11 +146,18 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
self
.
parse_notification
(
buf
)
self
.
parse_notification
(
buf
)
def
verify_server
(
self
,
buf
):
def
verify_server
(
self
,
buf
):
"""
Helper function used to verify the server version according to the
client major and minor version number. If the server version does not
match, disconnect immediately.
"""
match
=
re
.
match
(
'^CHAT/(
\
d
\
.
\
d)/([^
\
x00-
\
x1F
/:]+)$'
,
buf
)
match
=
re
.
match
(
'^CHAT/(
\
d
\
.
\
d)/([^
\
x00-
\
x1F
/:]+)$'
,
buf
)
if
not
match
:
if
not
match
\
or
match
.
group
(
1
)
!=
'%d.%d'
%
(
MAJOR_VERSION
,
MINOR_VERSION
):
self
.
debug_log
(
'Failed to verify connection'
)
self
.
debug_log
(
'Failed to verify connection'
)
sys
.
exit
(
-
1
)
self
.
main
.
execute
(
'disconnect'
)
return
self
.
debug_log
(
'Server version is %s'
%
match
.
group
(
1
))
self
.
debug_log
(
'Server version is %s'
%
match
.
group
(
1
))
...
@@ -144,6 +168,10 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -144,6 +168,10 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
self
.
send_queue
.
put
(
'CHAT'
)
self
.
send_queue
.
put
(
'CHAT'
)
def
parse_response
(
self
,
buf
):
def
parse_response
(
self
,
buf
):
"""
Helper function used to parse the server response. This way, message
parsing is separated from the response logic.
"""
if
'response'
in
self
.
event_list
:
if
'response'
in
self
.
event_list
:
self
.
event_list
[
'response'
](
self
,
buf
)
self
.
event_list
[
'response'
](
self
,
buf
)
...
@@ -167,7 +195,6 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -167,7 +195,6 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
self
.
request_user_list_sent
=
True
self
.
request_user_list_sent
=
True
self
.
retrieve_users
()
self
.
retrieve_users
()
else
:
else
:
# TODO: process user list
self
.
retrieved_user_list
=
True
self
.
retrieved_user_list
=
True
msg
=
'Users: [%s]'
%
'] ['
.
join
(
buf
.
split
(
'
\
r
\
n
'
)[
1
:])
msg
=
'Users: [%s]'
%
'] ['
.
join
(
buf
.
split
(
'
\
r
\
n
'
)[
1
:])
if
'info'
in
self
.
event_list
:
if
'info'
in
self
.
event_list
:
...
@@ -176,6 +203,10 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -176,6 +203,10 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
self
.
debug_log
(
msg
)
self
.
debug_log
(
msg
)
def
parse_notification
(
self
,
buf
):
def
parse_notification
(
self
,
buf
):
"""
Helper function used to parse the server notifications. This way,
message parsing is separated from the notification logic.
"""
match
=
re
.
match
(
'^SAY ([^
\
r
\
n
:/]+)/(.+)'
,
buf
)
match
=
re
.
match
(
'^SAY ([^
\
r
\
n
:/]+)/(.+)'
,
buf
)
if
match
:
if
match
:
if
'message'
in
self
.
event_list
:
if
'message'
in
self
.
event_list
:
...
...
telematica/ass1/asyncbase.py
View file @
10ebfad0
import
asyncore
,
socket
import
asyncore
,
socket
from
Queue
import
Queue
from
Queue
import
Queue
# Major and minor version of this server.
MAJOR_VERSION
=
1
MINOR_VERSION
=
0
class
AsyncBase
(
asyncore
.
dispatcher
):
class
AsyncBase
(
asyncore
.
dispatcher
):
"""
"""
Base class of the asynchronous connection of the chat system. The server and
Base class of the asynchronous connection of the chat system. The server and
...
...
telematica/ass1/doc/implementation.rst
View file @
10ebfad0
...
@@ -5,10 +5,76 @@ Implementation details
...
@@ -5,10 +5,76 @@ Implementation details
Client side
Client side
----------
----------
The client side of our chat system is generally divided into two parts:
1. **Connection to server.** We use an asynchronous connection to the server
(and also for the server itself to handle the clients; see "Server side"
section) to send and receive messages from/to the chat server.
2. **User interface for user.** We use a text-based user interface built with
the python bindings for the curses library, which is the de-facto standard
for portable advanced terminal handling.
The user interface depends on the client connection and the connection itself is
a completely independent component. Due to its event mechanism (see below), the
client connection can easily be used for different user interfaces.
Client chat connection
Client chat connection
~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~
Foo
First, we implemented the chat connection and we built the user interface on
top of the connection. This way, we're able to test the connection with the
test server provided by the assignment. Otherwise, debugging the connection is
much harder; text-based interface means the entire console is used to display
the interface, so it's not possible to report any exceptions to stdout.
Since some functionality of the chat connection is used in both the client and
server, it is wise to create a base class containing those methods: *AsyncBase*.
We used the *asyncore.dispatcher* class (part of the python standard library) to
create a nice abstraction layer on top of the socket. The abstraction layer is
event-oriented and provides various event callback functions:
1. **handle_write**.
2. **handle_read**.
3. **handle_connect**.
4. **handle_close**.
Because those function callbacks have self-documenting names, the callbacks are
not further discussed here (for more information about those callbacks, see the
implementation in *async.py* and *asyncbase.py*). This event callback mechanism
allowed us to easily built an interactive user interface on top of the
connection. Those various callback functions will handle the connection-specific
logic and triggers a new UI event based on the event occurred.
For example, if an authentication response is sent to the client (when the
client sent **USER foo\\r\\n** to the server), the function **handle_read()** is
called. This callback will parse the response and, if the authentication is done
successfully, trigger an *authenticated* UI event (which will be received by the
text-based user interface). This event mechanism allows a developer to built
different user interfaces on top to a client connection, instead of developing
the response handling over and over again.
The connection runs in a separated thread from the user interface thread. The
reason behind this decision is based on experiences with user interface; when an
user presses a key (and holds it), the connection will not respond until the
user releases the key pressed. If the user interface is separated into a
different thread, the connection is able to respond (due to context switches)
and the user can presses a key as long as he wants. Note: there are different
ways to deal with this problem, but this is a solid solution and it is easy to
implement.
In order to send chat messages to the chat server in a thread-safe manner, we
used a synchronized queue as message buffer, since we're using the two threads
(one for the UI and one for the connection to the server). This synchronized
queue is part of the python standard library and called Queue.Queue. The user
interface adds its messages to the message queue and the client connection reads
the messages from the queue in FIFO (first-in, first-out) order. The client
connection does take messages from the queue, if its local message buffer is
empty (which means all its data is sent to the server or the connection is
just initialised).
Text-based interface (using curses)
Text-based interface (using curses)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
...
...
telematica/ass1/server.py
View file @
10ebfad0
...
@@ -8,15 +8,11 @@ import re
...
@@ -8,15 +8,11 @@ import re
import
socket
import
socket
import
sys
import
sys
from
asyncbase
import
AsyncBase
from
asyncbase
import
AsyncBase
,
MAJOR_VERSION
,
MINOR_VERSION
# Greeting message sent to a connected client.
# 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
MINOR_VERSION
=
0
class
SocketError
(
RuntimeError
):
class
SocketError
(
RuntimeError
):
pass
pass
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment