网络编程
1.基本概念
- mac地址 :,直译为媒体访问控制地址,也称为局域网地址(LAN Address),以 太网地址(Ethernet Address)或物理地址(Physical Address),它是一个用来确认网上设备位置的地址
- IP地址 ;IP地址是指互联网协议地址(英语:Internet ProtocolAddress,又译为网际协议地址),是IP Address的缩写。IP 地址是IP协议提供的一种统一的地址格式,它为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽 物理地址的差异。
- 交换机 :交换机只认识mac地址,不认识IP地址。负责局域网内的通信,从网段内部帮你找到具体的机器。能进行广播、单 播、组播的操作
2.网络开发架构
- C/S架构 :Client与Server ,中文意思:客户端与服务器端架构,这种架构也是从用户层面(也可以是物理层面)来划分的。
C/S架构的优点:可以离线使用、功能更完善、安全性更高 - B/S架构 :Browser与Server,中文意思:浏览器端与服务器端架构,这种架构是从用户层面来划分的。
B/S架构的优点:不用安装就可以使用、统一PC端用户的入口。因此B/S架构是未来的趋势
3.socket:套接字
【server.py】
import socket
# 创建一个server端的对象,即实例化一个socket类对象。第一个socket是模块名,第二个是类名。
sk = socket.socket()
# 给server端绑定一个地址,【注意】括号里面是一个元组,【注意】元组的第一项是字符串(只能写自己的IP地址或者 本地回环地址,不能写别人的IP地址),元组的第二项是数字(端口号)
sk.bind(('127.0.0.1', 9001))
# 开始监听,可以接收客户端给我的连接了
sk.listen()
# 建立连接 conn是连接,addr是对面的IP地址
conn, addr = sk.accept()
# conn, _ = sk.accept() 由于这里并没有用到addr,因此可将其写成 _
#本机向对方说话
conn.send(b'hello')
#接收对方说的话,最多接收1024字节
msg = conn.recv(1024) print(msg)
# 关闭与对方的连接
conn.close()
# 关闭服务
sk.close()
【client.py】
import socket
sk = socket.socket()
# 给server端绑定一个地址,【注意】这也是一个元组,【注意】元组的两项必须和服务端sk.bind的元组保持一致 sk.connect(('127.0.0.1',9001))
msg = sk.recv(1024)
print(msg)
sk.send(b'byebye') # 因为客户端和服务端的机制不一样,所以这里是sk不是conn
sk.close() #【注意】 # 启动的时候,一定要先启server端,否则报错
4.OSI七层协议、OSI五层协议
【第五层】应用层:写的python代码
【第四层】传输层:封装了和端口(port)相关的内容,或解析了和端口相关的内容(有TCP协议、UDP协议)(设备:四层路由器、四层交换机)
【第三层】网络层:与IP相关(有IPv4协议、IPv6协议)(设备:路由器、三层交换机)
【第二层】数据链路层:和mac地址相关(有ARP协议)(设备:网卡、二层交换机)
【第一层】物理层:与电信号相关
5.TCP协议和UDP协议
- TCP协议(类比打电话)例子:线下缓存高清电影、qq远程控制、发邮件 :tcp协议 : 效率低 面向连接\可靠\全双工的通信
需要先建立连接 然后才能通信
特点:占用连接、可靠(消息不会丢失,依赖回执机制)、实时性高、慢 全双工的通信
总体来说,不在乎时间的,在乎数据准确性的,一般都是TCP协议
- UDP协议(类比发消息) :udp协议 : 效率高 无连接的\不可靠
例子:qq发消息、微信消息、在线播放视频(用户在乎快,在乎是否流畅,不在乎是否丢帧)
不需要建立连接 就可以通信的
不占用连接、不可靠(消息因为网络不稳定丢失)、快
总体来说,在乎时间的,一对多操作的,一般都是UDP协议
6.三次握手四次挥手
# 三次握手
# 客户端向服务器端发送syn请求,
# 服务端向客户端回复ack并发送syn请求,
# 客户端接收到请求之后再回复ack表示建立连接
# 由客户端的connect + 服务端的accept
# 四次挥手
# 客户端向服务端发送fin请求,
# 服务端回复ack确认
# 服务端向客户端发送fin请求,
# 客户端回复ack确认
# 有客户端的close和服务端的close
7.tcp协议多次联系沟通
server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9001)) # 申请操作系统的资源
sk.listen()代表socket服务的开启
while True: # 为了和多个客户端进行握手
conn,addr = sk.accept() # 能够和多个客户端进行握手了
print('conn : ',conn)
while True:
send_msg = input('>>>')
conn.send(send_msg.encode('utf-8')) 直接通过连接发送消息,不需要写地址
if send_msg.upper() == 'Q':
break
msg = conn.recv(1024).decode('utf-8')
if msg.upper() == 'Q': break
print(msg)
conn.close() # 挥手 断开连接
sk.close() # 归还申请的操作系统的资源
# str -encode('utf-8')-> bytes
# str -encode('gbk')-> bytes
# '你' 字符
# utf-8 b'181921' b'181921' -decode('utf-8')-> 你
# gbk b'17210' b'17210' -decode('gbk') -> 你
client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001)) 客户端/tcp协议的方法,和server端建立连接
while True:
msg = sk.recv(1024)
msg2 = msg.decode('utf-8')
if msg2.upper() == 'Q':break
print(msg,msg2)
send_msg = input('>>>')
sk.send(send_msg.encode('utf-8'))
if send_msg.upper() == 'Q':
break
sk.close()
8.udp协议多次链接
server端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9001))
while True:
msg,addr= sk.recvfrom(1024) 接受消息和地址
print(msg.decode('utf-8'))
msg = input('>>>')
sk.sendto(msg.encode('utf-8'),addr 需要写一个对方的地址
client端
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
server = ('127.0.0.1',9001)
while True:
msg = input('>>>')
if msg.upper() == 'Q':break
sk.sendto(msg.encode('utf-8'),server)
msg = sk.recv(1024).decode('utf-8')
if msg.upper() == 'Q':break
print(msg)
9.粘包
- 粘包现象:同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包。
- 为什么出现:因为tcp协议是流式传输,只出现在tcp协议中,因为tcp协议 多条消息之间没有边界,并且还有一大堆优化算法
- 发送端:发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
- 接受端:接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
- 怎么处理:设置边界
10.struct模块
struct模块:该模块可以把一个类型,如数字,转成固定长度的bytes
import struct
num1 = 129469649
num2 = 123
num3 = 8
ret1 = struct.pack('i',num1)
print(len(ret1))
ret2 = struct.pack('i',num2)
print(len(ret2))
ret3 = struct.pack('i',num3)
print(len(ret3))
print(struct.unpack('i',ret1))
print(struct.unpack('i', ret2))
print(struct.unpack('i', ret3))
tcp与udp协议的多人多次通信总结
# 和一个人通信说多句话
# 和一个人聊完再和其他人聊
# socket() tcp协议的server
# bind 绑定一个ip和端口
# listen 监听,代表socket服务的开启
# accept 等,到有客户端来访问和客户端建立连接
# send 直接通过连接发送消息,不需要写地址
# recv 只接收消息
# connect 客户端/tcp协议的方法,和server端建立连接
# close 关闭服务/连接
# udp协议的多人通信
# socket(type=socket.SOCK_DGRAM)
# sendto 需要写一个对方的地址
# recvfrom 接收消息和地址
# close 关闭服务/连接
# 每一句话什么意思?执行到哪儿程序会阻塞?为什么阻塞?什么时候结束阻塞?
# input() # 等待,直到用户输入enter键
# accept 阻塞,有客户端来和我建立完连接之后
# recv 阻塞,直到收到对方发过来的消息之后
# recvfrom 阻塞,直到收到对方发过来的消息之后
# connect 阻塞,直到server端结束了对一个client的服务,开始和当前client建立连接的时候
# 粘包现象
# 什么粘包?
# 两条或更多条分开发送的信息连在一起就是粘包现象
# 发生在发送端 : 发送间隔短,数据小,由于优化机制就合并在一起发送了
# 发生在接收端 : 接收不及时,所以数据就在接收方的缓存端黏在一起了
# 粘包发生的本质 : tcp协议的传输是流式传输 数据与数据之间没有边界
# 怎么解决粘包 : 自定义协议 struct模块
# 先发送四字节的数据长度 # 先接受4字节 知道数据的长度
# 再按照长度发送数据 # 再按照长度接收数据
tcp协议的自定义协议解决粘包问题
- recv(1024)不代表一定收到1024个字节,而是最多只能收到这么多
- 两条连续发送的数据一定要避免粘包问题
先发送数据长度,在发送数据
发送的数据相关的内容组成json:先发json的长度,再发json,json中存了接下来要发送的数据长度,在发送数据server端
import json
import socket
# 接收
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()conn,_ =sk.accept()
msg = conn.recv(1024).decode('utf-8')
msg = json.loads(msg)with open(msg['filename'],'wb') as f:
while msg['filesize'] > 0:
content = conn.recv(1024)
msg['filesize'] -= len(content)
f.write(content)
conn.close()
sk.close()client
import os
import json
import socket
# 发送
sk = socket.socket()
# sk.connect(('192.168.14.109',9012))
sk.connect(('127.0.0.1',9001))# 文件名\文件大小
abs_path = r'D:\python22期\day28 课上视频\3.网络基础概念.mp4'
filename = os.path.basename(abs_path)
filesize = os.path.getsize(abs_path)
dic = {'filename':filename,'filesize':filesize}
str_dic = json.dumps(dic)
sk.send(str_dic.encode('utf-8'))with open(abs_path,mode = 'rb') as f:
while filesize>0:
content = f.read(1024)
filesize -= len(content)
sk.send(content)
sk.close()
11验证客户端的合法性
利用hashlib模块进行
server端
import os
import socket
import hashlib
secret_key = b'alex_sb'
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
conn,addr = sk.accept()
# 创建一个随机的字符串
rand = os.urandom(32)
# 发送随机字符串
conn.send(rand)
# 根据发送的字符串 + secrete key 进行摘要
sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()
# 等待接收客户端的摘要结果
res_client = conn.recv(1024).decode('utf-8')
# 做比对
if res_client == res:
print('是合法的客户端')
# 如果一致,就显示是合法的客户端
# 并可以继续操作
conn.send(b'hello')
else:
conn.close()
# 如果不一致,应立即关闭连接
client端
import socket
import hashlib
secret_key = b'alex_sb979'
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
# 接收客户端发送的随机字符串
rand = sk.recv(32)
# 根据发送的字符串 + secret key 进行摘要
sha = hashlib.sha1(secret_key)
sha.update(rand)
res = sha.hexdigest()
# 摘要结果发送回server端
sk.send(res.encode('utf-8'))
# 继续和server端进行通信
msg = sk.recv(1024)
print(msg)
12.并发的TCP协议server端--socketserver
#当客户端来链接的时候直接和handle中中的代码交互self.request==conn
server端
import time
import socketserver
class Myserver(socketserver.BaseRequestHandler):
def handle(self):
conn = self.request
while True:
try:
content = conn.recv(1024).decode('utf-8')
conn.send(content.upper().encode('utf-8'))
time.sleep(0.5)
except ConnectionResetError:
break
server = socketserver.ThreadingTCPServer(('127.0.0.1',9001),Myserver)
server.serve_forever()
client端
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
while True:
sk.send(b'hello')
content = sk.recv(1024).decode('utf-8')
print(content)
13.操作系统基础
1.I/O操作:输入Input输出Qutput (相对内存来说)
- 输入是怎么输入的:键盘\input\read\recv
- 输出是怎么输出的;显示器\print\write\send
2.多道操作系统:一个程序遇到io就让出cpu给别人
- 顺序的一个一个执行的思路变成共同存在一台计算机中,其中一个程序执行让出cpu之后,另一个程序能继续使用cpu
- 来提高cpu的利用率
- 单纯的切换会不会占用时间:会
- 但是多道操作系统的原理整体还是节省了时间,提高了cpu的利用率
- 时空复用的概念
3.单cpu 分时操作系统: 把时间分成很小的段,每个时间都是一个时间片
- 没有提高cpu的利用率 \提高了用户的体验
4.实时操作系统
14.并发相关介绍
进程:进行中的程序
- 占用资源 需要操作系统调度
- pid:能够唯一标识一个进程
- 进程是计算机中最小的资源分配单位
并发;
多个程序同时进行:只有一个cpu,多个程序轮流在一个cpu中执行
宏观上:多个程序同时执行微观上:多个程序轮流在一个cpu中执行 本质上还是串行
并行 并行比较高效
- 多个程序同时执行,并且同时在多个cpu上执行
同步
- 在做a事的时候发起b事件,必须等待b事件结束之后才能继续做a事件
异步:在做a事的时候发起b事件,不需要等待b事件结束之后就可以做a事件
阻塞 :如果cpu不工作 input accept sleep connect
非阻塞:如果cpu工作
线程
- 线程是进程中的一个单位,不能脱离进程存在
- 线程是计算机中能够被cpu调度的最小单位
同步阻塞 调用函数必须等待结果\cpu不工作 例如 input sleep
input sleep recv recvfrom
同步非阻塞 调用函数必须等待结果\cpu工作 例如 调用了一个高计算的函数 strip eval('1+2+4')
# ret = eval('1+2+3+4+5')
# def func(*args):
# count = 0
# for i in args:
# count += i
# return count
# a = 1
# b = 2
# c = a + b
# d = func(a,b,c)
# print(d)
异步非阻塞(阻塞 blocking) 调用函数不需要立即获取结果,也不需要等 例如 start() terminate()
异步阻塞 调用函数不需要立即获取结果,而是继续做其他的事,在获取结果的时候不知道先获取谁的,但是总之需要等(阻塞)
15.进程
进程的三状态图
- 就绪 运行 阻塞
进程的调度算法
- 给所有的进程分配资源或者分配cpu使用权的一种方法
- 短作业优先
- 先来先服务
- 多级反馈算法
- 多个任务队列,优先级从高到低
- 新来的任务总是优先级最高的
- 每一个新任务几乎会立即或得一个时间片时间
- 执行完一个时间片之后会降到下一级的队列中
- 总是优先级最高的任务都执行完了才执行优先级低的队列
- 并且优先级越高时间片越短
进程的开启和关闭
multiprocessing模块 多元的处理进程的模块
# import os
# from multiprocessing import Process
#
# def func():
# print(os.getpid(),os.getppid())
# # pid process id 进程id
# # ppid parent process id 父进程id
# if __name__ == '__main__':
# # 只会在主进程中执行的所有的代码你写在name = main下
# print('main :',os.getpid(),os.getppid())
# p = Process(target=func)
# p.start()
# 为什么要用 if __name__ == '__main__':
# 能不能给子进程传递参数
# import os
# from multiprocessing import Process
#
# def func(name,age):
# print(os.getpid(),os.getppid(),name,age)
#
# if __name__ == '__main__':
# # 只会在主进程中执行的所有的代码你写在name = main下
# print('main :',os.getpid(),os.getppid())
# p = Process(target=func,args=('alex',84))
# p.start()
# 能不能获取子进程的返回值 # 不能
# 能不能同时开启多个子进程
# import os
# import time
# from multiprocessing import Process
#
# def func(name,age):
# print('%s start'%name)
# time.sleep(1)
# print(os.getpid(),os.getppid(),name,age)
#
# if __name__ == '__main__':
# # 只会在主进程中执行的所有的代码你写在name = main下
# print('main :',os.getpid(),os.getppid())
# arg_lst = [('alex',84),('太白', 40),('wusir', 48)]
# for arg in arg_lst:
# p = Process(target=func,args=arg)
# p.start() # 异步非阻塞
# join的用法
import os
import time
import random
from multiprocessing import Process
def func(name,age):
print('发送一封邮件给%s岁的%s'%(age,name))
time.sleep(random.random())
print('发送完毕')
if __name__ == '__main__':
#方法一
arg_lst = [('大壮',40),('alex', 84), ('太白', 40), ('wusir', 48)]
p_lst = []
for arg in arg_lst:
p = Process(target=func,args=arg)
p.start()
p_lst.append(p)
for p in p_lst:p.join()
#方法二
p_l = []
p = Process(target=func, args=('大壮',40))
p.start()
p_l.append(p)
p = Process(target=func, args=('alex', 84))
p.start()
p_l.append(p)
p = Process(target=func, args=('太白', 40))
p.start()
p_l.append(p)
p = Process(target=func, args=('wusir', 48))
p.start()
p_l.append(p)
for p in p_l:p.join()
print('所有的邮件已发送完毕') #多个子进程是异步执行不需要等待结果 且进程执行的慢 所以print先打印完毕
# 同步阻塞 异步非阻塞
# 同步阻塞 join
# 异步非阻塞 start
# 多进程之间的数据是否隔离
# from multiprocessing import Process
# n = 0
# def func():
# global n
# n += 1
#
# if __name__ == '__main__':
# p_l = []
# for i in range(100):
# p = Process(target=func)
# p.start()
# p_l.append(p)
# for p in p_l:p.join()
# print(n)
使用多进程实现一个并发的socket的server
sever端
def talk(conn):
while True:
msg = conn.recv(1024).decode('utf-8')
ret = msg.upper().encode('utf-8')
conn.send(ret)
conn.close()
if __name__ == '__main__':
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
while True:
conn, addr = sk.accept()
Process(target = talk,args=(conn,)).start()
sk.close()
client端
import time
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
while True:
sk.send(b'hello')
msg =sk.recv(1024).decode('utf-8')
print(msg)
time.sleep(0.5)
sk.close()
Process类拾遗
- 开启进程的另一种方法
- 面向对象的方法,通过继承和重写run方法完成了启动子进程
通过重写init方法和调用父类的init完成了给子进程传参数
import os
import time
from multiprocessing import Processclass MyProcess(Process):
def init(self,a,b,c):
self.a = a
self.b = b
self.c = c
super().__init__()def run(self): time.sleep(3) print(os.getppid(),os.getpid(),self.a,self.b,self.c)
if name == 'main':
p = MyProcess(1,2,3)
p.start()
print(p.pid,p.ident)
print(p.name)
print(p.is_alive())
p.terminate() # 强制结束一个子进程 同步 异步非阻塞
print(p.is_alive())
time.sleep(0.01)
print(p.is_alive())
Process类的一些其他方法\属性
name pid ident
daemon(默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置)
terminate() isalive()
p = MyProcess(1,2,3)
p.start() #启动进程 并调用该子进程的p.run()
print(p.pid,p.ident)# 进程的pid
print(p.name)
print(p.is_alive())
p.terminate() # 强制结束一个子进程 同步 异步非阻塞
print(p.is_alive()) #若p仍然运行,返回True
time.sleep(0.01)
print(p.is_alive())
1守护进程
守护进程使用方法:在start一个进程之前设置daemon = True
- 守护进程会等待主进程代码结束后就立即结束
- 守护进程会等待其他子进程结束m?不会
- 为什么守护进程不是等主进程结束后才结束
因为主进程要最后结束,回收守护进程资源
import time
from multiprocessing import Process
def son1():
while True:
print('--> in son1')
time.sleep(1)
def son2(): # 执行10s
for i in range(10):
print('in son2')
time.sleep(1)
if name == 'main': # 3s
p1 = Process(target=son1)
p1.daemon = True # 表示设置p1是一个守护进程
p1.start()
p2 = Process(target=son2,)
p2.start()
time.sleep(3)
print('in main')
2.进程同步Lock 锁
为了解决进程之间的数据安全问题
#抢票系统例子
import json
import time
from multiprocessing import Process,Lock
def search(i):
with open('ticket',encoding='utf-8') as f:
ticket = json.load(f)
print('%s :当前的余票是%s张'%(i,ticket['count']))
def buy_ticket(i):
with open('ticket',encoding='utf-8') as f:
ticket = json.load(f)
if ticket['count']>0:
ticket['count'] -= 1
print('%s买到票了'%i)
time.sleep(0.1)
with open('ticket', mode='w',encoding='utf-8') as f:
json.dump(ticket,f)
def get_ticket(i,lock):
search(i)
with lock: # 代替acquire和release 并且在此基础上做一些异常处理,保证即便一个进程的代码出错退出了,也会归还钥匙
buy_ticket(i)
if __name__ == '__main__':
lock = Lock() # 互斥锁
for i in range(10):
Process(target=get_ticket,args=(i,lock)).start()
# import time
# from multiprocessing import Lock,Process
# def func(i,lock):
# lock.acquire() # 拿钥匙
# print('被锁起来的代码%s'%i)
# lock.release() # 还钥匙
# time.sleep(1)
#
# if __name__ == '__main__':
# lock = Lock()
# for i in range(10):
# p = Process(target=func,args=(i,lock))
# p.start()
3.进程之间的通信 ---队列
# 进程之间数据隔离
# 进程之间通信(IPC) Inter Process communication
# 基于文件 :同一台机器上的多个进程之间通信
# Queue 队列
# 基于socket的文件级别的通信来完成数据传递的
# 基于网络 :同一台机器或者多台机器上的多进程间通信
# 第三方工具(消息中间件)
# memcache
# redis
# rabbitmq
# kafka
# from multiprocessing import Queue,Process
# def pro(q):
# for i in range(10):
# print(q.get())
# def son(q):
# for i in range(10):
# q.put('hello%s'%i)
#
# if __name__ == '__main__':
# q = Queue()
# p = Process(target=son,args=(q,))
# p.start()
# p = Process(target=pro, args=(q,))
# p.start()
4.生产者消费者模型
# 生产者消费者模型
# 爬虫的时候
# 分布式操作 : celery
# 本质 :就是让生产数据和消费数据的效率达到平衡并且最大化的效率
# import time
# import random
# from multiprocessing import Queue,Process
#
# def consumer(q): # 消费者:通常取到数据之后还要进行某些操作
# for i in range(10):
# print(q.get())
#
# def producer(q): # 生产者:通常在放数据之前需要先通过某些代码来获取数据
# for i in range(10):
# time.sleep(random.random())
# q.put(i)
#
# if __name__ == '__main__':
# q = Queue()
# c1 = Process(target=consumer,args=(q,))
# p1 = Process(target=producer,args=(q,))
# c1.start()
# p1.start()
import time
import random
from multiprocessing import Queue,Process
def consumer(q,name): # 消费者:通常取到数据之后还要进行某些操作
while True:
food = q.get()
if food:
print('%s吃了%s'%(name,food))
else:break
def producer(q,name,food): # 生产者:通常在放数据之前需要先通过某些代码来获取数据
for i in range(10):
foodi = '%s%s'%(food,i)
print('%s生产了%s'%(name,foodi))
time.sleep(random.random())
q.put(foodi)
if __name__ == '__main__':
q = Queue()
c1 = Process(target=consumer,args=(q,'alex'))
c2 = Process(target=consumer,args=(q,'alex'))
p1 = Process(target=producer,args=(q,'大壮','泔水'))
p2 = Process(target=producer,args=(q,'b哥','香蕉'))
c1.start()
c2.start()
p1.start()
p2.start()
p1.join()
p2.join()
q.put(None)
q.put(None)
#生产者消费者爬虫例子
import requests
from multiprocessing import Process,Queue
url_dic = {
'cnblogs':'https://www.cnblogs.com/Eva-J/articles/8253549.html',
'douban':'https://www.douban.com/doulist/1596699/',
'baidu':'https://www.baidu.com',
'gitee':'https://gitee.com/old_boy_python_stack__22/teaching_plan/issues/IXSRZ',
}
def producer(name,url,q):
ret = requests.get(url)
q.put((name,ret.text))
def consumer(q):
while True:
tup = q.get()
if tup is None:break
with open('%s.html'%tup[0],encoding='utf-8',mode='w') as f:
f.write(tup[1])
if __name__ == '__main__':
q = Queue()
pl = []
for key in url_dic:
p = Process(target=producer,args=(key,url_dic[key],q))
p.start()
pl.append(p)
Process(target=consumer,args=(q,)).start()
for p in pl:p.join()
q.put(None)
5.进程之间的数据共享--Manager
进程间数据是独立的,可以借助于队列或管道实现通信,二者都是基于消息传递的
虽然进程间数据独立,但可以通过Manager实现数据共享,事实上Manager的功能远不止于此
from multiprocessing import Process,Manager,Lock
def change_dic(dic,lock):
with lock:
dic['count'] -= 1
if __name__ == '__main__':
# m = Manager()
with Manager() as m:
lock = Lock()
dic = m.dict({'count': 100})
# dic = {'count': 100}
p_l = []
for i in range(100):
p = Process(target=change_dic,args=(dic,lock))
p.start()
p_l.append(p)
for p in p_l : p.join()
print(dic)
6.进程总结
from multiprocessing import Process
# 开启进程的另一种方式
# class 类名(Process):
# def __init__(self,参数):
# self.属性名 = 参数
# super().__init__()
# def run(self):
# print('子进程要执行的代码')
# p = 类名()
# p.start()
# 守护进程 : 会等待主进程代码结束之后就立即结束
# p = 类名()
# p.daemon = True # 设置守护进程
# p.start()
# 一般情况下,多个进程的执行顺序,可能是:
# 主进程代码结束--> 守护进程结束-->子进程结束-->主进程结束
# 子进程结束 -->主进程代码结束-->守护进程结束-->主进程结束
# 锁:会降低程序的运行效率,保证数据的安全.
from multiprocessing import Lock # 互斥锁
lock = Lock()
# lock.acquire()
# ...
# lock.release()
#
# with lock:
# ...
# 队列 ipc 进程之间通信 -- 数据安全
# 基于socket\pickle\Lock实现的
# pipe管道基于socket\pickle实现的,没有锁数据不安全
from multiprocessing import Queue,Pipe
# 生产者消费者模型
# 把原本获取数据处理数据的完整过程进行了 解耦
# 把生产数据和消费数据分开,根据生产和消费的效率不同,来规划生产者和消费者的个数,
# 让程序的执行效率达到平衡
# 如果你写了一个程序所有的功能\代码都放在一起,不分函数不分类也不分文件
# 紧耦合的程序
# 拆分的很清楚的程序
# 松耦合的程序
16.线程
1.线程概念
概念:能被操作系统调度(给cpu执行)的最小单位
同一个进程中的多个线程同时被cpu执行???? 可能
数据共享,操作系统调度的最小单位,可以利用多核, 操作系统调度, 数据不安全开启关闭切换时间开销小
在cpython中的多线程___节省io操作的时间
- gc垃圾回收机制 线程
- 引用计数 + 分代回收
- 全局解释器锁的出现主要是为了完成gc的回收机制,对不同线程的引用计数的变化记录的更加精准
- 全局解释器锁 GIL
- 导致了同一个进程中的多个线程只能有一个线程真正被cpu执行
2.threading模块开启线程
#面向函数开启线程
import os
import time
from threading import Thread,current_thread,enumerate,active_count
# from multiprocessing import Process as Thread
def func(i):
print('start%s'%i,current_thread().ident)
time.sleep(1)
print('end%s'%i)
if __name__ == '__main__':
tl = []
for i in range(10):
t = Thread(target=func,args=(i,))
t.start()
print(t.ident,os.getpid())
tl.append(t)
print(enumerate(),active_count())
for t in tl:t.join()
print('所有的线程都执行完了')
# current_thread() 获取当前所在的线程的对象 current_thread().ident通过ident可以获取线程id
# 线程是不能从外部terminate
# 所有的子线程只能是自己执行完代码之后就关闭
# enumerate 列表 存储了所有活着的线程对象,包括主线程
# active_count 数字 存储了所有活着的线程个数.包括主线程
3.面向对象的方式起线程
from threading import Thread
class MyThread(Thread):
def __init__(self,a,b):
self.a = a
self.b = b
super().__init__()
def run(self):
print(self.ident)
t = MyThread(1,2)
t.start() # 开启线程 才在线程中执行run方法
print(t.ident)
threading
对象 = 实例化结果:指定target args
start
join
ident
4.线程之间的数据共享
from threading import Thread
n = 100
def func():
global n
n -= 1
t_l = []
for i in range(100):
t = Thread(target=func)
t.start()
t_l.append(t)
for t in t_l:
t.join()
print(n)
5.守护线程
1.主线程会等待子线程才结束
- 原因:主线程结束后进程就会结束
2.守护线程随着主线程结束而结束
3.守护线程会在主线程的代码结束之后继续守护其他子线程吗? 会的
4.守护线程和守护进程区别
- 如果主进程代码结束之后还有其他子进程在运行,守护进程不守护
- 如果主线程代码结束之后还有其他子线程在运行,守护线程也守护
5.为什么
- 守护线程和守护进程的结束原理不同
- 守护进程需要主进程来回收资源
- 守护线程是随着进程的结束才结束的7
- 其他子线程-->主线程结束-->主进程结束-->整个进程中的所有资源都被回收-->守护线程也会被回收
进程是资源分配单位
子进程都需要他的父进程来回收资源
线程是进程中的资源
所有的线程都会随着进程的结束而被回收import time
from threading import Threaddef son():
while True: print('in son') time.sleep(1)
def son2():
for i in range(3): print('in son2 ****') time.sleep(1)
# flag a 0s
t = Thread(target=son)
t.daemon = True
t.start()
Thread(target=son2).start()
# flag b
6.线程中的数据不安全现象
# += -= *= /= while if 数据不安全 + 和 赋值是分开的两个操作
# append pop strip数据安全 列表中的方法或者字典中的方法去操作全局变量的时候 数据安全的
# 线程之间也存在数据不安全
from threading import Thread
n = 0
def add():
for i in range(500000):
global n
n += 1
def sub():
for i in range(500000):
global n
n -= 1
t_l = []
for i in range(2):
t1 = Thread(target=add)
t1.start()
t2 = Thread(target=sub)
t2.start()
t_l.append(t1)
t_l.append(t2)
for t in t_l:
t.join()
print(n)
from threading import Thread
import time
n = []
def append():
for i in range(500000):
n.append(1)
def pop():
for i in range(500000):
if not n:
time.sleep(0.0000001)
n.pop()
t_l = []
for i in range(20):
t1 = Thread(target=append)
t1.start()
t2 = Thread(target=pop)
t2.start()
t_l.append(t1)
t_l.append(t2)
for t in t_l:
t.join()
print(n)
7.线程锁
from threading import Thread,Lock
n = 0
def add(lock):
for i in range(500000):
global n
with lock:
n += 1
def sub(lock):
for i in range(500000):
global n
with lock:
n -= 1
t_l = []
lock = Lock()
for i in range(2):
t1 = Thread(target=add,args=(lock,))
t1.start()
t2 = Thread(target=sub,args=(lock,))
t2.start()
t_l.append(t1)
t_l.append(t2)
for t in t_l:
t.join()
print(n)
import dis
a = 0
def func():
global a
a += 1
'''
锁
0 LOAD_GLOBAL
2 LOAD_CONST
4 INPLACE_ADD
# GIL锁切换了
6 STORE_GLOBAL
释放锁
'''
from threading import Thread,Lock
import time
n = []
def append():
for i in range(500000):
n.append(1)
def pop(lock):
for i in range(500000):
with lock:
if not n:
time.sleep(0.0000001) # 强制CPU轮转
n.pop()
t_l = []
lock = Lock()
for i in range(20):
t1 = Thread(target=append)
t1.start()
t2 = Thread(target=pop,args=(lock,))
t2.start()
t_l.append(t1)
t_l.append(t2)
for t in t_l:
t.join()
print(n)
# 不要操作全局变量,不要在类里操作静态变量
# += -= *= /= if while 数据不安全
# queue logging 数据安全的
8.线程锁的单例模式
import time
class A:
from threading import Lock
__instance = None
lock = Lock()
def __new__(cls, *args, **kwargs):
with cls.lock:
if not cls.__instance:
time.sleep(0.000001) # cpu轮转
cls.__instance = super().__new__(cls)
return cls.__instance
def func():
a = A()
print(a)
from threading import Thread
for i in range(10):
Thread(target=func).start()
9.互斥锁和递归锁
Lock 互斥锁 效率高
RLock 递归锁 (recursion) 效率相对低
l = Lock()
l.acquire()
print('希望被锁住的代码')
l.release()
rl = RLock() # 在同一个线程中可以被acquire多次
rl.acquire()
print('希望被锁住的代码')
rl.release()
from threading import Thread,RLock as Lock
def func(i,lock):
lock.acquire()
lock.acquire()
print(i,': start')
lock.release()
lock.release()
print(i, ': end')
lock = Lock()
for i in range(5):
Thread(target=func,args=(i,lock)).start()
锁死现象
# 死锁现象是怎么产生的?
# 多把(互斥/递归)锁 并且在多个线程中 交叉使用
# fork_lock.acquire()
# noodle_lock.acquire()
#
# fork_lock.release()
# noodle_lock.release()
# 如果是互斥锁,出现了死锁现象,最快速的解决方案把所有的互斥锁都改成一把递归锁
# 程序的效率会降低的
# 递归锁 效率低 但是解决死锁现象有奇效
# 互斥锁 效率高 但是多把锁容易出现死锁现象
# 一把互斥锁就够了
10.队列
import queue # 线程之间数据安全的容器队列
from queue import Empty # 不是内置的错误类型,而是queue模块中的错误
1.q = queue.Queue(4) # fifo 先进先出的队列
q.get()
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print('4 done')
q.put_nowait(5)
print('5 done')
try:
q.get_nowait()
except Empty:pass
print('队列为空,继续其他内容')
2.from queue import LifoQueue # last in first out 后进先出 栈
lq = LifoQueue()
lq.put(1)
lq.put(2)
lq.put(3)
print(lq.get())
print(lq.get())
print(lq.get())
3.from queue import PriorityQueue # 优先级队列
priq = PriorityQueue()
priq.put((2,'alex'))
priq.put((1,'wusir'))
priq.put((0,'太白'))
print(priq.get())
print(priq.get())
print(priq.get())
11.池
- 开好的线程或者进程会一直存在池中,可以被多个任务反复调用,,这样极大的减少了开启关闭调度线程 或者进程的时间开销
- 池中的线程/进程个数控制了操作系统需要调度的任务个数,控制池中的单位,有利于提高操作系统的效率,减轻了操作系统的负担
- 实例化创建池 向池中中提交
发展过程
# 发展过程
# threading模块 没有提供池
# multiprocessing模块 仿照threading写的 Pool
# concurrent.futures模块 线程池,进程池都能够用相似的方式开启\使用
1.线程池
import time
import random
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor
def func(a,b):
print(current_thread().ident,'start',a,b)
time.sleep(random.randint(1,4))
print(current_thread().ident,'end')
if __name__ == '__main__':
tp = ThreadPoolExecutor(4)
for i in range(20):
tp.submit(func,i,b=i+1)
# 实例化 创建池
# 向池中提交任务,submit 传参数(按照位置传,按照关键字传)
2.进程池
#1.进程池模型
import os
import time,random
from concurrent.futures import ProcessPoolExecutor
def func(a,b):
print(os.getpid(),'start',a,b)
time.sleep(random.randint(1,4))
print(os.getpid(),'end')
if __name__ == '__main__':
tp = ProcessPoolExecutor(4)
for i in range(20):
tp.submit(func,i,b=i+1)
#1. 获取任务结果
import os
import time,random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(a,b):
print(os.getpid(),'start',a,b)
time.sleep(random.randint(1,4))
print(os.getpid(),'end')
return a*b
if __name__ == '__main__':
tp = ProcessPoolExecutor(4)
futrue_l = {}
for i in range(20): # 异步非阻塞的
ret = tp.submit(func,i,b=i+1)
futrue_l[i] = ret
# print(ret.result()) # Future未来对象
for key in futrue_l: # 同步阻塞的
print(key,futrue_l[key].result())
#3. map 只适合传递简单的参数,并且必须是一个可迭代的类型作为参数
import os
import time,random
from concurrent.futures import ThreadPoolExecutor,ProcessPoolExecutor
def func(a):
print(os.getpid(),'start',a[0],a[1])
time.sleep(random.randint(1,4))
print(os.getpid(),'end')
return a[0]*a[1]
if __name__ == '__main__':
tp = ProcessPoolExecutor(4)
ret = tp.map(func,((i,i+1) for i in range(20)))
for key in ret: # 同步阻塞的
print(key)
#4.回调函数 : 效率最高的
import time,random
from threading import current_thread
from concurrent.futures import ThreadPoolExecutor
def func(a,b):
print(current_thread().ident,'start',a,b)
time.sleep(random.randint(1,4))
print(current_thread().ident,'end',a)
return (a,a*b)
def print_func(ret): # 异步阻塞
print(ret.result())
if __name__ == '__main__':
tp = ThreadPoolExecutor(4)
futrue_l = {}
for i in range(20): # 异步非阻塞的
ret = tp.submit(func,i,b=i+1)
ret.add_done_callback(print_func) # ret这个任务会在执行完毕的瞬间立即触发print_func函数,并且把任务的返回值对象传递到print_func做参数
# 异步阻塞 回调函数 给ret对象绑定一个回调函数,等待ret对应的任务有了结果之后立即调用print_func这个函数
# 就可以对结果立即进行处理,而不用按照顺序接收结果处理结果
3.回调函数
from concurrent.futures import ThreadPoolExecutor
import requests
import os
def get_page(url): # 访问网页,获取网页源代码 线程池中的线程来操作
print('<进程%s> get %s' %(os.getpid(),url))
respone=requests.get(url)
if respone.status_code == 200:
return {'url':url,'text':respone.text}
def parse_page(res): # 获取到字典结果之后,计算网页源码的长度,把https://www.baidu.com : 1929749729写到文件里 线程任务执行完毕之后绑定回调函数
res=res.result()
print('<进程%s> parse %s' %(os.getpid(),res['url']))
parse_res='url:<%s> size:[%s]\n' %(res['url'],len(res['text']))
with open('db.txt','a') as f:
f.write(parse_res)
if __name__ == '__main__':
urls=[
'https://www.baidu.com',
'https://www.python.org',
'https://www.openstack.org',
'https://help.github.com/',
'http://www.sina.com.cn/'
]
# 获得一个线程池对象 = 开启线程池
tp = ThreadPoolExecutor(4)
# 循环urls列表
for url in urls:
# 得到一个futrue对象 = 把每一个url提交一个get_page任务
ret = tp.submit(get_page,url)
# 给futrue对象绑定一个parse_page回调函数
ret.add_done_callback(parse_page) # 谁先回来谁就先写结果进文件
# 不用回调函数:
# 按照顺序获取网页 百度 python openstack git sina
# 也只能按照顺序写
# 用上了回调函数
# 按照顺序获取网页 百度 python openstack git sina
# 哪一个网页先返回结果,就先执行那个网页对应的parserpage(回调函数)
# 会起池\会提交任务
# 会获取返回值\会用回调函数
# 1.所有的例题 会默
# 2.进程池(高计算的场景,没有io(没有文件操作\没有数据库操作\没有网络操作\没有input)) : >cpu_count*1 <cpu_count*2
# 线程池(一般根据io的比例定制) : cpu_count*5
# 5*20 = 100并发
17.协程
定义
# 进程是是程序正在运行的实例,是cpu资源分配和调度的最小单位
# 线程
#能被操作系统调度(给cpu执行)的最小单位
# 正常的开发语言 多线程可以利用多核
# cpython解释器下的多个线程不能利用多核 : 规避了所有io操作的单线程
# 协程
# 是操作系统不可见的
# 协程本质就是一条线程 多个任务在一条线程上来回切换
# 利用协程这个概念实现的内容 : 来规避IO操作,就达到了我们将一条线程中的io操作降到最低的目的
# import time
# def func1():
# print('start')
# time.sleep(1)
# print('end')
#
# def func2():
# print('start')
# time.sleep(1)
# print('end')
# 切换 并 规避io 的两个模块
# gevent = 利用了 greenlet 底层模块完成的切换 + 自动规避io的功能
# asyncio = 利用了 yield 底层语法完成的切换 + 自动规避io的功能
# tornado 异步的web框架
# yield from - 更好的实现协程
# send - 更好的实现协程
# asyncio模块 基于python原生的协程的概念正式的被成立
# 特殊的在python中提供协程功能的关键字 : aysnc await
# 进程 数据隔离 数据不安全 操作系统级别 开销非常大 能利用多核
# 线程 数据共享 数据不安全 操作系统级别 开销小 不能利用多核 一些和文件操作相关的io只有操作系统能感知到
# 协程 数据共享 数据安全 用户级别 更小 不能利用多核 协程的所有的切换都基于用户,只有在用户级别能够感知到的io才会用协程模块做切换来规避(socket,请求网页的)
# 用户级别的协程还有什么好处:
# 减轻了操作系统的负担
# 一条线程如果开了多个协程,那么给操作系统的印象是线程很忙,这样能多争取一些时间片时间来被CPU执行,程序的效率就提高了
# a = 1
# def func():
# global a
# # 切换
# a += 1
# # 切换
#
# import dis
# dis.dis(func)
# 对于操作系统 : python代码--> 编译 --> 字节码 --> 解释 --> 二进制010101010010101010
# 二进制 反编译过来的 --> LOAD_GLOBAL
# 4cpu
# 进程 :5个进程
# 线程 :20个
# 协程 :500个
# 5*20*500 = 50000
2.gevent内置模块
协程例子
# import gevent
#
# def func(): # 带有io操作的内容写在函数里,然后提交func给gevent
# print('start func')
# gevent.sleep(1)
# print('end func')
#'
# g1 = gevent.spawn(func)
# g2 = gevent.spawn(func)
# g3 = gevent.spawn(func)
# gevent.joinall([g1,g2,g3])
# g1.join() # 阻塞 直到协程g1任务执行结束
# g2.join() # 阻塞 直到协程g1任务执行结束
# g3.join() # 阻塞 直到协程g1任务执行结束
import time
print(time.sleep)
from gevent import monkey
monkey.patch_all()
import time
import gevent
def func(): # 带有io操作的内容写在函数里,然后提交func给gevent
print('start func')
time.sleep(1)
print('end func')
g1 = gevent.spawn(func)
g2 = gevent.spawn(func)
g3 = gevent.spawn(func)
gevent.joinall([g1,g2,g3])
3.asyncio 内置模块
import asyncio
async def func(name):
print('start',name)
# await 可能会发生阻塞的方法
# await 关键字必须写在一个async函数里
await asyncio.sleep(1)
print('end')
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait([func('alex'),func('太白')]))
aiohttp模块 并发的爬虫
flask轻量级的web框架/sanic异步的轻量级的web框架
4.使用协程实现并发的tcp server端
#server端
import socket
print(socket.socket) # 在patch all之前打印一次
from gevent import monkey # gevent 如何检测是否能规避某个模块的io操作呢?
monkey.patch_all()
import socket
import gevent
print(socket.socket) # 在patch all之后打印一次,如果两次的结果不一样,那么就说明能够规避io操作
def func(conn):
while True:
msg = conn.recv(1024).decode('utf-8')
MSG = msg.upper()
conn.send(MSG.encode('utf-8'))
sk = socket.socket()
sk.bind(('127.0.0.1',9001))
sk.listen()
while True:
conn,_ = sk.accept()
gevent.spawn(func,conn)
#client
import time
import socket
from threading import Thread
def client():
sk = socket.socket()
sk.connect(('127.0.0.1',9001))
while True:
sk.send(b'hello')
msg = sk.recv(1024)
print(msg)
time.sleep(0.5)
for i in range(500):
Thread(target=client).start()
协程总结
# gil锁
# 全局解释器锁,Cpython解释器下有的
# 导致了python的多线程不能利用多核(不能并行)
# 内容回顾
# 池 concurrent.futures
# 进程池 p = ProcessPoolExecutor(n)
# 线程池 p = ThreadPoolExecutor(n)
# future = submit 提交任务
# future.result()获取结果
# map 循环提交任务
# add_done_callback 回调函数
# 协程
# 协程:本质是一个线程
# 能够在一个线程内的多个任务之间来回切换
# 节省io操作的时间也只能是和网络操作相关的
# 特点:数据安全,用户级别,开销小,不能利用多核,能够识别的io操作少
# gevent 第三方模块 完成并发的socket server
# 协程对象.spawn(func,参数)
# 能识别的io操作也是有限的
# 并且要想让gevent能够识别一些导入的模块中的io操作
# from gevent import monkey;monkey.patch_all()
# asyncio 内置模块
# await 写好的asyncio中的阻塞方法
# async 标识一个函数时协程函数,await语法必须用在async函数中
# import asyncio
# async def func():
# print('sjkhaf')
# await asyncio.sleep(1)
# print('sjkhaf')
# loop = asyncio.get_event_loop()
# loop.run_until_complete(func())
# loop.run_until_complete(asyncio.wait([func(),func(),func()]))