软件开发架构
C/S:
client:客户端
server:服务端
优点:占用网络资源少,软件的使用稳定
缺点:服务端更新后,客户端也要更新,需要使用多个软件,需要下载多个客户端
B/S:
Browser:浏览器
server:服务端
服务端与客户端作用:
服务端:24小时不间断提供服务
客户端:需要体验服务端时,再去连接服务端,并享受服务
网络编程
七层协议
应用层,表示层,会话层,传输层,网络层,数据链路层,物理连接层
-
物理连接层
基于电信号发送二进制数据
-
数据链路层
-
规定好电信号的分组方式
-
必须要有一块网卡
mac地址:
12位唯一的16进制字符串
- 前6位:厂商号
- 后6位:流水号
以太网协议:
在同一个局域网内通信。
-
单播
1对1通信
-
广播
多对多通信
-
广播风暴:
不能夸局域网通信
-
-
-
网络层
-
ip:定位局域网的位置
-
arp协议:
将mac地址获取,并解析成ip和port
-
-
传输层
-
TCP
特点:
tcp协议称之为流式协议
想要通信,必须建立连接,并建立双向通道
-
三次握手,四次挥手
-
三次握手建连接
- 客户端往服务端发送请求建立通道
- 服务端要确认客户端的请求,并网客户端也发送请求建立通道
- 客户端接收到服务端建立连接的请求,并返回确认
- 建立双向通道
-
双向通道:
-
反馈机制
客户端往服务端发送请求获取数据,服务端务必返回数据,客户端确认收到,否则会反复发送,一直到某个时间段内,会停止发送
-
-
四次挥手断连接
- 客户端往服务端发送断开连接,服务端返回确认收到
- 服务端需要再次发送断开连接请求
- 客户端返回确认收到
- 最终确认断开连接
-
-
-
UDP
- 数据不安全
- 不需要建立双向通道
- 传输速度快
- 不会有粘包问题
- 客户端发送数据,不需要服务端确认收到
-
TCP和UPD的区别:
TCP:比喻成在打电话
UDP:比喻成发送短信
-
-
应用层
-
ftp
-
http:
可以携带一堆数据
-
http+ssl
-
socket
socket用来写台阶自客户端与服务端的模块,内部帮我们封装好了7层协议需要做的事情
socket套接字模板
- 服务端:
import socket
server = socket.socket()
server.bind(
(ip, port)
) # 绑定手机号
server.listen(6) # 半连接池: 可以接待7个客户端
# 监听连接
conn, addr =server.accept()
# 接收消息
data = conn.recv(1024)
# 发送消息
conn.send('消息内容'.encode('utf-8'))
- 客户端:
import socket
client = socket.socket()
client.connect(
(ip, port)
)
# 发送消息
client.send()
# 接收消息
client.recv(1024)
粘包问题
- 不能确认对方发送数据的大小
- 在短时间内,间隔时间短,并且数据量小的情况,默认将这些数据打包成一个多次发送的数据---》一次性发送
struct解决粘包问题
初级版:
i: 4
可以将一个数据的长度打包成一个固定长度的报头.
struct.pack('模式i', '源数据长度')
data = 'gagawagwaga'
# 打包成报头
headers = struct.pack('i', len(data))
# 解包获取数据真实长度
data = struct.unpack('i', headers)[0]
注意: 以什么方式打包,必须以什么方式解包.
升级版:
先将数据存放到字典中,将字典打包发送过去
- 字典的好处:
- 真实数据长度
- 文件的描述信息
- 发送的数据,更小
dic = {
'data_len': 1000000000000000000000046546544444444444444444444444444444444444444,
文件的描述信息
}
上传大文件数据
# 客户端
dic = {
文件大小,
文件名
}
with open(文件名, 'rb') as f:
for line in f:
client.send(line)
# 服务端
dic = {
文件大小,
文件名
}
init_recv = 0
with open(文件名, 'wb') as f:
while init_recv < 文件大小:
data = conn.recv(1024)
f.write(data)
init_recv += len(data)
socketserver
可以支持并发
import socketserver
# 定义类
# TCP: 必须继承BaseRequestHandler类
class MyTcpServer(socketserver.BaseRequestHandler):
- handle
# 内部实现了
server = socket.socket()
server.bind(
('127.0.0.1', 9527)
)
server.listen(5) ---
while True:
conn, addr = server.accept()
print(addr)
# 必须重写父类的handle, 当客户端连接时会调用该方法
def handle(self):
print(self.client_address)
while True:
try:
# 1.接收消息
# request.recv(1024) == conn.recv(1024)
data = self.request.recv(1024).decode('utf-8')
send_msg = data.upper()
self.request.send(send_msg.encode('utf-8'))
except Exception as e:
print(e)
break
并发编程
多道技术
-
多道:切换+保存状态
-
空间上的复用
支持多个程序使用
-
时间上的复用
- 遇到IO操作就会切换程序
- 程序占用CPU时间过长会切换
-
并发与并行
并发:看起来像同时运行:多道技术
并行:真正意义上的同时运行:多核下
进程
进程是资源单位,每创建一个进程都会生成一个名称空间,占用内存资源
-
程序与进程
程序就是一堆代码,进程就是一堆代码运行的过程
-
进程调度
-
时间片轮转法
10个进程,将固定时间,等分成10份时间片,分配给每一个进程
-
分级反馈队列
1级最高
-
-
进程的三个状态
-
就绪态
创建多个进程,必须要排队准备运行
-
运行态
进程开始运行,1. 结束 2. 阻塞
-
阻塞态
当运行态遇到IO操作,就会进阻塞态
-
-
同步与异步
提交任务的方式
- 同步:同步提交,串行,一个任务结束后,另一个任务才能提交并执行
- 异步:异步提交,多个任务可以并发运行
-
阻塞与非阻塞
-
阻塞:
阻塞态
-
非阻塞:
就绪态
运行态
-
-
同步和异步、阻塞和非阻塞的区别
两者是不同的概念,不能混为一谈
-
创建进程的两种方式
-
p=Process(target=任务,args=(任务的参数,))
p.deamon=True #必须放在start()前,否则报错
p.start() # 向操作系统提交创建进程的任务
p.join() # 向操作系统发送请求,等所有子进程结束,父进程再结束
-
class MyProcess(Process):
def run(self):
任务的过程
p=Process(target=任务,args=(任务的参数,))
p.deamon=True #必须放在start()前,否则报错
p.start() # 向操作系统提交创建进程的任务
p.join() # 向操作系统发送请求,等所有子进程结束,父进程再结束
-
-
回收进程资源的两种条件
- 调用join让子进程结束后,主进程才能结束
- 主进程正常结束
僵尸进程与孤儿进程
僵尸进程:凡是子进程结束后,pid号还在,主进程意外死亡的都会变成僵尸进程
孤儿进程:凡是子进程没有结束,而主进程已经意外结束的,就是孤儿进程,操作系统的优化机制会回收这些进程
守护进程
只要父进程结束,所有的子进程都必须结束
互斥锁
将并发编程穿行,牺牲执行效率,保证数据安全
from multiprocessing import Lock
mutex=Lock()
# 加锁
mutex.acquire()
# 修改数据
mutex.reliease()
队列
-
FIFO队列:先进先出
from multiprocessing import Queue q=Queue(5) # 添加数据,若队列添加数据满了,则等待 q.put() # 添加数据,若队列添加数据满了,直接报错 q.put_nowait() # 获取队列中的数据 q.get() # 若队列中没有数据,会卡住等待 q.get_nowait() # 若队列中没有数据,会直接报错
堆栈
LIFO
IPC进程间通信
- 进程间的数据是个例的
- 队列可以让进程间实现通信
- 把一个程序放入队列中,另一个程序从队列中获取,实现进程间数据交互
生产者与消费者 模型
生产者:生产数据
消费者:使用数据
目的是为了保证供需平衡
通过队列实现,生产者将数据扔进队列中,消费者从队列中获取数据
可以保证一边生产一边消费
线程
-
什么是线程
-
进程:资源单位
-
线程:执行单位
- 创建进程时,会自带一个线程
一个进程下可以创建多个线程
-
-
线程的好处
节省资源开销
-
进程与线程的优缺点
- 进程
优点:多核下可以并行执行,计算密集型情况下提高效率
缺点:开销资源远高于线程
-
线程
优点:占用资源远小于进程,IO密集型情况下提高效率
缺点:无法利用多核优势
线程间数据是共享的
GIL全局解释锁
只有Cpython才有自带一个GIL全局解释器锁
-
GIl本质上是一个互斥锁
-
GIL是为了阻止同一个进程内多个线程同时执行(并行)
- 单个进程下的多个线程不发实现并行,但能实现并发
-
这把锁主要是因为Cpython的内存管理不是“线程安全”的
- 内存管理
- 垃圾回收机制
注意:多个线程过来执行,一旦遇到IO操作,就会立马释放GIL解释器锁,交给下一个先进来的线程
总结:GIL的存在就是为了保证线程安全的,保证数据安全
- 内存管理
多线程使用的好处
-
多线程:
IO密集型,提高效率
-
多进程:
计算密集型,提高效率
死锁现象
递归锁
解决死锁现象
信号量
信号量也是一把锁,可以让多个任务一起使用
互斥锁:
只能让一个任务使用
信号量:
可以让多个任务一起使用
sm= Semaphore(5)
线程队列
使用场景:
若线程间数据不安全情况下使用,为了保证线程间数据的安全
import queue
-
FIFO 先进先出队列
queue.Queue()
-
LIFO 后进后出队列
queue.LifoQueue()
-
优先级队列
-
根据数字大小判断,判断出队优先级
-
进队数据是无序的
queue.PriorityQueue()
-
event事件
可以控制线程的执行,让一些线程控制另一些线程的执行
e=Event()
-
线程1
e.set() # 给线程2发送信号,让他执行
-
线程2
e.wait() # 等待线程1的信号
进程池与线程池
为了控制进程/线程创建的数量,保证了硬件能正常运行
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
pool1=ProcessPoolExecutor() # 默认CPU个数
pool2=ThreadPoolExecutor() # CPU个数*5
# 将函数地址的执行结果,给回调函数
pool3.submit(函数地址,参数).add_done_callback(回调函数地址)
-
回调函数(必须接受一个参数res):
获取值
res2 =res.result()
协程
-
进程:资源单位
-
线程:执行单位
-
协程:单线程下实现并发,不是任何的单位,是程序员Yy出来的名字
-
单线程下实现并发
好处是节省资源,单线程《多线程《多进程
-
IO密集型下:
协程有优势
-
计算密集型下:
进程有优势
-
-
高并发
- 多进程+多线程+协程(Nginx)
协程的创建:
手动实现切换+保存状态:
-
yield
-
函数一直调用next()
会不停的切换
yield不能监听IO操作的任务
- gevent可以
gevent
from gevent import monkey
monkey.patch_all() # 设置监听所有IO
from gevent import spawn,joinall # 实现 切换+保存状态
#实现了单线程下并发
s1=spawn(任务一)
s2=spawn(任务二)
joinall([s1,s2])
IO模型
- 阻塞IO
- 非阻塞IO
- 多路复用IO
- 异步IO