【后端学习记录】UDP实现文件的上传和下载

学习了一些课程后试着写的代码,有适当的改动,比较小白哈


前言

这里放下原理图
请添加图片描述

操作码功能
1请求上传文件
2请求下载文件
3发送数据包
4ACK
5错误
6上传/下载完成

服务端

from socket import *
import struct
# struct用于处理C结构数据

s = socket(AF_INET, SOCK_DGRAM)
# 绑定接口
s.bind(('', 69))

下载

def download(filename, user):
    socket_down = socket(AF_INET, SOCK_DGRAM)
    # 创建用于下载文件的套接字,AF_INET用于Internet进程间通信,SOCK_DREAM数据报套接字,用于UDP协议
    num = 1
    # num表示数据包的序号
    flag = True
    # 定义客户端退出的标签

    # 打开客户端要下载的文件
    try:
        f = open(filename, 'rb')
    except:
        error_data = struct.pack("!HH5sb", 5, 5, 'error'.encode('utf-8'), 0)
        socket_down.sendto(error_data, user)
        # 文件不存在时发送,注意元组
        print("文件不存在")
        flag = False
        socket_down.close()

    # 文件存在时发送数据包,数据包多次发送用死循环
    while flag:
        # 读取512字节的数据
        read_data = f.read(512)
        ack_code = 3
        # 发送数据包
        send_data = struct.pack("!HH", ack_code, num) + read_data

        # 判断数据大小,数据小于512字节,认为数据读取完毕
        if len(read_data) < 512:
            ack_code = 6
            # 操作码变为6,表示发送的数据包是最后一个并且在控制台打印”传输完成“最后关闭文件
            send_data = struct.pack("!HH", ack_code, num) + read_data
            # 将数据发送以后再退出循环
            socket_down.sendto(send_data, user)
            print('传输完成,对方下载成功')
            if f:
                f.close()
                break

        # 数据正常大小,正常发送操作码为3的数据包
        socket_down.sendto(send_data, user)

        # 发送数据包后接收ACK
        ack_data = socket_down.recv(1024)
        ack_num, num = struct.unpack("!HH", ack_data)
        # 接收到ACK后,将接收到的块编码加一作为下一个数据包的序号
        num += 1

        # 如果操作码异常,退出程序
        if int(ack_num) != 4 or int(ack_num) < 1:
            break
    socket_down.close()

上传

这里一开始遇到了一个小小的问题,因为客户端下载的时候设置了IP地址和端口,服务器就没有接收客户端的IP和端口,后面Debug的时候发现客户端的IP和端口丢了我才补上 client_port = ()

def post(filename, user):
    socket_post = socket(AF_INET, SOCK_DGRAM)

    flag = True
    # 和客户端下载函数类似,但因为服务端第一次接收到的是客户端的上传请求,第二次才能接收到数据,所以需要一个标签
    first = 1
    num = 1
    while flag:
        # 接受客户端的数据
        if first != 1:
            try:
                recv_data = socket_post.recv(1024)
            except:
                print("文件不存在!")
                break
            ack_code = recv_data[1]
            if ack_code == 6:
                print("文件接收完毕!")
                f.close()
                break
            num += 1
            f = open("server_" + filename, 'ab')
            f.write(recv_data[4:])
        # 第一次接受到客户端的请求后改变first的值
        first = 2
        # 向客户端发送ACK
        ack_data = struct.pack("!HH", 4, num)
        socket_post.sendto(ack_data, user)
    socket_post.close()

主函数

def main():
    # 接收并且解包客户端的请求
    recv_data, client_port = s.recvfrom(1024)
    if struct.unpack('!b5sb', recv_data[-7:]) == (0, b'octet', 0):
        ack_code = struct.unpack('!H', recv_data[:2])

        # 根据操作码判断客户端想做什么,调用对应的函数
        if ack_code[0] == 2:
            filename = recv_data[2:-7].decode("utf-8")
            print("对方想下载文件:"+filename)
            download(filename, client_port)
        elif ack_code[0] == 1:
            filename = recv_data[2:-7].decode("utf-8")
            print("对方想上传文件:" + filename)
            post(filename, client_port)

客户端

from socket import *
import struct

host_port = ("127.0.0.1", 69)
client = socket(AF_INET, SOCK_DGRAM)

下载

def download(filename):
    while True:
        # 发送请求
        request_data = struct.pack("!H%dsb5sb" % len(filename), 2, filename.encode('utf-8'), 0, 'octet'.encode('utf-8'), 0)
        client.sendto(request_data, host_port)

        # 接收服务器发送的数据
        recv_data, (sever_ip, sever_port) = client.recvfrom(1024)
        ack_code, num = struct.unpack("!HH", recv_data[:4])

        # 判断操作码
        # 操作码为5,服务器返回错误,循环中断
        if ack_code == 5:
            print(f"服务器返回{filename}不存在!")
            break

        # 操作码为3,服务器发送数据,客户端接收数据后发送ACK
        elif ack_code == 3:
            ack_data = struct.pack("!HH", 4, num)
            client.sendto(ack_data, (sever_ip, sever_port))
            f = open("client_" + filename, 'ab')
            f.write(recv_data[4:])

        # 操作码为6,服务器数据发送完毕,客户端关闭文件并且关闭套接字
        elif ack_code == 6:
            print("客户端下载完毕!")
            f.close()
            break

        # 异常操作码跳出循环
        else:
            print("异常!")
            break
    client.close()

上传

def post(filename):
    client_post = socket(AF_INET, SOCK_DGRAM)
    request_data = struct.pack("!H%dsb5sb" % len(filename), 1, filename.encode('utf-8'), 0, 'octet'.encode('utf-8'),0)
    client_post.sendto(request_data, host_port)
    num = 1
    flag = True
    try:
        f = open(filename, 'rb')
    except:
        print("文件不存在!")
        flag = False

    while flag:
        # 和服务端发送数据类似
        ack_data, (sever_ip, sever_port) = client_post.recvfrom(1024)
        # 判断操作码
        ack_num, num = struct.unpack("!HH", ack_data)
        # 操作码为4表示服务端收到数据,继续向服务端发送数据
        if ack_num == 4:
            read_data = f.read(512)
            ack_code = 3
            send_data = struct.pack("!HH", ack_code, num) + read_data
            if len(read_data) < 512:
                ack_code = 6
                send_data = struct.pack("!HH", ack_code, num) + read_data
                client_post.sendto(send_data, (sever_ip, sever_port))
                f.close()
                print('传输完成,对方下载成功')
                break
            client_post.sendto(send_data, (sever_ip, sever_port))
        # 操作码异常退出循环
        else:
            break
    client_post.close()

主函数

def main():
    # 设计简单的界面,让用户选择上传或下载
    print("按1选择上传文件\n按2选择下载文件")
    answer = input("请选择:")

    if answer == '2':
        filename = input("请输入文件名字:")
        download(filename)

    if answer == '1':
        filename = input("请输入文件名字:")
        post(filename)

总结

除了文里提到的问题还有几个经常碰见的问题:

  1. 服务器或者客户端套接字没关:因为某一方没有发送最后一个数据包就自己关了套接字
  2. 服务器和客户端在某个地方谁也不理谁了:得一步一步Debug才知道那个地方被阻塞住了
  3. 文件不存在,但因为是open打开的文件,出现了明明这个文件不存在下载/上传的时候客户端/服务器自己创建了:判断操作码为3时再用open打开文件

本来说给传输数据,接收ACK,发送请求,发送错误分别写一个函数,这样上传和下载函数都能调用,但是跑着跑着那些函数的数据给我释放了…大概用到类就不会出现这种情况了?呃对于自己的水平有很清楚的认知还是老老实实地写了上传和下载函数。

正在努力学习中,有什么问题和建议还请大家指出来
源码放在github上了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值