最近学习python到socketserver,本着想试一下水的深浅,采用Python3.6.
目录结构如下:
receive_file和file为下载或上传文件存放目录,ftp_client为ftp客户端,ftp_server为server端。
server端源码:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socketserver
import os
error_code = {'400':'FILE IS NOT EXISTS'}
file_path = os.path.join(os.path.abspath('.'),'file') #获取文件目录路径
'''服务端采用socketserver方式'''
class MyTCPHandler(socketserver.BaseRequestHandler):
def handle(self):
while True:
# print('new conn',self.client_address)
data = self.request.recv(100) #接收客户端请求
if not data.decode():
break
elif data.decode().split()[0] == 'get': #server判断是下载还是上传文件,get是下载
offset = data.decode().split('|')[1] #取出偏移量
file = data.decode().split()[1].split('|')[0] #取出要下载的文件名
filename = os.path.join(file_path,file)
read_len = 0
if os.path.exists(filename) : #判断是否有资源
with open(filename,'rb') as fd:
while True:
send_data = fd.read(1024)
read_len += len(send_data) #记录读取数据长度
if send_data and read_len > int(offset): #达到偏移量发送数据
ack_msg = "SEND SIZE|%s" % len(send_data)
self.request.send(ack_msg.encode())
client_ack = self.request.recv(50)
if client_ack.decode() =="CLIENT_READY_TO_RECV":
self.request.send(send_data)
elif read_len <= int(offset):
continue
else:
send_data ='END'
self.request.send(send_data.encode()) #数据传输完毕发送finally信号
break
else:
msg = '400'
self.request.send(msg.encode())
elif data.decode().split()[0] == 'put': #判断客户端是不是上传行为
file = data.decode().split()[1] #获取需要上传的文件名
filename = os.path.join(file_path,file) #定义文件路径
log = "%s.%s" % (file,'log') #指定记录偏移日志文件名
logname = os.path.join(file_path,log) #定义日志路径
if os.path.exists(filename) and os.path.exists(logname): #如果要上传的文件和日志文件同时存在,说明需要进行续传
with open(logname) as f:
offset = f.read().strip() #读取偏移量
else:
offset = 0 #表示不需要进行续传,直接从头开始传
server_syn_msg = "offset %s" % offset #把偏移信息发送给客户端
self.request.send(server_syn_msg.encode())
total_len = int(offset) #获取已传输完的文件长度,即从这个位置开始接收新的数据
while True:
receive_ack = self.request.recv(100) #客户端接收到偏移信息后通知服务端要发送数据的长度信息,相当于一个ack
res_msg = receive_ack.decode().split('|')
if receive_ack.decode() == 'END': #判断文件是否上传完成,完成后删掉偏移日志
os.remove(logname)
break
elif res_msg[0].strip() =='SEND SIZE': #如果服务端收到了客户端发送过来的ack,给客户端返回一个syn信息,表示可以开始传数据了
res_size = res_msg[1]
self.request.send(b'CLIENT_READY_TO_RECV')
recv_data = self.request.recv(1024) #接收数据
total_len += len(recv_data) #记录接收数据长度
with open(filename,'ab') as fd: #以追加的方式写入文件
fd.write(recv_data)
with open(logname,'w') as f: #把已接收到的数据长度写入日志
f.write(str(total_len))
if __name__ == '__main__':
host,port = "localhost",5000
server = socketserver.ThreadingTCPServer((host,port),MyTCPHandler)
server.serve_forever() #开启服务端
客户端源码:
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import socket
import os,sys
receive_file_path = os.path.abspath(os.path.join(os.path.abspath('.'),'receive_file')) #指定文件目录路径
error_code = {'400':'FILE IS NOT EXISTS'}
'''使用类的方式,方便反射'''
class SOCKET(object):
def __init__(self,ip,port):
self.ip = ip
self.port = port
def socket_obj(self):
sk = socket.socket()
sk.connect((self.ip,self.port))
return sk
def get(self): #get表示从服务端下载文件到本地
conn = self.socket_obj() #生成对象
user_input = input('get filename:') #指定输入命令格式 get filename
# print(msg,type(msg))
filename = user_input.split()[1] #获取文件名
file = os.path.join(receive_file_path,filename) #下载文件的绝对路径
logname = '%s.%s' % (filename,'log') #生成日志名
log = os.path.join(receive_file_path,logname) #偏移量日志的绝对路径
if os.path.exists(log) and os.path.exists(file): #判断是否需要续传,如果需要就读出偏移量
with open(log) as f:
offset = f.read().strip()
else:
offset = 0 # 否则偏移量置0
msg = "%s|%s" %(user_input,offset)
conn.send(msg.encode())
total_length = int(offset) #记录传输完成了多少
while True:
server_ack_msg = conn.recv(100) #接收第一个ack
if server_ack_msg.decode().strip() == '400': #如果ftp服务器没有这个资源,返回错误
print('400', error_code['400'])
conn.close()
break
elif server_ack_msg.decode().strip() == 'END': #传输完成,ftp server返回字段,并删除偏移量日志
conn.close()
os.remove(log)
break
res_msg = server_ack_msg.decode().split('|') #接收server的syn和传输数据大小的信息
if res_msg[0].strip() == "SEND SIZE":
res_size = int(res_msg[1])
conn.send(b'CLIENT_READY_TO_RECV') #给server返回ack
receive_data = conn.recv(1024) #接收server的数据
total_length += len(receive_data) #记录接收到了多少数据
# print(receive_data.decode())
# print(total_length)
with open(file,'ab') as fd: #以追加的方式写文件
fd.write(receive_data)
with open(log,'w') as f: #把已接收数据长度写进日志
f.write(str(total_length))
def put(self): #put表示上传文件至服务端
conn = self.socket_obj() #生成对象
msg = input('put filename:') #指定命令输入格式,put filename
filename = os.path.join(receive_file_path, msg.split()[1]) #生成上传文件路径
if os.path.exists(filename): #判断文件存在与否,不存在返回错误
conn.send(msg.encode()) #发送文件行为与文件名至服务端
server_syn_msg = conn.recv(100) #接收服务端发送的偏移量信息
offset = server_syn_msg.decode().split()[1]
read_length = 0 #重置需要读取文件的长度
with open(filename,'rb') as fd:
while True:
send_data = fd.read(1024) #开始读取文件,每次读取1024字节
read_length += len(send_data) #记录读取数据长度
if send_data and read_length> int(offset): #和服务端发送的偏移量进行比较,只有数据不为空和读到超过偏移量才会发送数据
ack_msg = "SEND SIZE|%s" %len(send_data) #给服务端发送本次要发送数据的长度,相当于一个syn
conn.send(ack_msg.encode())
client_ack = conn.recv(100) #接收到服务端发送的ack确认信息,收到之后开始传输数据
if client_ack.decode() =='CLIENT_READY_TO_RECV':
conn.send(send_data)
elif read_length <= int(offset): #如果读取到的数据长度没到偏移量就继续循环读取文件
continue
else:
send_data = 'END' #文件已经读完,表示已经全部发送完成,给服务端发送信息说明客户端已经发送完成
conn.send(send_data.encode())
break
else:
print('400', error_code['400'])
if __name__ == '__main__':
c = SOCKET('127.0.0.1',5000)
if hasattr(c,sys.argv[1]):
func = getattr(c,sys.argv[1])
func()
由于时间原因,存在在传输的过程中有些文件里面涉及到中文的可能会报错的bug,只是功能基本实现,给大家分享一下我的思路,方便交流