我的python学习之路-tup/udp/hashlib/hmac
本节内容:
一.网络基本概念
1. 2.网段
1.3.端口
二、TCP 和 UDP 用法
三、socketserver 使用
四、 加密模块的使用
一、网络基本概念
1、网络开发的两个架构
1.C/S架构
2.B/S 架构
一台主机有两个重要标识:
(1)mac地址:标记一台机器的物理地址 (不可变)
(2)ip 地址:#标记一台机器的逻辑地址 (可变)
2、网段
网段
主要用来划分同一区域里的某些机器是否能够互相通信。
在一个网段里可以不同过因特网,直接对话
子网掩码:
区分网段和主机
判别的依据:
如果IP地址和子网掩码相与得到的值相同就是同一网段
3.端口
通过ip+端口号 : 可以找到世界上任何一个主机下的一个应用(软件) => 192.168.33.81:80
0~65535 : 自定义端口时,不要使用知名厂商已经用过的端口号, 起8000以上端口号,避免冲突
20 FTP=>文件传输协议(默认数据口)
21 FTP=>文件传输协议(控制)
22 SSH远程登录协议 (xshell , putty)
25 SMTP服务器所开放的端口,用于发送邮件
80 http,用于网页浏览,木马Executor开放此端口
443 https , 基于http,对数据进行加密传输
3306 MySQL开放此端口
4. osi 网络七层模型
应用层:(应用层,表示层,会话层)
封装数据:
根据不同的协议,封装不同个数的数据
http (超文本传输协议)
https (加密传输的超文本传输协议)
FTP (文件传输协议)
SMTP (邮件传输协议)
传输层:
封装端口:
指定传输协议(TCP协议/UDP协议)
网络层:
封装ip:
ipv4 / ipv6
数据链路层:
封装mac地址:
指定mac地址(arp协议[ip->mac] rarp协议[mac->ip])
物理层:
打成数据包,变成二进制字节流,通过网络进行传输
5.交换机 和 路由器
交换机:对同一网段的不同机器之间进行数据转发的设备 [每一台机器和交换机相连,形成通信]
路由器:对不同网段的不同机器之间进行数据转发的设备 [每一个局域网和路由器相连,形成通信]
交换机 : 从下到上拆2层,拆到数据链路层 (转发内网数据)
路由器 : 从下到上拆3层,拆到网络层 (转发外网数据)
arp协议:每台主机都有arp缓存表 ,主要作用通过ip找mac的一个协议规则
【实现方式:通过交换机一次广播,一次单播找到的】
arp协议: 通过ip->mac
rarp协议: 通过mac->ip
arp协议完整过程:
电脑a发现目标主机没有mac,标记全F广播地址,发送arp广播包,给了交换机
交换机接受arp广播包,从下到上拆2层,拆到数据链路层,发现是全F的广播地址,此时开始广播,给所有连接在这个交换机的设备发一份arp广播包
每台主机在接收到arp广播包后,开始拆包,如果不归属于自己,该数据包自动舍弃
路由器得到arp广播包后,从下到上拆包,拆三层到网络层,获取到ip,解析出对应的网段信息;打开路由器信息表找对应的网关(路由器层面)
把arp广播包发送给对应网关的交换机
交换机拿到数据之后,从下到上拆2层,发现全F广播地址,在广播.
数据库得到广播包之后,从下到上进行拆包,发现要找的这个主机,就是自己.
此时,把对应ip->mac的数据返回给交换机,
交换机拿到该数据之后,进行单播,返回给数据的原发送方
原数据发送发拿到数据后,更新自己的arp解析表,方便下次使用
6.TCP / UDP 协议
TCP(Transmission Control Protocol)一种面向连接的、可靠的、传输层通信协议(比如:打电话)
优点:可靠,稳定,传输完整稳定,不限制数据大小
缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认
应用:Web浏览器,电子邮件,文件传输,大量数据传输的场景
UDP(User Datagram Protocol)一种无连接的,不可靠的传输层通信协议(比如:发短信)
优点:速度快,可以多人同时聊天,耗费资源少,不需要建立连接
缺点:不稳定,不能保证每次数据都能接收到(大数据不稳定)
应用:IP电话,实时视频会议,聊天软件,少量数据传输的场景
客户端和服务端在建立连接时: 三次握手
客户端和服务端在断开连接时: 四次挥手
SYN 创建连接
ACK 确认响应
FIN 断开连接
TCP 三次握手:
特点: 先建立连接 , 在发送数据
建立连接 (三次握手)
客户端发送一个请求消息,与服务端建立连接
服务端接受请求,发出响应,回复客户端,与客户端建立连接的一个请求;
客户端接受服务端的响应消息后,发送回复消息(表达同意)
到此,客户端和服务端真正的建立了连接,接下来开始发送数据.
发送数据 特点:有回执消息
每发送一个数据,都会对应有回执消息.如果没有回执消息,会把该数据在发送一次(保证数据的稳定,可靠,不丢包)
断开连接 (四次挥手)
等双方都没有数据要互发,断开连接,所以是4次挥手; 特点:最大等待2msl 大概1分钟;
客户端向服务端发送一个断开连接的请求(代表客户端已经没有数据发送给服务端)
服务端接受请求,发出响应 (回复好的)
等到服务端彻底把数据发送完毕之后,
服务端向客户端发送一个断开连接的请求,
客户端接受请求,发出响应 (回复好的)
双方彻底断开连接.
二、TCP 和 UDP 用法
1、TCP的基本用法
(1)客户端
# ### 客户端 import socket # 1.创建一个socket对象 sk = socket.socket() # 2.与服务端建立连接 sk.connect( ("127.0.0.1",8001) ) # 3.发送数据(二进制的字节流) sk.send("地狱空荡荡,魔鬼在人间,但行好事,莫问前程".encode("utf-8")) # 4.关闭连接 sk.close()
(2)服务器端
1 # ### 服务端 2 """必须注意:一发一收需要成对,否则出现数据异常""" 3 import socket 4 # 1.创建socket对象 5 sk = socket.socket() 6 # 2.绑定对应的ip和端口号(注册网络,让其他电脑可以访问到该主机) 7 # 127.0.0.1 默认本地ip , 参数为一个元组 8 sk.bind( ("127.0.0.1",8001) ) 9 # 3.开启监听 10 sk.listen() 11 12 # 4.建立三次握手 13 conn,addr = sk.accept() 14 15 print("<======1=====>") 16 print(conn) 17 print(addr) 18 """ 19 conn : <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8001), raddr=('127.0.0.1', 45466)> 20 addr : ('127.0.0.1', 45466) 21 """ 22 print("<======2=====>") 23 24 # 5.处理收发数据的逻辑 25 res = conn.recv(1024) # 最多一次接受1024个字节 26 print(res.decode("utf-8")) 27 28 # 6.四次挥手 29 conn.close() 30 # 7.退还端口 31 sk.close()
2、TCP循环发送消息
1 # ### 客户端 2 import socket 3 # 1.创建socket对象 4 sk = socket.socket() 5 # 2.连接服务端 6 sk.connect( ("127.0.0.1",8001) ) 7 # 3.处理收发数据的逻辑 8 9 while True: 10 # 发送数据 11 strvar = input("请输出您要发送的数据[客户端]>>>") 12 sk.send(strvar.encode()) 13 14 # 接受数据 15 res = sk.recv(1024) 16 17 if res == b'q' or res == b"Q": 18 break 19 20 print(res.decode()) 21 22 # 4.关闭连接 23 sk.close()
1 # ### 服务端 2 import socket 3 # 1.创建socket对象 4 sk = socket.socket() 5 6 # 让一个端口重复绑定多个程序(仅限在测试环节) 7 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 8 9 10 # 2.绑定ip端口号(在网络中注册该主机) 11 sk.bind( ("127.0.0.1",8001) ) 12 # 3.开启监听 13 sk.listen() 14 15 while True: 16 17 # 4.建立三次握手 18 conn,addr = sk.accept() 19 # 5.处理收发数据的逻辑 20 21 while True: 22 # 接受数据 23 res = conn.recv(1024) 24 print(res,type(res)) 25 print(res.decode()) 26 27 # 发送数据 28 strvar = input("请输出您要发送的数据[服务端]>>>") 29 conn.send(strvar.encode()) 30 31 if strvar.upper() == "Q": 32 break 33 34 # 6.四次挥手 35 conn.close() 36 37 38 # 7.退还端口 39 sk.close()
3、UDP的基本用法
1 # ### 客户端 2 import socket 3 # 1.创建udp对象 4 sk = socket.socket(type=socket.SOCK_DGRAM) 5 # 2.收发数据的逻辑 6 7 # 发送数据 8 msg = "你好么老婶~" 9 # sendto( 数据[二进制字节流] , (ip,端口号) ) 10 sk.sendto( msg.encode() , ("127.0.0.1",8005) ) 11 12 # 接受数据 13 msg,ser_addr = sk.recvfrom(1024) 14 print(msg.decode()) 15 print(ser_addr) 16 17 # 3.关闭连接 18 sk.close()
1 # ### 服务端 2 import socket 3 # 1.创建udp对象 4 sk = socket.socket(type=socket.SOCK_DGRAM) 5 # 2.绑定ip端口号 6 sk.bind( ("127.0.0.1",8005) ) 7 # 3.针对于udp服务器,第一次只能接受数据 8 9 # 接受数据 10 msg,cli_addr = sk.recvfrom(1024) 11 print(msg.decode()) # 你好么老婶~ 12 print(cli_addr) # ('127.0.0.1', 46190) 13 14 15 # 发送数据 16 msg = "我是你妈妈" 17 sk.sendto( msg.encode() , cli_addr ) 18 19 # 4.关闭连接 20 sk.close()
4、UDP循环发送消息
1 # ### 客户端 2 import socket 3 # 1.创建udp对象 4 sk = socket.socket(type=socket.SOCK_DGRAM) 5 6 # 2.处理收发数据的流程 7 while True: 8 # 发送数据 9 message = input("请输入要发送的内容[客户端]") 10 sk.sendto( message.encode() , ("127.0.0.1",8005) ) 11 12 # 接受数据 13 msg , ser_addr = sk.recvfrom(1024) 14 print(msg.decode()) 15 16 # 3.关闭连接 17 sk.close()
1 # ### 服务端 2 import socket 3 # 1.创建udp对象 4 sk = socket.socket(type=socket.SOCK_DGRAM) 5 # 2.绑定ip端口号 6 sk.bind( ("127.0.0.1",8005) ) 7 8 # 3.针对于udp服务器,第一次只能接受数据 ,处理收发数据的流程 9 while True: 10 # 接受数据 11 msg,cli_addr = sk.recvfrom(1024) 12 print(msg.decode()) 13 print(cli_addr) 14 15 # 发送数据 16 message = input("请输入要发送的内容[服务端]") 17 sk.sendto(message.encode() , cli_addr) 18 19 20 # 4.关闭连接 21 sk.close() 22 23 24 """ 25 tcp : 默认是1对1, 默认必须等待连接断开之后,下个客户端才能连接 26 udp : 默认1对多,不需要建立连接,即可发送或者接受数据 27 """
5、黏包
(1)造成原因
1 # ### 客户端 2 import socket 3 import time 4 sk = socket.socket() 5 sk.connect( ("127.0.0.1",8001) ) 6 7 8 # [接收端]出现黏包: 接受数据太慢了 9 # 处理收发数据的逻辑 10 # 1024 最多接受1024个字节 11 time.sleep(3) 12 res1 = sk.recv(1024) 13 print(res1.decode(),"1111") 14 res2 = sk.recv(1024) 15 print(res2.decode(),"2222") 16 17 sk.close()
1 # ### 服务端 2 import socket 3 import time 4 sk = socket.socket() 5 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 6 sk.bind( ("127.0.0.1",8001) ) 7 sk.listen() 8 conn,addr = sk.accept() 9 10 11 # [发送端]出现黏包: 1.数据太小, 2.数据之间时间间隔太短了; 12 # 处理收发数据的逻辑 13 conn.send("feifei".encode()) 14 time.sleep(1) 15 conn.send("你站起来".encode()) 16 17 18 conn.close() 19 sk.close()
(2)解决方法
1 # ### 客户端 2 import socket 3 sk = socket.socket() 4 sk.connect( ("127.0.0.1",8001) ) 5 6 # 处理收发数据的逻辑 7 # 1024 最多接受1024个字节 8 9 # 第一步,先接受接下来要发送的数据 10 res = sk.recv(1) 11 num = int(res.decode()) # 6 12 13 # 第二步,接受真实的数据[feifei] 14 res1 = sk.recv(num) 15 print(res1.decode(),"1111") 16 17 # 第三步,接受真实的数据[你站起来] 18 res2 = sk.recv(1024) 19 print(res2.decode(),"2222") 20 21 sk.close()
1 # ### 服务端 2 import socket 3 import time 4 sk = socket.socket() 5 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 6 sk.bind( ("127.0.0.1",8001) ) 7 sk.listen() 8 conn,addr = sk.accept() 9 10 # 处理收发数据的逻辑 11 # 第一步,先把接下来要发送的数据的字节大小传过去 12 conn.send("6".encode()) 13 14 # 第二步,发送真实的数据[feifei] 15 conn.send("feifei".encode()) 16 17 # 第三步,发送真实的数据[你站起来] 18 conn.send("你站起来".encode()) 19 20 21 22 conn.close() 23 sk.close()
(3)struct 模块
pack 打包
把任意长度数字转换成具有固定4个字节长度的字节流
unpack 解包
把4个字节长度的值恢复成原来的数字,返回是元组
1 import struct 2 # pack 3 # i => int 要转换的当前类型是整型 4 res = struct.pack("i" , 191992131) 5 print( res , len(res)) 6 7 res = struct.pack("i" , 1) 8 print( res , len(res)) 9 10 res = struct.pack("i" , 233241) 11 print( res , len(res)) 12 13 # -2147483648 <= number <= 2147483647 长度在-21个亿 ~ 21个亿左右 大概不到1.9g的数据 14 res = struct.pack("i" , 9999999999) 15 print( res , len(res)) 16 17 18 # unpack 19 # i => 把对应的数据转换成整型 20 tup = struct.unpack("i",res) 21 res = tup[0] 22 print(res , type(res)) 23 print(tup , type(tup))
(4)优化方法
1 # ### 客户端 2 import socket 3 import struct 4 sk = socket.socket() 5 sk.connect( ("127.0.0.1",8001) ) 6 7 # 处理收发数据的逻辑 8 # 第一次接受数据的大小 9 num = sk.recv(4) 10 print(num) 11 tup = struct.unpack("i",num) 12 num = tup[0] 13 14 # 第二次接受真实的数据,按照num这么大截取 15 res = sk.recv(num) 16 print(res.decode()) 17 18 # 第三次接受真实的数据 19 res = sk.recv(1024) 20 print(res.decode()) 21 22 23 sk.close()
1 # ### 服务端 2 import socket 3 import time 4 import struct 5 sk = socket.socket() 6 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 7 sk.bind( ("127.0.0.1",8001) ) 8 sk.listen() 9 conn,addr = sk.accept() 10 11 # 处理收发数据的逻辑 12 strvar = input("请输入您想要发送的数据:[服务端]") 13 # 先发送这个数据的大小 14 msg = strvar.encode() 15 res = len(msg) 16 print(res) 17 18 res_bytes = struct.pack("i",res) 19 20 # 第一次发送数据的大小 21 conn.send(res_bytes) 22 23 # 第二次发送真实的数据 24 conn.send(msg) 25 26 # 第三次发送真实的数据 27 conn.send("飞飞,旁边的自豪你也站起来吧~".encode()) 28 29 conn.close() 30 sk.close()
三、socketserver 使用
1、基本用法
# ### 客户端 import socket sk = socket.socket() sk.connect( ("127.0.0.1",8005) ) # 写处理收发数据的逻辑 sk.close()
# ### 服务端 import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): print("handler这个方法触发了 ... ") # ThreadingTCPServer( (ip,端口号) , 自定义类 ) server = socketserver.ThreadingTCPServer( ("127.0.0.1",8005) , MyServer ) server.serve_forever()
2、循环发送消息
1 # ### 客户端 2 import socket 3 sk = socket.socket() 4 sk.connect( ("127.0.0.1",8005) ) 5 6 # 写处理收发数据的逻辑 7 while True: 8 # 发送数据 9 sk.send(b"i love you") 10 # 接受数据 11 res = sk.recv(1024) 12 print(res.decode()) 13 sk.close()
1 # ### 服务端 2 """支持TCP协议下的多线程并发,一个服务端同时连接多个客户端""" 3 import socketserver 4 5 class MyServer(socketserver.BaseRequestHandler): 6 def handle(self): 7 print("handler这个方法触发了 ... ") 8 """ 9 print(self.request) 10 print(self.client_address) 11 conn: <socket.socket fd=4, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8005), raddr=('127.0.0.1', 54606)> 12 add : ('127.0.0.1', 54606) 13 """ 14 conn = self.request 15 while True: 16 # 接受数据 17 res = conn.recv(1024) 18 res2 = res.decode() 19 print(res2) 20 # 发送数据 21 conn.send(res2.upper().encode()) 22 23 24 # ThreadingTCPServer( (ip,端口号) , 自定义类 ) 25 server = socketserver.ThreadingTCPServer( ("127.0.0.1",8005) , MyServer ) 26 server.serve_forever()
四、加密模块的使用
1 、hashlib模块使用
(1)md5算法
md5算法 : 可以把字符串变成具有固定长度的32位十六进制字符串
加密之后,不能反解
1 import hashlib 2 import random 3 # 一.md5算法 4 # 1.创建md5对象 5 hs = hashlib.md5() 6 # 2.把要加密的数据放到新的对象里,update(参数:二进制字节流) 7 hs.update("111".encode("utf-8")) 8 # 3.获取十六进制 32为长度字符串 9 res = hs.hexdigest() 10 print(res , len(res)) 11 12 # 加盐 (加key,加关键字) 13 hs = hashlib.md5("XboyWW".encode()) 14 hs.update("111".encode()) 15 res = hs.hexdigest() 16 # 848ace59d5c61fc6b202db5301fbafa1 32 17 print(res , len(res)) 18 19 # 动态加盐 (动态加盐) 20 res = str(random.randrange(300,500000)) 21 hs = hashlib.md5(res.encode()) 22 hs.update("xboyaa".encode()) 23 res = hs.hexdigest() 24 print(res , len(res))
(2) sha系列算法
# 二.sha系列算法 hs = hashlib.sha256() # 64位长度 hs = hashlib.sha512() # 128位长度 hs.update("111222".encode()) res = hs.hexdigest() print(res , len(res))
2、hmac加密算法 (推荐)
1、基本语法
import hmac key = b"aa" msg = b"111222" # 语法: new(盐(二进制字节流) ,密码(二进制字节流) ) hm = hmac.new(key,msg) # 返回32位长度的十六进制字符串 res = hm.hexdigest() print(res , len(res))
2、动态加盐
# 随机二进制字节流 import os # os.urandom(位数) 返回随机的二进制字节流,长度由位数决定 res = os.urandom(32) print(res , len(res)) # hmac动态加盐效果 key = os.urandom(32) msg = b"123" hm = hmac.new(key,msg) res = hm.hexdigest() print(res , len(res))
3 文件校验
(1) 针对于小文件的内容,进行校验
def check_md5(filename): hs = hashlib.md5() with open(filename , mode="rb") as fp: hs.update(fp.read()) return hs.hexdigest() res1 = check_md5("ceshi1.py") res2 = check_md5("ceshi2.py") print(res1 , res2)
(2) 针对于大文件的内容,进行校验
结论:update方法,可以把一个字符串拆解成多分
进行分开加密,得出的结果和作为一个整体加密的值是一致的;
1 # 写法一 2 hs = hashlib.md5() 3 strvar = "全球疫情肆虐,我们出门要带口罩" 4 hs.update(strvar.encode()) 5 print(hs.hexdigest()) # 98bf8735876269b1750c997f330017c9
1 # 写法二 2 hs = hashlib.md5() 3 strvar1 = "全球疫情肆虐," 4 hs.update(strvar1.encode()) 5 strvar2 = "我们出门要带口罩" 6 hs.update(strvar2.encode()) 7 print(hs.hexdigest()) # 98bf8735876269b1750c997f330017c9
1 def check_md5(filename): 2 hs = hashlib.md5() 3 with open(filename , mode="rb") as fp: 4 while True: 5 # 一次最多读取10个字节 6 content = fp.read(10) 7 if content: 8 hs.update(content) 9 else: 10 break 11 12 return hs.hexdigest() 13 14 res1 = check_md5("ceshi1.py") 15 res2 = check_md5("ceshi2.py") 16 print(res1 == res2)
1 import os 2 def check_md5(filename): 3 hs = hashlib.md5() 4 filesize = os.path.getsize(filename) 5 print(filesize) 6 with open(filename , mode="rb") as fp: 7 # 一次减去最多10个字节,直到filesize = 0的时,循环终止了; 8 while filesize: 9 # 一次最多读取10个字节 3 10 content = fp.read(10) 11 hs.update(content) 12 filesize -= len(content) 13 return hs.hexdigest() 14 15 res1 = check_md5("ceshi1.py") 16 res2 = check_md5("ceshi2.py") 17 print(res1 == res2)
4、服务器校验的合法性
1 # ### 服务端1 2 import socket 3 import hmac 4 5 sk = socket.socket() 6 sk.connect( ("127.0.0.1" , 8003) ) 7 8 9 # 处理收发数据的逻辑 10 def auth(secret_key): 11 msg = sk.recv(32) 12 # hmac.new(key(盐),msg(密码)) 13 hm = hmac.new( secret_key.encode() , msg ) 14 # 获取加密后的字符串[32为长度的十六进制字符串] 15 cli_res = hm.hexdigest() 16 # 把加密后的字符串转换成字节流发送给服务端做校验. 17 sk.send(cli_res.encode()) 18 19 # 接受服务端最后的校验结果 20 res = sk.recv(1024).decode() 21 return res 22 23 24 secret_key = "芝麻开门吧小怪怪" 25 res = auth(secret_key) 26 print(res , type(res)) 27 28 """""" 29 if res == "True" : 30 print("服务端校验成功~") 31 else: 32 print("服务端校验失败~") 33 34 35 sk.close()
1 import socketserver 2 import os 3 import hmac 4 5 class MyServer(socketserver.BaseRequestHandler): 6 secret_key = "芝麻开门吧小怪怪" 7 8 def auth(self): 9 conn = self.request 10 # 创建一个随机的32位字节流 11 msg = os.urandom(32) 12 # 把字节流发送给客户端 13 conn.send(msg) 14 # 服务端接受客户端发送过来的结果 15 cli_res = conn.recv(1024).decode() 16 17 # 服务端进行数据加密 18 hm = hmac.new(self.secret_key.encode() , msg) 19 # 获取服务端校验的数据结果 20 ser_res = hm.hexdigest() 21 22 # 让客户端和服务端的结果做校验 23 return "True" if cli_res == ser_res else "False" 24 25 def handle(self): 26 # 获取校验结果 27 res = self.auth() 28 # 把结果发送给对应的客户端 29 self.request.send(res.encode()) 30 31 32 33 server = socketserver.ThreadingTCPServer( ("127.0.0.1" , 8003) , MyServer ) 34 server.serve_forever()