python 网络编程socket示例(ftp上传)

我们知道上传一个文件的流程原理即将一个文件内容读取出来,在目标位置新建这个文件,再把内容写入进去。想想跟上篇文章 python 网络编程socket示例(远程ssh)类似,所不同的只是多了一个文件操作句柄,同样的也会出现黏包,使用stuct来解决黏包问题。因为代码中都写有注释,这里就不详细赘述了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# 服务器端
import socket
import pickle
import os
server = socket.socket()
ip_port = ('10.10.10.103', 9969)
server.bind(ip_port)
# 监听连接,最大等待数
server.listen(3)
print('服务器运行中………………')
# 接收客户端连接,保持服务器持续运行
while 1:
# 接收客户端连接句柄、地址
conn, addr = server.accept()
print(addr, '已成功连接至服务器>>>>>')
# 保持客户端持久连接
while 1:
# 使用异常捕获,作用是当客户端出现异常关闭等情况时,服务器自动与其断开连接
try:
# 接收客户端传来的pickle序列化消息并使用pickle.loads解码
recive_data = pickle.loads(conn.recv(1024))
# 向客户端发送200的状态码
conn.send(b'200')
# 在uploads中以写字节模式新建一个名称为客户端传来的文件名
with open('uploads/'+os.path.basename(recive_data['file_name']), 'wb') as f:
# 累加每次接收客户端传来的内容长度
data_lens = 0
# 判断内容是否接收完(累计接收长度是否小于文件总长度)
while data_lens < recive_data['file_size'] :
# 每次接收1024长度内容
data = conn.recv(1024)
# 累加每次接收的长度
data_lens += len(data)
# 将每次接收的内容写入文件
f.write(data)
# 累计接收的文件大小的百分比
percentage = format(data_lens/recive_data['file_size']*100, '.2f')
print('文件总大小:{},已上传:{},百分比:{}%'.format(recive_data['file_size'], data_lens, percentage))
print(os.path.basename(recive_data['file_name']),'上传成功!!!')
except Exception as e:
break
print(addr, '已断开连接服务器>>>>>')
conn.close()
server.close()
# 客户端
import socket
import os
import pickle
client = socket.socket()
ip_port = ('10.10.10.103', 9969)
# 连接服务器
client.connect(ip_port)
# 保持与服务器的持久连接
while 1:
# 获取要上传的文件路径
file = input('请输入您要上传的文件:')
# 将文件与文件大小封装在一个字典中
file_info = {'file_name':file, 'file_size':os.path.getsize(file)}
# 使用pickle.dumps序列化文件信息字典
pickle_file = pickle.dumps(file_info)
# 发送文件信息至服务器
client.send(pickle_file)
# 接收服务器返回的消息
status_code = client.recv(1024).decode('utf8')
# 判断服务器返回的状态码是否为200
if status_code == '200':
# 以读字节模式打开文件
with open(file, 'rb') as f:
# 循环文件每一行并发送至服务器
for line in f:
client.send(line)
else:
print('上传失败')
break
client.close()

进阶:我们传输文件的流程是先把文件信息传送过去,再把文件内容传送过去,中间利用了recv接收服务端返回的状态码强制阻塞,分开了两次传输,name我们可不可以直接将文件信息和文件内容一起传过去呢?答案是肯定的。

因为我们可以获取文件信息这个字典的长度,把这个字典的长度也穿过去,大概传过去的数据包样式长这样:【文件信息长度 | 文件信息 | 文件内容】,看到这种形式其实就已经明白了。我们可以先接收文件信息长度,因为我们使用了struct,字节长度固定为4,转换后获取到了文件信息的长度,再利用这个文件信息长度,去接受文件信息这一块的内容。文件信息中可是包含文件内容长度的,利用这个长度又可以循环接收文件内容了,这样我们的目的不就达到了嘛:

上面的代码稍微改动即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# 服务器端
import socket
import pickle
import os
import struct
server = socket.socket()
ip_port = ('10.10.10.103', 9969)
server.bind(ip_port)
# 监听连接,最大等待数
server.listen(3)
print('服务器运行中………………')
# 接收客户端连接,保持服务器持续运行
while 1:
# 接收客户端连接句柄、地址
conn, addr = server.accept()
print(addr, '已成功连接至服务器>>>>>')
# 保持客户端持久连接
while 1:
# 使用异常捕获,作用是当客户端出现异常关闭等情况时,服务器自动与其断开连接
try:
# 接收前4位字节并转为数字,即文件信息的长度
file_info_lens = struct.unpack('i', conn.recv(4))[0]
print('文件信息长度:',file_info_lens)
# 根据获取的文件信息长度来获取文件信息并解码
file_info = pickle.loads(conn.recv(file_info_lens))
print('文件信息:', file_info)
# 从文件信息中获取文件名称
file = os.path.basename(file_info['file_name'])
# 从文件信息中获取文件大小
file_size = file_info['file_size']
# 在uploads中以写字节模式新建一个名称为客户端传来的文件名
with open('uploads/'+file, 'wb') as f:
# 累加每次接收客户端传来的内容长度
data_lens = 0
# 判断内容是否接收完(累计接收长度是否小于文件总长度)
while data_lens < file_size:
# 每次接收1024长度内容
data = conn.recv(1024)
# 累加每次接收的长度
data_lens += len(data)
# 将每次接收的内容写入文件
f.write(data)
# 累计接收的文件大小的百分比
percentage = format(data_lens/file_size*100, '.2f')
print('文件总大小:{},已上传:{},百分比:{}%'.format(file_size, data_lens, percentage))
print(os.path.basename(file),'上传成功!!!')
except Exception as e:
break
print(addr, '已断开连接服务器>>>>>')
conn.close()
server.close()
# 客户端
import socket
import os
import pickle
import struct
client = socket.socket()
ip_port = ('10.10.10.103', 9969)
# 连接服务器
client.connect(ip_port)
# 保持与服务器的持久连接
while 1:
# 获取要上传的文件路径
file = input('请输入您要上传的文件:')
# 将文件与文件大小封装在一个字典中
file_info = {'file_name':file, 'file_size':os.path.getsize(file)}
# 使用pickle.dumps序列化文件信息字典为字节
pickle_file = pickle.dumps(file_info)
# 获取文件信息字节长度
pickle_file_lens = struct.pack('i', len(pickle_file))
# 发送文件信息长度至服务器
client.send(pickle_file_lens)
# 发送文件信息至服务器
client.send(pickle_file)
# 以读字节模式打开文件
with open(file, 'rb') as f:
# 循环文件每一行并发送至服务器
for line in f:
client.send(line)
client.close()

总结:有些同学会用json来序列化字典,我们这里使用了pickle,因为网络通信传输的是字节,json把字典转成了字符串,你要多一步再转为字节,而利用pickle直接将字典转为了字节

再加一句:上传看自己是否要做一致性校验,原理就是客户端读取文件每行内容发送时顺便将该行文本追加加密,即md5.update(第一行)md5.update(第二行)……,直至整个文件内容加密完成;服务端接收时也是一行一行的写入新文件,此时也是用md5进行加密,最后整个文件加密的结果发送给客户端,客户端用两个加密的结果进行比较,相等则代表校验成功