原理:
- 使用Socket与服务器建立TCP连接
- 使用open函数以二进制模式读取文件,并分割成指定大小的数据包发送
- 规定在传输的开始先发送文件名,传输的结尾发送停止代号
发送端:
sender.py
:
FLAG='$END$'
def sendFile_TCP(FILEPATH,ADDR,BUF=1024*64):
import socket
import os
sk = socket.socket() #初始化套接字
sk.connect(ADDR) #尝试连接目标主机,如果失败会引发异常,这里为了简便没有进行异常处理
sum = 0
sp='\\'
if sp not in FILEPATH:
sp='/'
filename = FILEPATH.split(sp)[-1] #分割路径字符串,获取文件名
sk.send(filename.encode('utf-8'))
with open(FILEPATH,'rb') as f:
while True:
os.system('title 正在发送文件:{} 已发送 {} KB'.format(filename,int(sum/1024)))
data=f.read(BUF)
if not data:
break
sk.send(data)
sum += len(data)
sk.send(FLAG.encode())
os.system('title FileSender')
sk.close() #关闭套接字
if __name__ == "__main__":
import os
addr = ('127.0.0.1',20020)
while True:
print('【拖拽文件到此处】')
path=input()
sendFile_TCP(path,addr)
注意:
- 读取路径之后,通过路径分隔符
\
(Windows系统)或/
(Linux系统)对路径字符串FILEPATH
调用split函数分割并使用返回列表的最后一个(即文件名)
也可以用现成的函数 os.path.basename(path) 来返回一个路径字符串path
中的文件名,这是完全等价的 - f.read函数读取指定字节数的数据,如果读到了文件尾部就会返回空数据
b''
因此使用if not data
来判断是否读完了文件 - socket发送和接收的都是二进制数据,如果是字符串应该先encode,如果是常量字符串在引号前加一个记号
b
,如此处的结束标志FLAG = b'$END$'
,这里文件名用utf-8
编码是考虑到文件名可能是中文 - 通过os.system 调用命令行显示当前发送了多少数据,这里的时间花销比较大,如果追求最大速度可以去掉。
接收端:
recv.py
FLAG=b'$END$'
def recvFile_TCP(ADDR,BUF=1024):
import socket
import os
sk=socket.socket()
sk.bind(ADDR) #绑定套接字到本机的网络地址
sk.listen(10) #10表示最大连接数
while True:
client,cli_addr = sk.accept() #接受连接请求
filename = client.recv(BUF).decode('utf-8')
print('正在接受来自 [{}] 的文件[{}]'.format(cli_addr,filename))
i = 0
while os.path.exists(filename):
filename='{}-{}'.format(i,filename)
i += 1
with open(filename ,'ab') as f:
while True:
data=client.recv(BUF)
if data == FLAG:
break
f.write(data)
print('已成功接收文件{} | 文件大小 : {} KB'.format(filename,int(os.path.getsize(filename) / 1024)))
client.close()
if __name__ == "__main__":
addr=('127.0.0.1',20020) #本机的ip地址和端口号
recvFile_TCP(addr)
如果服务器设置了DMZ主机,(需要在外网访问)ip地址应该写局域网内的IPv4地址,例如192.168.1.100
Windows下使用命令ipconfig
Linux下使用命令ifconfig -a
(基于net-tools,如果没装先apt install net-tools
)
即可查看本机局域网ip