一、案例要求:
(1)利用tcp传输方式完成客户端和服务端的数据传输。
(2)利用文件读写操作完成文件读取、传输和下载。
(3)服务端引入多线程,可满足多个客户端进行链接及文件下载。
二、需求分析:
(1)tcp通信,需要socket套接字完成数据传递
(2)下载文件原理:客户端请求下载内容,服务端判断如果文件存在,则读取文件,并将读取到的文件内容以二进制方式传递给客户端,客户端收到数据,写入到文件中。
(3)服务端同时满足多个客户端的链接和下载操作,需要创建子线程,accept方法等待链接和recv方法接收数据、send方法发送文件内容同步进行。
三、代码
3.1 服务端代码:
import threading
import socket
import os
# 定义文件下载函数
def download_file(tcp_client_socket):
# 接收来自客户端发来的文件名的二进制数据
file_name_data = tcp_client_socket.recv(1024)
# 对文件名进行解码
file_name = file_name_data.decode("gbk")
print(ip_port, file_name, sep=":")
# 判断如果该文件服务端存在,则发送任意一个标识字符给客户端,证明文件存在,客户端可以接收。
if os.path.exists(file_name):
tcp_client_socket.send("1".encode("utf-8")) # 可发送任意非控字符,标识文件存在
print("文件存在,已经将标识字段'1'发给客户端。开始发送...")
# 文件存在,打开文件,以二进制方式循环读取数据,发送给客户端
with open(file_name, "rb") as file:
while True:
send_data = file.read(1024)
if send_data:
tcp_client_socket.send(send_data)
# 最后一次读取到的为空,跳出循环
else:
break
# 如果文件不存在,则端开与客户端的链接,然后客户端会收到长度为0 的信息,表示文件不存在不必再接收。
else:
print("文件不存在,将断开与该客户端的链接。此时客户端收到字段为空...")
tcp_client_socket.close()
# 文件存在,读取完毕跳出循环以后执行关闭客户端链接
tcp_client_socket.close()
if __name__ == '__main__':
# 创建服务端tcp套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 设置socket选项,防止程序退出端口不立即释放的问题
tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
# 绑定服务端端口
tcp_server_socket.bind(("", 8080))
# 变套接字主动为被动,可以建立链接
tcp_server_socket.listen(128)
# 循环接收客户端的连接请求, 提示:现在的下载是同步下载,一个用户下载完成以后另外一个用户才能下载
while True:
# 等待链接
tcp_client_socket, ip_port = tcp_server_socket.accept()
# 创建子线程执行文件下载任务,用于执行读取数据发送给已经链接成功的客户端
download_file_thread = threading.Thread(target=download_file, args=(tcp_client_socket,))
# 启动子线程执行对应任务
download_file_thread.start()
tcp_server_socket.close()
3.1.1 服务端执行结果展示:
3.2 客户端代码:
import socket
# 程序入口
if __name__ == '__main__':
# 创建tcp套接字,AF_INET表示ipv4,SOCK_STREAM表示tcp传输类型
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 客户端建立到服务端的链接
tcp_client_socket.connect(("192.168.14.39", 8080))
# 输入需要下载的文件名,并编码为二进制文件发给服务端
file_name = input("请输入需要下载的文件名:")
tcp_client_socket.send(file_name.encode("utf-8"))
# 接收服务端回复文件是否存在的消息
recv_data = tcp_client_socket.recv(1024)
# 如果存在,则创建文件并循环接收服务端发来的文件的二进制数据,并写入到创建的文件中
if recv_data:
print("文件存在,开始接收下载")
with open("副本"+file_name, "wb") as file:
# 循环接收数据
while True:
recv_data = tcp_client_socket.recv(1024)
if recv_data:
file.write(recv_data)
# 最后一次读取信息为空,跳出循环
else:
print("下载完成!")
break
# 如果文件不存在,则服务端会关闭与客户端的链接。客户端收到的信息长度为0,通过0判断文件不存在
else:
print("文件不存在")
tcp_client_socket.close()
3.2.1 客户端执行结果展示:
四、注意点:
(1)判断文件是否存在,导入os模块,利用os.path.exists(路径或文件名)
来判断,如果存在,就约定给客户端一个标识,告诉客户端文件是否存在。
(2)需要掌握tcp网络程序流程,客户端:创建套接字、建立连接(三次握手)、send发送数据、recv接收数据、关闭套接字(四次挥手);服务端:创建套接字,绑定服务端端口、listen设置监听、accept等待连接、send发送数据、recv接收数据、关闭与客户端的套接字、关闭主套接字。
(3)掌握文件读写操作。掌握 with open("文件名",“wb”) as file:
的用法,不用手动关闭文件。
(4)创建子线程完成多任务。
创建子线程:线程名 = threading.Thread(target= 函数名或方法名)
开启子线程:线程名.start()