网络编程+并发编程
架构:B/S 和 C/S
C/S:充分发挥PC机的性能
B/S:统一了应用的接口,隶属于CS架构
OSI模型 七层:表示层,会话层,应用层,传输层,网络层,数据链路层,物理层。
我们用五层
应用层 http协议 https协议 ftp协议 snmp/pop3/stmp/dns
传输层 tcp udp 协议 (四层交换机)
网络层 IP协议/icmp协议 路由器 (三层交换机)
数据链路层 arp协议,交换机 (二层交换机)
物理层 网卡,双绞线 (传输电信号)
arp/rarp
Arp协议(地址解析协议):通过目标ip地址获取目标mac地址
arp涉及到的物理设备:二层交换机
rarp:通过mac地址找ip地址
物理地址:mac地址全球唯一,每个电脑的网卡上
Ip地址:
四位点分十进制 三十二位点分二进制
ipv4/ipv6 : 4位点分十进制,6为冒分16进制
交换机的通信方式:
单播:点对点
组播:点对多
广播:向多个pc端发送数据包
交换机和路由器的区别?
交换机是组织局域网的,经过内部处理解析数据,将数据已点对点,点对多的方式发送给目标
路由器是跨网段的数据传输,路由出网络传输的最佳路径
socket 介于应用层和传输层的socket抽象层
Tcp:
可靠、面向连接的双全工通信 无边界的字节流 慢 占用系统中的连接资源
1 全双工的 面向字节流的(无边界的字节流,多条tcp数据之间没有边界)
2 可靠,不是不安全,更多的是保证数据的网址性
3 (面相链接,必须要先建立连接再进行同学)三次握手,四次挥手
4 慢:通信 建立/断开连接 保证数据的完整性的机制
5 占用系统资源(核心要解决的问题)
在不使用任何异步机制(进程/线程/协程),在阻塞io模型中,一个server端只能和一个client端相连
conn1,conn2,conn3
sk = socket.socket()
sk.setblocking(False) # 异步的概念
conn_lst = []
try:
conn,addr = sk.accept()
conn_lst.append(conn)
except BlockingIOError:
for conn in conn_lst:
try:
conn1.recv() # 有数据在缓存里面就接收,没有数据就报错
except BlockingIOError:pass
Udp:
无连接的,面向数据报,面向数据报的,不可靠,速度快,能传输的数据长度有限。可以同时和多个客户端通信
不会粘包、速度快、可以和任意多个客户端进行互相通信
容易丢失数据、传输的数据长度有限
三次握手:(必须是客户点先发起)
1.客户端发起SYN请求链接服务端
2.服务端收到请求,向客户端发送可以连接的请求 ACK
3.客服端收到连接,连接成功 ACK
四次挥手:(客户端和服务端都可以发起)
1.客户端发起断开连接的FIN请求
2.服务端收到请求,然后准备断开连接的一些事物
3.服务端发送请求,我已经准备完毕,可以断开了
4.客户端收到请求,断开连接。
为什么挥手是四次?
当我仍然有数据发送的时候,还可以发送!
为什么会发生黏包:因为有缓存机制,连续发或者连续收。(在数据传输过程中,接收数据端接收数据时不知道该如何接收,造成的数据混乱现象)
合包机制:nagle算法 — 发数据的时候一点一点发,但是接收的时候会一次收到。
拆包机制:发送的数据太大,分开发送,接收的时候,最后不能刚刚接完,就会发生黏包。
都是发生在发送端。
所有的数据都会在接收端的缓存区中被合在一起,如果没有及时获取信息,那么消息会粘在一起。
发生在接收端
如何解决黏包?
自定义表头:用struct模块,事先将要发送文件的大小存下来,发送 过去,然后按着指定数据开始接收。
Struct 模块 可以将一个整数,封装为一个指定(一般为4)字节数的数字,这样接收端就可以指定大小接收这个数字。
各种网络设备
传输层 四次防火墙/四层防火墙/四层交换机
网络层 路由器 防火墙 三层交换机
数据链路层 交换机 网卡
物理层 网线
socketserver(threading/socket/selector)
帮助你完成server端的并发效果模块
socket在应用层和传输层之间,socket是一组封装了后四层协议数据拼接的接口。
操作系统
Dos系统 #单用户单任务
Windows系统 #单用户多任务(早起的Windows)
Unix系统 #多用户多任务
操作系统的作用:1)封装接口,让用户方便使用 2)对系统资源的分配与调度
计算机的五大组成部分:运算器。控制器。存储器。输入设备。输出设备。
编程语言发展史:机器语言,汇编语言,高级语言
并行:(两件事或者多件事)同一时间点,同时执行 (多个CPU)
并发:(两件事或者多件事)同一时间间隔,交替执行
阻塞:程序因为类似IO等待、等待时间等导致无法继续执行
非阻塞:程序遇到类似于IO操作时,不再阻塞等待,如果没有及时的处理IO,就会报错或者跳过等待其他操作,
同步:某一个任务的执行必须依赖另一个任务的返回结果 -任务(当前所在的代码和另一个独立的任务)
异步:一个任务的执行,不需要依赖另一个任务的返回,只需要告诉另一个任务一声
### p.start() 只是向操作系统发了一个启动的命令,至于操作系统什么时候启动我不管,这就是一个典型的异步非阻塞
进程:cpu资源分配的最小单位 进程由三部分组成:代码段,数据段,PCB(进程控制块)
资源独立,能利用多核,占用操作系统的资源大,有数据安全问题
线程:cpu资源调度的最小单位 线程由三部分组成:代码段,数据段,TCB(线程控制块)
资源共享,能利用多核,占用操作系统的资源相对少,有数据安全问题
协程:操作系统不可见
资源共享,不能利用多核,本质是一条线程,占用操作系统的资源就 相当于一条线程,不存在数据安全问题
进程:
资源独立
线程
处理并发 (同时处理多个任务)
GIL锁 CPython解释器中管理线程的机制
保证多个线程,只有一个线程在同一时间点能访问cpu
CPython解释器导致了我们不能充分利用多线程来访问多个cpu
多进程:帮助我们Cpython解释器下利用多核
协程
在单线程中,有n个任务,这n个任务如果是同步执行,那么所有的io时间是累加在一起的
如果这n个任务能够共享所有的io时间
完成一个任务,遇到io之后能够切换到另一个任务执行,所有的io操作的时间还能做其他任务的执行
这样就能通过一条线程完成多个任务。
协程:切换也是有开销的,切换的开销就和函数调用的开销是一样的。
asyncio 内置的异步模块 (进程、线程、协程)
gevent 扩展的协程模块
aiohttp 扩展模块 帮助我们利用协程完成http请求的模块
数据安全
多个进程操作一个文件,加锁
多个线程操作一个全局变量,加锁
如果是+= *= /= -= 都存在数据不安全的问题
append remove pop extend 不存在数据不安全的问题
协程 永远不会数据不安全,因为协程是由程序员控制的,而程序员控制的只能是代码,而不是CPU指令
import dis
dis.dis() # 查看CPU指令
进程的三大基本状态:
就绪状态:已经获得运行所需的所有资源,除CPU
执行状态:已经获得所有资源包括CPU,处于正在运行
阻塞状态:因为各种原因,进程放弃了CPU,导致进程无法继续执行,此时进程处于内存中,继续等待获取CPU的一种状态。?
进程学的东西: multiprocessing
1)Process模块
线程的创建
1)直接创建
p = Process(target = func, args = (元组形式, 为func所传的参数)) #实例化进程对象
2)继承 (Process)
多线程的开启 1)for循环 2)多个对象实例化
方法:
start() #开启进程
join() #感知进程的结束,异步变同步
is_alive() #判断进程是否存活
terminate() #杀死一个进程
属性:
name #获取进程名
pid #获取进程号
daemon = True #守护进程
守护进程的特点:
#当主进程代码结束后,守护进程随主进程结束
#守护进程不能再创建子进程
#守护进程必须在start之前
2) 锁 Lock模块 (互斥锁/同步锁) 只能acquire一次
lock = Lock() #实例化一个锁对象
lock.acquire() #上锁
lock.release() #解锁
RLock模块 (递归锁)
递归锁可以同时acquire多次,但是必须acquire几次就必须release几次。都在就会陷入死锁状态
死锁
典型的例子:科学家吃面 (一个人拿着面,一个人拿着叉子,到最后谁也吃不上面)
信号量 Semaphore模块
sem = Semaphore(int数据类型) #可以指定有多少线程可以同时拿到锁
sem.acquire() #需要上锁将这些数据锁住
sem.release()
事件 Event模块
e = Event()
e.wait() #根据is_set()的状态来决定,自身的阻塞状态 如果is_set()为False则为阻塞,如果is_set()为True则为非阻塞
e.is_set() #默认为False,
e.set() #将is_set()的状态变为True
e.clear() #将is_set()的状态变为False
典型例子:红绿灯事件
3)进程间通信(IPC)
Queue模块 #队列 先进先出 First in first out
q = Queue() #创建队列对象(可以指定大小)
q.put() #向队列中添加元素(如果队列以满处于阻塞状态,只有当队列不满才可以继续添加)
q.get() #取出队列中元素(如果队列中元素为空处于阻塞状态,只有对列中有元素才可以继续取出)
q.full() #判断一个对列是否 已满
q.empty() #判断一个对列是否为空
q.put_nowait() #不阻塞,如果可以继续往队列中放数据就继续放,不能放就报错
q.get_nowait() #不阻塞,如果有数据就直接获取,没有数据就报错
JoinableQueue()模块
q = JoinableQueue()
#继承了Queue模块,但是新增了两个方法
q.task_done() #统计对列q有多少个元素被拿走(拿走一个数据就给join返回一个结果),通常与q.get()在一起用 用于生产者
q.join() #感知一个对列的数据被全部执行完毕 与q.put()在一起用 用于消费着
队列 = 管道 + 锁
重点:生产者消费着模型 应用场景:爬虫/高并发的web程序server
Pipe模块 #管道 (本身是不安全的) (双全工)
p = Pipe()
conn1, conn2 = Pipe()
管道是不安全的
管道是用于多进程之间通信的一种方式
如果在单进程中使用管道,那么就是conn1收数据,conn2发数据
如果是conn1发数据,那么conn2收数据
如果在多进程中使用管道,那么就必须是父进程中用con1收,子进程中使用conn2发
父进程使用conn1发,子进程中使用conn2收
父进程使用conn2收,子进程中使用conn1发
父进程使用conn2发,子进程中使用conn1收
在管道中有一个著名的错误叫做EOFERrror。
是指:父进程中如果关闭了发送端,子进程还继续接受数据,那么就会引发EOFError
4)数据共享 Manager模块 Value模块
men = Manager()
(1)
m.list(列表数据)
m.dict(字典数据)
(2)
with Manager() as m:
……
5)进程池 Pool模块
p = Pool(os.cup_count() +1) #开启多进程之后,每次处理数据只能指定个数个处理
p.close()
p.join() #close在join之前
方法:
map(func, itreable) #异步处理 itreable ,有返回值,返回值是,每一个func的返回值组成的列表, 自带close和join
apply(func, args) #同步处理 有返回值,返回值为func的返回值 不需要加close和join
apply_async(func, args, callback) #异步处理,有返回值,返回一个对象,这个对象有get方法,可以获取func的返回值
#这个get只能一个一个获取,以我们一般处理完所有线程后再获取数据
#func的返回值可以作为参数传给callback这个回调函数。回调函数在主进程中执行
apply函数中的所有进程都为普通进程
apply_async函数中的所有进程都为守护进程
线程学的东西:threading
GIL:全局解释器锁(只有CPython才有)
锁的是线程:同一时间只允许一个线程访问CPU #(没有真正的并行)
1)Thread模块
线程的创建
1)t = Thresd(target= func. args = (元组,为func所传的参数)) 实例化线程对象
2)继承
多线程的创建
1)for 循环
2)直接实例化多个对象
2) 锁
Lock #互斥锁(同步锁)
RLock #递归锁
死锁 #死锁
信号量 Semaphore模块
sem = Semaphore(int数据类型) #可以指定有多少线程可以同时拿到锁
sem.acquire() #需要上锁将这些数据锁住
sem.release()
事件 Event模块
e = Event()
e.wait() #根据is_set()的状态来决定,自身的阻塞状态 如果is_set()为False则为阻塞,如果is_set()为True则为非阻塞
e.is_set() #默认为False,
e.set() #将is_set()的状态变为True
e.clear() #将is_set()的状态变为False
3)条件 Condition模块
条件是让程序员自行去调度线程的一个机制
方法:
acquire()
release()
wait() #让线程阻塞住
notify(int数据类型) #是指给wait发一个信号,让wait变成不阻塞 #int数据类型,是指你要给多少wai发信号
4)定时器 Timer模块
创建:Timer(time, func)
#time:睡眠时间,以秒为单位
#func:睡眠之后,需要执行的任务
5)线程池
进程与线程的区别:
进程资源分配的基本单位,线程是cpu调度的基本单位。
线程不可以自己独立拥有资源。线程的执行,必须依赖于所属进程中的资源。
进程中必须至少应该有一个线程。
线程和进程的比较:
1)cpu切换进程比切换线程慢很多,在python中如果IO操作过多的话,使用线程最好
2)在同一个进程内,所有线程共享这个进程pid,也就是说所有线程共享所属进程的所有资源和内存地址
3)在同一个进程内,所有线程共享该进程中的全局变量
4)因为GIL锁的存在,在CPython中,没有真正的线程并行。但是有真正的多进程并行
当你的任务是计算密集的情况下,使用多进程好。
总结:在CPython中,IO密集用多线程,计算密集用多线程。
5)关于守护线程和守护进程的事情:(注意:代码执行结束,并不代表程序结束)
守护进程:要么自己正常结束,要么根据父进程的代码执行结束而结束
守护线程:要么自己正常结束,要么根据父线程的执行结束而结束(会等其余子线程运行结束)