【一】网络编程
互联网的本质就是一些网络协议
【1】网络开发架构
( 1 ) C / S 架构
C : client (客户端)
S: server (服务端)
APP - 就是服务端
C/S 架构通过客户端软件和服务器之间的交互,实现了前端界面和后端业务逻辑的分离,提供了一种稳定、可靠的架构模式。
( 2 ) B / S 架构
B : brower(网页端)
S: server (服务端)
打开QQ音乐----> 向QQ音乐发起请求
将资源在本地的浏览器进行渲染
B/S 架构通过浏览器和服务器之间的交互,实现了前端界面和后端业务逻辑的分离,提供了一种灵活、可扩展的架构模式。
( 3 ) B / S 架构 和 C / S机构 的优缺点
B / S 架构
-
维护和升级方式简单 : 我只需要更新服务器的资源就可以了
-
成本也比较低, 选择性也很多
-
服务数据太多了, 负载严重
C / S 架构
-
服务器压力有所减轻, 把一部分资源分跟客户端保存
-
数据的存储和管理比较透明,APP逆向
-
成本较高,维护费力
C / S 架构是主流趋势
【2】互联网协议
(1)什么是网络编程
-
网络编程的研究前提就是基于互联网
-
网络编程就是基于互联网写代码
(2)网络编程的媒介
-
网络编程的媒介是计算机网络。
-
而网络协议和网络编程框架和库则是实现网络编程的基础。
【3】OSI七层协议
网络通信协议标准
-
应用层:负责处理特定的应用程序协议,包括电子邮件、文件传输、远程登录。
-
表示层:负责数据的表示和编码,在不同系统中传输前将数据进行转换和压缩等操作。
-
会话层:负责建立、管理和终止会话,包括数据交换的同步和检查点的创建与恢复等功能。
-
传输层:负责端到端的数据传输,提供可靠数据传输服务,包括流量控制、拥塞控制、错误恢复和数据重传等功能。
-
网络层:负责实现不同计算机之间的数据包转发和路由器选择,并提供逻辑寻址和拥塞控制等功能。
-
数据链路层:通过帧来传输数据,负责数据的可靠传输。
-
物理层:负责在物理媒介上传输比特流,包括传输介质、物理接口和电气特征
优点:
-
易于理解和实现:由于OSI模型具有清晰的分层结构,因为易于理解和实现
-
可扩展性好:由于这个体系结构明确地定义了不同的层次和协议,因此它具有很好的可扩展性,可以随时添加新的协议和服务。
-
提高了协议的互操作性:由于OSI模型对不同协议提供了明确的指导,所以它可以促进不同厂商和供应商的设备之间的互操作性。
-
降低了复杂性:与其他体系结构相比,OSI模型具有更少的复杂性,因为每个层次的功能都是固定的。
缺点:
-
过于理论化:OSI模型过于理论化,导致它在实际实现中的使用受到限制。
-
缺乏灵活性:由于OSI模型在每一层都定义了特定的功能,因此缺乏灵活性,不能完全适应不同网络环境的要求。
-
太过繁琐:由于OSI模型分为7层,因此在实际应用中会导致协议的复杂性和资源消耗增加。
-
实现代价高:由于OSI模型需要使用大量的协议和设备来实现各个层次之间的通信,因此实现代价很高。
【4】TCP五层协议
-
应用层:负责处理网络应用程序之间的通信。
-
传输层:传输层协议提供端到端的同学协议,确保数据在网络上可靠传输。
-
网络层:处理数据在网络中的传输和路由。
-
数据链路层:在物理网络上提供了可靠的数据传输。它将数据包封装成帧,通过物理介质进行传输。
-
物理层:负责在物理媒介上传输比特流,包括传输介质、物理接口和电气特征
优点:
-
简单明了:TCP/IP五层协议简单明了,易于理解和实现。
-
开放性强:TCP/IP协议是一种开放式标准,具有很好的兼容性和可扩展性。
-
稳定可靠:TCP协议提供了可靠的数据传输服务,保证数据的完整性和可靠性。
-
灵活性高:TCP/IP协议支持多种不同的应用程序,如电子邮件、文件传输、网页浏览等。
-
安全性高:TCP/IP协议提供了一些安全机制,如IPSec、SSL等,保证数据的安全性和私密性。
缺点:
-
复杂性较高:TCP/IP协议的实现比较复杂,需要深入了解网络协议的原理和相关技术。
-
性能有限:TCP协议为了保证数据的可靠性,会引入一定的延迟和开销,对实时性要求较高的应用程序不太适合。
-
安全性不足:TCP/IP协议中的一些安全机制容易受到攻击,需要采取额外的措施来保证安全性。
-
可靠性有限:TCP/IP协议虽然提供了可靠的数据传输服务,但在网络拥塞时会出现丢包和延迟等问题,需要采取一些手段来解决。
-
不适合大规模部署:由于TCP/IP协议没有很好地考虑网络管理和维护的问题,因此在大规模的网络部署中可能会出现一些问题。
【5】以太网协议
-
规范了我们上网的标准
是一种广泛的有线局域网技术之一,用于在计算机网络中进行数据通信。
它定义了计算机之间的物理层和数据链路层的通信规则和格式。
以太网协议的特点:
-
灵活性
-
高速性
-
简单性
-
可靠性
-
容错性
-
扩展性
-
可靠性
【6】IP协议
-
是一种网络通信协议
TCP/IP 协议是网络层协议,它负责将数据包将数据包从源地址传输到目的的地址
它定义了数据在互联网上如何传输和路由
IP协议的特点:
-
无链接性
-
简单灵活
-
分组传输
-
路由选择
-
IP地址
-
版本号
-
协议类型
【7】Mac地址
-
网卡
它又称物理地址,是指网络设备(如计算机、路由器、交换机、网卡)在制造时分配的全球唯一的地址
查找名为“物理地址”或“MAC地址”的字段
【8】广播
-
同一个局域网内进行数据交互
【9】TCP协议
-
流式协议
-
可靠
-
基于一条通道进行传输的
-
只有符合规范才会允许建立通道
-
(1)三次握手
-
客户端向服务端发送连接请求(带着客户端的标识 aaa)
-
服务端接收到客户端的连接请求,向客户端回请求(带着服务端的标识和客户端的标识 bbb)
-
客户端和服务端进行建立连接
(2)四次挥手
-
客户端向服务端发送断开请求
-
服务端接受到客户端的请求,继续处理完没有完成的数据
-
向客户端发送断开请求
-
客户端接收到服务端的断开请求,断开连接
【10】UDP协议
-
报式协议
UDP协议是在传输层的协议
特点:
-
无连接性:UDP在发送数据之前不需要建立连接。
-
简单快速:UDP的协议头部相对较小。通信开销夜宵,因此传输相对较快。
-
不可靠性:UDP不提供可靠性保证,数据包的传输顺序不被保证。
-
支持广播和多播:可以连接局域网的所有设备进行广播
-
适用于实时应用:
【11】socket协适用于实时应用议
-
如何建立TCP连接 / UDP连接
什么是socket:
Scoket是可以理解为一种抽象端点,它可以用来建立网络连接、发送和接收数据。
Scocket是一种用于实现网络通信的编程接口,它允许应用程序通过网络在不同的计算机之间进行数据传输和同学。
【12】粘包问题
(1)产生原因
-
TCP的流式协议
-
发送频率较快
(2)解决办法
-
在服务端向客户端返回数据的时候加一个报头
-
先获取到数据总长度
-
把总长度打到字典里编程json字符串
-
json字符串转二进制
-
json二进制数的长度达成struct的包
-
打出来的长度只有四个
-
-
发送数据
-
先发四个长度的struct
-
json二进制数据
-
总数据
-
-
-
客户端
-
先接受4个长度的struct ---> 解包
-
json二进制数数据 ---> json字符串 -- 字典
-
根据总的数据长度迭代取数据
-
【二】并发编程
【1】操作系统的发展史
(1)什么是操作系统:
操作系统是一种管理的计算机硬件的软件资源的程序。
(2)操作系统的概念
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS),它是一个进行软硬件资源管理的软件。
笼统的理解,操作系统包括:
内核(进程管理,内存管理,文件管理,驱动管理)
其他程序(例如函数库, shell程序等等)
为什么操作系统要管理软硬件呢?
操作系统对下通过合理地管理软硬件资源(手段)对上来为用户提供良好的(稳定的、高效的,安全的)执行环境(目的)。
【2】多道技术
-
时间上的复用+空间上的复用,就成为多道技术
-
空间上的复用:
-
将内存分为几个部分,每个部分放入一个程序,这样,同一内存中就有了多道程序
-
-
时间上的复用:
-
将多个信号按照时间片刻划分的方式进行传输。每个信号在不同的时间段被发送,接收方根据时间信息来分离和恢复各个信号。
-
【3】进程理论
(1)程序和进程
-
程序其实就是一堆代码和数据
-
进程是跑起来的程序
(2)进程通信
-
队列 先进先出
-
管道 后进先出
举个例子来说明:
-
队列:想象一下排队买票的情景,第一个来的人首先排在队列的最前面,当窗口处理完当前的人之后,队列中的下一个人才能进行处理。这就是典型的先进先出(FIFO)模式。
-
堆栈:类似于叠放书本,每次新的书本都会放在堆栈的顶部,而取书的时候也是从顶部开始取。只有最上面的书本可见,其他书本需要先取走上面的书本才能看到下面的书本。这就是典型的后进先出(LIFO)模式。
【4】开启进程的两种方式
(1)Process类 进程
from multiprocessing import Process def run(): print('这个是子程序!') if __name__ == '__main__': p = Process(target=run) # 启动程序 p.start() # 结束程序 p.join() # 这个是子程序!
# 进程 import multiprocessing def run(): print('这个是子程序!') if __name__ == '__main__': p = multiprocessing.Process(target=run) # 启动程序 p.start() # 结束程序 p.join() # 这个是子程序!
(2)继承Process类
-
重写了 run 方法
import multiprocessing class MyProcess(multiprocessing.Process): def run(self): print('这是一个子进程!') if __name__ == '__main__': p = MyProcess() p.start() p.join() # 这是一个子进程!
(3)启动
task = Porcess(target=函数名,args=(参数)) task.start() ---》 run方法 join方法 ---> 主进程等待所有子进程结束后再结束
线程的启动跟进程大致相同
import threading class MyThread(threading.Thread): def run(self): print("Hello from a thread!") if __name__ == "__main__": # 创建线程对象 t = MyThread() # 启动线程 t.start() # 等待线程结束 t.join()
【5】进程理论的重要概念
(1)进程的状态
进程可以处于就绪、运行或者阻塞三种状态之一。
-
就绪表示进程已经准备好允许
-
但是处理器暂时没有空闲时间;
-
-
运行状态表示进程正在执行;
-
阻塞状态表示进程被正在执行
-
直到某些时间(比如等待I/O操作)完成后再继续执行
-
(2)进程控制块
-
进程控制块是操作系统为每个操作系统为每个进程所创建的数据结构
-
用于保存进程本身的状态信息 比如进程(ID、优先级、上下文等)
-
-
PCB可以看作是程序和操作系统之间交互的纽带
-
在进程需要进行状态切换时
-
操作系统使用该数据结构记录并管理进程状态
-
(3)进程同步和进程通信
-
当多个进程同时访问共享资源时,需要进行进程同步和协调。
-
进程同步机制包括信号量、互斥量、条件变量等方式,以保证多个进程按照待定的顺序访问共享资源
-
而进程通信机制包括管理、消息队列、共享内存等方式,让多个进程之间可以相互传递信息、协调工作、完成复杂的任务
-
【6】互斥锁
是一种同步机制,用于保护对共享资源的访问,避免多个线程同时修改共享数据造成的竞争条件。
-
为了保护一个可能会被重复修改的数据
# 互斥锁 import threading share_resourse = 45 lock = threading.Lock() def run(): global share_resourse # 获取互斥锁 lock.acquire() # 访问共享资源 share_resourse += 1 # 释放互斥锁 lock.release() if __name__ == '__main__': threads = [] for i in range(10): t = threading.Thread(target=run) threads.append(t) t.start() t.join() print(share_resourse)
【7】死锁和递归锁
死锁指的是在多线程或多进程环境中,两个或多个线程/进程无法继续执行,因为它们互相等待对方释放资源。
死锁的特点:
-
互斥:线程/进程持有至少一个资源,而这些资源只能被一个线程/进程同时占用
-
占有和等待:线程/进程已经占有一个资源,并等待获取其他资源。
-
不可剥夺:已经分配的资源不能被其他线程/进程强行剥夺。
-
环路等待:存在一个资源的循环等待待链,每个线程/进程都在等待下一个资源。
递归锁是一种特殊的互斥锁,允许同一个线程多次获取同一个锁。
递归锁的特点:
-
可以被联系的acquire和release
-
但是只能被第一个抢到这把锁执行上述操作
-
它的内部有一个计数器 每acquire 一次计数加一 每release 一次技术减一
-
只要技术不为0 那么其他人都无法抢到该锁
【8】生产和消费者模型
-
生产者无法满足消费者
-
引入了队列 ---> 标志位
-
joinable模块
-
join
-
task_done()
-
(1)生产者
生产者负责生成数据,并将数据放入共享的缓冲区。
(2)消费者
消费者则从缓冲区中获取数据进行处理。
import threading import queue import time # 共享的缓冲区 buffer = queue.Queue(5) # 退出标志位,初始为 False exit_flag = False # 生产者函数 def producer(): while not exit_flag: # 模拟生产数据 data = f'数据' print(f'生产者生产了:{data}') # 将数据放入缓冲区 buffer.put(data) time.sleep(1) # 消费者函数 def consumer(): while not exit_flag or not buffer.empty(): # 从缓冲区获取数据 data = buffer.get() print(f'消费者消费了:{data}') # 模拟处理数据 time.sleep(2) buffer.task_done() if __name__ == '__main__': # 创建生产者线程 producer_thread = threading.Thread(target=producer) # 创建消费者线程 consumer_thread = threading.Thread(target=consumer) # 启动线程 producer_thread.start() consumer_thread.start() # 等待用户输入,回车键退出 input("按回车键退出程序\n") # 设置退出标志位为 True exit_flag = True # 等待线程结束 producer_thread.join() consumer_thread.join()
小结:
【一】生产者和消费者模型总结 生产者 : 生产数据 生产完所有数据以后要加一个标志位 消费者 : 消费数据 从生产者生产的数据中拿出所有数据 【二】解决生产者和消费者不平稳一 Queue 在生产者生产数据的结尾 ---> 放一个标志位 消费者消费数据 ---> 消费到最后一个数据的时候,得到标志位知道生产的数据空了 --> 结束了 【三】解决生产者和消费者不平稳二 JoinableQueue
在生产者生产数据的结尾 ---> 放一个标志位 join() 在消费者消费数据的 ----> 正产消费 ---> 在消费数据的结尾 task_done() ---> 自动检索上面生产者是否存在 join\
【8】线程理论
线程是操作系统中能够独立执行的最小单位
-
轻量性:相比与进程,线程更加轻量级,创建和销毁线程的开销较小。
-
并发性:多个线程可以同时执行,实现并发处理。
-
共享内存:线程之间可以共享相同的地址空间,即它们可以访问相同的全局变量和静态变量。
-
同步机制:线程可以使用同步机制来协调彼此的行为,确保访问共享资源的顺序和正确性
-
运行状态:线程可以处于就绪、运行和阻塞三种状态。
-
上下文切换:线程之间的切换由操作系统负责,称为上下文切换。
-
缺乏独立性:相比于进程,线程之间的独立性较差。一个线程错会影响整个进程的稳定性。
【9】开启线程的两种方式
import threading def my_function(): # 线程执行的逻辑代码 print("线程开始执行") for i in range(5): print(f"线程正在执行,当前值为:{i}") print("线程执行完毕") # 创建线程对象并指定执行的函数 my_thread = threading.Thread(target=my_function) # 启动线程 my_thread.start() # 线程开始执行 # 线程正在执行,当前值为:0 # 线程正在执行,当前值为:1 # 线程正在执行,当前值为:2 # 线程正在执行,当前值为:3 # 线程正在执行,当前值为:4 # 线程执行完毕
第二种
import threading class MyClass: def __call__(self): # 线程执行的逻辑代码 print("线程开始执行") for i in range(5): print(f"线程正在执行,当前值为:{i}") print("线程执行完毕") # 创建可调用对象的实例 my_object = MyClass() # 创建线程对象并指定执行的方法 my_thread = threading.Thread(target=my_object) # 启动线程 my_thread.start() # 线程开始执行 # 线程正在执行,当前值为:0 # 线程正在执行,当前值为:1 # 线程正在执行,当前值为:2 # 线程正在执行,当前值为:3 # 线程正在执行,当前值为:4 # 线程执行完毕
【10】线程池和进程池
进程池/线程池是一组预先创建的进程/线程,这些可以并行地执行任务。
# 线程池 import concurrent.futures # 定义任务函数 def task_func(num): print(f'执行任务 {num}') result = num * 2 return result if __name__ == '__main__': # 创建线程池, 指定线程数量为4 pool = concurrent.futures.ThreadPoolExecutor(max_workers=4) # 定义任务列表 tasks = [1, 2, 3, 4, 5, 6, 7] # 使用进程池执行任务 results = pool.map(task_func, tasks) # 关闭进程池 pool.shutdown() print(list(results)) # 执行任务1 # 执行任务2 # 执行任务3 # 执行任务4 # 执行任务5 # # 执行任务6 # 执行任务7 # [2, 4, 6, 8, 10, 12, 14]
【11】GIL全局解释器锁
(1)面试题(为什么有了GIL锁还要互斥锁)
是一种CPython解释器中使用的机制, 它保证统一时刻只有一个线程执行Python字节码。
(2)注意:
GIL(全局解释器)只存在于CPython解释器中,同时Python解释器的一个特定实现。其他编程语言和环境没有这个概念。
GIL是保证解释器级别的数据的安全 。
同一个进程下的多个线程无法利用多核优势!
【12】协程
-
必须在只有一个单线程里实现并发
-
修改共享数据不需要加锁
-
用户程序里自己保存多个控制流的上下文栈
-
附加:一个协程遇到IO操作自动切换到其他协程(如何实现检测IO、yield、greenlet都无法实现,就用到了gevent模块(select机制))
协程是一个轻量级的线程,可以避免线程切换带来的开销和复杂性。协程可以看作是一种特殊的函数,可以在任意时刻暂停执行,并在稍后恢复执行。
【13】IO模型的了解
I/O模型指的是在进行输入/输出操作时,应用程序与底层操作系统之间的交互方式。它描述了数据在应用程序和底层设备之间传输的方式和机制。
阻塞
非阻塞
I/O多路复用
信号驱动
异步