学习了一些课程后试着写的代码,有适当的改动,比较小白哈
前言
这里放下原理图
操作码 | 功能 |
---|---|
1 | 请求上传文件 |
2 | 请求下载文件 |
3 | 发送数据包 |
4 | ACK |
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)
总结
除了文里提到的问题还有几个经常碰见的问题:
- 服务器或者客户端套接字没关:因为某一方没有发送最后一个数据包就自己关了套接字
- 服务器和客户端在某个地方谁也不理谁了:得一步一步Debug才知道那个地方被阻塞住了
- 文件不存在,但因为是open打开的文件,出现了明明这个文件不存在下载/上传的时候客户端/服务器自己创建了:判断操作码为3时再用open打开文件
本来说给传输数据,接收ACK,发送请求,发送错误分别写一个函数,这样上传和下载函数都能调用,但是跑着跑着那些函数的数据给我释放了…大概用到类就不会出现这种情况了?呃对于自己的水平有很清楚的认知还是老老实实地写了上传和下载函数。
正在努力学习中,有什么问题和建议还请大家指出来
源码放在github上了