Socket之文件下载
Kubrnete 574 3

文件下载的基本概念

我们肯定了解要下载一个文件肯定先读取目标文件,然后再将读取的文件不停的写下来,从而保存到本地。

在这里我们要有一个概念,我们不能凭空从目标下载文件,所以我们需要一个客户端用于发送指令,那么就需要有一个服务端用于接收指令,从而对服务器内的文件进行读取,而后将读取到的数据回送给客户端,从而实现下载功能。

我们前面也编写过UDP,TCP类型的聊天器,对于文件下载,也是基于TCP聊天器的性质进行改编的。

客户端的实现

我们还是先编写一个TCP类型的客户端。

我们先来熟悉一下编写一个TCP类型的客户端的步骤。

  1. 创建套接字
  2. 连接服务器
  3. 发送数据

我们知道了这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"))

接下来,我们再编写一个服务端用于接收数据。

服务端的实现

还是一样先熟悉一下编写服务端的过程。

  1. 创建套接字
  2. 绑定本地IP及其端口
  3. 设置一次允许接入的最大次数
  4. 等待一个连接并创建一个新的套接字服务于新的连接
  5. 接收数据

然后我们将它编写下来。

第一步,创建套接字

# 创建套接字
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"))

具体测试结果可以线下测试。

我们知道了怎么读写,接下来就好办了,我们只需要将这些功能加入到我们的服务端和客户端里即可。

在这里会出现各种各样的问题,所以流程一定要清楚 Socket之文件下载 接下来就看代码

客户端

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() #退出进程#
评论区

索引目录