思路
- 首先客户端与服务端都要先生成socket对象,创建socket实例。
- 然后服务端通过bind函数绑定端口,并进行监听,服务端再使用accept方法进入阻塞状态,等待一个连接的到来
- 客户端使用connect函数通过以元组形式传入一个IP地址与端口号,与服务端建立连接
- 客户端读取用户的输入,我们可以判断一下,如果这串字符以get开头,那么就把get后面的内容也就是文件名赋值给filename
- 客户端把接收到的文件名发送给服务端。
- 服务端接收到文件名后,先使用os.isfile(path)函数判断一下是否有这个文件,如果有再使用os.stat()函数获取这个文件的大小,然后把这个文件的大小发送给客户端。接下来要创建这个文件的md5对象
- 服务端要把这个文件打开,使用一个for循环进行读取文件的内容,每次读取的时候都要进行md5检验值的更新
- 客户端收到服务端发过来的文件大小之后,接下来使用while循环来接收服务端发送过来的数据,并把这些数据写入到一个新文件里面,这样就实现了文件的发送与接收
注意事项
编写代码时特别容易犯错的以下几点,我就犯了几次细节的错误,大家注意
- 调用md5对象的update方法进行校验码的更新的时候记得参数必须传bytes类型(二进制字节流)
- 要考虑粘包的问题,所以必须要在两次send()里面加上一个recv()接收ack信息,这样吧两次send()分开,可以避免粘包的出现
- 为了防止出现粘包的现象,在循环接收的时候,最后一次接收中进行判断,看还剩多少数据没收到的,剩多少就收多少。
实现
ftp server
- 读取文件名
- 检测文件是否存在
- 打开文件
- 检测文件大小
- 发送文件大小给客户端
- 等客户端确认
- 开始边读边发数据
- 发送md5
代码:
import hashlib
import socket ,os,time
server = socket.socket()
server.bind(('0.0.0.0',9999) )
server.listen()
while True:
conn, addr = server.accept()
print("new conn:",addr)
while True:
print("等待新指令")
data = conn.recv(1024)
if not data:
print("客户端已断开")
break
cmd,filename = data.decode().split()
print(filename)
if os.path.isfile(filename):
f = open(filename,"rb")
m = hashlib.md5()
file_size = os.stat(filename).st_size
conn.send( str(file_size).encode() ) #send file size
conn.recv(1024) #wait for ack
for line in f:
m.update(line)
conn.send(line)
print("file md5", m.hexdigest())
f.close()
conn.send(m.hexdigest().encode()) #send md5
print("send done")
server.close()
ftp client
import socket
import hashlib
client = socket.socket()
client.connect(('localhost', 9999))
while True:
cmd = input(">>:").strip()
if len(cmd) == 0: continue
if cmd.startswith("get"):
client.send(cmd.encode())
server_response = client.recv(1024) # 接收文件的size
print("servr response:", server_response)
client.send(b"ready to recv file")
file_total_size = int(server_response.decode())
received_size = 0
filename = cmd.split()[1]
f = open(filename + ".new", "wb")
m = hashlib.md5()
while received_size < file_total_size:
if file_total_size - received_size > 1024: # 要收不止一次
size = 1024
else: # 最后一次了,剩多少收多少
size = file_total_size - received_size
print("last receive:", size)
data = client.recv(size)
received_size += len(data)
m.update(data)
f.write(data)
# print(file_total_size,received_size)
else:
new_file_md5 = m.hexdigest()
print("file recv done", received_size, file_total_size)
f.close()
server_file_md5 = client.recv(1024)
print("server file md5:", server_file_md5)
print("client file md5:", new_file_md5)
client.close()
参考:
alex的博客
廖雪峰的Python教程
使用socketserver
import socketserver
import json,os
class MyTCPHandler(socketserver.BaseRequestHandler):
def put(self,*args):
'''接收客户端文件'''
cmd_dic = args[0]
filename = cmd_dic["filename"]
filesize = cmd_dic["size"]
if os.path.isfile(filename):
f = open(filename + ".new","wb")
else:
f = open(filename , "wb")
self.request.send(b"200 ok")
received_size = 0
while received_size < filesize:
data = self.request.recv(1024)
f.write(data)
received_size += len(data)
else:
print("file [%s] has uploaded..." % filename)
def handle(self):
while True:
try:
self.data = self.request.recv(1024).strip()
print("{} wrote:".format(self.client_address[0]))
print(self.data)
cmd_dic = json.loads(self.data.decode())
action = cmd_dic["action"]
if hasattr(self,action):
func = getattr(self,action)
func(cmd_dic)
except ConnectionResetError as e:
print("err",e)
break
if __name__ == "__main__":
HOST, PORT = "localhost", 9999
# Create the server, binding to localhost on port 9999
server = socketserver.ThreadingTCPServer((HOST, PORT), MyTCPHandler)
server.serve_forever()