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
bbd17ad8
Commit
bbd17ad8
authored
Mar 10, 2011
by
Taddeüs Kroes
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of
ssh://vo20.nl/home/git/repos/uva
parents
8b945169
b0ef31ea
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
191 additions
and
36 deletions
+191
-36
telematica/ass1/async.py
telematica/ass1/async.py
+42
-4
telematica/ass1/base_bar.py
telematica/ass1/base_bar.py
+3
-3
telematica/ass1/cli.py
telematica/ass1/cli.py
+12
-11
telematica/ass1/server.py
telematica/ass1/server.py
+134
-18
No files found.
telematica/ass1/async.py
View file @
bbd17ad8
...
@@ -4,6 +4,10 @@ import re
...
@@ -4,6 +4,10 @@ import re
import
random
import
random
from
asyncbase
import
AsyncBase
from
asyncbase
import
AsyncBase
# Socket error "Resource temporarily unavailable": try again ;)
EAGAIN
=
11
class
ClientConnection
(
object
,
AsyncBase
,
asyncore
.
dispatcher
):
class
ClientConnection
(
object
,
AsyncBase
,
asyncore
.
dispatcher
):
def
__init__
(
self
):
def
__init__
(
self
):
...
@@ -51,6 +55,29 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -51,6 +55,29 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
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
):
if
hasattr
(
self
,
'main'
):
import
sys
t
,
v
,
tb
=
sys
.
exc_info
()
tbinfo
=
[]
while
tb
:
tbinfo
.
append
((
tb
.
tb_frame
.
f_code
.
co_filename
,
tb
.
tb_frame
.
f_code
.
co_name
,
str
(
tb
.
tb_lineno
)
))
tb
=
tb
.
tb_next
file
,
function
,
line
=
tbinfo
[
-
1
]
info
=
' '
.
join
([
'[%s|%s|%s]'
%
x
for
x
in
tbinfo
])
self
.
debug_log
(
'Exception raised in "%s" (%s line %s). Traceback:'
\
%
(
function
,
file
,
line
))
self
.
debug_log
(
info
)
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
):
self
.
close
()
self
.
close
()
...
@@ -61,7 +88,18 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -61,7 +88,18 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
# Receive a (multiline) message.
# Receive a (multiline) message.
while
True
:
while
True
:
chunk
=
self
.
recv
(
1
)
try
:
chunk
=
self
.
recv
(
1
)
except
socket
.
error
,
e
:
# Suppress "Resource temporarily unavailable" exceptions.
if
e
.
errno
==
EAGAIN
:
# Wait 5 ms
import
time
time
.
sleep
(
0.005
)
continue
else
:
raise
if
not
chunk
:
if
not
chunk
:
raise
RuntimeError
(
'socket connection broken'
)
raise
RuntimeError
(
'socket connection broken'
)
if
chunk
in
[
'-'
,
'+'
]:
if
chunk
in
[
'-'
,
'+'
]:
...
@@ -78,7 +116,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -78,7 +116,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
:
...
@@ -112,7 +150,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -112,7 +150,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
:
...
@@ -145,5 +183,5 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
...
@@ -145,5 +183,5 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
if
__name__
==
'__main__'
:
if
__name__
==
'__main__'
:
client
=
ClientConnection
()
client
=
ClientConnection
()
client
.
connect
((
'
ow150.science.uva.nl
'
,
16897
))
client
.
connect
((
'
localhost
'
,
16897
))
client
.
init_loop
()
client
.
init_loop
()
telematica/ass1/base_bar.py
View file @
bbd17ad8
...
@@ -16,15 +16,15 @@ class BaseBar(object):
...
@@ -16,15 +16,15 @@ class BaseBar(object):
self
.
color_pair
=
curses
.
color_pair
(
0
)
self
.
color_pair
=
curses
.
color_pair
(
0
)
self
.
_prefix
=
''
self
.
_prefix
=
''
def
prefix
(
self
,
value
=
''
):
def
prefix
(
self
,
value
=
None
):
if
not
value
:
if
value
==
None
:
# Note: an empty string can also be set.
return
self
.
_prefix
return
self
.
_prefix
self
.
_prefix
=
value
self
.
_prefix
=
value
self
.
display
(
self
.
_msg
)
self
.
display
(
self
.
_msg
)
def
display
(
self
,
msg
):
def
display
(
self
,
msg
):
self
.
_msg
=
msg
self
.
_msg
=
msg
if
self
.
prefix
:
if
self
.
prefix
()
:
msg
=
self
.
prefix
()
+
' '
+
msg
msg
=
self
.
prefix
()
+
' '
+
msg
# Curses will raise a 'curses.error' when the last possible character is
# Curses will raise a 'curses.error' when the last possible character is
# written. This exception should therefore always be catched. The raised
# written. This exception should therefore always be catched. The raised
...
...
telematica/ass1/cli.py
View file @
bbd17ad8
import
curses
import
curses
import
sys
import
sys
import
threading
import
threading
from
time
import
sleep
from
async
import
ClientConnection
from
async
import
ClientConnection
from
chat_window
import
ChatWindow
from
chat_window
import
ChatWindow
...
@@ -102,6 +103,8 @@ class CLI:
...
@@ -102,6 +103,8 @@ class CLI:
c
=
self
.
stdscr
.
getch
()
c
=
self
.
stdscr
.
getch
()
if
c
!=
curses
.
ERR
and
self
.
command_bar
.
handle_input
(
c
):
if
c
!=
curses
.
ERR
and
self
.
command_bar
.
handle_input
(
c
):
break
break
# wait 1 milliseconds
sleep
(.
001
)
except
KeyboardInterrupt
:
except
KeyboardInterrupt
:
self
.
execute
(
'quit'
)
self
.
execute
(
'quit'
)
...
@@ -141,6 +144,7 @@ class CLI:
...
@@ -141,6 +144,7 @@ class CLI:
# The chat connection is ran in a separate thread.
# The chat connection is ran in a separate thread.
self
.
connection_thread
=
threading
.
Thread
()
self
.
connection_thread
=
threading
.
Thread
()
self
.
connection_thread
.
daemon
=
True
self
.
connection_thread
.
run
=
main
.
connection
.
init_loop
self
.
connection_thread
.
run
=
main
.
connection
.
init_loop
self
.
connection_thread
.
start
()
self
.
connection_thread
.
start
()
...
@@ -152,10 +156,13 @@ class CLI:
...
@@ -152,10 +156,13 @@ class CLI:
and the 'Offline' message is set to the info bar.
and the 'Offline' message is set to the info bar.
"""
"""
self
.
connection
.
close
()
del
self
.
connection_thread
self
.
connection
=
None
self
.
connection
=
None
self
.
display_info
(
'Offline. Type "/connect HOST" to connect'
\
self
.
display_info
(
'Offline. Type "/connect HOST" to connect'
\
+
' to another chat server.'
)
+
' to another chat server.'
)
self
.
debug_window
.
clear
()
self
.
info_bar
.
prefix
(
''
)
self
.
refresh
()
def
help
(
main
):
def
help
(
main
):
if
main
.
chat_window
.
displayed_help
:
if
main
.
chat_window
.
displayed_help
:
...
@@ -204,13 +211,6 @@ All commands listed below should be preceded by a slash:
...
@@ -204,13 +211,6 @@ All commands listed below should be preceded by a slash:
main
.
chat_window
.
window
.
refresh
()
main
.
chat_window
.
window
.
refresh
()
def
quit
(
main
):
def
quit
(
main
):
# Disconnect the connection
e
=
None
try
:
del
self
.
connection
except
Exception
,
e
:
pass
# Reverse the curses-friendly terminal settings.
# Reverse the curses-friendly terminal settings.
curses
.
nocbreak
();
curses
.
nocbreak
();
self
.
stdscr
.
keypad
(
0
);
self
.
stdscr
.
keypad
(
0
);
...
@@ -219,9 +219,10 @@ All commands listed below should be preceded by a slash:
...
@@ -219,9 +219,10 @@ All commands listed below should be preceded by a slash:
# Restore the terminal to its original operating mode.
# Restore the terminal to its original operating mode.
curses
.
endwin
()
curses
.
endwin
()
if
e
:
# Disconnect the connection
raise
e
if
hasattr
(
self
,
'connection_thread'
):
del
self
.
connection_thread
self
.
connection
=
None
sys
.
exit
(
0
)
sys
.
exit
(
0
)
def
raw
(
main
,
cmd
):
def
raw
(
main
,
cmd
):
...
...
telematica/ass1/server.py
View file @
bbd17ad8
...
@@ -4,16 +4,25 @@ import asyncore
...
@@ -4,16 +4,25 @@ import asyncore
import
logging
import
logging
import
logging.config
import
logging.config
import
os
import
os
import
re
import
socket
import
socket
import
sys
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
class
SocketError
(
RuntimeError
):
pass
class
ClientData
(
object
):
pass
class
Server
(
asyncore
.
dispatcher
):
class
Server
(
asyncore
.
dispatcher
):
"""
"""
Basic server which will listen on an host address and port. The given
Basic server which will listen on an host address and port. The given
...
@@ -27,18 +36,18 @@ class Server(asyncore.dispatcher):
...
@@ -27,18 +36,18 @@ 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
)
self
.
set_reuse_addr
()
self
.
bind
((
ip
,
port
))
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
.
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
)
self
.
active_clients
=
{}
# Dictonary which maps ip/port tuple to client data object.
self
.
clients
=
{}
def
handle_accept
(
self
):
def
handle_accept
(
self
):
"""
"""
...
@@ -48,53 +57,160 @@ class Server(asyncore.dispatcher):
...
@@ -48,53 +57,160 @@ 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
(
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
,
self
)
client
.
handler
=
self
.
handler
(
conn
,
addr
,
client
,
self
)
client
.
username
=
''
self
.
clients
[
'%s:%d'
%
addr
]
=
client
return
client
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
]
except
KeyError
:
pass
def
change_username
(
self
,
addr
,
username
):
"""
Update the username of the client.
"""
self
.
clients
[
'%s:%d'
%
addr
].
username
=
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
,
server
):
def
__init__
(
self
,
conn
,
address
,
client
,
server
):
AsyncBase
.
__init__
(
self
)
AsyncBase
.
__init__
(
self
)
asyncore
.
dispatcher
.
__init__
(
self
,
conn
)
asyncore
.
dispatcher
.
__init__
(
self
,
conn
)
self
.
address
=
address
self
.
address
=
address
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.
try
:
while
True
:
while
True
:
chunk
=
self
.
recv
(
1
)
chunk
=
self
.
recv
(
1
)
if
not
chunk
:
if
not
chunk
:
raise
RuntimeError
(
'socket connection broken'
)
raise
SocketError
(
'socket connection broken'
)
elif
chunk
==
'
\
n
'
and
buf
[
-
1
]
==
'
\
r
'
:
elif
chunk
==
'
\
n
'
and
buf
[
-
1
]
==
'
\
r
'
:
break
break
buf
+=
chunk
buf
+=
chunk
except
SocketError
,
socket
.
error
:
self
.
log
.
info
(
'client %s:%d disconnected or socket is broken.'
\
%
self
.
address
)
self
.
server
.
disconnect_client
(
self
.
address
)
return
# 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
)
def
handle_error
(
self
):
self
.
server
.
disconnect_client
(
self
.
address
)
self
.
send_positive
(
'Ok'
)
def
parse_response
(
self
,
buf
):
#self.send_negative('Ok')
"""
>>> class DummyServer(object):
... def __init__(self): self.log = None
... def change_username(self, addr, username): pass
... def send_all(self, msg, sender): pass
>>> req = RequestHandler(None, None, None, DummyServer())
>>> assert req.parse_response('CHAT')
>>> assert req.parse_response('USER foo')
>>> # Some error handling
>>> assert not req.parse_response('CHAT ') # Client must send CHAT
>>> assert not req.parse_response('USER') # No username given.
>>> assert not req.parse_response('USER j/k') # username has a /
"""
cmd
=
buf
.
split
(
' '
)[
0
]
if
buf
==
'CHAT'
:
# Client wants to chat.
return
self
.
send_positive
(
'Ok'
)
if
cmd
==
'USER'
:
# User changes/sets its username.
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
:])
return
self
.
send_positive
(
'Ok'
)
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'
:
clients
=
self
.
server
.
clients
self
.
send_raw
(
'+Ok:
\
r
\
n
'
\
+
''
.
join
([
clients
[
c
].
username
+
'
\
r
\
n
'
for
c
in
clients
]))
return
True
return
self
.
send_negative
(
'Unsupported command.'
)
def
send_positive
(
self
,
msg
):
def
send_positive
(
self
,
msg
):
self
.
send_raw
(
'+%s'
%
msg
)
self
.
send_raw
(
'+%s'
%
msg
)
return
True
def
send_negative
(
self
,
msg
):
def
send_negative
(
self
,
msg
):
self
.
send_raw
(
'-%s'
%
msg
)
self
.
send_raw
(
'-%s'
%
msg
)
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
):
self
.
username
=
username
self
.
server
.
change_username
(
self
.
address
,
username
)
if
__name__
==
'__main__'
:
if
__name__
==
'__main__'
:
if
len
(
sys
.
argv
)
!=
3
:
if
len
(
sys
.
argv
)
!=
3
:
...
...
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