软件开发架构
C/S:
Client:客户端
Server:服务端
优点:占用网络资源少,软件的使用稳定
缺点:服务端更新后,客户端也得跟着更新
需要使用多个软件,需要下载多个客户端
B/S
Browser:浏览器(客户端)
Server:服务端
服务端与客户端作用:
服务端:24小时不间断提供服务
客户端:需要体验服务端时,在去连接服务端,并享受
网络编程
互联网七层协议
应用层
表示层
会话层
传输层
网络层
数据链路层
物理连接层
物理连接层
基于电信号发送二进制数据
数据链路层
规定好电信号的分组方式
必须要有一块网卡
mac地址:
12位唯一的16进制字符串
前6位:厂商号
后6位:流水号
以太网协议:
在同一个局域网内通信
单播
一对一
广播
多对多
广播风暴
不能跨局域网通信
网络层
IP:定位局域网
port:唯一标识一台计算机上的一个应用程序
arp协议:将mac地址获取,并解析成IP和port
运输层
TCP
特点:TCP协议称之为流式协议
若想要通信,必须建立连接,并建立双向通道
三次握手,四次挥手
三次握手建连接
客户端往服务端发送请求连接通道
服务端要确认客户端的请求,并往客户端也发送请求建立通道
客户端接收到服务端建立连接请求,并返回确认
建立双向通道
双向通道
反馈机制
客户端往服务端发送QQ群获取数据,服务端务必返回数据,客户端确认收到
否则会反复发送,一直到某个时间段内,会停止发送
四次挥手
C往S发送断开连接请求,S返回确认收到
S需要再次发送断开连接请求
C返回确认收到
最终确认断开连接
UDP
数据不安全
不需要建立双向通道
传输速度快
不会有粘包问题
客户端发送数据,不需要服务端确认收到
TCP与UDP的区别
TCP:好比打电话
UDP:好比发短信
应用层
ftp
http
可以携带一堆数据
http+ssl
socket
socket用来写套接字客户端与服务端的模块,内部帮我们封装好了7层协议需要做的事情
socket套接字模版
服务端
import socket server = socket.socket() server.bind(('127.0.0.1',9527)) #绑定IP和端口 server.listen(5) #半连接池:执行1人等待5人(共6人) while True: #监听数据 conn,addr = server.accept() print(addr) #打印监听到的客户端地址 while True: try: #接收数据 data = conn.recv(1024) if data == 0: continue #打印接收的数据 print(data.decode('utf-8')) #发送数据 conn.send(data) except Exception as e: print(e) break
客户端
import socket client = socket.socket() client.connect(('127.0.0.1',9527)) #连接IP和端口 while True: msg = input('输入信息:') #发送数据 client.send(msg.encode('utf-8')) #接收数据 data = client.recv(1024) print(data.decode('utf-8'))
subproess(了解)
用来通过代码往cmd创建一个管道,并且发送命令和接收cmd返回的结果
import subprocess obj = subprocess.Popen( 'cmd命令', shell = True, #接收正确结果 stdout = subprocess.PIPE, #接收错误结果 stderr = subprocess.PIPE ) #接收的正确消息 success = obj.stderr.read() #接收的错误消息 error = obj.stderr.read() #两个消息的拼接 msg = success + error
粘包问题
不能确定对方发送数据的大小
在短时间内,发送时间间隔短,且数据量小的数据,就会默认把数据打包发送(本该多次发送的数据,变一次性打包发送了)
struct解决粘包问题
初级版
struct.pack('i模式','原数据的长度') #i可以将一个数据的长度打包成一个固定长度(4)的报头 data = '1234567891234556677' #打包成报头 headers = struct.pack('i',len(data)) #解包获取数据真实长度 data = struct.unpack('i',headers)[0]
注意:以什么方式打包,必须以什么方式解包
服务端
import socket import struct server = socket.socket() server.bind(('127.0.0.1',9527)) #绑定IP和端口 server.listen(5) #半连接池:执行1人等待5人(共6人) while True: #监听数据 conn,addr = server.accept() print(addr) #打印监听到的客户端地址 while True: try: #先接收数据报头 headers = conn.recv(4) #解报头 data_len = struct.unpack('i',headers)[0] #在接收真实数据 pack_data = conn.recv(data_len) if pack_data == 0: continue pack_data = pack_data.decode('utf-8') #打印接收的数据 print(pack_data) pack_data2 = pack_data #发送数据 headers2 = struct.pack('i',len(pack_data2.decode('utf-8'))) conn.send(headers2) conn.send(pack_data2.encode('utf-8')) except Exception as e: print(e) break conn.close()
客户端
import socket import struct client = socket.socket() client.connect(('127.0.0.1',9527)) #连接IP和端口 while True: msg = input('输入信息:') #获取数据报头 headers = struct.pack('i',len(msg.encode('utf-8'))) #先发送报头 client.send(headers) #在发送真实数据 client.send(msg.encode('utf-8')) #接收报头 headers2 = client.recv(4) #获取真实数据长度 data_len = struct.unpack('i',headers2)[0] #接收数据 data = client.recv(data_len) #打印接收数据 print(data.decode('utf-8'))
升级版
先将数据存放到字典中,将字典打包发送过去
字典好处:
真实数据长度
文件的描述信息
发送的数据更小
上传大文件数据
客户端
dic = { 文件大小, 文件名 } with open(文件名,'rb')as fr: for line in fr: client.send(line)
服务端
dic = { 文件大小, 文件名 } init_recv = 0 with open(文件名,'wb')as fw: while init_recv < 文件大小: data = conn.recv(1024) fw.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: #接收消息 #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
TCP
SOCK_DGRAM conn.recv()
UDP模版
SOCK_DGRAM server.recvfrom()
服务端
import socket server = socket.socket(type = socket.SOCK_DGRAM) server.bind((ip,port)) data,addr = server.recvfrom(1024) server.sendto(data,addr)
客户端
import socket client = socket.socket(type = socket.SOCK_DGRAM) ip_port = (ip,port) client.sendto(data,ip_port) data,_ = client.recvfrom(1024) # _ 接收的是无关变量值(约定俗成当成无) print(data)
并发编程
多道技术
单道
多个用户使用CPU时是串联运行的
必须让上一个程序运行完,才能运行下一个程序
多道
切换+保存状态
空间上的复用
支持多个程序使用
时间上的复用
遇见IO操作就会切换程序
程序占用CPU时间过长也会切换程序
并发与并行
并发:看起来像同时运行:多道技术
并行:真正意义上的同时运行:多核状态
进程
进程是资源单位,每创建一个进程都会生成一个名称空间,占用内存资源
程序与进程
程序就是一堆代码
进程就是一堆代码运行的过程
进程调度
时间片轮转法
10个进程,将固定时间,平均分成10份,分配给每一个进程
分级反馈
1级别
2级别
3级别
……
数字越低级别越高,1级别等级最高
进程的三种状态
就绪态
创建多个进程,必须要排队准备运行
运行态
进程开始运行后,要么结束,要么阻塞
阻塞态
当运行态遇见IO操作,就会进入阻塞态
同步与异步
提交任务的方式
同步:同步提交,串行,一个任务结束后,下一个任务才能开始
异步:异步提交,多个任务可以并发运行
阻塞与非阻塞
阻塞:阻塞态
非阻塞:就绪态和运行态
同步和异步,阻塞和非阻塞的区别
两者不是同一个概念,不能混为一谈
创建进程的两种方式
一、
p = Process(target=任务,args=(任务的参数,)) #任务的参数是元组,必须要加逗号 p.daemon = True #必须放在strat()前,否则会报错 p.start() #向操作系统提交创建进程的任务 p.join() #向操作系统发送请求,等所有子进程结束,父进程在结束
二、
class MyProcess(Process): def run(self): #self == p 任务的过程 p = MyProcess() p.daemon = True #必须放在start()前,否则报错 p.start() #向操作系统提交创建进程的任务 p.join() #向操作系统发送请求,等所有子进程结束后,父进程在结束
回收进程资源的两种条件
调用join让子进程结束后,主进程才能结束
主进程正常结束
僵尸进程与孤儿进程(了解)
僵尸进程
凡是子进程结束后,PID号还在,主进程意外死亡,没法给子进程回收资源
每个子进程结束后,都会变成僵尸进程(PID)
孤儿进程
凡是子进程没有结束,但是主进程意外死亡,操作系统优化机制(孤儿院),会将没有主进程,并且存活的进程,在该进程结束后回收资源
守护进程
只要父进程结束,所有子进程都必须结束
互斥锁
将并发变成串行,牺牲了执行效率,保证了数据的安全
from multiprocessing import Lock mutex = Lock() #加锁 mutex.acquire() #修改数据 mutex.release()
队列
FIFO队列:先进先出队列
from multiprocessing import Queue #队列数为5 q = Queue(5) #添加数据,若队列满了,则等待 q.put() #添加数据,若队列满了,直接报错 q.put_nowait() #获取队列中的数据 #若队列中没有数据,会卡住等待 q.get() #若队列中没有数据,则报错 q.get_nowait()
堆栈
LIFO:后进先出队列
IPC进程间通信
进程的数据是隔离的
队列可以让进程间进行通信
把一个程序放入队列中,另一个程序从队列中获取,实现进程间的数据交互
生产者和消费者
生产者:生产数据
消费者:使用数据
为了保证,供需平衡
通过队列实现:生产者将数据存入队列,消费者从队列中获取数据
线程
什么是线程
进程
资源单位
线程
执行单位
创建一个进程,就会自带一个线程
一个进程下可以创建多个线程
线程优点
减少CPU的内存占用
进程与线程的优缺点
进程
优点:
多核下可以实现并行
计算密集型情况下可以提高效率
缺点:
占用CPU内存资源大
线程
优点:
占用CPU内存资源小
IO密集型情况下可以提高效率
缺点:
无法利用多核优势
同一进程内线程间的数据是共享的
GIL全局解释器锁
只有Cpython才有自带一个GIL全局解释器锁
GIL本质上就是一个互斥锁
GIL是为了阻止同一个进程内多个线程同时执行(并行)
单个进程下的多个线程无法实现并行,但能实现并发
GIL锁主要是因为Cpython的内存管理不是"线程安全"的
内存管理
垃圾回收机制
注意:多个线程过来执行,一旦遇到IO操作,就会立马释放GIL解释器锁,交给下一个先进来的线程
总结:GIL的存在就是为了保证线程安全的,保证数据安全
多线程优点
多线程
IO密集型,提高效率
多进程
计算密集型,提高效率
死锁现象(了解)
当A锁钥匙在B锁内,B锁钥匙在A锁内时,就会出现死锁现象
递归锁
解决死锁问题
mutex = Lock() #只能引用一次 mutex1,mutex2 = RLock() #可以引用多次 只要锁的引用计数为0就释放该锁,让下一个让使用,就不会出现死锁现象
信号量
信号量也是一把锁,可以让多个任务一起执行
互斥锁
只能让一个任务使用
信号量
可以让多个任务一起使用
sm = Semaphore(5) #可以让5个任务使用
线程队列
使用场景
线程间数据不安全情况下使用线程队列,为了保证线程间的数据安全
import queue #FIFO:先进先出队列 queque.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 = ProcessPoolExecutor(100) #进程个数上限为100 pool4 = ThreadPoolExecutor(100) #线程个数上限为100 #将函数地址的执行结果,给回调函数 pool4.submit(函数地址,参数).add_done_callback(回调函数地址) 回调函数(必须接收一个参数res): #获取值 res2 = res.result()
协程
进程:资源单位
线程:执行单位
协程:单线程下实现并发,不是任何的单位,是程序员自己取的名字
单线程下实现并发,节省资源,单线程<多线程<多进程
IO密集型:协程有优势
计算密集型:进程有优势
高并发:多进程+多线程+协程
协程的创建
手动实现切换+保存状态
yield:保存状态
函数一直调用next()会不停的切换
yield不能监听IO操作的任务
gevent来实现监听IO操作
gevent
pip install gevent from gevent import monkey monkey.patch_all() #设置监听所有IO from gevent import spawn,joinall #实现切换+保存状态 #实现单线程下实现并发 s1 = spawn(任务1) s2 = spawn(任务2) joinall([s1,s2])
IO模型(了解)
阻塞IO
非阻塞IO
多路复用IO
异步IO