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
import
random
from
asyncbase
import
AsyncBase
# Socket error "Resource temporarily unavailable": try again ;)
EAGAIN
=
11
class
ClientConnection
(
object
,
AsyncBase
,
asyncore
.
dispatcher
):
def
__init__
(
self
):
...
...
@@ -51,6 +55,29 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
if
'connect'
in
self
.
event_list
:
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
):
self
.
close
()
...
...
@@ -61,7 +88,18 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
# Receive a (multiline) message.
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
:
raise
RuntimeError
(
'socket connection broken'
)
if
chunk
in
[
'-'
,
'+'
]:
...
...
@@ -78,7 +116,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
buf
+=
chunk
buf
=
buf
[:
-
1
]
self
.
debug_log
(
'< %s'
%
buf
)
self
.
debug_log
(
'< %s'
%
buf
.
replace
(
'
\
n
'
,
'
\
\
n'
).
replace
(
'
\
r
'
,
'
\
\
r'
)
)
# Invoke the proper callback function.
if
not
self
.
verified
:
...
...
@@ -112,7 +150,7 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
if
not
self
.
authentification_sent
:
self
.
send_queue
.
put
(
'USER %s'
%
self
.
user
)
self
.
authentification_sent
=
True
elif
buf
[
1
:
9
]
==
'Username
'
:
elif
buf
[
0
]
==
'+
'
:
# TODO: handle 'username is taken'.
self
.
authenticated
=
True
if
'authenticated'
in
self
.
event_list
:
...
...
@@ -145,5 +183,5 @@ class ClientConnection(object, AsyncBase, asyncore.dispatcher):
if
__name__
==
'__main__'
:
client
=
ClientConnection
()
client
.
connect
((
'
ow150.science.uva.nl
'
,
16897
))
client
.
connect
((
'
localhost
'
,
16897
))
client
.
init_loop
()
telematica/ass1/base_bar.py
View file @
bbd17ad8
...
...
@@ -16,15 +16,15 @@ class BaseBar(object):
self
.
color_pair
=
curses
.
color_pair
(
0
)
self
.
_prefix
=
''
def
prefix
(
self
,
value
=
''
):
if
not
value
:
def
prefix
(
self
,
value
=
None
):
if
value
==
None
:
# Note: an empty string can also be set.
return
self
.
_prefix
self
.
_prefix
=
value
self
.
display
(
self
.
_msg
)
def
display
(
self
,
msg
):
self
.
_msg
=
msg
if
self
.
prefix
:
if
self
.
prefix
()
:
msg
=
self
.
prefix
()
+
' '
+
msg
# Curses will raise a 'curses.error' when the last possible character is
# written. This exception should therefore always be catched. The raised
...
...
telematica/ass1/cli.py
View file @
bbd17ad8
import
curses
import
sys
import
threading
from
time
import
sleep
from
async
import
ClientConnection
from
chat_window
import
ChatWindow
...
...
@@ -102,6 +103,8 @@ class CLI:
c
=
self
.
stdscr
.
getch
()
if
c
!=
curses
.
ERR
and
self
.
command_bar
.
handle_input
(
c
):
break
# wait 1 milliseconds
sleep
(.
001
)
except
KeyboardInterrupt
:
self
.
execute
(
'quit'
)
...
...
@@ -141,6 +144,7 @@ class CLI:
# The chat connection is ran in a separate thread.
self
.
connection_thread
=
threading
.
Thread
()
self
.
connection_thread
.
daemon
=
True
self
.
connection_thread
.
run
=
main
.
connection
.
init_loop
self
.
connection_thread
.
start
()
...
...
@@ -152,10 +156,13 @@ class CLI:
and the 'Offline' message is set to the info bar.
"""
self
.
connection
.
close
()
del
self
.
connection_thread
self
.
connection
=
None
self
.
display_info
(
'Offline. Type "/connect HOST" to connect'
\
+
' to another chat server.'
)
self
.
debug_window
.
clear
()
self
.
info_bar
.
prefix
(
''
)
self
.
refresh
()
def
help
(
main
):
if
main
.
chat_window
.
displayed_help
:
...
...
@@ -204,13 +211,6 @@ All commands listed below should be preceded by a slash:
main
.
chat_window
.
window
.
refresh
()
def
quit
(
main
):
# Disconnect the connection
e
=
None
try
:
del
self
.
connection
except
Exception
,
e
:
pass
# Reverse the curses-friendly terminal settings.
curses
.
nocbreak
();
self
.
stdscr
.
keypad
(
0
);
...
...
@@ -219,9 +219,10 @@ All commands listed below should be preceded by a slash:
# Restore the terminal to its original operating mode.
curses
.
endwin
()
if
e
:
raise
e
# Disconnect the connection
if
hasattr
(
self
,
'connection_thread'
):
del
self
.
connection_thread
self
.
connection
=
None
sys
.
exit
(
0
)
def
raw
(
main
,
cmd
):
...
...
telematica/ass1/server.py
View file @
bbd17ad8
...
...
@@ -4,16 +4,25 @@ import asyncore
import
logging
import
logging.config
import
os
import
re
import
socket
import
sys
from
asyncbase
import
AsyncBase
# Greeting message sent to a connected client.
GREETING_MSG
=
'Hi there!'
# Major and minor version of this server.
MAJOR_VERSION
=
1
MINOR_VERSION
=
0
class
SocketError
(
RuntimeError
):
pass
class
ClientData
(
object
):
pass
class
Server
(
asyncore
.
dispatcher
):
"""
Basic server which will listen on an host address and port. The given
...
...
@@ -27,18 +36,18 @@ class Server(asyncore.dispatcher):
self
.
port
=
port
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'
)
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
.
listen
(
5
)
self
.
active_clients
=
{}
# Dictonary which maps ip/port tuple to client data object.
self
.
clients
=
{}
def
handle_accept
(
self
):
"""
...
...
@@ -48,53 +57,160 @@ class Server(asyncore.dispatcher):
try
:
conn
,
addr
=
self
.
accept
()
self
.
log
.
info
(
'accepted client %s:%d'
%
addr
)
client
=
self
.
connect_client
(
conn
,
addr
)
except
socket
.
error
:
self
.
log
.
warning
(
'warning: server accept() threw an exception.'
)
return
except
TypeError
:
self
.
log
.
warning
(
'warning: server accept() threw EWOULDBLOCK.'
)
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
# 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
):
def
__init__
(
self
,
conn
,
address
,
server
):
def
__init__
(
self
,
conn
,
address
,
client
,
server
):
AsyncBase
.
__init__
(
self
)
asyncore
.
dispatcher
.
__init__
(
self
,
conn
)
self
.
address
=
address
self
.
client
=
client
self
.
server
=
server
self
.
username
=
''
self
.
log
=
self
.
server
.
log
self
.
send_welcome_message
()
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"
\
%
(
MAJOR_VERSION
,
MINOR_VERSION
,
GREETING_MSG
))
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
=
''
# Receive a message from the client.
while
True
:
chunk
=
self
.
recv
(
1
)
if
not
chunk
:
raise
RuntimeError
(
'socket connection broken'
)
elif
chunk
==
'
\
n
'
and
buf
[
-
1
]
==
'
\
r
'
:
break
buf
+=
chunk
try
:
while
True
:
chunk
=
self
.
recv
(
1
)
if
not
chunk
:
raise
SocketError
(
'socket connection broken'
)
elif
chunk
==
'
\
n
'
and
buf
[
-
1
]
==
'
\
r
'
:
break
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
]
self
.
debug_log
(
'< %s'
%
buf
)
self
.
parse_response
(
buf
)
def
handle_error
(
self
):
self
.
server
.
disconnect_client
(
self
.
address
)
self
.
send_positive
(
'Ok'
)
#self.send_negative('Ok')
def
parse_response
(
self
,
buf
):
"""
>>> 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
):
self
.
send_raw
(
'+%s'
%
msg
)
return
True
def
send_negative
(
self
,
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
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