自己用socket写一个FTP客户端,模拟主动被动模式。(先支持LIST命令)
# -*- coding: utf-8 -*-
import socket, sys, thread, threading
def main_sock(daddr, actions, saddr=()):
if saddr:
try:
sc=socket.create_connection(daddr, 3, saddr)
#print "Now %s connecting to %s ... ..."%(saddr, daddr)
except socket.error:
print 'TCP socket connect %s => %s failed'%(saddr, daddr)
return
else:
try:
sc=socket.create_connection(daddr, 3)
except socket.error:
print 'TCP socket connect %s failed'%(daddr)
return
#step 1 : print welcome informations
print "<--recive: ",sc.recv(1024)
#step 2 login
login(sc, actions[0])
#step 3 use ASCII TYPE send and recv data
sc.send('TYPE A\r\n')
recv=sc.recv(1024)
print "send--> TYPE A\n <---recive: ",recv
#step 4 use mode PORT OR PASV
if actions[1][0]==1: # PORT mode
if daddr[0].find(':')!=-1: #IPV6
sc.send('EPRT |2|%s|%s|\r\n'%(actions[1][1], actions[1][2]))
elif daddr[0].find(':')==-1: #ipv4
sc.send('PORT %s\r\n'%handle_PORT(actions[1][1], actions[1][2]))
print "<--recive: ",sc.recv(1024)
#step 5 build sub connection
th_1=threading.Thread(target=sub_bindSock, args=(actions[1][1], actions[1][2]))
th_1.start()
sc.send(actions[2]) #send LIST command
print "<--recive: ",sc.recv(1024)
th_1.join()
elif actions[1][0]==0: #PASV mode
if daddr[0].find(':')!=-1: #ipv6
sc.send('EPSV\r\n')
recv=sc.recv(1024)
subDport=int(recv[recv.find('(|||')+4:recv.find('|)')])
elif daddr[0].find(':')==-1: #ipv4
sc.send('PASV\r\n')
recv=sc.resv(1024)
subDport=handle_PASV(recvStr)
subDaddr=(daddr[0], subDport)
#step 5 build sub connection
th_2=threading.Thread(target=sub_sock, args=(subDaddr, ))
th_2.start()
sc.send(actions[2])
th_2.join()
while 1:
try:
data=sc.recv(1024)
if not len(data):
break
else:
print "<--recived: ",data
except:
print "timeout!"
break
sc.close()
def sub_sock(daddr, saddr=()):
try:
subSc=socket.create_connection(daddr, 3, saddr)
#print "Now %s connecting to %s ... ..."%(saddr, daddr)
except socket.error:
print 'TCP socket connect %s => %s failed'%(saddr, daddr)
while 1:
try:
data=subSc.recv(2048)
if not len(data):
break
else:
print "<--subCon recived:",data
except:
print "timeout!"
break
subSc.close()
def sub_bindSock(saddr, sport):
if saddr.find(':')==-1:
sc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sc.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
else:
sc = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
try:
sc.bind((saddr, sport))
sc.listen(5)
sc.settimeout(3)
except:
print "bind %s:%s failed!"%(sip, sport)
return -1
peerSock, peerAddr=sc.accept()
print "sub connect is builded\r\n%s<------>%s"%((saddr, sport), peerAddr)
while 1:
try:
data=peerSock.recv(2048)
if not len(data):
break
else:
print "<--subCon recived:",data
except:
print "timeout!"
break
sc.close()
def login(sock, loginInfo):
sock.send('USER %s\r\n'%loginInfo[0])
recv=sock.recv(1024)
print "send--> username ok!\n<--recive: ",recv
sock.send('PASS %s\r\n'%loginInfo[1])
recv=sock.recv(1024)
print "send--> password ok!\n<--recive: ",recv
def handle_PORT(saddr, sport):
'''主动模式时,如果地址是IPV4,将地址和端口的格式进行转换,然后放到Port 命令中发送 '''
addrStr=','.join(saddr.split('.'))
temp=divmod(sport, 256)
portStr=str(temp[0])+','+str(temp[1])
return addrStr+','+portStr
def handle_PASV(recvStr):
'''针对IPV4,将受到的子连接端口字符串信息转换为真正的端口,并返回'''
temp=recvStr[recvStr.find('Mode (')-1:recvStr.find(')')]
a=temp.split(',')[4]
b=temp.split(',')[5]
subPort=int(a)*256+int(b)
return subPort
if __name__=='__main__':
#target='192.168.10.112'
target='4001::112'
port=21
Daddr=(target, port)
username='anonymous'
passwd='IE@126.com'
loginInfo=(username, passwd)
FTPmode=1 # 1 means PORT mode, 0 means PASV mode
#saddr='192.168.10.102'
saddr='2000::5d80:b20f:8631:9782'
sport=11178
PASV_or_PORT=(FTPmode, saddr, sport)
LIST='LIST\r\n'
actions=(loginInfo, PASV_or_PORT, LIST)
main_sock(Daddr, actions)
FTP协议是典型的多连接协议。通信的时候会建立两个通道:主连接和子连接。主连接传输控制信令(例如上传下载列出目录等),当需要传输数据的时候,会在主连接中使用被动模式或者主动模式协商好端口,然后打开一个子连接,传输完后,子连接就立即被拆掉了。
通过自己动手实现一个FTP客户端,可以加深对FTP协议的理解。
以上,main_sock为FTP的主连接处理,sub_sock为子连接的处理函数,sub_bindSock为主动模式时,开启一个端口监听,等待服务器主动过来连接。
好了,代码中都有注释。
转载于:https://blog.51cto.com/ybtest/1840025