网络编程

网络开发的两大架构

最初时期
a文件想要给b文件进行数据交流,需要借助c文件中转数据
a文件先将数据放入c文件中, b文件从c文件中获取
b文件把数据放入c文件中, a文件再从中获取
构成了最早期的数据交互原理 => socket(套接字)的模型
socket(套接字)是收发数据的一个工具

后来有了网络之后
a文件中的数据 , 可以通过网络协议 , 转换成101010的电信号, 进行发送
a文件借助socket发送数据
b文件借助socket接受数据



#两大架构

c/s  client  server
	c = client 客户端
		是具体的一个软件 , 比如 QQ 微信 lol
	s = server 服务端
		服务器 例如天河三号,可以一秒达成百亿亿次计算



b/s  Brower  server
	b = brower 浏览器
		通过输入网址 , 访问对方的服务器 , 对方的服务器响应请求之后
		会将对应的数据进行返回, 就可以在浏览器中看到
	s = 服务端



# b/s 和 c/s 两大架构, 更好的是b/s 是未来的发展方向
	未来将更多的向b/s发展
	1.可以省去复杂漫长的下载安装环节,节省手机或硬盘的空间
	2.因为手机便捷性,随时随地可以访问到网站和相应的服务,提升效率,加快速度

网络的概念

#1.ip

	(1)mac地址:标记一台机器的物理地址  (不可变)
	(2)ip 地址:标记一台机器的逻辑地址 (可变)



	ip => cmd => ipconfig
	ip地址的最后一位中0或者255 这两个数字不能用
	一般最后一位0表达的是网段
	255代表广播地址



	内网 : 以下地址为预留地址,永远不会被当做公网ip来分配
		192.168.0.0 - 192.168.255.255
		172.16.0.0 - 172.31.255.255
		10.0.0.0 - 10.255.255.255
		
	外网 :
		在任何地方都可以访问的就是外网(排除防火墙的因素)


#2.网段
	网段的作用,主要用来划分同一区域里的某些机器是否能够互相通信。
	在一个网段里可以不通过因特网,直接对话
	
	判别的依据: 如果IP地址和子网掩码相与 得到的值相同就是同一网段
	
	子网掩码: 区分网段和主机的一串ip
	
	#案例一
	#ip 192.168.31.156
	11000000 10101000 00011111‬ 10011100
	#子网掩码 255.255.255.0
	11111111 11111111 11111111 00000000
	# ip & 子网掩码 就是网段
	11000000 10101000 00011111 00000000
	ip1的网段 : 192.168.31.0


	#ip 192.168.30.155
	11000000 10101000 00011110‬ 10011011‬
	#子网掩码 255.255.255.0
	11111111 11111111 11111111 00000000
	# ip & 子网掩码 就是网段
	11000000 10101000 00011110 00000000
	ip2的网段 : 192.168.30.0

	ip1的网段和ip2的网段不同, 不一样, 所以不在同一个网段不能通信




	#案例二
	#ip 192.168.31.156
	11000000 10101000 00011111‬ 10011100
	#子网掩码 255.255.0.0
	11111111 11111111 00000000 00000000
	# ip & 子网掩码 就是网段
	11000000 10101000 00000000 00000000
	ip1的网段 : 192.168.0.0


	#ip 192.168.30.155
	11000000 10101000 00011110‬ 10011011‬
	#子网掩码 255.255.0.0
	11111111 11111111 00000000 00000000
	# ip & 子网掩码 就是网段
	11000000 10101000 00000000 00000000
	ip2的网段 : 192.168.0.0

	ip1的网段和ip2的网段完全相同, 所以在同一个网段中可以通信


#ping 域名 可以查看网络是否通畅(顺便可以拿到ip)
	'''
	ping www.baidu.com
	正在 Ping www.a.shifen.com [182.61.200.7] 具有 32 字节的数据:
	来自 182.61.200.7 的回复: 字节=32 时间=4ms TTL=53
	来自 182.61.200.7 的回复: 字节=32 时间=4ms TTL=53
	来自 182.61.200.7 的回复: 字节=32 时间=4ms TTL=53
	来自 182.61.200.7 的回复: 字节=32 时间=4ms TTL=53

	182.61.200.7 的 Ping 统计信息:
		数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
	往返行程的估计时间(以毫秒为单位):
		最短 = 4ms,最长 = 4ms,平均 = 4ms
	'''



#3.端口 : 是英文port的意译   某个程序与外界通讯的出口
	通过ip+端口 可以找到世界上任何一台电脑的任何一个软件
	端口的范围 : 0 ~ 65535  形式 : 192.168.2.1:8000
	#https://blog.csdn.net/qq_34646546/article/details/88545165 知名端口
	自定义端口时,起8000以上
	
	端口 20 : FTP文件传输协议(默认数据口)
	端口 21 : FTP文件传输协议(控制)
	端口 22 : SSH远程登陆协议
	端口 23 : telnet(终端仿真协议),木马 Tiny Telnet Servar 开放此端口
	端口 25 : SMTP服务器所开放的端口,用于发送邮件
	端口 80 : http 用于网页浏览 , 木马 Executor 开放此端口
	端口 443: 基于TLS/SSL 的网页浏览端口 , 能提供加密和通过安全端口传输的另一种HTTP
	端口3306: MySQL 开放此端口

osi 网络七层模型

应用层(应用层,表示层,会话层)
	封装数据
		依据不同的协议,封装对应格式的数据消息
		HTTP [超文本传输协议]
		HTTPS [加密传输超文本传输协议]
		FTP  [文件传输协议]
		SMTP [电子邮件传输协议]

传输层:
	封装端口
		指定传输的协议(TCP协议/UDP协议)

网络层:
	封装id
		版本ipv4 / ipv6

数据链路层:
	封装mac地址
		指定链路层的协议(arp协议(ip -> mac) / rarp协议(mac -> ip))

物理层:
	打成数据包,变成二进制字节流,通过网络进行传输

交换机与路由器

#交换机:对同一网段的不同机器之间进行数据转发的设备 
		每一台机器都与交换机相连,形成通信
		交换机从下到上拆2层,拆掉物理层和数据链路层,可以找到mac

#路由器:对不同网段的不同机器之间进行数据转发的设备
		每一个局域网和路由器相连,形成通信



#arp协议 : 每台主机都有arp缓存表  主要作用是通过ip找mac的一个协议规则
		通过ip -> mac (arp地址解析协议)
		通过交换机的一次广播和一次单播找到对应的mac
		
		在发送数据时,如果没有mac地址,主机a会先发送一个arp的请求包,
		以发出寻找mac的请求,交换机接收到arp的请求包时,会从下到上进行
		拆包拆开2层,在数据链路层得到mac为全F的地址,随后重新打包进行广播
		所有连接在交换机设备都会收到这个请求包,都会依次进行拆包并对照自己
		本机中的arp解析表,如果没有就会舍弃该包,
		而路由器允许从下到上拆包得到ip,并重新打包,找到对应的接口发送数据包
		对应的主机得到请求包后进行从下到上拆包,发现ip正确,符合条件,
		就会把自己的ip和mac封装在arp的响应包中往回进行一次单播,
		返回给发送数据请求的主机,同时在返回过程中所有相应的主机都会拿相应包中的数据
		更新自己的arp解析表,以方便下次使用

TCP/UDP协议

TCP(Transmission Control Protocol) 是一种面向连接,可靠的传输层通信协议(比如:打电话)
	优点: 可靠,稳定,传输过程完整稳定,不限制数据大小
	缺点: 慢,效率低,占用系统资源高,一发一收都需要对方确认
	应用: web浏览器,电子邮件,文件传输,大量数据传输的场景


UDP(User Datagram Protocol) 一种无连接不可靠的传输层通信协议(比如发短信)
	优点: 速度快,可以多人同时聊天,耗费资源少,不需要建立连接
	缺点: 不稳定,不能保证每次数据都能接收到
	应用: IP电话,实时视频会议,聊天软件,少量数据传输的场景



客户端和服务端在建立连接时 : 三次握手
客户端和服务端在断开连接时 : 四次挥手

SYN 创建链接
ACK 确认相应
FIN 断开连接

#TCP 的三次握手
	客户端发送一个请求,与服务端建立连接
	服务端接受到这个请求,并相应与客户端建立连接的请求
	(服务端的响应和请求是在一次发送当中完成的)
	客户端接受到服务端的响应请求之后,会把消息再响应回服务端
	
	接下来客户端和服务端就可以发送数据了
	每发送一个数据出去,对应的主机都会有一个回执消息,确认数据的接受情况
	如果没有得到回执消息,该数据会重发一次,保证数据的完整
	但是不会一直不停的发下去,当超过时间最大允许周期时,会自动断开连接

#四次挥手
	客户端向服务端发送一个请求消息,断开连接(代表客户端没有数据传输了)
	服务端接受请求 , 发出响应
	等到服务端所有数据收发完毕之后
	服务端向客户端发送断开连接的请求
	客户端接受请求 , 发出响应
	等到2msl , 最大报文生存时间之后
	客户端与服务端彻底断开连接

TCP基本语法

#socket 服务器
	#客户端和服务端在收发数据时
	#一发一收是一对,否则会导致数据异常
	#send发送  recv接收

import socket

#1.创建socket对象
sk = socket.socket()

#2.绑定对应的ip和端口(注册网络,让其他主机能够找到)
	#127.0.0.1 代表本地ip
sk.bind(  ("127.0.0.1",9001)  )

#3.开启监听
sk.listen()

#4.建立三次握手
conn,addr = sk.accept()
print(conn)
print(addr)
'''
<socket.socket fd=116, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 9001), raddr=('127.0.0.1', 61461)>
('127.0.0.1', 61461)
'''

#5.收发数据(recv里面的参数单位是字节,代表一次最多接受多少数据)
res = conn.recv(1024)
print(res)	#b'\xe4\xbb\x8a\xe5\xa4\x9c\xe6\x98\x9f\xe5\x85\x89\xe9\x97\xaa\xe9\x97\xaa'
print(res.decode("utf-8"))	#今夜星光闪闪

#6.四次挥手
conn.close()

#7.退还端口
sk.close()



#客户端
import socket
#1.创建一个socket对象
sk = socket.socket()


#2.与服务器进行连接
sk.connect(  ("127.0.0.1",9001)  )


#3.发送数据 (二进制的字节流)
sk.send("今夜星光闪闪".encode("utf-8"))


#4.关闭连接
sk.close()

TCP循环发送

#tcp服务端
import socket

#1.创建一个socket对象
sk = socket.socket()

#让当前端口重复绑定多个程序(仅仅在测试阶段使用)
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

#2.在网络中注册主机(绑定ip和端口号)
sk.bind(  ("127.0.0.1",9001)  )

#3.监听端口
sk.listen()

#4.三次握手
#conn,addr = sk.accept()

#5.收发数据
	#数据类型 : 二进制的字节流
	#b修饰的字符串 =>代表的是二进制的字节流
	#里面的字符必须是ascii编码中的字符 , 不能是中文 否则报错
	
#conn.send(b"i love you ")
while True:
	conn,addr = sk.accept()
	while True:
		res = conn.recv(1024)
		print(res.decode())
		strvar = input("服务端给客户端发送 : ")
		conn.send(strvar.encode())
	
	if strvar == "q":
		break
#6.四次挥手
conn.close()

#7.退还端口
sk.close()




#tcp客户端
import socket

#1.创建一个socket对象
sk = socket.socket()

#2.连接服务器
sk.connect(  ("127.0.0.1",9001)  )

#3.收发数据
#res = sk.recv(1024)
#print(res)	#b'i love you '
#print(res.decode("utf-8"))	#i love you 
while True:
	strvar = input("客户端给服务端发送消息 : ")
	sk.send(strvar.encode())
	
	res = sk.recv(1024)
	if res == b"q":
		break
	print(res.decode())	
#4.关闭连接
sk.close()

UDP基本语法

#服务端

import socket
#type = socket.SOCK_DGRAM => 返回udp协议对象

#1.创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)

#2.绑定地址端口号
sk.bind(  ("127.0.0.1",9000)  )

#3.接受消息(udp作为服务端的时候 , 第一次一定是接受消息)
msg,cli_addr = sk.recvfrom(1024)
print(msg.decode())
print(cli_addr)

#服务端给客户端发消息
msg = "我是秃头小宝贝"
sk.sendto(msg.encode(),cli_addr)


#4.关闭连接
sk.close()



#客户端



import socket

#type = socket.SOCK_DGRAM =>  返回udp协议对象

#1.创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)

#2.发送数据
msg = "月亮睡了你不睡"
#sendto(二进制字节流 , (ip,端口号))
sk.sendto(msg.encode() , ("127.0.0.1",9000) )

#客户端接受服务端发过来的数据
msg,ser_addr = sk.recvfrom(1024)
print(msg.decode())
print(ser_addr)

#3.关闭连接
sk.close()

udp循环发消息

#服务端

import socket

sk = socket.socket(type=socket.SOCK_DGRAM)

sk.bind(  ("127.0.0.1",9000)  )

while True:
	msg,cli_addr = sk.recvfrom(1024)
	print(msg.decode())
	msg = input("服务端要发送的消息 : ")
	if msg == "q":
		sk.sendto("服务器已断开连接".encode(),cli_addr)
		break
	sk.sendto(msg.encode(),cli_addr)

sk.close()



#客户端


import socket

sk = socket.socket(type=socket.SOCK_DGRAM)

while True:
	msg = input("客户端发送的内容 : ")
	sk.sendto(msg.encode(), ("127.0.0.1",9000) )
	msg,ser_addr = sk.recvfrom(1024)
	if msg.decode() == "服务器已断开连接":
		print("服务器已断开连接")
		break
	print(msg.decode())


sk.close()

黏包

#tcp协议在发送数据时,会出现黏包现象.	
	(1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,
	缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区都会设置的比较大。
	(2)在收发数据频繁时,由于tcp传输消息的无边界,不清楚应该截取多少长度
	导致客户端/服务器端,都有可能把多条数据当成是一条数据进行截取,造成黏包


#黏包会出现的情况:

#黏包现象一:
	在发送端,由于两个数据短,发送的时间隔较短,所以在发送端形成黏包
#黏包现象二:
	在接收端,由于两个数据几乎同时被发送到对方的缓存中,所有在接收端形成了黏包
#总结:
	发送端,包之间时间间隔短 或者 接收端,接受不及时, 就会黏包
	核心是因为tcp对数据无边界截取,不会按照发送的顺序判断


#tcp协议:
	缺点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包 
	优点:不限制数据包的大小,稳定传输不丢包

#udp协议:
	优点:接收时候数据之间有边界,传输速度快,不黏包
	缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包

#tcp和udp对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
	但是tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
	而udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包


#需要解决黏包场景:
	应用场景在实时通讯时,需要阅读此次发的消息是什么
#不需要解决黏包场景:
	下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓.

struct防止黏包方法

import struct


'''
pack : 
	把任意长度的数字转化成具有4个字节的固定长度字节流

unpack :
	把4个字节值恢复成原本的数字 , 返回的是元组

'''
#i => int  要转化的当前数据是整型
res = struct.pack("i",123456789)	#最大使用两亿的数字
print(res)	#b'\x15\xcd[\x07'
print(len(res))	#4


#i => int  把对应的数据转换成int , 最后返回元组

tup = struct.unpack("i",res)
print(tup)	#(123456789,)
print(tup[0])	#123456789



#TCP协议的黏包现象

#服务端
import socket,time,struct

sk = socket.socket()
# 把这句话写在bind之前,让一个端口绑定多个程序,可以重复使用(仅用在测试环节)
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
#绑定ip和端口
sk.bind(  ("127.0.0.1",9000)  )
#监听端口
sk.listen()


#三次握手
conn,addr = sk.accept()


#处理收发数据逻辑
strvar = input("请输入宁要发送的内容给客户端 : ")
msg = strvar.encode()
length = len(msg)

#第一次把长度发过去
res = struct.pack("i",length)
conn.send(res)

#第二次把真实数据发过去
conn.send(msg)

#第三次再发送一个数据
conn.send("你是秃头小宝贝".encode())




#客户端
import socket
import time
import struct
sk = socket.socket()
sk.connect(  ("127.0.0.1",9000)  )


#处理收发逻辑
#第一次接受发送过来的数据(数据大小)
n = sk.recv(4)
print(n)
tup = struct.unpack("i",n)
n = tup[0]
print(n)

#第二次接受真实数据
res1 = sk.recv(n)
print(res1.decode())

#第三次接受真实数据
res2 = sk.recv(1024)
print(res2.decode())

#关闭连接
sk.close()

socketserver 模块

#网络协议的最底层就是socket,基于原有socket模块,又封装了一层,就是socketserver
	#socketserver 为了实现tcp协议,server端的并发.



#服务端
import socketserver

class Myserver(socketserver.BaseRequestHandler):
	def handle(self):
		print("该handle方法被执行")

server = socketserver.ThreadingTCPServer(  ("127.0.0.1",9000) , Myserver  )
#循环调用,建立连接
server.serve_forever()



#客户端

import socket

sk = socket.socket()

sk.connect(  ("127.0.0.1",9000)  )

#收发数据逻辑
pass

sk.close()

TCP循环多线程

#服务端
import socketserver

class Myserver(socketserver.BaseRequestHandler):
	def handle(self):
		#self.request => 建立三次握手返回的连接对象conn
		#print(self.request)
		#self.client_address =>客户端的ip端口号addr
		#print(self.client_address)
		conn = self.request
		while True:
			msg = conn.recv(1024)
			msg2 = msg.decode("utf-8")
			print(msg2)
			conn.send(msg2.upper().encode("utf-8"))

server = socketserver.ThreadingTCPServer(  (("127.0.0.1",9000)) , Myserver )
server.serve_forever()




#客户端

import socket

sk = socket.socket()

sk.connect(("127.0.0.1",9000))


#收发数据逻辑

while True:
	sk.send(b"i love you")
	msg = sk.recv(1024)
	print(msg.decode("utf-8"))


sk.close()

hashlib 模块

#hashlib 这个模块是一堆加密算法的集合体,哈希算法的加密方式不止一种

#https://www.cmd5.com/ md5解密

# 应用场景:在需要效验功能时使用
	#用户密码的 => 加密,解密
	#相关效验的 => 加密,解密

#哈希算法也叫摘要算法,相同的数据始终得到相同的输出,不同的数据得到不同的输出。

	#(1)哈希将不可变的任意长度的数据,变成具有固定长度的唯一值	
	#(2)字典的键和集合的值是通过哈希计算存储的,存储的数据是散列(无序)

import hashlib
import random


#基本用法

#1.创建一个md5算法的对象
hs = hashlib.md5()

#2.把要加密的字符串通过update更新到hs对象中运算
hs.update("123456".encode("utf-8")) #里面的数据是二进制字节流

#3.获取32位16进制的字符串
res = hs.hexdigest()
print(res , len(res))	#e10adc3949ba59abbe56e057f20f883e 32




#加盐(加key => Zker_   加一个关键字配合原字符串,让密码更加复杂,不容易破解)
	hs = hashlib.md5("Zker_".encode("utf-8"))
	hs.update("123456".encode("utf-8"))
	res = hs.hexdigest()
	print(res,len(res))		#2bc3cf8ca08b9ed2a81836f702b0d56a 32



#动态加盐
	res = str(random.randrange(100000,1000000))
	print(res)	#327867
	hs = hashlib.md5(res.encode("utf-8"))
	hs.update("123456".encode("utf-8"))
	res = hs.hexdigest()
	print(res)	#cf6e4c544ba148e79661d1ce7a116881



#sha 算法

#sha 算出来的十六进制的串是40位 加密稍慢 , 安全性稍高
#md5 算出来的十六进制的串是32位 加密速度快,安全性一般


hs = hashlib.sha1()
hs.update("123456".encode("utf-8"))
res = hs.hexdigest()
print(res , len(res))	#7c4a8d09ca3762af61e59520943dc26494f8941b 40


hs = hashlib.sha512()
hs.update("123456".encode("utf-8"))
res = hs.hexdigest()
print(res , len(res))	#ba3253876aed6bc22d4a6ff53d8406c6ad864195ed144ab5c87621b6c233b548baeae6956df346ec8c17f5ea10f35ee3cbc514797ed7ddd3145464e2a0bab413 128

hmac 模块

#hmac 加密算法更加复杂 , 不容易破解

import hmac

#必须指定盐
key = b"Zker_"
#密码
msg = b"123456"
hm = hmac.new(key,msg)
res = hm.hexdigest()
print(res , len(res))	#ac7724f4178f9ae33cb1f3a4e5e9b372 32


#动态加盐
import os

	#基本使用:
		urandom 返回随机的二进制字节流 , 参数: 代表的长度
		res = os.urandom(64)
		print(res , len(res))	#b'\xbak\x9cl\xd0\x03$\xea\xa0\x89ny\xf0as#eI1\xea\x8b9\xf7\xe2O\xe7\xda\x9b\x07\xa9\xba\xc9\xce\x8c\x89\x00\xdc\xeb\xde \xfa\x86\xf21\xe0E\x0f\xc25\x012\xa5\xd1^\xa0U\xbf\xa4\x07\xb4G\xdd\x0b\x8c' 64

key = os.urandom(32)
msg = b"123456"
hm = hmac.new(key,msg)
res = hm.hexdigest()
print(res , len(res))	#a0e5ee95fc974a3a0927c3785dd645a7 32

文件校验

import hashlib

hm = hashlib.md5()
hm.update("123456".encode("utf-8"))
res = hm.hexdigest()
print(res , len(res))	#e10adc3949ba59abbe56e057f20f883e 32


'''
mode = r    read(数字 > 字符个数)

mode = rb   read(数字 > 字节个数)

#二进制模式不接受编码参数
with open("ceshi1.txt",mode="rb")as fp:
	res2 = fp.read(2)
	print(res2)	#b'\x80\x03'


'''

#针对小文件进行内容校验
	def check_md5(file):
		with open(file,mode="rb")as fp:
			hs = hashlib.md5()
			hs.update(fp.read())
		return hs.hexdigest()
	
	res1 = check_md5("ceshi1.txt")
	res2 = check_md5("ceshi2.txt")
	print(res1,res2)
	#两文件内容不同结果不同
		#b47706d29cdc276d81235b666757d81e
		#202cb962ac59075b964b07152d234b70
	#两文件内容相同则结果相同
		#202cb962ac59075b964b07152d234b70
		#202cb962ac59075b964b07152d234b70
		





#针对于大文件进行内容校验
	hs = hashlib.md5()
	hs.update("月亮睡了你不睡".encode("utf-8"))
	res = hs.hexdigest()
	print(res)	#07878132fcf7d011a22cb47f9ee88fab

	hs = hashlib.md5()
	hs.update("月亮睡了".encode("utf-8"))
	hs.update("你不睡".encode("utf-8"))
	res = hs.hexdigest()
	print(res)	#07878132fcf7d011a22cb47f9ee88fab


#方法一
	def check_md5(file):
		hs = hashlib.md5()
		with open(file,mode="rb")as fp:
			while True:
				#一次最多读出10个字节
				content = fp.read(10)
				#如果有内容就进行计算
				if content:
					hs.update(content)
				else:
					break
			return hs.hexdigest()
	
	print(check_md5("ceshi1.txt"))	#202cb962ac59075b964b07152d234b70
	print(check_md5("ceshi2.txt"))	#202cb962ac59075b964b07152d234b70


#方法二
import os
def check_md5(file):
	hs = hashlib.md5()
	#计算文件大小 , 会返回一个字节个数
	file_size = os.path.getsize(file)
	with open(file,mode="rb")as fp:
		content = fp.read(10)
		hs.update(content)
		#按照实际读取个数进行相减
		file_size -= len(content)
	return hs.hexdigest()
print(check_md5("ceshi1.txt"))	#202cb962ac59075b964b07152d234b70
print(check_md5("ceshi2.txt"))	#202cb962ac59075b964b07152d234b70

服务器的合法性校验

#服务端2(支付宝)

#首先需要与支付宝达成协议 , 支付宝会提供一个密钥
#每次在校验时, 支付宝都会给客户端发送一个随机值
#其次在每次进行验证时, 客户端都拿密钥加上支付宝提供的特殊算法进行演算
#随后客户端按照规定算法与密钥推出一个值发送给支付宝
#通过验证则可以继续往下操作


import socket
import os
import hmac

#规定一个密钥
server_key = "Zker"



#定义一个校验函数
def auth(conn,server_key):
	#随机产生一个32位二进制字节流
	msg = os.urandom(32)
	#给对方发过去
	conn.send(msg)
	#密钥加盐用hmac方法演算
	hm = hmac.new(server_key.encode(),msg)
	#得到结果
	res = hm.hexdigest()
	#接受对方的结果
	res1 = conn.recv(1024).decode("utf-8")
	#两者进行比对 , 如果成功则进行下一步
	if res == res1:
		print("合法用户 , 欢迎")
		return True
	else:
		return False


#创建sk对象
sk = socket.socket()
#绑定ip和端口
sk.bind(  ("127.0.0.1",9000)  )
#开启监听
sk.listen()


#三次握手
conn,cli_addr = sk.accept()



#校验环节调用函数
res_client = auth(conn,server_key)
#校验如果成功则开始互动
if res_client:
	conn.send("Can I Hlpe You?".encode())
	pr = conn.recv(1024).decode()
	print(pr)


#四次挥手
conn.close()
#退还端口
sk.close()

-----------------

#服务端1(公司)


import socket
import os 
import hmac

#合法用户密钥是已知的
server_key = "Zker"



#定义校验函数
def auth(sk,server_key):
	#接受支付宝发来的32位二进制字节流
	msg = sk.recv(32)
	#使用固定算法加盐演算
	hm = hmac.new(server_key.encode(),msg)
	#得到结果
	res = hm.hexdigest()
	#发送结果等待校验
	sk.send(res.encode())


#创建对象
sk = socket.socket()

#连接ip
sk.connect(  ("127.0.0.1",9000)  )



#调用函数校验
auth(sk,server_key)
#通过校验开始互动
pr = sk.recv(1024).decode()
print(pr)
sk.send("come baby~".encode())

#贤者模式溜了溜了
sk.close()

进程

进程就是正在运行的程序,它是操作系统中,资源分配的最小单位.
资源分配:分配的是cpu和内存等物理资源
进程号是进程的唯一标识

同一个程序执行两次之后是两个进程
进程和进程之间的关系: 数据彼此隔离,通过socket通信

#并行和并发
	并发:一个cpu同一时间不停执行多个程序
	并行:多个cpu同一时间不停执行多个程序

#cpu的进程调度方法
	先来先服务fcfs(first come first server):先来的先执行
	短作业优先算法:分配的cpu多,先把短的算完
	时间片轮转算法:每一个任务就执行一个时间片的时间.然后就执行其他的.
	多级反馈队列算法

	越是时间长的,cpu分配的资源越少,优先级靠后
	越是时间短的,cpu分配的资源越多

import os,time
'''
当前进程id(子进程)
res = os.getpid()
print(res)	#13612
当前进程的父进程id
res2 = os.getppid()
print(res)	#8432
'''
from multiprocessing import Process

#1.进程使用的基本语法
	process 创建子进程 , 返回进程的对象p
	target 指定要执行的任务
	args 指定传递的参数 , args的类型是元组 , 多个参数之间用逗号隔开
	
	def func():
		print("1.子进程id>>>{} , 2.父进程id>>>{}".format(os.getpid(),os.getppid()))

	#windows里面下面这句话必须要加:
	if __name__ == "__main__":
		print("3.子进程id>>>{} , 4.父进程id>>>{}".format(os.getpid(),os.getppid()))

		#创建子进程 , 返回一个进程对象 , 执行func任务
		p = process(target=func)	#target指定任务
		#调用子进程
		p.start()



#2.创建带有参数的进程
	def func(n):
		for i in range(1,n+1):
			print("3.子进程id>>>{} , 4.父进程id>>>{}".format(os.getpid(),os.getppid()))

	if __name__ == "__main__":
		print("1.子进程id>>>{} , 2.父进程id>>>{}".format(os.getpid(),os.getppid()))
		n = 5
		#创建子进程
		p = Process(target=func,args=(n,))
		p.start()
		for i in range(1,n+1):
			print("*" * i)


#3.进程之间的数据隔离
	count = 100
	def func():
		global count
		count += 1
		print("子进程 count={}".format(count))
	
	if __name__ == "__main__":
		p = Process(target=func)
		p.start()
		time.sleep(1)
		print(count)


#4.多个进程可以异步开发, 子父进程之间的关系

	程序在异步并发任务时,因为cpu调度策略问题,不一定先执行谁,后执行谁
	整体而言,主进程速度快于子进程,cpu遇到阻塞立刻切换其他任务,等到进程的就绪态再切换回来
	
	主程序会默认等待所有的子进程执行结束之后,再关闭程序,释放资源
	若不等待,子进程并不方便管理,容易造成僵尸进程,在后台不停的占用系统资源


	def func(args):
		print("1.子进程id>>>{} , 2.父进程id>>>{}".format(os.getpid(),os.getppid() , args))

	if __name__ == "__main__":
		for i in range(1,11):
			Process(target=func,args = (i,)).start()
		print("主进程执行结束...")

join

等待所有子进程全部执行完毕之后,主进程任务再继续运行
(用来同步子父进程速度的)

import os
from multiprocessing import Process

#1.join的基本语法
	def func():
		print("1")
	if __name__ == "__main__":
		p = Process(target=func)
		p.start()
		#必须等待子进程执行结束之后,再继续执行主程序的代码,用来同步代码一致性
		p.join()
		print("2")



#2.多个子进程配合join使用
	def func(index):
		print("%s" % (index))
	if __name__ == "__main__":
		lst = []
		for i in range(10):
			p = Process(target=func,args=(i,))
			p.start()
			lst.append(p)
			
		for i in lst:
			print(i)
			i.join
		print("主进程结束")

使用自定义类的方式创建进程(拓展)

自定义类创建进程要求:
	必须继承Process这个父类
	所有进程执行任务的逻辑要写run方法里面

#1.基本语法
	class MyProcess(Process):
		def run(self):
			print("1")
	if __name__ == "__main__":
		p = MyProcess()
		p.start()
		print("2")


#2.有参数的进程函数
	class MyProcess(Process):
		def __init__(self,arg):
			#手动调用一下父类的构造方法(实现进程的创建)
			super().__init__()
			self.arg = arg

		def run(self):
			print("1.子进程id>>>{} , 2.父进程id>>>{}".format(os.getpid(),os.getppid()),self.arg)


	if __name__ == "__main__":
		p = MyProcess("参数")
		p.start()
		print("333")

守护进程

守护进程守护的是主进程 , 如果主进程执行结束了,意味着守护进程的寿命立刻终止
语法:
	进程.daemon = True 设置当前进程为守护进程
	必须写在start()调用进程之前进行设置
默认情况下, 主进程会等待所有子进程执行完毕之前,关闭程序释放资源
守护进程在主进程代码执行结束之后,直接杀掉

from multiprocessing import Process
#1.基本语法
	def func():
		print("start")
		print("end")
	if __name__ == "__main__":
		p = Process(target=func)
		p.daemon = True
		p.start()
		print("gogogo")


#2.多个子进程的场景
	import time
	def func1():
		count = 1
		while True:
			print("*" * count)
			time.sleep(1)
			count += 1

	def func2():
		print("start")
		time.sleep(3)
		print("end")
	
	if __name__ == "__main__":
		p1 = Process(target=func1)
		p2 = Process(target=func2)
		#设置当前进程为守护进程
		p1.daemon = True
		p1.start()
		p2.start()
		print("主程序结束")
		#非守护进程 , 主进程还是会默认等待


#3.守护进程的实际用途: 监听报活
	import time
	def alive():
		while True:
			print("当前服务器运行正常")
			time.sleep(2)
	def func():
		for i in range(10):
			print(i)
			time.sleep(1)
	if __name__ == "__main__":
		p1 = Process(target=alive)
		p2 = Process(target=func)
		p1.daemon = True
		p2.start()
		p1.start()
		p2.join()
		print("服务器异常")

锁 lock

#1.lock的基本语法
	上锁和解锁是一对,只上锁不解锁会发生死锁现象(程序发生阻塞, 下面的代码不执行了)
	互斥锁: 互斥锁是进程之间的互相排斥,谁抢到了资源谁就先使用,后抢到资源的后使用

	#创建一把锁
	lock = Lock()
	#上锁
	lock.acquire()
	#执行操作
	#解锁
	lock.release()
	print("执行程序")

#2.模拟12306抢票软件

	from multiprocessing import Process,Lock
	import json,time
	#读取票数,更新票数
	def wr_info(sign,dic=None):
		if sign == "r":
			with open("zzz.txt",mode="r",encoding="utf-8")as fp:
				dic = json.load(fp)
			return dic
		elif sign == "w":
			with open("zzz.txt",mode="w",encoding="utf-8")as fp:
				json.dump(dic,fp)
	#抢票
	def getpiao(person):
		#获取数据库中实际数据
		dic = wr_info("r")
		#模拟网络延迟
		time.sleep(0.1)
		if dic["code"] > 0 :
			print("%s抢到票了" % (person))
			dic["code"] -= 1
			#更新到数据库
			wr_info("w",dic)
		else:
			print("%s这次没有抢到票" % (person))
	def run(person,lock):
		#读取数据库中实际票数
		dic = wr_info("r")
		print("%s查询票数: %s " % (person,dic["code"]))
		#上锁
		lock.acquire()
		#抢票
		getpiao(person)
		#解锁
		lock.release()

	if __name__ == "__main__":
		lock = Lock()
		lst =["云超1","张恒2","尉翼麟3","王振4","黎建忠5","刘鑫炜6","刘思敏7","李天兆8","魏小林9","李博10"]
		for i in lst:
			p = Process(target=run,args=(i,lock))
			p.start()

	#总结 : 区分同步和异步
	#		当创建十个进程的时候是异步的,直到查完票数为止
	#		当执行getpiao这个方法时,各个进程之间是同步的

信号量 Semaphore 本质上就是锁,可以控制上锁的数量

sem = Semaphore(4)
sem.acquire()
#执行相应操作
sem.release()

from multiprocessing import Process,Semaphore
import time,random

def ktv(person,sem):
	sem.acquire()
	print("%s进去唱歌了" % (person))
	time.sleep(random.randrange(1,3))
	print("%s唱完出来了" % (person))
	sem.release()

if __name__ == "__main__":
	sem = Semaphore(4)
	for i in range(1,11):
		p = Process(target=ktv,args=(i,sem))
		p.start()


#总结:
	Semaphore 可以设置上锁的数量
	同一时间最多允许几个进程上锁
	创建进程的时候是异步的
	在执行任务时是同步的

事件 Event

#阻塞事件:
	e = Event()  生成事件对象e
	e.wait()  动态给程序加阻塞
	程序当中是否加阻塞完全取决于该对象中的is_set() [默认False]
		如果是True 不加阻塞
		如果是False 加阻塞
	
#控制这个属性的值
	set()  将这个属性值改成True
	clear() 将这个属性的值改成False
	is_set() 判断当前属性是否为True(默认上来是False)

Lock,Semaphore,Event 进程和进程之间的数据彼此隔离,但是可以通过socket互相联系


#基本语法
	from multiprocessing import Event,Process
	#1
	e = Event()
	print("运行中")
	e.wait()
	print("还运行中")

	#2
	e = Event()
	#将阻塞事件中的值改成True
	e.set()
	print(e.is_set())
	e.wait()
	print("运行中")
	#将阻塞事件中的值改成False
	e.clear()
	e.wait()
	print("还运行中")


	#3
	e = Event()
	#参数:最多等待时间是五秒,过了五秒之后阻塞放行
	e.wait(5)
	print("运行中")



#模拟红绿灯效果
import time,random
def hld(e):
	print("红灯亮")
	while True:
		#如果为True,说明是绿灯,亮一秒改红灯
		if e.is_set():
			time.sleep(1)
			print("红灯亮")
			e.clear()
		else:
			time.sleep(1)
			print("绿灯亮")
			e.set()

def car(e,i):
	#如果不是真的,就是红灯
	if not e.is_set():
		print("car%s在等待" % (i))
		#加阻塞 是绿灯再走
		e.wait()
	else:
		print("car%s通过了"% (i))
		
if __name__ == "__main__":
	lst = []
	e = Event()
	p1 = Process(target=hld,args=(e,))
	#把红绿灯变成守护进程
	p1.daemon = True
	p1.start()
	#创建小车
	for i in range(1,21):
		time.sleep(random.randrange(1,3))
		p2 = Process(target=car,args=(e,i))
		p2.start()
		lst.append(p2)
	#所有小车通过终止红绿灯
	for i in lst:
		i.join()
	print("over")

进程队列

from multiprocessing import Process,Queue
先进先出,后进后出
import queue #线程队列

#1.基本语法
	q = Queue()
	#put存值
	q.put(1)
	q.put(2)
	#get取值
	res = q.get()
	print(res)
	res = q.get()
	print(res)
	#如果队列没有数据了,再调用get会阻塞
	res = q.get()
	print(res)


	#get_nowait 存在兼容性问题(不推荐)
	res = q.get_nowait()
	print(res)#queue.Empty  这个报错说明队列有问题
	
	try:
		res = q.get_nowait()
		print(res)
	except queue.Empty:
		pass	#使用try可以抑制报错



#2.可以限定Queue队列的长度
	q = Queue(3)
	q.put(1)
	q.put(2)
	q.put(3)
	#如果队列满了还继续往里塞就会阻塞
	q.put(4)
	#如果满了用put_nowait()塞就会报错



#3.进程之间通过队列交换数据
	def func(q):
		#子进程取数据
		res = q.get()
		print(res)
		#子进程存数据
		q.put("aaa")


	if __name__ == "__main__":
		q = Queue()
		p = Process(target=func, args=(q,))
		p.start()
		#主进程添加数据
		q.put("bbb")
		#等待子进程把数据塞入队列,再获取 , 加join
		p.join()
		#主进程拿数据
		res = q.get()
		print("结束,拿到了{}".format(res))

生产者和消费者模型

#爬虫例子:
	1号进程负责抓取页面中的内容放入队列
	2号进程负责把内容取出来,配合正则表达式,扣取关键字
	
	1号进程可以理解成生产者
	2号进程可以理解成消费者
	
	相对理想的生产者和消费者模型
		追求彼此的速度相对均匀
	
	从程序上来说
		生产者负责存储数据(put)
		消费者负责获取数据(get)


#生产者模型
def shengchan(q,name,food):
	for i in range(5):
		time.sleep(random.uniform(0.1,1))
		print("%s生产了%s%s" % (name,i,food))
		q.put(str(i)+food)

#消费者模型
def xiaofei(q,name):
	while True:
		food = q.get()
		if food is None:
			break
		time.sleep(random.uniform(0.1,1))
		print("%s吃了%s" % (name,food))


if __name__ == "__main__":
	q = Queue()
	#生产者一号
	p1 = Process(target=shengchan,args=(q,"老二","辣条"))
	p1.start()
	#生产者二号
	p2 = Process(target=shengchan,args=(q,"老八","辣酱"))
	p2.start()
	
	#消费者一号
	a1 = Process(target=xiaofei,args=(q,"小王"))
	a1.start()
	#消费者二号
	a2 = Process(target=xiaofei,args=(q,"小张"))
	a2.start()
	#在生产完所有数据后,在队列末尾加上一个None
	p1.join()
	p2.join()
	#消费者模型如果拿到None ,代表停止消费
	q.put(None)
	q.put(None)

JoinableQueue

put 存储
get 获取
task_done 队列计数减一
join 阻塞

tack_dong 配合 join 一起使用
[1,2,3,4,5]
队列计数为5
put 一次 是每存放一个值,队列计数器加1
get 一次 通过tack_done 让队列计数器减一
join 函数 , 会根据队列中的计数器来判定是阻塞还是放行
如果计数器变量是0 , 意味着放行,其他情况阻塞


#1.基本使用
	jq = JoinableQueue()
	#put 会让队列计数器加一
	jq.put(1)
	print(jq.get())
	#通过task_done 让队列计数器减一
	jq.task_done()
	#只有队列计数器是0的时候,才会放行
	jq.join() #队列.join
	print("over")


#2.改造生产者消费者模型
	def shengchan(q,name,food):
		for i in range(1,6):
			time.sleep(random.uniform(0.1,1))
			print("%s生产了%s%s" % (name,i,food))
			q.put(str(i)+food)

	def xiaofei(q,name):
		while True:
			food = q.get()
			time.sleep(random.uniform(0.1,1))
			print("%s吃了%s" % (name,food))
			q.task_done()
	
	if __name__ == "__main__":
		q = JoinableQueue()
		#生产者一号
		p1 = Process(target=shengchan,args=(q,"老八","秘制小汉堡"))
		p1.start()
		#消费者一号
		p2 = Process(target=xiaofei,args=(q,"老王"))
		#把消费者设置为守护进程
		p2.daemon = True
		p2.start()
		#把生产者所有数据都装载入队列
		p1.join()
		#等到消费者中所有数据都被task_done之后,变成0代表消费结束
		q.join()
		print("over")

Manager (list dict) 进程之间的共享数据(列表或字典)

from multiprocessing import Process,Lock,Manager

def work(data1,data2, lock):
	#用with语法简化上锁解锁
	with lock:
		data1["count"] += 1
		data2[2] -= 1
	

if __name__ == "__main__":
	m = Manager()
	lock = Lock()
	lst = []
	#创建一个共享的字典和列表
	data1 = m.dict(  {"count":1}  )
	data2 = m.list(  [1,2,3]  )
	
	for i in range(100):
		p = Process(target=work,args=(data1,data2,lock))
		p.start()
		lst.append(p)
	#要确保所有进程执行完毕之后,再向下运行,否则报错
	for i in lst:
		i.join()
	print(data1)
	print(data2)
	print("over")

线程

进程: 资源分配的最小单位
线程: cpu执行程序的最小单位


#1.一份进程资源中可以包含多个线程
	from threading import Thread
	from multiprocessing import Process
	import os
	
	def func(num):
		print("当前线程{},所归属的进程号id{}".format(num,os.getpid()))
	
	for i in range(10):
		#异步创建十个子线程
		t = Thread(target=func,args=(i,))
		t.start()
	#主线程执行任务
	print(os.getpid())



#2.并发多线程和多进程,谁的速度快? 多线程!
	
	def func(num):
		print("当前线程{},所归属的进程id号{}".format(num,os.getpid()))
	
	#多线程时间
	if __name__ == "__main__":
		#记录开始时间
		time1 = time.time()
		lst = []
		for i in range(1000):
			t = Thread(target=func,args=(i,))
			t.start()
			lst.append()
		#等所有子线程执行完毕
		for i in lst:
			i.join()
		#记录结束时间
		time2 = time.time()
		print("over",(time2-time1))#0.09m


	#多进程时间
	if __name__ == "__main__":
		#记录开始时间
		time1 = time.time()
		lst = []
		for i in range(1000):
			p = Process(target=func,args=(i,))
			p.start()
			lst.append(p)
		for i in lst:
			i.join()
		#记录结束时间
		time2 = time.time()
		print("over",time2-time1)#17m



#3.多线程,共享同一份进程资源
	num = 1000
	def func():
		global num
		num -= 1
	for i in range(1000):
		t = Thread(target=func)
		t.start()
	print(num)	#0

用类定义线程

from threading import Thread
from threading import currentThread
import time,os

class Mythread(Thread):
	def __init__(self,name):
		super().__init__()
		self.name = name
	
	def run(self):
		#time.sleep(1)
		print("正在run",self.name)

if __name__ == "__main__":
	t = Mythread("又在祈祷了吗?")
	t.start()
	print("over")

线程相关函数

线程.is_alive()  		检测线程是否仍然存在
线程.setName()  		设置线程名字
线程.getName()  		获取线程名字
currentThread().ident  	查看线程id号
enumerate()  			返回目前正在运行的线程列表
activecount()  			返回目前正在运行的线程数量


def func():
	time.sleep(1)
if __name__ == "__main__":
	t = Thread(target=func)
	t.start()
	print(t,type(t))#<Thread(Thread-1, started 15464)> <class 'threading.Thread'>
	print(t.is_alive())	#True
	print(t.getName())	#Thread-1
	t.setName("zker")
	print(t.getName())	#zker
	



#1.currentThread().ident  查看线程id号
	
	def func():
		print("子线程id号",currentThread().ident,os.getpid())
	if __name__ == "__main__":
		t = Thread(target=func)
		t.start()
		print("主线程id号",currentThread().ident,os.getpid())
	#子线程id号 7048 9820
	#主线程id号 2408 9820



#2.enumerate()  返回目前正在运行的线程列表
	from threading import enumerate
	from threading import activeCount
	def func():
		print("子线程id",currentThread().ident,os.getpid())
		time.sleep(1)
	if __name__ == "__main__":
		for i in range(10):
			t = Thread(target=func)
			t.start()
		lst = enumerate()
		#子线程10个 主线程1个 = 11个
		print(lst,len(lst))
		#3.返回目前正在运行的线程数量
		print(activeCount)	#11

守护线程 : 等待所有线程全部执行完毕之后,再自己终止,守护的是所有线程

from threading import Thread
import time

def func1():
	while True:
		time.sleep(0.5)
		print("保护")
def func2():
	print("臭妹妹")
	time.sleep(3)
	print("你可别被我逮住了")

if __name__ == "__main__":
	t1 = Thread(target=func1)
	t2 = Thread(target=func2)
	#在start调用之前设置好守护线程
	t1.setDaemon(True)
	
	t2.start()
	t1.start()
	print("over")

Lock 保证线程数据安全

from threading import Lock,Thread
import time 
n = 0

def func1(lock):
	global n
	lock.acquire()
	for i in range(1000):
		n += 1
	lock.release()

def func2(lock):
	global n 
	with lock:
		for i in range(1000):
			n -= 1

if __name__ == "__main__":
	lock = Lock()
	lst = []
	time1 = time.time()
	for i in range(10):
		t1 = Thread(target=func1,args=(lock,))
		t2 = Thread(target=func2,args=(lock,))
		
		t1.start()
		t2.start()
		
		lst.append(t1)
		lst.append(t2)
	
	for i in lst:
		i.join()
		
		
	time2 = time.time()
	print(n,time2-time1)	#0 0.003988027572631836

信号量 Semaphore(线程)

from threading import Semaphore,Thread
import time
def func(i,sm):
	with sm:
		print(i)
		time.sleep(1)

if __name__ == "__main__":
	sm = Semaphore(2)
	for i in range(20):
		t = Thread(target=func,args=(i,sm))
		t.start()

#总结:
	在创建线程的时候是异步创建
	在执行任务的时候,由于Semaphore加了锁,所以线程变成了同步

线程的缺陷

#python中的线程可以并发,但是不能并行(同一个进程下的多个线程不能分开被多个cpu同时执行)
#原因:
   全局解释器锁(Cpython解释器特有) GIL锁:同一时间,一个进程下的多个线程只能被一个cpu执行
   python是解释型语言,执行一句编译一句,而不是一次性全部编译成功,不能提前规划,都是临时调度
   容易造成cpu执行调度异常.所以加了一把锁叫GIL   	
	
#想要并行的解决办法:
    (1)用多进程间接实现线程的并行
    (2)换一个Pypy,Jpython解释器

#程序分为计算密集型和io密集型
	对于计算密集型程序会过度依赖cpu,但网页,爬虫,OA办公,这种io密集型的程序里,python绰绰有余

死锁 互斥锁 递归锁

加一把锁,就对应解一把锁.形成互斥锁.
从语法上来说,锁可以互相嵌套,但不要使用,
不要因为逻辑问题让上锁分成两次.导致死锁
递归锁用于解决死锁,但只是一种应急的处理办法

from threading import Thread,RLock,Lock
import time

#死锁
noodle_lock = Lock()
ll_lock = Lock()
def eat1(name):
	noodle_lock.acquire()
	print("%s拿到了面条" % (name))
	ll_lock.acquire()
	print("%s拿到了筷子" % (name))
	time.sleep(1)
	
	ll_lock.release()
	print("%s放下了筷子" % (name))
	noodle_lock.release()
	print("%s放下了面条" % (name))

def eat2(name):
	ll_lock.acquire()
	print("%s拿到了筷子" % (name))
	noodle_lock.acquire()
	print("%s拿到了面条" % (name))
	time.sleep(1)
	
	noodle_lock.release()
	print("%s放下了面条" % (name))
	ll_lock.release()
	print("%s放下了筷子" % (name))
	
if __name__ == "__main__":
	name_lst = ["王振","王赢钱"]
	name2_lst = ["李天兆","魏小林"]
	for name in name_lst:
		Thread(target=eat1,args=(name,)).start()
	for name in name2_lst:
		Thread(target=eat2,args=(name,)).start()



#递归锁
递归锁专门用来解决死锁现象
临时用于快速解决项目因死锁问题不能正常运行的场景
用来处理异常死锁
rlock = RLock() #递归锁
lock = Lock() #互斥锁
lock = Lock()
rlock = RLock()
def func():
	'''
	lock.acquire()
	lock.acquire()
	print("111")
	lock.release()
	lock.release()
	print("222")
	'''
	rlock.acquire()
	rlock.acquire()
	print("333")
	rlock.release()
	rlock.release()
	print("444")
func()


#用递归锁来解决死锁
noodle_lock = ll_lock = RLock()
def eat1(name):
	noodle_lock.acquire()
	print("%s拿到了面条" % (name))
	ll_lock.acquire()
	print("%s拿到了筷子" % (name))
	time.sleep(1)
	
	ll_lock.release()
	print("%s放下了筷子" % (name))
	noodle_lock.release()
	print("%s放下了面条" % (name))

def eat2(name):
	ll_lock.acquire()
	print("%s拿到了筷子" % (name))
	noodle_lock.acquire()
	print("%s拿到了面条" % (name))
	time.sleep(1)
	
	noodle_lock.release()
	print("%s放下了面条" % (name))
	ll_lock.release()
	print("%s放下了筷子" % (name))
	
if __name__ == "__main__":
	name_lst = ["王振","王赢钱"]
	name2_lst = ["李天兆","魏小林"]
	for name in name_lst:
		Thread(target=eat1,args=(name,)).start()
	for name in name2_lst:
		Thread(target=eat2,args=(name,)).start()


#互斥锁
尽量使用一把锁解决问题,不要互相嵌套,否则容易死锁
mylock = Lock()
def eat1(name):
	mylock.acquire()
	print("%s拿到了面条" % (name))
	print("%s拿到了筷子" % (name))
	time.sleep(1)
	
	print("%s放下了筷子" % (name))
	print("%s放下了面条" % (name))
	mylock.release()

def eat2(name):
	mylock.acquire()
	print("%s拿到了筷子" % (name))
	print("%s拿到了面条" % (name))
	time.sleep(1)
	
	print("%s放下了面条" % (name))
	print("%s放下了筷子" % (name))
	mylock.release()
	
if __name__ == "__main__":
	name_lst = ["王振","王赢钱"]
	name2_lst = ["李天兆","魏小林"]
	for name in name_lst:
		Thread(target=eat1,args=(name,)).start()
	for name in name2_lst:
		Thread(target=eat2,args=(name,)).start()

事件 Event

from threading import Thread,Event
import random,time

e = Event()
wait 动态加阻塞
set  把阻塞事件的值改成True
clear 把阻塞事件的值改成False
is_set 获取阻塞事件的值

#基本语法
	e = Event()
	print(e.is_set())
	e.set()
	e.clear()
	e.wait(3)
	print("over")


#模拟连接远程数据库
	连接三次数据库,如果都不成功,就抛出异常
	
	def check(e):
		#模拟网络延迟
		time.sleep(random.randrange(1,6))
		#检测链接合法性
		print("检测链接合法性")
		e.set()
	
	def connect(e):
		sign = False
		for i in range(1,4):
			#设置最大等待时间
			e.wait(1)
			if e.is_set():
				print("数据连接成功")
				sign = True
				break
			else:
				print("尝试连接%s次失败" % (i))
		if sign == False:
			#超时抛异常
			raise TimeoutError
	
	e = Event()
	#线程1负责执行检测任务
	Thread(target=check,args=(e,)).start()
	#线程2负责执行连接任务
	Thread(target=connect,args=(e,)).start()

线程队列

from queue import Queue

put 存
get 取
put_nowait 存,超出队列长度不阻塞直接报错
gei_nowait 取,没有数据可取不阻塞直接报错

linux,windows 线程中put_nowait,get_nowait都支持


#1.Queue
	先进先出,后进后出
	q = Queue()
	q.put(1)
	q.put(2)
	print(q.get())
	print(q.get())
	#取不出来会报错,如果换成get会阻塞
	q.get_nowait()

	q = Queue(3)
	q.put(1)
	q.put(2)
	q.put(3)
	#放不进去会阻塞
	q.put(4)
	#放不进去会报错
	q.put_nowait(5)



#2.LifoQueue 先进后出,后进先出(按照栈的特点设计)
	from queue import LifoQueue
	lq = LifoQueue(3)
	lq.put(1)
	lq.put(2)
	lq.put(3)
	print(lq.get())
	print(lq.get())
	print(lq.get())


#3.PriorityQueue 按照优先级顺序排序(从小到大)
	from queue import PriorityQueue
	#如果都是数字 , 从小到大排序
	pq = PriorityQueue()
	pq.put(1)
	pq.put(3)
	pq.put(2)
	print(pq.get())
	print(pq.get())
	print(pq.get())

	#如果都是字符串 , 按照ascii编码排序
	pq = PriorityQueue()
	pq.put("aasdf")
	pq.put("gasfg")
	pq.put("zasgq")
	print(pq.get())
	print(pq.get())
	print(pq.get())


	#只能是纯数字或纯字符串 不能混合
	pq2 = PriorityQueue()
	pq2.put(13)
	pq2.put("aaa")
	pq2.put("拟稿")
	#TypeError: '<' not supported between instances of 'str' and 'int'


	#可以放元组进行混合
	默认按照元组第一个元素排序
	pq = PriorityQueue()
	pq.put((1,"asd"))
	pq.put((2,"zxc"))
	pq.put((3,"qwe"))
	print(pq.get())
	print(pq.get())
	print(pq.get())

进程池和线程池

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
import os,time
def func(i):
	print("start  ", os.getpid())
	time.sleep(3)
	print("end  " , i)
	return i

#ProcessPoolExecutor 进程池的基本使用
默认如果一个进程短时间内可以完成更多的任务,就不会创建额外新的进程,以节省资源
if __name__ == "__main__":
	lst = []
	#print(os.cpu_count())	#12
	#创建进程池对象
	#进程池里面最多创建os.cpu_count()显示数量的进程,所有任务全由这几个进程完成,不会额外创建
	p = ProcessPoolExecutor()
	
	#异步提交任务
	for i in range(13):
		res = p.submit(func,i)#括号中为执行的函数和需要的参数
		lst.append(res)
	
	#获取当前进程池返回值
	for i in lst:
		print(i.result())

	#等待所有子进程执行结束
	p.shutdown() 	#join
	
	print("over")



#ThreadPoolExecutor 线程池的基本用法
默认如果一个线程短时间内可以完成更多任务,就不会创建额外新的线程以节省资源
from threading import current_thread as cthread #引入并用as起别名

def func(i):
	print("strat  " , current_thread().ident, i )
	time.sleep(3)
	print("end  " , i )
	return current_thread().ident

if __name__ == "__main__":
	lst = []
	setvar = set()
	#创建线程池对象		最多创建os.cpu_count*5个线程数
	tp = ThreadPoolExecutor()
	#异步提交任务
	for i in range(100):
		res = tp.submit(func,i)
		lst.append(res)
	#获取返回值
	for i in lst:
		setvar.add(i.result())

	#等待所有子线程结束
	tp.shutdown()
	
	print(len(setvar),setvar)
	print("over")


#线程池map
from concurrent.futures import ThreadPoolExecutor
from threading import current_thread as cthread
from collections import Iterator

def func(i):
	print("start  "  current_thread().ident)
	print("end..." , i)
	time.sleep(1)
	return "*" * i

if __name__ == "__main__":
	setvar = set()
	lst = []
	tp = ThreadPoolExecutor(5)
	#map(自定义函数,可迭代性数据)  可迭代性数据(容器类型数据,range,迭代器)
	it = tp.map(func,range(20))
	print(isinstance(it,Iterator))
	
	tp.shutdown()
	
	for i in it:
		print(i)

回调函数

当一个函数被当作参数传递给另一个函数时
另一个函数运行完毕后会再调用一下作为参数的函数
被作为参数的函数就是回调函数

#例子
功能:
	打印状态 : a属性
	支付状态 : b属性
	退款状态 : c属性
	转账状态 : d属性
	
	把以上想要的相关成员或者相关逻辑写在自定义函数中
	支付宝接口在正常执行之后会调用自定义函数来执行逻辑
	那么这个函数就是回调函数

from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
from threading import current_thread
import os,time

def func1(i):
	print("Process start  " ,os.getpid())
	time.sleep(0.5)
	print("Process end  " , i)
	return "*" * i

def func2(i):
	print("Thread start " , current_thread().ident)
	time.sleep(0.5)
	print("Thread end  " ,i)
	return "$" * i

def call_back1(obj):
	print("回调函数callback进程号 : "  , os.getpid())
	print(obj.result())

def call_back2(obj):
	print("回调函数callback线程号 : " , current_thread().ident)
	print(obj.result())


#1.进程池的回调函数 : 由主进程执行调用完成
if __name__ == "__main__":
	p = ProcessPoolExecutor(5)
	for i in range(1,11):
		res = p.submit(func1,i)
		#进程对象.add_done_callback(回调函数)
		#add_done_callback 可以把res本对象和回调函数自动传递到函数中
		res.add_done_callback(call_back1)
	p.shutdown()
	print("over" , os.getpid())


#2.线程池的回调函数 : 由当前子进程执行调用完成
if __name__ == "__main__":
	tp = ThreadPoolExecutor(5)
	for i in range(1,11):
		res = tp.submit(func2,i)
		res.add_done_callback(callback2)
	tp.shutdown()
	print("over" , current_thread().ident)


#add_done_callback 原型
class Ceshi():
	def add_done_callback(self,func):
		print("one ..")
		print("tow ..")
		func(self)
	def result(self):
		return 123456

def call_back(obj):
	print(obj)
	print(obj.result())

obj = Ceshi()
obj.add_done_callback(call_back)

协程

首先要安装协程模块  gevent
协程帮助你记住哪个任务执行到哪个位置上了,并且实现安全的切换
一个任务一旦阻塞卡顿,立刻切换到另一个任务继续执行,保证线程总是忙碌的,更加充分的利用CPU,抢占更多的时间片
一个线程可以由多个协程来实现,协程之间不会产生数据安全问题

#协程模块
# greenlet  gevent的底层,协程,切换的模块
# gevent    直接用的,gevent能提供更全面的功能

#用协程改写生产者消费者模型
def shengchan():
	for i in range(100):
		yield i
def xiaofei(gen):
	for i in range(10):
		print(next(gen))

gen = shengchan()
xiaofei(gen)
xiaofei(gen)



#协程的最好使用攻略
from gevent import monkey
monkey.patch_all()#把下面所有引入的模块中的阻塞识别一下
import time 
import gevent

def eat():
	print("eat 1")
	time.sleep(1)
	print("eat 2")

def play():
	print("play one")
	time.sleep(1)
	print("play two")
#利用gevent.spwan创建协程对象g1
g1 = gevent.spawn(eat)
#利用gevent.spwan创建协程对象g2
g2 = gevent.spawn(play)

#阻塞,必须g1协程执行完毕为止
g1.join()
#阻塞,必须g2协程执行完毕为止
g2.join()

print("over")

协程的例子

spawn(函数,参数1,参数2,参数3 ...) 启动协程
join 阻塞 , 直到某个协程任务执行完毕之后再放行
joinall 等待所有协程任务都执行完毕之后,再放行
推荐 : g1.join()  g2.join()  ==> gevent.joinall(  [g1,g2]  ) 
value 获取协程任务中的返回值 g1.value  g2.value 获取对应协程中返回值

#语句和语句之间可以用分号隔开
a =1 
b =2
print(a,b)

a=1 ;b=2
print(a,b)

#协程的其他写法
把下面的所有引入模块阻塞识别一下
from gevent import monkey ; monkey.patch_all()
import time,gevent

def eat():
	print("eat 1")
	time.sleep(1)
	print("eat 2")
	return "吃完了"

def play():
	print("play  1")
	time.sleep(1)
	print("play  2")
	return "玩完了"

g1 = gevent.spawn(eat)
g2 = gevent.spawn(play)
#默认 : 主线程不会等待所有的协程都执行完毕就会终止程序
#等待协程任务都执行完毕之后再向下执行
gevent.joinall(  [g1,g2]  )
print("over")
print(g1.value)
print(g2.value)

用协程爬取数据

requests 抓取页面数据的模块  需要安装

HTTP 状态码
200  ok 
404  not found
400  bad request

from gevent import monkey;monkey.patch_all()
import time
import gevent
import requests

response = requests.get("http://www.baidu.com")
print(response)	#<Response [200]>
#获取状态码
print(response.status_code)	#200
#获取网页中的字符编码
res = response.apparent_encoding
print(res)	#utf-8
#设置编码集 , 防止乱码
response.encoding = res
#获取网页当中的内容
res = response.text
print(res)



url_list = ["http://www.baidu.com","http://www.4399.com/",""http://www.taobao.com/","http://www.jingdong.com/"]

def get_url(url):
	response = requests.get(url)
	if response.status_code == 200:
		#print(response.text)
		time.sleep(0.1)

#正常爬取
starttime = time.time()
for i in url_list():
	get_url(i)
endtime = time.time()
print("over time : ",endtime - starttime)

#正则卡图片

strvar = '<img lz_src="http://i5.7k7kimg.cn/cms/cms10/20200609/113159_2868.jpg"'
obj = re.search(r'<img lz_src="(.*?)"',strvar)
print(obj.groups()[0])



#用协程方式爬取数据
lst = []
starttime = time.time()
for i in url_list:
	g = gevent.spawn(get_url,i)
	lst.append(g)
	
gevent.joinall(lst)
endtime = time.time()
print("over time : ",endtime-starttime)

利用好多进程多线程多协程可以让服务器运行速度更快
并且可以抗住更多用户访问

FTP 服务器实现登陆注册上传下载

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值