1> 基本通信
不需要像TCP一样先建立连接,直接创建socket对象,绑定IP和端口,就能收发数据了,客户端就更简单,
创建对象就行,如下
为什么是recvfrom,sendto?
因为通讯不基于连接,服务端需要知道是谁发来的消息,才能回复,所以recvfrom得到的对象包含 (数据和
客户端IP与端口) ,同理,sendto里面也包含数据和 对端IP和端口,因为没有连接,所以需要指明这个包是发往
哪里 而是之前的TCP协议是建立好了通道再进行数据传送,所以不必IP和端口信息,因为通过通道就是发往对
端的,很明确的知道对端是谁。
2>数据包协议,为何没有粘包现象
可以看到,客户端发了5 个字节的hello,但是服务端收取上限为2,windows下报错了,说缓冲区比数据包小,
linux下直接收取2个字节,其他的丢弃UDP没有通道的概念,自然不有存放残留数据的机制,他每个包都是独立
封装并发送,所以不会有粘包现象,所以它也叫数据报协议。
所以它的收发 也是 一对一的。
3>并且你可以发送空,因为就算你内容为空,UDP自带头部信息(对端IP和端口等),实际上这条消息(这个数据报)并
不为空(因为包含头部大小),所以操作系统是会帮你发送的。
4>应用场景
绝大多数的应用都是基于TCP的,但也有些UDP通信的场景或应用,如QQ,DNS域名解析(输入网址,DNS服务器
会转换成IP+端口再访问,这个时候对数据的安全性啥的要求并不太高, 请求断就断了,我再发一次就是,但是
追求速度,速度正是UDP的长处)等类似一些这样的场景。
二>基于TCP实现文件上传和下载(面向对象版)
服务端
class Ftp_server(): ''' 服务端的文件操作类,包含上传,下载,运行服务等功能 ''' def __init__(self, addr, port): self.addr = addr self.port = port self.socket_obj = socket.socket() self.serfile_dir = conf['server']['file_dir'] self.socket_obj.bind((self.addr, self.port)) self.socket_obj.listen(5) def wait_client_conn(self): ''' 服务端启动函数,这里建立连接,并且等待客户端的指令,反射至对应的函数 ''' while True: print('文件服务器已经启动,等待客户端接入...') conn, data = self.socket_obj.accept() while True: try: cmd = conn.recv(1024) if not cmd:continue operation = cmd.decode().split()[0] file_name = cmd.decode().split()[1] if hasattr(self, operation): func = getattr(self, operation) func(conn, file_name) except ConnectionResetError: print('客户端已关闭连接!') break conn.close() def download(self, obj, file_name): # 制作消息头 file = self.serfile_dir+file_name file_size = os.path.getsize(file) with open(file, 'rb') as f1: file_data = f1.read() sec_value = hashlib.sha1(file_data).hexdigest() head_dic = {'sec_value':sec_value, 'file_size':file_size} head_str = json.dumps(head_dic) head_size = len(head_str) head_size_bytes = struct.pack('i', head_size) print('头消息制作完毕') # 发送消息头大小 obj.send(head_size_bytes) print('头消息大小:%s'%head_size) # 发送消息头数据 obj.send(head_str.encode()) print('头消息:%s'%head_str) # 发送真实数据 with open(file, 'rb') as f: for line in f: obj.send(line) print('文件发送完毕') def upload(self, obj, b=0): # 接收数据头大小 head_size_bytes = obj.recv(4) head_size = struct.unpack('i', head_size_bytes) print('头部数据大小:%s'%head_size) # 根据头大小接收头数据 head_data_bytes = obj.recv(head_size[0]) head_data_str = head_data_bytes.decode() print('头部文件内容:%s'%head_data_str) head_data_dic = json.loads(head_data_str) # 根据头数据中的文件大小接收文件 file_size = head_data_dic['file_size'] file_name = head_data_dic['file_name'] file = self.serfile_dir+file_name recieved_size = 0 with open(file, 'wb') as f: while recieved_size < file_size: res_data = obj.recv(8096) f.write(res_data) recieved_size += len(res_data) print('文件下载完成!') server = Ftp_server('127.0.0.1', 8080) server.wait_client_conn()客户端
class Ftp_client(): ''' 客户端的文件操作类,包含上传,下载,查看文件,查看空间等功能 ''' def __init__(self, addr, port, username): self.addr = addr self.port = port self.max_space = int(conf[username]['max_space']) self.home_dir = conf[username]['home_dir'] self.serfile_dir = conf['server']['file_dir'] self.socket_obj = socket.socket() self.socket_obj.connect((self.addr, self.port)) def upload_to_server(self, file_name): ''' 上传功能,1、制作消息头,2、发送头大小,3、发送头信息,4、发送真实数据 ''' if file_name in os.listdir(self.home_dir): # 判断家目录是否有该文件 # 制作上传指令并发送 download_cmd = 'upload %s'%file_name self.socket_obj.send(download_cmd.encode()) print('正请求服务端接收文件,等待其响应。。。') time.sleep(1) # 因为这里基于数据包还要多一条指令,直接休眠1秒避免粘包 # 制作消息头 file = self.home_dir+file_name file_size = os.path.getsize(file) head_dic = {'file_size':file_size, 'file_name':file_name} head_str = json.dumps(head_dic) head_size = len(head_str) head_size_bytes = struct.pack('i', head_size) print('头消息制作完毕') # 发送消息头大小 self.socket_obj.send(head_size_bytes) print('头消息大小:%s'%head_size) # 发送消息头数据 self.socket_obj.send(head_str.encode()) print('头消息:%s'%head_str) # 发送真实数据 with open(file, 'rb') as f: for line in f: self.socket_obj.send(line) print('文件上传完毕') else: print('文件名错误!') def download_from_server(self, file_name): ''' 下载功能,1、发送下载指令,2、接收头大小,3、接收头信息,4、接收真实数据,5、验证文件一致性 ''' if file_name in os.listdir(self.serfile_dir): # 判断服务端是否有该文件 file_size = os.path.getsize(self.serfile_dir+file_name) if file_size <= (int(self.max_space)-self.used_size): # 判断空间是否足够 # 制作下载指令并发送 download_cmd = 'download %s'%file_name self.socket_obj.send(download_cmd.encode()) # print('发送下载指令') # 接收数据头大小 head_size_bytes = self.socket_obj.recv(4) head_size = struct.unpack('i', head_size_bytes) # print('头部数据大小:%s'%head_size) # 根据头大小接收头数据 head_data_bytes = self.socket_obj.recv(head_size[0]) head_data_str = head_data_bytes.decode() # print('头部文件内容:%s'%head_data_str) head_data_dic = json.loads(head_data_str) # 根据头数据中的文件大小接收文件 file_size = head_data_dic['file_size'] file = self.home_dir+file_name recieved_size = 0 with open(file, 'wb') as f: while recieved_size < file_size: res_data = self.socket_obj.recv(8096) f.write(res_data) recieved_size += len(res_data) self.progressbar(recieved_size*10/file_size,10) # print('文件下载完成!') # 根据头数据中的加密串验证文件是否一致 sec_value = head_data_dic['sec_value'] with open(file, 'rb') as f1: file_data = f1.read() file_sec = hashlib.sha1(file_data).hexdigest() # print(os.path.getsize(file)) # print(file_sec) if sec_value == file_sec: print('文件对比完成,两文件一致!') else: print('家目录存储空间不足。') else: print('文件名错误!')
@staticmethod def progressbar(cur,total): ''' 进度条函数 ''' percent = '{:.2%}'.format(cur / total) sys.stdout.write('\r') sys.stdout.write('[%-50s] %s' % ( '#' * int(math.floor(cur * 50 /total)),percent)) sys.stdout.flush() if cur == total: sys.stdout.write('\n')程序还有很大的可扩展性,如 客户端这边需要查看服务端的文件,也可以send一个'check XXX'的命令过去,服务端定义
相应的 check()函数就行,其他的地方都基本不必改动,
最后一个进度条函数,直接从网上找的,没具体研究怎么用,只是发现刚好能套进去,就引入了(实现下载文件有进度条
显示功能)。