You are not logged in.
I got bored and decided to write a file server.
Server:
#!/usr/bin/env python
import optparse, os, socket, select, signal, sys
e_ok = 0
e_emptyreq = 1
e_notfile = 2
e_other = 3
fsize_len = 16 # length of file size word
def err(msg):
sys.stderr.write(msg + '\n')
sys.exit(1)
# send a message with appropriate status code
def sendmsg(sock, code, msg):
sock.sendall('{0}{1}'.format(chr(code), msg))
if code > e_ok:
sock.close()
# turn an integer into a series of octets -- maximum value is 2^128 - 1
def makefsize(size):
i = 0
word = ''
while i < fsize_len:
word = '{0}{1}'.format(chr(size & 0xFF), word)
size >>= 8
i += 1
return word
def main():
parser = optparse.OptionParser(usage = 'Usage: %prog <options>')
parser.add_option('-d', '--docroot', dest = 'docroot', action = 'store', default = os.path.expanduser('~'), help = 'directory to serve files from [~]')
parser.add_option('-p', '--port', dest = 'port', action = 'store', type = 'int', default = 6666, help = 'port to listen on [%default]')
parser.add_option('--allow-dotfiles', dest = 'allow_dotfiles', action = 'store_true', default = False, help = 'allow clients to access dotfiles [no]')
parser.add_option('--send-blocksize', dest = 'send_blocksize', action = 'store', type = 'int', default = 4096, help = 'number of bytes to send at a time [%default]')
parser.add_option('--connect-backlog', dest = 'connect_backlog', action = 'store', type = 'int', default = 3, help = 'backlog for connect() [%default]')
opts, args = parser.parse_args()
master = socket.socket(socket.AF_INET, socket.SOCK_STREAM);
master.bind(('localhost', opts.port))
master.listen(opts.connect_backlog);
os.chroot(opts.docroot)
signal.signal(signal.SIGCHLD, signal.SIG_IGN) # let the system auto-reap zombie processes
while True:
select.select([master], [], []) # not sure what to use here; something in {poll}, maybe?
# fire-and-forget a new process to handle the request
if os.fork() == 0:
client = master.accept()
print('Incoming connection from {0}:{1}'.format(client[1][0], client[1][1]))
# hmm, this just sits there and blocks if the client requests an empty file
req = client[0].recv(4096).rstrip() # get rid of trailing newline
if len(req) == 0:
sendmsg(client[0], e_emptyreq, 'Empty request')
sys.exit(1)
if not os.path.isfile(req) or (not opts.allow_dotfiles and os.path.basename(req)[0] == '.'):
sendmsg(client[0], e_notfile, 'Not a file')
sys.exit(1)
try:
fp = open(req, 'rb')
except IOError, e:
sendmsg(client[0], e_other, str(e))
sys.exit(1)
# send info: status code + file size
client[0].sendall('{0}{1}'.format(chr(e_ok), makefsize(os.stat(req).st_size)))
while 1:
read = fp.read(opts.send_blocksize)
if read == '': # at EOF, terminate client
fp.close()
client[0].close()
break
client[0].sendall(read)
sys.exit(1) # done with this process
master.close()
try: main()
except: raise
Client:
#!/usr/bin/env python
import optparse, os, socket, sys
e_ok = 0
fsize_len = 16 # length of file size word
def err(msg, fatal = False):
sys.stderr.write(msg + '\n')
if fatal: sys.exit(1)
# turn a series of octets into an integer
def getfsize(word):
if len(word) != fsize_len:
err('Bad file size')
size = i = 0
while i < fsize_len:
size <<= 8
size |= ord(word[i])
i += 1
return size
def main():
parser = optparse.OptionParser(usage = 'Usage: %prog [options] [file1] [file2] ...')
parser.add_option('-s', '--server', dest = 'server', action = 'store', default = 'localhost', help = 'server to download files from [%default]')
parser.add_option('-p', '--port', dest = 'port', action = 'store', default = 6666, type = 'int', help = 'port on server [%default]')
parser.add_option('-d', '--dl-dir', dest = 'dldir', action = 'store', default = '.', help = 'directory to place downloaded files in [%default]')
parser.add_option('--recv-blocksize', dest = 'recv_blocksize', action = 'store', default = 4096, type = 'int', help = 'number of bytes to receive at a time [%default]')
opts, filenames = parser.parse_args()
for filename in filenames:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((opts.server, opts.port))
print('Requesting file {0}'.format(filename))
s.sendall(filename)
inf = s.recv(1 + fsize_len) # status code + file size
if len(inf) == 0:
err('Empty response')
continue
code = ord(inf[0])
inf = inf[1:]
if code != e_ok:
err('Error: ' + inf)
continue
if len(inf) != fsize_len:
err('Server sent invalid file information')
continue
already_exists = False
filename = opts.dldir + '/' + os.path.basename(filename)
print('Destination: {0}'.format(filename))
while os.path.exists(filename):
already_exists = True
filename += '_'
if already_exists: print('Already exists, writing to: {0}'.format(filename))
fp = open(filename, 'wb')
size = getfsize(inf)
rsize = 0
read = ''
sys.stdout.write('\033[?25l') # hide the cursor
while 1:
sys.stdout.write('\033[sDownloading... {0}/{1} {2}% \033[u'
.format(rsize, size, int((float(rsize) / float(size)) * 100)))
read = s.recv(opts.recv_blocksize)
if read == '': break
rsize += len(read)
fp.write(read)
fp.close()
s.close()
print('\033[?25h\nDone.') # show the cursor
try: main()
except:
sys.stdout.write('\033[?25h') # restore the cursor if something went wrong
raise
Can has feedback?
(Note: I'm in the process of reworking clutter from an earlier (read: worse) design, so please point out anything useless in the code.)
Last edited by Peasantoid (2009-05-19 23:11:35)
Offline
sooo, this is basically a read only ftp client-server application without the ftp?
neat
Offline
Nice and compact. I'll try it out this afternoon.
Offline
nice and minimal... great reference for python beginners. ^^
Offline
lol i was just about to start working on something like this.
"is adult entertainment killing our children or is killing our children entertaining adults?" Marilyn Manson
Offline
Hey thanks, guys. Wasn't expecting to get such positive feedback — it's my first major Python project.
I plan to add command-line option support soon, once I figure out Python's getopt module.
Offline
just the thing for you then
Let me know how you get on
"is adult entertainment killing our children or is killing our children entertaining adults?" Marilyn Manson
Offline
just the thing for you then
![]()
Let me know how you get on
![]()
Thanks, that was great!
On a related note, I've updated the code.
Offline
whoops, missed you code updates. Feel free to delete.
Last edited by keenerd (2009-05-20 13:43:48)
Offline