网络编程+并发编程总结
网络编程+并发编程 架构: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)关于守护线程和守护进程的事情:(注意:代码执行结束,并不代表程序结束) 守护进程:要么自己正常结束,要么根据父进程的代码执行结束而结束 守护线程:要么自己正常结束,要么根据父线程的执行结束而结束(会等其余子线程运行结束)