网络编程与并发变编程复习
软件开发架构
c/s:
Client:客户端
Server:服务端
优点:
占用网路资源少,软件的使用的稳定
缺点:
服务端更新后,客户端也得跟着更新
需要使用多个软件,需要下载多个客户端
B/S:
Browser:浏览器(客户端)
Server:服务器
服务端与客户端作用:
服务端:24小时不间断提够服务
客户端:需要体验服务端时,再去连接服务端,并享受服务
一网络编程
1 互联网协议OSI七层协议
1)应用层
2)表示层
3)会话层
4)传输层
5)网络层
6)数据链路层
7)物理连接层
—物理连接层:基于电信号发送二进制数据
—数据链路层:规定好电信号的分组方式,必须有网卡
Mac地址:12位唯一的16进制字符串
前6位:厂商号
后6位:流水号
—以太网协议:在同一局域网内通信
单播:1对1对话
广播:多对多对话
—广播风暴
—不能跨局域网通信
—网络层
— ip:定位局域网的位置
—port:唯一标识一台计算机上一个应用程序
—ARP协议:将Mac地址获取,并解析程ip和port
— 传输层
—TCP特点:TCP协议称之为流失协议
若想要通信,必须建立连接,并建立双向通道
—三次握手,四次挥手
— 三次握手建连接
—客户端往服务端发送请求建立连接
—服务端要确认客户端的请求,并往客户端也发送请求建立连接
—客户端接收到服务端建立连接的请求,并返回确认
—建立双向通道
—双向通道:
—反馈机制
客户端往服务端发送请求获取数据,服务端务必返回数据,客户端确认收到
反则会反复发送,一直到某个时间段内,会停止发送
—四次挥手断连接
—客户端往服务端发送断开连接请求,服务端返回确认收到
—服务端再次发送断开连接请求
—客户端确认收到
—最终确认断开连接
—UDP
1)数据不安全
2)不需要建立双向通道
3)传输速度快
4)不会有粘包问题
5)客户端发送数据,不需要服务端确认收到,爱收不收
TCP与UDP的区别:
TCP:比喻成再打电话
UDP:比喻成发送短信
—应用层
—FTP
—HTTP:可以携带一堆数据
—HTTP + SSL
2 socket
socket用来’写套接字客户端与服务端的模块,内部帮我们封装好了7层协议需要做的事情
3 手撸socket套接字模板
服务端
import socket
server = socket.socket()
server.bind((ip,port))
server.listen(5) # 半连接池:可以接待6个客户端
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) # 接受消息
4 subprocess(了解)
用来通过代码往cmd创建一个管道,并且发送命令和接受cmd返回的结果
import subprocess
obj = subprocess.Popen(
'cmd命令',
shell=True,
# 接收正确结果
stdout=subprocess.PIPE,
# 接收错误结果
stderr=subprocess.PIPE
)
success = obj.stdout.read()
error = obj.stderr.read()
msg = success + error
5 粘包问题
1不能确认对方发送数据的大小
2在短时间内,间隔时间短,并且数据量小的情况下,默认将这些数据打包成一个
多次发送数据------------->一次发送
6 struct解决粘包问题
初级版:
i:4
可以将一个数据的长度打包成一个固定长度的报头
struct.pack('模式i', '原数据长度')
data = 'gsjfdfytaytsf'
headers = struct.pack('i' ,len(data)) # 打包成报头
data = struct.unpack('i' ,headers)[0] # 解包获取数据的真实长度
注意:以什么方式打包,必须以什么方式解包
升级版:
先将数据存放到字典中,将字典打包发送发送过去
—字典的好处:
—真实数据长度
—文件的描述信息
—发送的数据更小
dic = {'data_len':10000000000000000000000000444444444444444444,
文件的描述信息}
7 上传大文件数据
客户端
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)
10 socketserver(现阶段,了解)
—可以支持并发
import socketserver
定义类
TCP:必须继承BaseRequestHandler类
class MyTcpServer(socketserver.BaseRequestHandler):
—handle
#内部实现了
server = socket.socket()
server.bind(('127.0.0.1,8080'))
server.listen(5) --------
while True:
conn,addr = server.accept()
print(addr)
必须重写父类的handle,当客户端连接时回调用该方法
def handle(self):
print(self.client_adddress)
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 sa e:
print(e)
break
TCP:
SOCK_STREAM
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)
二 并发编程
12 多道技术
单道
多道:切换+保存状态
—空间上的复用
支持多个程序使用
—时间上的复用
遇到io操作就会切换程序
程序占用cpu时间过长切换
13 并行与并发
并发:看起来像同时运行:多道技术
并行:真正意义上的同时运行,多核下
14 进程
进程是资源单位,每创建一个进程都会生成一个名称空间,占用内存资源
—程序与进程
程序就是一堆代码
进程就是一堆代码运行的过程
—进程调度
—时间片轮转法
10个进程,将固定时间,等分程10份时间片,分配给每一个进程
—分级反馈队列
1级别
2级别
3级别
—进程的三种状态:
—就绪态:创建多个进程,必须要排队准备运行
—运行态:进程开始运行,1结束 2阻塞
—阻塞态:当运行态遇到io操作,就会进入阻塞态
——同步与异步:提交任务的方式
同步:同步提交,串行,一个任务结束后,另一个任务才能提交并执行
异步:异步提交,多个任务可以并发执行
- 阻塞与非阻塞
- 阻塞:
阻塞态
- 非阻塞:
就绪态
运行态
- 同步和异步、阻塞和非阻塞的区别。
两者是不同的概念,不能混为一谈.态
—创建进程的两种方式
p = Process(target = 任务,args = (任务的参数,))
p.daemon = True # 必须放在start()前,否则报错
p.start() # 向操作系统提交创建进程的任务
p.join # 向操作系统发送请求,等所有子进程结束,主进程在结束
二 :
class Myprocess(Process):
def run(self): # self === p
任务过程
p = Myprocess()
p.daemon = True # 必须放在start()前,否则报错
p.start() # 向操作系统提交创建进程的任务
p.join() # 向操作系统发送请求,等所有子进程结束,主进程在结束
—回收进程资源的两种条件:
—调用join让子进程结束后,主进程才能结束
—主进程正常结束
15僵尸进程与孤儿进程(了解)
僵尸进程:凡是子进程结束后,pid号还在,主进程意外死亡,没法给子进程回收资源
—每个子进程结束后,都会变成僵尸进程(pid)
孤儿进程:凡是子进程没有结束,但是主进程意外死亡,操作系统优化机制(孤儿院),
会将没有主进程,并且存活的进程,在该进程结束后回收资源
16 守护进程
只要主进程结束,所有的子进程都必须结束
17 互斥锁
将并发变成串行,牺牲执行效率,保证数据安全
from multiprocessing iport Lock
mutex = Loock()
mutex.acquire() # 加锁
mutux.release() # 修改数据
18 队列
FIFO队列:先进先出
from multiprocessing import Queue
q = Queue(5)
q.put() # 添加数据,若队列添加数据满了,则等待
q.put_nowait() # 添加数据,若队列添加数据满了,直接报错
# 获取队列中的数据
q.get() # 若队列中没有数据,会卡主等待
q.get_nowait() # 若队列中没有数据,会直接=报错
19 堆栈
LIFO
20 IPC进程间通信
—进程间数据是隔离的
—队列可以让进程间通信
—把一个程序放入队列中,另一个程序从队列中获取,实现进程间数据交互
21 生产者与消费者
生产者:生产数据
消费者:使用数据
为了保证,供需平衡
22 线程
—什么是线程
进程:资源单位
线程:执行单位
—创建进程时,会自带一个线程
一个进程下可以创建多个线程
—使用线程的好处
节省资源的开销
— 进程与线程优缺点:
—进程:
优点:
—多核下可以并行执行
—计算密集型下提高效率
缺点:
—开销资源远高于线程
—线程:
优点:
—占用资源远比进程小
—io密集型下提高效率
缺点:
—无法利用多核优势
23 线程间数据是共享的
—画图
24 GIL全局解释器锁
—只有cpython才有自带一个GIL全局解释器锁
1 GIL本质上就是一个互斥锁
2 GIL为了阻止同一个进程内多个线程同时执行(并行)
—单个进程下的多线程无法实现并行,但能实现并发
3 这把锁主要是因为cpython的内存管理不是“线程安全”的
—内存管理
—垃圾回收机制
注意:多个线程过来执行,一旦遇到io操作,机会立马释放GIL解释器锁,交给下一个先进来的线程
总结:GIL存在的目的就是为了保证线程阿安全的,保证数据安全
25 多线程使用的好处
—多线程:
io密集型,提高效率
—多进程
计算密集型,提高效率
26 死锁现象(了解)
画图
27 递归锁(了解)
解决死锁现象
mutex = Lock() # 只能引用1次
mutex1, mutex2 = RLock() # 可以引用多次
+1, 只要这把锁计数为0释放该锁, 让下一个人使用, 就不会出现死锁现象.
28 信号量
信号量也是一把锁,可以让多个任务一起使用
互斥锁:
只能让一个任务使用
信号量:
可以让多个任务一起使用
sm = Semaphore(5) 可以让5个任务使用
29 线程队列
使用场景:
若线程间数据不安全情况下使用线程队列,为了保证线程间数据的安全
import queue
—FIFO:先进先出队列
queue.Queue()
—LIFO:后进先出队列
—根据数字大小判断,判断出队友的优先级
—队列数据是无序的
queue.priorityQueue()
30.event事件
可以控制线程的执行,让一些线程控制另一些线程的执行.
e = Event()
- 线程1
e.set() # 给线程2发送信号,让他执行
- 线程2
e.wait() # 等待线程1的信号
31.进程池与线程池
为了控制进程/线程创建的数量,保证了硬件能正常运行.
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
pool1 = ProcessPoolExecutor() # 默认CPU个数
pool2 = ThreadPoolExecutor() # CPU个数 * 5
pool3 = ProcessPoolExecutor(100) # 100个
pool4 = ThreadPoolExecutor(200) # 200个
# 将函数地址的执行结果,给回调函数
pool4.submit(函数地址, 参数).add_done_callback(回调函数地址)
- 回调函数(必须接收一个参数res):
# 获取值
res2 = res.result()
32.协程
- 进程: 资源单位
- 线程: 执行单位
- 协程: 单线程下实现并发, 不是任何的单位,是程序员YY出来的名字.
- 单线程下实现并发
好处是节省资源, 单线程 < 多线程 < 多进程
- IO密集型下:
协程有优势
- 计算密集型下:
进程有优势
- 高并发:
- 多进程 + 多线程 + 协程 (Nginx)
协程的创建:
手动实现切换 + 保存状态:
- yield
- 函数一直在调用next()
会不停地切换
yield不能监听IO操作的任务
- gevent来实现监听IO操作
33.gevent
pip3 install gevent
from gevent import monkey
monkey.patch_all() # 设置监听所有IO
from gevent import spawn, joinall # 实现 切换 + 保存状态
- 实现了单线程下实现并发
s1 = spawn(任务1)
s2 = spawn(任务2)
joinall([s1, s2])
34.IO模型(了解)
- 阻塞IO
- 非阻塞IO
- 多路复用IO
- 异步IO