day19-网络编程基础(二)
今天没有很多概念性的东西,主要是方法性的东西以及编程的一些方法吧
今日份目录
1.UDP传输的特点以及实验
2.UTP与UDP传输的区别
3.基于tcp的low版带验证功能的FTP小程序
4.基于socketserver的多人聊天
5.验证客户端的完整性
6.socket的阻塞模型与非阻塞模型
开始今日份整理
1.UDP传输的特点以及实验
UDP与TCP都是传输层中的重要传输协议,下面说一下UDP的特点
1.1 UDP传输特点
无连接的,面向数据包,不可靠的,快速的,不需要accept/connect 也没有握手
1.2 基于UDP的多人聊天
#服务端 import socket sk = socket.socket(type=socket.SOCK_DGRAM) sk.bind(('127.0.0.1',8500)) while True: msg,addr = sk.recvfrom(1024) print(msg.decode()) send_message = input('>>>').strip() sk.sendto(send_message.encode(),(addr)) sk.close()
#客户端 import socket sk = socket.socket(type= socket.SOCK_DGRAM) while True: msg = input('>>>').strip() send_msg ='test1:'+ msg sk.sendto(send_msg.encode(),('127.0.0.1',8500)) msg,addr =sk.recvfrom(1024) print(msg.decode()) sk.close()
多个
客户端只要多拷贝几份client代码就可以了,就会发现在服务器端出现很多用户发过来的信息,由于是在命令行界面,在input界面会阻塞,所以并不会马上看到多人发来的信息。在图形化界面就不会这样了
2.UTP与UDP传输的区别
UTP的特性
- 面向连接的 可靠的 全双工的 流式传输
- 面向连接 :同一时刻只能和一个客户端通信
- 三次握手、四次挥手
- 可靠的 :数据不丢失、慢
- 全双工 :能够双向通信
- 流式传输 :粘包 无边界
UDP的特性
- 无连接的 面向数据包 不可靠的 快速的
- 无连接的 :不需要accept/connect 也没有握手
- 面向数据包的 :不会粘包
- 不可靠的 :没有自动回复的机制
- 快速的 :没有那些复杂的计算、保证数据传输的机制
我们从上面看到,tcp为什么会黏包的原因:本地端不知道发送端具体发送了多长的字符串,解决办法就是自定义协议,规定在传输之前先要传输的大小先行发送过去。在对方来接收规定长度的文件。
3.基于tcp的low版带验证功能的FTP小程序
做实验前谨记一个传大文件需要先传一个字典过去,让对方知道
3.1实验一
目的:服务器以及客户端互传文件并动态校验MD5
#服务端,比较low
import socket import struct import json import hashlib import os sk = socket.socket()#网络传输基本配置 sk.bind(('127.0.0.1',8500))#本地端口 sk.listen() conn,addr = sk.accept() def download(file_dic):#客户端上传 print(3) while True: file_name = file_dic['file_name']#获取文件名 file_size = file_dic['file_size']#获取文件大小 ret = file_write(file_size,file_name)#收到的文件的MD5值 print(ret) send_header(ret)#发送给客户端,服务器端的MD5值 print(addr,'接收成功!') break def upload(file_dic):#客户端下载 while True: isfile_dict = {} file_name = file_dic['file_name'] if os.path.isfile(file_name): isfile_dict['flag'] =1 send_header(isfile_dict) send_file_dic ={} send_file_dic['file_name'] = file_name send_file_dic['file_size'] = os.path.getsize(file_name) print(send_file_dic) send_header(send_file_dic) #发送头文件 ret =file_read(file_name)#传输具体的文件获取MD5值 send_header(ret) # 发送给客户端,服务器端的MD5值 print(addr,'发送成功!') break else: isfile_dict['flag'] = 0 send_header(isfile_dict) break def get_header(date):#获取头文件 header_json = date.decode() header_dic = json.loads(header_json) return header_dic def send_header(obj):#发送头文件 header_json = json.dumps(obj) # 头文件的变为str类型 header_bytes = header_json.encode() # 头文件变为bytes类型 header_len = struct.pack('i', len(header_bytes)) # 计算头文件的长度 conn.send(header_len) # 发送头文件的长度 conn.send(header_bytes) # 发送头文件 def file_write(file_size,filename):#文件写入,同时得到写入后的文件的MD5值 recevive_num = 0 has = hashlib.md5() with open(filename,'wb')as f2: while recevive_num< file_size: contact = conn.recv(1024) if contact: f2.write(contact) recevive_num += len(contact) has.update(contact) else: break return {'MD5':has.hexdigest()} def file_read(filename):#文件读取,同时得到他的MD5值 has = hashlib.md5() with open(filename,'rb')as f1: while True: contact = f1.read(1024) conn.send(contact) if contact: has.update(contact) else: break return {'MD5':has.hexdigest()} def run(): print(addr) while True: file_len = conn.recv(4)[0] file_bytes = conn.recv(file_len) file_dic =get_header(file_bytes) print(file_dic) if file_dic['operation']== 'download': upload(file_dic) elif file_dic['operation'] == 'upload': download(file_dic) run()
#客户端
import socket import struct import json import hashlib import os import sys header_dic ={} menu=[ ('上传文件',), ('下载文件',) ] def __upload(): while True: file_get = input('请输入你要上传文件的绝对路径>>>').strip() if os.path.isfile(file_get): file_name = os.path.basename(file_get)#获取文件名 header_dic['file_name']=file_name file_size = os.path.getsize(file_get)#获取文件大小 header_dic['file_size'] = file_size header_dic['operation'] = 'upload' send_header(header_dic)#发送文件的头文件 ret =file_read(file_get,file_size)#传输具体的文件获取MD5值 MD5_len= sk.recv(4)[0] MD5_bytes = sk.recv(MD5_len) MD5_dict = get_header(MD5_bytes) if ret['MD5'] == MD5_dict['MD5']: print('上传成功!') break else: print('上传失败!') break else: print('你输入的文件路径不存在,请重新确认!') def __download(): while True: file_get = input('请输入你要下载的文件的文件名>>>').strip() header_dic['file_name'] = file_get header_dic['operation'] = 'download' send_header(header_dic)#发送一个头文件给服务器 header_len = sk.recv(4)[0]#接收头文件的长度 header_bytes = sk.recv(header_len)#接收头文件的bytes类型 file_dic = get_header(header_bytes)#解包,获得头文件 if file_dic['flag'] == 0: print('你要下载的文件不存在,请重新选择......') else: print('文件存在') recv_file_header =sk.recv(4)[0] recv_file_header_bytes = sk.recv(recv_file_header) recv_file_dict = get_header(recv_file_header_bytes) file_size = recv_file_dict['file_size'] file_name = recv_file_dict['file_name'] ret = file_write(file_size,file_name) MD5_len = sk.recv(4)[0]#获得MD5字典的长度 MD5_bytes = sk.recv(MD5_len) MD5_dict = get_header(MD5_bytes)#获取MD5字典的内容 if ret['MD5'] == MD5_dict['MD5']: print('文件校验成功!') break else: print('你接收的文件损坏,无法匹配!') break def send_header(obj):#发送头文件 header_json = json.dumps(obj) # 头文件的变为str类型 header_bytes = header_json.encode() # 头文件变为bytes类型 header_len = struct.pack('i', len(header_bytes)) # 计算头文件的长度 sk.send(header_len) # 发送头文件的长度 sk.send(header_bytes) # 发送头文件 def get_header(date):#获取头文件 header_json = date.decode() header_dic = json.loads(header_json) return header_dic def file_read(filename,file_size):#文件读取,同时得到他的MD5值 has = hashlib.md5() recevive_num = 0 with open(filename,'rb')as f1: while True: contact = f1.read(1024) if contact: sk.send(contact) has.update(contact) float_rate = int(recevive_num / file_size) rate = round(float_rate * 100, 2) sys.stdout.write('\r已下载:\033[1;32m{0}%\033[0m'.format(rate)) else: break return {'MD5':has.hexdigest()} def file_write(file_size,filename):#文件写入,同时得到写入后的文件的MD5值 recevive_num = 0 has = hashlib.md5() with open(filename,'wb')as f2: while recevive_num < file_size: contact = sk.recv(1024) if contact: f2.write(contact) recevive_num += len(contact) has.update(contact) float_rate = float(recevive_num / file_size) rate = round(float_rate * 100, 2) sys.stdout.write('\r已下载:\033[1;32m{}%\033[0m'.format(rate)) else: break print(2) return {'MD5':has.hexdigest()} if __name__ == '__main__': sk = socket.socket() # 网络传输基本配置 sk.connect(('127.0.0.1', 8500)) # 远端端口 while True: for i,j in enumerate(menu,1): print(i,j[0]) choice = int(input('请输入你想要实现的功能>>>').strip()) if choice == 1: __upload() elif choice == 2: __download()
4.基于socketserver的多人聊天
#服务端 import socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): conn = self.request while True: date = conn.recv(1024).decode() print(date) msg = input('>>>').encode() conn.send(msg) server = socketserver.ThreadingTCPServer(('127.0.0.1',8500),Myserver) server.serve_forever()
#客户端 import socket sk = socket.socket() sk.connect(('127.0.0.1',8500)) while True: msg ='one' + input('>>>').strip() sk.send(msg.encode()) data = sk.recv(1024).decode() print(data) sk.close()
5.验证客户端的完整性
如果你想在分布式系统中实现一个简单的客户端链接认证功能,又不像SSL那么复杂,那么利用hmac+加盐的方式来实现
#服务端 import os import hmac import socket sk = socket.socket() sk.bind(('127.0.0.1',8500)) sk.listen() def auth(conn): secret_key = b'alexsb' rand_b = os.urandom(32) conn.send(rand_b) obj = hmac.new(secret_key, rand_b) res1 = obj.digest() res2 = conn.recv(1024) cmp_res = hmac.compare_digest(res1, res2) return cmp_res conn,addr = sk.accept() res = auth(conn) if res : print('正常客户端') conn.send(b'hello') else: conn.close() conn.close() sk.listen()
#客户端 import hmac import socket sk = socket.socket() sk.connect(('127.0.0.1',8500)) def auth(sk): secret_key = b'alexsb' rand_b =sk.recv(32) obj = hmac.new(secret_key,rand_b) res2 = obj.digest() sk.send(res2) auth(sk) msg = sk.recv(1024) print(msg) sk.close()
6.socket的阻塞模型与非阻塞模型
只是涉及很浅显的一点阻塞模型
# 阻塞模型 # accept # recv # recvfrom # 非阻塞 # accept # recv # recvfrom import socket # sk = socket.socket() # sk.setblocking(False) # sk.bind(('127.0.0.1',9000)) # sk.listen() # while True: # try: # sk.accept() # except BlockingIOError: # pass