Python 连接FTP可以直接使用ftplib类库,操作起来很方便。它的原理也就是通过Socket连接FTP服务器,然后发送FTP指令,接受FTP服务器返回的数据。
1.下面这个是我在Python开源代码中精简的FTP代码:
import os import sys # Import SOCKS module if it exists, else standard socket module socket try: import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn except ImportError: import socket from socket import _GLOBAL_DEFAULT_TIMEOUT __all__ = ["FTP","Netrc"] # Magic number from <socket.h> MSG_OOB = 0x1 # Process data out of band # The standard FTP server control port FTP_PORT = 21 # Exception raised when an error or invalid response is received class Error(Exception): pass class error_reply(Error): pass # unexpected [123]xx reply class error_temp(Error): pass # 4xx errors class error_perm(Error): pass # 5xx errors class error_proto(Error): pass # response does not begin with [1-5] # All exceptions (hopefully) that may be raised here and that aren't # (always) programming errors on our side all_errors = (Error, IOError, EOFError) # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF) CRLF = '/r/n' B_CRLF = b'/r/n' # The class itself class FTP: host = '' port = FTP_PORT sock = None file = None welcome = None passiveserver = 1 encoding = "latin1" def __init__(self, host='', user='', passwd='', acct='', timeout=_GLOBAL_DEFAULT_TIMEOUT): self.timeout = timeout if host: self.connect(host) if user: self.login(user, passwd, acct) def connect(self, host='', port=0, timeout=-999): if host != '': self.host = host if port > 0: self.port = port if timeout != -999: self.timeout = timeout self.sock = socket.create_connection((self.host, self.port), self.timeout) self.af = self.sock.family self.file = self.sock.makefile('r', encoding=self.encoding) self.welcome = self.getresp() return self.welcome # Internal: send one line to the server, appending CRLF def putline(self, line): line = line + CRLF self.sock.sendall(line.encode(self.encoding)) # Internal: send one command to the server (through putline()) def putcmd(self, line): self.putline(line) # Internal: return one line from the server, stripping CRLF. # Raise EOFError if the connection is closed def getline(self): line = self.file.readline() if not line: raise EOFError if line[-2:] == CRLF: line = line[:-2] elif line[-1:] in CRLF: line = line[:-1] print(line) return line def getmultiline(self): line = self.getline() if line[3:4] == '-': code = line[:3] while 1: nextline = self.getline() line = line + ('/n' + nextline) if nextline[:3] == code and / nextline[3:4] != '-': break return line def getresp(self): resp = self.getmultiline() self.lastresp = resp[:3] c = resp[:1] if c in ('1', '2', '3'): return resp if c == '4': raise error_temp(resp) if c == '5': raise error_perm(resp) raise error_proto(resp) def voidresp(self): """Expect a response beginning with '2'.""" resp = self.getresp() if resp[:1] != '2': raise error_reply(resp) return resp def sendcmd(self, cmd): '''Send a command and return the response.''' self.putcmd(cmd) return self.getresp() def voidcmd(self, cmd): """Send a command and expect a response beginning with '2'.""" self.putcmd(cmd) return self.voidresp() def login(self, user = '', passwd = '', acct = ''): '''Login, default anonymous.''' if not user: user = 'anonymous' if not passwd: passwd = '' if not acct: acct = '' if user == 'anonymous' and passwd in ('', '-'): passwd = passwd + 'anonymous@' resp = self.sendcmd('USER ' + user) if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd) if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct) if resp[0] != '2': raise error_reply(resp) return resp def pwd(self): '''Return current working directory.''' resp = self.sendcmd('PWD') return parse257(resp) def quit(self): '''Quit, and close the connection.''' resp = self.voidcmd('QUIT') self.close() return resp def close(self): '''Close the connection without assuming anything about it.''' if self.file: self.file.close() self.sock.close() self.file = self.sock = None def parse257(resp): '''Parse the '257' response for a MKD or PWD request. This is a response to a MKD or PWD request: a directory name. Returns the directoryname in the 257 reply.''' if resp[:3] != '257': raise error_reply(resp) if resp[3:5] != ' "': return '' # Not compliant to RFC 959, but UNIX ftpd does this dirname = '' i = 5 n = len(resp) while i < n: c = resp[i] i = i+1 if c == '"': if i >= n or resp[i] != '"': break i = i+1 dirname = dirname + c return dirname def test(): ftp = FTP('127.0.0.1','123456','654321') resp = ftp.sendcmd("PWD") print(repr(resp)) ftp.quit() if __name__ == '__main__': test()
效果:
>>> 220-FTP server ready. 220 This is a private system - No anonymous login 331 User 20000226 OK. Password required 230-User 20000226 has group access to: billing shiftcdr invoices methos 230 OK. Current restricted directory is / 257 "/" is your current location '257 "/" is your current location' 221-Goodbye. You uploaded 0 and downloaded 0 kbytes. 221 Logout. >>>
2.更精简FTP代码:
import socket HOST = '127.0.0.1' # The remote host PORT = 21 # The same port as used by the server myencoding = "latin1" #Latin1是ISO-8859-1的别名 CRLF = '/r/n' #发送FTP命令中必须包含 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) file = s.makefile('r', encoding = myencoding) print(repr(file.readline())) s.sendall(('USER 123456' + CRLF ).encode(myencoding)) #'USER 123456'与CRLF之间不可有空格,否则FTP服务器不识别。 print(repr(file.readline())) s.sendall(('PASS 654321' + CRLF ).encode(myencoding)) print(repr(file.readline())) s.sendall(('PWD ' + CRLF ).encode(myencoding)) print(repr(file.readline())) #print(repr(s.recv(1024))) #s.send(b'QUIT /r/n') #此方式也可行。直接发送字节串,send返回发送的字节串长度 s.sendall(('QUIT ' + CRLF ).encode(myencoding)) #发送编码之后的字节串,发送成功则返回None print(repr(file.readline())) print(repr(file.readline())) print(repr(file.readline())) print(repr(file.readline())) file.close() s.close()
效果:
>>> '220-FTP server ready./n' '220 This is a private system - No anonymous login/n' '331 User 20000226 OK. Password required/n' '230-User 20000226 has group access to: billing shiftcdr invoices methos /n' '230 OK. Current restricted directory is //n' '257 "/" is your current location/n' '221-Goodbye. You uploaded 0 and downloaded 0 kbytes./n' '221 Logout./n' >>>
Latin1解释:
Latin1是ISO-8859-1的别名,有些环境下写作Latin-1。
ISO-8859-1
ISO-8859-1编码是单字节编码,向下兼容ASCII,其编码范围是0x00-0xFF,0x00-0x7F之间完全和ASCII一致,0x80-0x9F之间是控制字符,0xA0-0xFF之间是文字符号。
ISO-8859-1收录的字符除ASCII收录的字符外,还包括西欧语言、希腊语、泰语、阿拉伯语、希伯来语对应的文字符号。欧元符号出现的比较晚,没有被收录在ISO-8859-1当中。
因为ISO-8859-1编码范围使用了单字节内的所有空间,在支持ISO-8859-1的系统中传输和存储其他任何编码的字节流都不会被抛弃。换言之,把其他任何编码的字节流当作ISO-8859-1编码看待都没有问题。这是个很重要的特性,MySQL数据库默认编码是Latin1就是利用了这个特性。ASCII编码是一个7位的容器,ISO-8859-1编码是一个8位的容器。