文件下载的基本概念
我们肯定了解要下载一个文件肯定先读取目标文件,然后再将读取的文件不停的写下来,从而保存到本地。
在这里我们要有一个概念,我们不能凭空从目标下载文件,所以我们需要一个客户端用于发送指令,那么就需要有一个服务端用于接收指令,从而对服务器内的文件进行读取,而后将读取到的数据回送给客户端,从而实现下载功能。
我们前面也编写过UDP,TCP类型的聊天器,对于文件下载,也是基于TCP聊天器的性质进行改编的。
客户端的实现
我们还是先编写一个TCP类型的客户端。
我们先来熟悉一下编写一个TCP类型的客户端的步骤。
- 创建套接字
- 连接服务器
- 发送数据
我们知道了这3个步骤,然后我们就将它编写下来。
第一步,创建套接字
# 创建套接字
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
第二步,连接服务器
# 连接目标服务器
ip_port = (("127.0.0.1"), 1234)
socket_TCP.connect(ip_port)
第三步,发送数据
# 发送数据
socket_TCP_data = "Hello World!"
socket_TCP.send(socket_TCP_data.encode("utf-8"))
整合一下,就是这个样子
# 创建套接字
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接目标服务器
ip_port = (("127.0.0.1"), 1234)
socket_TCP.connect(ip_port)
# 发送数据
socket_TCP_data = "Hello World!"
socket_TCP.send(socket_TCP_data.encode("utf-8"))
接下来,我们再编写一个服务端用于接收数据。
服务端的实现
还是一样先熟悉一下编写服务端的过程。
- 创建套接字
- 绑定本地IP及其端口
- 设置一次允许接入的最大次数
- 等待一个连接并创建一个新的套接字服务于新的连接
- 接收数据
然后我们将它编写下来。
第一步,创建套接字
# 创建套接字
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
第二步,绑定本地IP及其端口
# 绑定本地IP及端口
ip_port = (("127.0.0.1"), 1234)
socket_TCP.bind(ip_port)
第三步,设置一次允许最大的接入次数
# 允许一次接入的次数
socket_TCP.listen(128)
第四步,等待一个连接并创建一个新的套接字服务于新的连接
"""等待一个连接并创建一个新的套接字服务于新的连接"""
socket_new_TCP, Address = socket_TCP.accept()
第五步,接收数据
socket_new_TCP_data = socket_new_TCP.recv(1024).decode("utf-8")
ip = Address[0]
port = Address[1]
最后整合一下
# 创建套接字
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定本地IP及端口
ip_port = (("127.0.0.1"), 1234)
socket_TCP.bind(ip_port)
# 允许一次接入的次数
socket_TCP.listen(128)
"""等待一个连接并创建一个新的套接字服务于新的连接"""
socket_new_TCP, Address = socket_TCP.accept()
# 接收数据
socket_new_TCP_data = socket_new_TCP.recv(1024).decode("utf-8")
ip = Address[0]
port = Address[1]
print("[*] 来自 %s:%s 的信息:%s" % (ip, port, socket_new_TCP_data))
接下来我们开始检验我们的客户端是否能给服务端发送消息,并且服务端能够接收到。
[*] 来自 127.0.0.1:52358 的信息:Hello World!
我们运行后,服务端是能够接受到来自客户端的信息的。
服务端信息的回送
但是我们想要的不仅仅是服务器接受到信息,因为在服务端我们需要接受来自客户端的下载指令后,对本地服务器的目标文件进行读取,然后将读取到的内容回送回去,不过我们只需要让服务器接受到下载指令后,服务器回应一个下载成成果指令即可。
所以我们需要在客户端编写一个接受来自服务端数据的方法,这也很简单,我直接添加了一个Rece_data()方法用于接收来自服务端的回送信息,在服务端,利用Send_data()发送进行回送,当然我们用的肯定是产生的套接字。
客户端
def Rece_data(socket_TCP):
socket_TCP_data = socket_TCP.recv(1024).decode("utf-8")
print("来自目标的回送信息:%s" % socket_TCP_data)
服务端
def Send_data(socket_new_TCP, socket_new_TCP_data):
socket_new_TCP.send(("[成功]" + socket_new_TCP_data).encode("utf-8"))
我们运行后可以看到
服务端
[*] 来自 127.0.0.1:52493 的信息:Hello World!
客户端
来自目标的回送信息:[成功]Hello World!
我们完成了基本的信息回送,接下来就可以着手让服务端执行读取文件数据的操作了。
文件的读写
首先我们来看看Python是怎么读写文件的
在这里我在当前目录下新建了一个目录test下的test.txt文件,里面的内容为:
abc cba
两行
这里我们稍微解释下“rb”
‘rb’:表示以二进制方式读取文件。该文件必须已存在。
with open("test\\test.txt", "rb") as read:
data = read.read(1024)
print(data.decode("utf-8"))
运行后
abc
cba
当我们知道了怎么读取文件,我们还要学会写入文件。
这里提一下“wb”
‘wb’:表示以二进制写方式打开,只能写文件, 如果文件不存在,创建该文件;如果文件已存在,则覆盖写。
import os
try:
os.mkdir("test")
except:
print("文件已存在!")
with open("test\\test.txt", "wb") as write:
data = "abc\r\ncba\r\n"
write_data = write.write(data.encode("utf-8"))
具体测试结果可以线下测试。
我们知道了怎么读写,接下来就好办了,我们只需要将这些功能加入到我们的服务端和客户端里即可。
在这里会出现各种各样的问题,所以流程一定要清楚 接下来就看代码
客户端
def Rece_data(socket_TCP):
"""接收来自服务器发送的文件名用做作命名"""
socket_TCP_name = socket_TCP.recv(1024)
try:
os.mkdir(r"E:\来自服务器的下载")
except:
print("文件已存在!")
"""将接收到的数据写入本地文件"""
with open(r"E:\来自服务器的下载\[接收]%s" % socket_TCP_name.decode("utf-8"), "wb") as w:
while True:
# 循环接收1K数据
socket_TCP_data = socket_TCP.recv(1024)
# 存在就继续写入
if socket_TCP_data:
w.write(socket_TCP_data)
# 不存在就退出循环
else:
break
服务端
def Send_data(socket_new_TCP, socket_new_TCP_data):
# 将要下载的文件名回送给客户端
socket_new_TCP.send(socket_new_TCP_data.encode("utf-8"))
"""打开文件已只读的形式"""
with open(socket_new_TCP_data, "rb") as r:
# 循环读取数据每次1K
while True:
data = r.read(1024)
# 如果存在数据就回送回去
if data:
socket_new_TCP.send(data)
# 如果不存在就退出循环
else:
break
整合起来就如下所示。
客户端
import socket
import os
def main():
Send_command()
def Send_command():
# 创建套接字
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接目标服务器
ip_port = (("127.0.0.1"), 1234)
socket_TCP.connect(ip_port)
# 发送数据
socket_TCP_data = input("请输入你要下载的文件:")
socket_TCP.send(socket_TCP_data.encode("utf-8"))
Rece_data(socket_TCP)
def Rece_data(socket_TCP):
"""接收来自服务器发送的文件名用做作命名"""
socket_TCP_name = socket_TCP.recv(1024)
try:
os.mkdir(r"E:\来自服务器的下载")
except:
print("文件已存在!")
"""将接收到的数据写入本地文件"""
with open(r"E:\来自服务器的下载\[接收]%s" % socket_TCP_name.decode("utf-8"), "wb") as w:
while True:
# 循环接收1K数据
socket_TCP_data = socket_TCP.recv(1024)
# 存在就继续写入
if socket_TCP_data:
w.write(socket_TCP_data)
# 不存在就退出循环
else:
break
if __name__ == '__main__':
main()
服务端
import socket
def main():
Rece_command()
def Rece_command():
# 创建套接字
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定本地IP及端口
ip_port = (("127.0.0.1"), 1234)
socket_TCP.bind(ip_port)
# 允许一次接入的次数
socket_TCP.listen(128)
"""等待一个连接并创建一个新的套接字服务于新的连接"""
socket_new_TCP, Address = socket_TCP.accept()
# 接收数据
socket_new_TCP_data = socket_new_TCP.recv(1024).decode("utf-8")
"""将接收到客户端的下载指令放再下列方法进行处理"""
Send_data(socket_new_TCP, socket_new_TCP_data)
def Send_data(socket_new_TCP, socket_new_TCP_data):
# 将要下载的文件名回送给客户端
socket_new_TCP.send(socket_new_TCP_data.encode("utf-8"))
"""打开文件已只读的形式"""
with open(socket_new_TCP_data, "rb") as r:
# 循环读取数据每次1K
while True:
data = r.read(1024)
# 如果存在数据就回送回去
if data:
socket_new_TCP.send(data)
# 如果不存在就退出循环
else:
break
if __name__ == '__main__':
main()
我们也可以再进行优化优化。
服务端
import socket
def main():
# 创建套接字
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定本地IP及端口
ip_port = (("127.0.0.1"), 1234)
socket_TCP.bind(ip_port)
# 允许一次接入的次数
socket_TCP.listen(128)
"""等待一个连接并创建一个新的套接字服务于新的连接"""
print("等待客户端的连接中!")
socket_new_TCP, Address = socket_TCP.accept()
print("连接建立成功!")
Rece_command(socket_new_TCP)
def Rece_command(socket_new_TCP):
while True:
# socket_new_TCP, Address = socket_TCP.accept()
# 接收数据
socket_new_TCP_data = socket_new_TCP.recv(1024).decode("utf-8")
if socket_new_TCP_data == "exit":
socket_new_TCP.close()
break
print("等待客户端指令中!")
"""将接收到客户端的下载指令放再下列方法进行处理"""
Send_data(socket_new_TCP, socket_new_TCP_data)
def Send_data(socket_new_TCP, socket_new_TCP_data):
# 将要下载的文件名回送给客户端
socket_new_TCP.send(socket_new_TCP_data.encode("utf-8"))
"""打开文件已只读的形式"""
with open(socket_new_TCP_data, "rb") as r:
# 循环读取数据每次1K
while True:
data = r.read(1024)
# 如果存在数据就回送回去
if data:
socket_new_TCP.send(data)
# 如果不存在就退出循环
else:
print("下载完成!")
break
if __name__ == '__main__':
main()
客户端
import socket
import os
import time
def main():
# 创建套接字
socket_TCP = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接目标服务器
ip_port = (("127.0.0.1"), 1234)
try:
socket_TCP.connect(ip_port)
print("服务端连接成功!")
except:
print("请先启动服务端再开启客户端!")
time.sleep(10)
socket_TCP.close()
exit()
Send_command(socket_TCP)
def Send_command(socket_TCP):
while True:
# 发送数据
socket_TCP_data = input("请输入你要下载的文件:")
socket_TCP.send(socket_TCP_data.encode("utf-8"))
if socket_TCP_data == "exit":
socket_TCP.close()
break
Rece_data(socket_TCP)
def Rece_data(socket_TCP):
"""接收来自服务器发送的文件名用做作命名"""
socket_TCP_name = socket_TCP.recv(1024)
try:
os.mkdir(r"E:\来自服务器的下载")
except:
print("文件已存在!")
"""将接收到的数据写入本地文件"""
with open(r"E:\来自服务器的下载\[接收]%s" % socket_TCP_name.decode("utf-8"), "wb") as w:
# 接收来自服务端的数据
socket_TCP_data = socket_TCP.recv(1024 ** 2)
# 写入数据到本地文件
w.write(socket_TCP_data)
if __name__ == '__main__':
main()
当时进行编写的时候我犯了一个致命的错误,就是在接收来自服务端和写入本地文件的时候用了死循环,导致跳不出循环。然后我就去请教了一下,最后大家可以看看另一个版本。 不过我上面写的还是有一点问题。
服务端
import socket
import sys
import selectors
import types
class server:
def __init__(self, ip, port):
self.port = port
self.ip = ip
self.selector = selectors.DefaultSelector() # 初始化selector
def start(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.bind((self.ip, self.port))
s.listen()
print('等待连接:', (self.ip, self.port))
s.setblocking(False) # 非阻塞
self.selector.register(s, selectors.EVENT_READ, None) # 注册I/O对象
while True:
events = self.selector.select(timeout=None) # 阻塞调用,等待新的读/写事件
for key, mask in events:
if key.data is None: # 新的连接请求
self.accept_wrapper(key.fileobj)
else: # 收到客户端连接发送的数据
self.service_connection(key, mask)
except socket.error as e:
print(e)
sys.exit()
finally:
s.close() # 关闭服务端
def accept_wrapper(self, sock):
conn, addr = sock.accept() # Should be ready to read
print('接收客户端连接', addr)
conn.setblocking(False) # 非阻塞
data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'') # socket数据
events = selectors.EVENT_READ | selectors.EVENT_WRITE # 监听读写
self.selector.register(conn, events, data=data) # 注册客户端socket
def service_connection(self, key, mask):
sock = key.fileobj
data = key.data
if mask & selectors.EVENT_READ:
recv_data = sock.recv(1024) # 接收数据
if recv_data:
data.outb += recv_data
else: # 客户端断开连接
print('关闭连接', data.addr)
self.selector.unregister(sock) # 取消注册,防止出错
sock.close()
if mask & selectors.EVENT_WRITE:
if data.outb:
print('发送', repr(data.outb), '到', data.addr)
sent = sock.send(data.outb)
data.outb = data.outb[sent:]
if __name__ == '__main__':
s = server('', 1234)
s.start()
客户端
import socket
import sys
import re
import os
class Client:
def __init__(self,serverIp,serverPort):
self.serverIp=serverIp #待连接的远程主机的域名
self.serverPort = serverPort
self.bufferSize = 10240
def connet(self): #连接方法
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error as e:
print("Failed to create socket. Error: %s"%e)
try:
s.connect((self.serverIp,self.serverPort))
while True:
message = input('> ')#接收用户输入
if not message:
break
s.send(bytes(message, 'utf-8'))#发送命令
data = s.recv(self.bufferSize)#接收数据
if not data:
break
if re.search("^0001",data.decode('utf-8','ignore')):#判断数据类型
print(data.decode('utf-8')[4:])
else:#文件内容处理
s.send("File size received".encode())#通知服务端可以发送文件了
file_total_size = int(data.decode())#总大小
received_size = 0
f = open("new" +os.path.split(message)[-1], "wb")#创建文件
while received_size < file_total_size:
data = s.recv(self.bufferSize)
f.write(data)#写文件
received_size += len(data)#累加接收长度
print("已接收:", received_size)
f.close()#关闭文件
print("receive done", file_total_size, " ", received_size)
except socket.error:
s.close()
raise #退出进程
finally:
s.close()
if __name__ == '__main__':
cl = Client('127.0.0.1',8000)
cl.connet()
sys.exit() #退出进程#