Python之路(第三十二篇) 网络编程:udp套接字简单文件传输
Posted Nicholas--Altshuler
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python之路(第三十二篇) 网络编程:udp套接字简单文件传输相关的知识,希望对你有一定的参考价值。
服务端
# udp是无链接的,先启动哪一端都不会报错 # udp没有链接,与tcp相比没有链接循环,只有通讯循环 server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #创建一个服务器的套接字 server.bind() #绑定服务器套接字 inf_loop: #服务器无限循环 cs = server.recvfrom()/server.sendto() # 对话(接收与发送) server.close() # 关闭服务器套接字
客户端
client = socket() # 创建客户套接字 comm_loop: # 通讯循环 client.sendto()/client.recvfrom() # 对话(发送/接收) client.close() # 关闭客户套接字
简单例子
服务端
import socket ? ip_port = (‘127.0.0.1‘,8081) server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) server.bind(ip_port) while True: print(‘udp服务端开始运行了‘) data,addr = server.recvfrom(1024) print(data.decode(‘utf-8‘)) msg = input("请输入").strip() server.sendto(msg.encode("utf-8"),addr)
客户端
import socket ? ip_port = (‘127.0.0.1‘, 8081) server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True: print(‘udp客户端开始运行了‘) msg = input("请输入").strip() server.sendto(msg.encode("utf-8"), ip_port) data, addr = server.recvfrom(1024) print(data.decode("utf-8"))
注意:udp 可以发空 数据报协议 说是发空,其实不是空 ,还有一个IP 端口的信息,发空时 带个端口信息,
tcp:不是一一对应的,udp:是一一对应的 数据报完整的
用upd做一个ntp时间服务器
服务端
import socket import time ? ip_port = ("127.0.0.1",8080) buffer_size = 1024 ntp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) ntp_server.bind(ip_port) ? while True: data,addr = ntp_server.recvfrom(buffer_size) print("收到客户端的命令是",data.decode("utf-8")) if not data: fmt = "%Y-%m-%d %X" else: fmt = data.decode("utf-8") time_now = time.strftime(fmt,time.localtime()) ntp_server.sendto(time_now.encode("utf-8"),addr)
客户端
import socket ? ip_port = ("127.0.0.1",8080) buffer_size = 1024 ntp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) ? while True: msg = input(">>>") ntp_client.sendto(msg.encode("utf-8"),ip_port) recv_msg,addr = ntp_client.recvfrom(buffer_size) print(recv_msg.decode("utf-8"))
基于udp简单实现QQ聊天
服务端
from socket import * udp_server= socket(AF_INET,SOCK_DGRAM) udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) udp_server.bind((‘127.0.0.1‘,8080)) print(‘start running...‘) ? while True: qq_msg,addr = udp_server.recvfrom(1024) print(‘来自[%s:%s]的一条消息: 33[44m%s 33[0m‘%(addr[0],addr[1],qq_msg.decode(‘utf-8‘))) back_msg = input(‘回复消息:>>‘).strip() udp_server.sendto(back_msg.encode(‘utf-8‘),addr) udp_server.close()
客户端
from socket import * udp_client = socket(AF_INET,SOCK_DGRAM) qq_name_dic = { ‘pony‘:(‘127.0.0.1‘,8080), ‘jack‘:(‘127.0.0.1‘,8080), ‘charles‘:(‘127.0.0.1‘,8080), ‘nick‘:(‘127.0.0.1‘,8080) } while True: print("QQ名单列表:") for i in qq_name_dic.keys(): print(i) qq_name = input(‘请输入聊天对象:>>‘).strip() if qq_name not in qq_name_dic: continue while True: msg = input(‘请输入消息,回车发送:‘).strip() if msg==‘quit‘:break if not msg or not qq_name or qq_name not in qq_name_dic:continue udp_client.sendto(msg.encode(‘utf-8‘),qq_name_dic[qq_name]) back_msg,addr = udp_client.recvfrom(1024) print(‘来自[%s:%s]的一条消息: 33[41m%s 33[0m‘%(addr[0],addr[1],back_msg.decode(‘utf-8‘))) udp_client.close()
二、tcp与udp对比
tcp基于链接通信,数据流式协议
-
基于链接,则需要listen(backlog),指定连接池的大小
-
基于链接,必须先运行的服务端,然后客户端发起链接请求
-
对于mac系统:如果一端断开了链接,那另外一端的链接也跟着出错,recv将不会阻塞,收到的是空(解决方法是:服务端在收消息后加上if判断,空消息就break掉通信循环)
-
对于windows/linux系统:如果一端断开了链接,那另外一端的链接也跟着出错,recv将不会阻塞,收到的是空(解决方法是:服务端通信循环内加异常处理,捕捉到异常后就break掉通讯循环)
-
相对于upd传输速度慢
-
流式协议 会粘包 不可以发空 send recv 不是 一 一对应
-
tcp适用于:
-
数据一定要可靠
-
远程执行命令
-
下载文件
-
udp无链接,数据报式协议
-
无链接,因而无需listen(backlog),更加没有什么链接池
-
无链接,udp的sendto不用管是否有一个正在运行的服务端可以一直发消息,的发消息,只不过数据丢失
-
recvfrom收的数据小于sendto发送的数据时,在mac和linux系统上数据直接丢失,在windows系统上发送的比接收的大直接报错
-
只有sendto发送数据没有recvfrom收数据,数据丢失
-
数据报协议 不会粘包 可以发空 sendto recvfrom 一 一 对应 数据报协议 数据不安全 有可能发送数据 > 1024 或者网络网络异常 数据没了
-
udp适用于
-
QQ
-
查询操作 eg: ntp时间服务器 dns服务器(查域名,转ip) 能保证查询效率高,数据虽然不可靠,传输过程中可能会发生数据丢失
-
三、基于socket实现文件网络传输
简单版本
服务端
import socket import os import hashlib import json import struct ? ip_port = ("127.0.0.1",9001) back_log = 5 buffer_size = 1024 base_path = os.path.dirname(os.path.abspath(__file__)) share_path =os.path.join(base_path,"share") ? ftp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) ftp_server.bind(ip_port) ftp_server.listen(back_log) ? def creat_md5(file): md5_value = hashlib.md5() with open(file,"rb") as f: while True: data = f.read(1024) if not data: break md5_value.update(data) return md5_value.hexdigest() ? ? while True: print("FTP服务器开始运行啦!") conn,address = ftp_server.accept() while True: try: # 第一步:收命令 res = conn.recv(8096) #"get a.txt" if not res: continue # 第二步:解析命令, 提取相应的命令参数 cmds = res.decode("utf-8").split() file_name = cmds[1] if cmds[0] == "get": ? file_path = os.path.join(share_path,file_name) file_md5 = creat_md5(file_path) file_size = os.path.getsize(file_path) #第三步:以读的方式打开文件,读取文件内容 发送给客户端, # 1、先自制报头,传递文件的相关信息 header_dic = { "filename":file_name, "filemd5":file_md5, "filesize":file_size } header_json = json.dumps(header_dic).encode("utf-8") header_length = len(header_json) header_struct = struct.pack("i",header_length) # 2、发送报头的长度 conn.send(header_struct) # 3、发送报头,传递文件的各种信息 conn.send(header_json) # 4、打开文件,读取内容,一行一行的发送读取的内容给客户端 with open(file_path,"rb") as f: for line in f: conn.send(line) ? except Exception as e: print(e) break conn.close()
客户端
import socket import os import struct import json import time ? ip_port = ("127.0.0.1", 9001) buffer_size = 1024 base_path = os.path.dirname(os.path.abspath(__file__)) download_path = os.path.join(base_path,"download") ? ftp_client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ftp_client.connect(ip_port) ? while True: # 第一步:写命令,发送命令给服务端 cmd = input("请输入命令: ") if not cmd: continue if cmd == "quit": break ftp_client.send(cmd.encode("utf-8")) # 第二步:收取自制报头的长度 header_struct = ftp_client.recv(4) header_length = struct.unpack("i", header_struct)[0] print("报头长度",header_length) # 第三步:收取自制报头的信息 header_json = ftp_client.recv(header_length).decode("utf-8") header_dic = json.loads(header_json) print("报头字典",header_dic) # 第四步:根据报头信息拼出文件的各种信息 file_name = header_dic["filename"] file_md5 = header_dic["filemd5"] file_size = header_dic["filesize"] file_download_path = os.path.join(download_path,file_name) # 第五步:以写的方式打开一个新文件,接收服务端发来的文件内容写入客户的新文件 with open(file_download_path,"wb") as f: data_size = 0 start_time = time.perf_counter() while data_size < file_size: line = ftp_client.recv(buffer_size) f.write(line) data_size = data_size + len(line) # print("已经写入数据",data_size) download_percent = int((data_size/file_size)*100) # print("百分比",download_percent) a = "*" * download_percent # print(a) b = "." * (100 - download_percent) # print(b) c = (data_size/file_size)*100 during_time = time.perf_counter() - start_time print(" {:3.0f}%[{}-{}]共计用时:{:.3f}s".format(c,a,b,during_time),end="") # sys.stdout.flush() print(" " + "执行结束") ftp_client.close()
基于类写的文件传输
服务端
import socket import os import struct import pickle ? ? class TCPServer: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM listen_count = 5 max_recv_bytes = 8192 coding = ‘utf-8‘ allow_reuse_address = False # 下载的文件存放路径 down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), ‘share‘) # 上传的文件存放路径 upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), ‘upload‘) ? def __init__(self,server_address,bind_and_listen=True): self.server_address = server_address self.socket = socket.socket(self.address_family,self.socket_type) ? if bind_and_listen: try: self.server_bind() self.server_listen() except Exception: self.server_close() ? def server_bind(self): if self.allow_reuse_address: #重用ip和端口 self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) self.socket.bind(self.server_address) ? def server_listen(self): self.socket.listen(self.listen_count) ? def server_close(self): self.socket.close() ? def server_accept(self): return self.socket.accept() ? def conn_close(self,conn): conn.close() ? def run(self): print(‘starting...‘) while True: self.conn,self.client_addr = self.server_accept() print(self.client_addr) while True: try: res = self.conn.recv(self.max_recv_bytes) if not res:continue cmds = res.decode(self.coding).split() if hasattr(self,cmds[0]): func = getattr(self,cmds[0]) func(cmds) except Exception: break self.conn_close(self.conn) ? def get(self,cmds): """ 下载 1.找到下载的文件 2.发送 header_size 3.发送 header_bytes file_size 4.读文件 rb 发送 send(line) 5.若文件不存在,发送0 client提示:文件不存在 :param cmds: 下载的文件 eg:[‘get‘,‘a.txt‘] :return: """ filename = cmds[1] file_path = os.path.join(self.down_filepath, filename) if os.path.isfile(file_path): header = { ‘filename‘: filename, ‘md5‘: ‘xxxxxx‘, ‘file_size‘: os.path.getsize(file_path) } header_bytes = pickle.dumps(header) #直接用pickle转为bytes,不用json+encode转为bytes self.conn.send(struct.pack(‘i‘, len(header_bytes))) self.conn.send(header_bytes) with open(file_path, ‘rb‘) as f: for line in f: self.conn.send(line) else: self.conn.send(struct.pack(‘i‘, 0)) ? def put(self,cmds): """ 上传 1.接收4个bytes 得到文件的 header_size 2.根据 header_size 得到 header_bytes header_dic 3.根据 header_dic 得到 file_size 3.以写的形式 打开文件 f.write() :param cmds: 下载的文件 eg:[‘put‘,‘a.txt‘] :return: """ obj = self.conn.recv(4) header_size = struct.unpack(‘i‘, obj)[0] header_bytes = self.conn.recv(header_size) header_dic = pickle.loads(header_bytes) print(header_dic) file_size = header_dic[‘file_size‘] filename = header_dic[‘filename‘] ? with open(‘%s/%s‘ % (self.upload_filepath, filename), ‘wb‘) as f: recv_size = 0 while recv_size < file_size: res = self.conn.recv(self.max_recv_bytes) f.write(res) recv_size += len(res) ? ? tcp_server = TCPServer((‘127.0.0.1‘,8080)) tcp_server.run() tcp_server.server_close()
客户端
import socket import struct import pickle import os class FTPClient: address_family = socket.AF_INET socket_type = socket.SOCK_STREAM # 下载的文件存放路径 down_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), ‘download‘) # 上传的文件存放路径 upload_filepath = os.path.join(os.path.dirname(os.path.abspath(__file__)), ‘share‘) coding = ‘utf-8‘ max_recv_bytes = 8192 def __init__(self, server_address, connect=True): self.server_address = server_address self.socket = socket.socket(self.address_family, self.socket_type) if connect: try: self.client_connect() except Exception: self.client_close() def client_connect(self): self.socket.connect(self.server_address) def client_close(self): self.socket.close() def run(self): while True: # get a.txt 下载 put a.txt 上传 msg = input(">>>:").strip() if not msg: continue self.socket.send(msg.encode(self.coding)) cmds = msg.split() if hasattr(self,cmds[0]): func = getattr(self,cmds[0]) func(cmds) def get(self, cmds): """ 下载 1.得到 header_size 2.得到 header_types header_dic 3.得到 file_size file_name 4.以写的形式 打开文件 :param cmds: 下载的内容 eg: cmds = [‘get‘,‘a.txt‘] :return: """ obj = self.socket.recv(4) header_size = struct.unpack(‘i‘, obj)[0] if header_size == 0: print(‘文件不存在‘) else: header_types = self.socket.recv(header_size) header_dic = pickle.loads(header_types) print(header_dic) file_size = header_dic[‘file_size‘] filename = header_dic[‘filename‘] with open(‘%s/%s‘ % (self.down_filepath, filename), ‘wb‘) as f: recv_size = 0 while recv_size < file_size: res = self.socket.recv(self.max_recv_bytes) f.write(res) recv_size += len(res) print(‘总大小:%s 已下载:%s‘ % (file_size, recv_size)) else: print(‘下载成功!‘) def put(self, cmds): """ 上传 1.查看上传的文件是否存在 2.上传文件 header_size 3.上传文件 header_bytes 4.以读的形式 打开文件 send(line) :param cmds: 上传的内容 eg: cmds = [‘put‘,‘a.txt‘] :return: """ filename = cmds[1] file_path = os.path.join(self.upload_filepath, filename) if os.path.isfile(file_path): file_size = os.path.getsize(file_path) header = { ‘filename‘: os.path.basename(filename), ‘md5‘: ‘xxxxxx‘, ‘file_size‘: file_size } header_bytes = pickle.dumps(header) self.socket.send(struct.pack(‘i‘, len(header_bytes))) self.socket.send(header_bytes) with open(file_path, ‘rb‘) as f: send_bytes = b‘‘ for line in f: self.socket.send(line) send_bytes += line print(‘总大小:%s 已上传:%s‘ % (file_size, len(send_bytes))) else: print(‘上传成功!‘) else: print(‘文件不存在‘) ftp_client = FTPClient((‘127.0.0.1‘,8080)) ftp_client.run() ftp_client.client_close()
以上是关于Python之路(第三十二篇) 网络编程:udp套接字简单文件传输的主要内容,如果未能解决你的问题,请参考以下文章