11月20周报
软件开发架构
# 1. 软件开发架构的目的
程序员在编写软件的时候应该遵循的架构设计——三层架构
# 2.软件开发架构的分类
C/S架构
C:client(客户端)
S:server(服务端)
B/S架构
B:browser(浏览器)
S:server(服务器)
# 两种架构的优缺点
C/S架构:
优势:不同公司的客户端有不同的公司独立开发 可以高度定制化客户端功能
劣势:需要用户去下载才能使用
B/S架构:
优势:不用下载直接访问
劣势:无法高度定制化 并且需要遵循很多规则
网络编程简介
网络编程就是基于互联网来编写代码
OSI七层协议
"""
OSI七层协议:规定了所有的计算机在远程数据交互的时候必须经过相同的处理流程、在制造过程中必须拥有相同的功能软件
"""
应用层
表示层
会话层
传输层
网络层
数据链路层
物理连接层
"""
接收网络消息 数据从下往上
发送网络消息 数据从上往下
"""
# 物理连接层
主要确保计算机之间的物理连接介质 接收数据(bytes类型、二进制)
# 数据链路层
规定了电信号的分组方式
以太网协议
规定了计算机在出厂的时候都有一块网卡 网卡上有一串数字
该数字相当于计算机的身份证号是独一无二的
该数字特征:12位16进制数据
前6位产商编号 后6位流水线号
我们给这个编号称之为"MAC地址"/以太网地址
'''基于MAC地址可以实现计算机之间的数据通信'''
# 网络层
IP协议:规定了所有接入互联网的计算机必须有一个IP地址 类似于坐标(取决于网线 是可变的)
MAC地址是物理地址可以看成永远无法修改
IP地址是动态分配的 不同的场所IP是不同的
IP地址特征:
IPV4:点分十进制
最小:0.0.0.0
最大:255.255.255.2555
IPV6:冒号十六进制
'''能够给地球上每一粒沙分一个IP地址'''
ps:IP地址可以用来标识全世界独一无二的一台计算机
# 传输层
PORT协议(端口协议)
端口协议:
用来标识一台计算机上面某一个应用程序
特征:动态分配
端口号的范围:0~65535(也就是一台计算机可以一起运行65535个应用程序)
0~1024:系统常用的端口号
1024~8000:常用软件的端口号
所以我们在写项目的时候 推荐使用8000之后的端口号
注意事项:
同一时间同一台计算机的端口号不能重合
端口号是动态分配的
URL:统一资源定位符(网址)
网址的本质都是由IP+PORT组成
IP+PORT:能够定位全世界独一无二的一台计算机上面某一个应用程序
# 应用层
应用层相当于程序员自己写的应用程序 里面的协议非常多
常见的有:HTTP、HTTPS、FTP
网络相关专业名词
# 交换机
能够将所有接入交换机的计算机彼此链接起来
# 局域网
可以简单的理解为有单个交换机组成的网络
在局域网内可以直接使用mac地址通信
# 广播
首次查找接入同一个交换机的其他计算机 需要朝交换机里面吼一嗓子
# 单播
首次被查找的计算机回应查找它的计算机 并附带自己的mac地址
# 广播风暴
接入同一台交换机的多台计算机同时发广播
# 广域网
可以简单的理解为范围更大的局域网
# 互联网
由所有的局域网、广域网连接到一起形成的网络
# 路由器
不同的局域网计算机之间是无法直接实现数据交互的 需要路由器连接
TCP协议
TCP协议
三次握手:建链接
1.流式协议、可靠协议(数据不容易丢失)
造成数据不容易丢失的原因不是因为有双向通道 而是因为有反馈机制
给对方发消息之后会保留一个副本 直到对方回应消息收到了才会删除
否则会在一定时间内反复发送
2.洪水攻击
同一时间有大量的客户端请求建立链接 会导致服务端一直处于SYN_RCVD状态 服务端接收到了大量的syn请求,处于rcvd状态
3.服务端如何区分客户端建立链接的请求
可以对请求做唯一标识
四次挥手:断链接
四次不能合并成三次
因为中间需要确认消息是否发完(TIME_WAIT)
UDP协议
UTP协议
也称之为数据报协议、不可靠协议
使用UDP的原因就是因为很简单 快捷 粗暴 只要指定对方的地址就可以发消息了
socket模块(套接字)
如果我们需要编写基于网络进行数据交互的程序 意味着我们需要自己通过代码来控制我们之前所学习的OSI七层(很繁琐 很复杂 类似于我们自己编写操作系统)
socket类似于操作系统 封装了丑陋复杂的接口提供简单快捷的接口
socket也叫套接字
基于文件类型的套接字家族(单机)
AF_UNIX
基于网络类型的套接字家族(联网)
AF_INET
它为我们提供了快捷方式,不需要我们自己去处理OSI七层的每一层
import socket
socket.socket() 产生socket对象
bind() 绑定地址
listen() 半连接池
accept() 等待客户端链接
send() 发送消息
recv() 接收消息
connect() 链接服务端
通讯循环
# 1.解决信息固定的问题 聊天内容自定义
利用input获取用户输入
# 2.解决通信循环的问题 让聊天循环起来
将双方用于数据交互的代码循环起来 聊天部分用循环包起来
代码优化和链接循环
1.用户的输入消息不能为空
本质其实是两边不能都是recv或者send 一定是一方收一方发
统计长度然后进行判断(len)
2、反复重启服务端可能会发生报错: address in use
解决方法:
# 在最上面放一个
from socket import SOL_SOCKET,SO_REUSEADDR
# 在bind上面放
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
半连接池
server.listen(5) # 半连接池
py文件默认只能同时只能运行一次,如果想要单独分开运行多次:
Edit Configuration选择all
当有多个客户端来链接的情况下 我们可以设置等待数量(不考虑并发问题)
假设服务端只有一个人的情况下
在测试半连接池的时候 可以不用input获取消息 直接把消息写死即可
# 半连接池
设置最大等待人数 >>>: 节省资源,提升效率
黏包现象
1.服务端连续执行三次recv
2.客户端连续执行三次send
问题:服务端一次性接收到了客户端三次的消息 该现象称为"黏包现象"
# TCP协议的特点
将数据量比较小并且时间间隔比较短的数据整合到一起发送,并且还会受制于recv括号内的数字大小
流式协议:跟水流一样不间断
就是因为我们不知道recv括号不知道要接收多大的数据,使用才会出现黏包问题,那么我们只要能够判断接收的数据具体大小,就可以避免黏包问题
struct模块
# 能够精准确定数据的大小
使用struct模块
import struct
黏包代码实战
# 接收方
import socket
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
sock, addr = server.accept()
# 1.接收固定长度的字典报头
data_dict_head = sock.recv(4)
# 2.根据报头解析出字典数据的长度
data_dict_len = struct.unpack('i', data_dict_head)[0]
# 3.接收字典数据
data_dict_bytes = sock.recv(data_dict_len)
data_dict = json.loads(data_dict_bytes) # 自动解码再反序列化
# 4.获取真实数据的各项信息
方法一:
total_size = data_dict.get('file_size')
with open(data_dict.get('file_name'), 'wb') as f:
f.write(sock.recv(total_size))
方法二:
'''接收真实数据的时候 如果数据量非常大 recv括号内直接填写该数据量 不太合适 我们可以每次接收一点点 反正知道总长度'''
total_size = data_dict.get('file_size')
recv_size = 0
with open(data_dict.get('file_name'), 'wb') as f:
while recv_size < total_size:
data = sock.recv(1024)
f.write(data)
recv_size += len(data)
print(recv_size)
并发编程理论
操作系统发展史
1.穿孔卡片阶段
2.联机批处理系统
3.脱机批处理系统
多道技术
单道技术
所有的程序排队执行 过程中不能重合
多道技术
利用空闲时间提前准备其他数据 最大化提升CPU利用率
多道技术详细
1.切换
计算机的CPU在两种情况下会切换(不让你用 给别人用)
1.程序有IO操作
2.程序长时间占用CPU
2.保存状态
CPU每次切换走之前都需要保存当前操作的状态 下次切换回来基于上次的进度继续执行
进程理论
进程与程序的区别
程序:一堆死代码(还没有被运行起来)
进程:正在运行的程序(被运行起来)
进程的调度算法
1.FCFS(先来先服务)
对短作业不友好
2.短作业优先调度
对长作业不友好
3.时间片轮转法+多级反馈队列(目前在用)
进程的并行与并发
并行
多个进程同时执行 必须有多个CPU参与 单个CPU无法实现并行
并发
多个进程看上去像同时执行 单个CPU可以实现 多个CPU肯定也可以
进程的三状态
就绪态
所有进程在被CPU执行之前都必须先进入就绪态等待
运行态
CPU正在执行
阻塞态
进程运行过程中出现了IO操作 阻塞态无法直接进入运行态 需要先进入就绪态
同步与异步
同步
提交完任务之后原地等待任务的返回结果 期间不做任何事
异步
提交完任务之后不原地等待任务的返回结果 直接去做其他事 有结果 自动通知
阻塞与非阻塞
用来表达任务的执行状态
阻塞
阻塞态
非阻塞
就绪态、运行态
综合使用
同步异步:用来描述任务的提交方式
阻塞非阻塞:用来描述任务的执行状态
同步+阻塞:
银行排队办理业务,期间可不做任何事,就等着
同步+非阻塞:
银行排队办理业务,期间可以去做一些其他的事,但是人还在办理业务的队列中
异步阻塞:
在椅子上坐着,不做任何事
异步非阻塞:
在椅子上面坐着,在期间喝水、吃东西、工作、玩手机、、(这个过程就是把程序运行到了极致)
创建进程的多种方式
"""
1.创建进程的方式
鼠标双击桌面一个应用图标
代码创建
创建进程的本质:
在内存中要一块内存空间用来运行相应的程序代码
"""
# multiprocessing 进程模块
# Process P是大写
from multiprocessing import Process
import time
# 用函数创建进程
def task(name):
print('task is running', name)
time.sleep(3)
print('task is over', name)
"""
在不同的操作系统中创建进程底层原理不一样
windows
以导入模块的形式创建进程
linux/mac
以拷贝代码的形式创建进程
"""
if __name__ == '__main__':
p1 = Process(target=task,args=('superman',)) # 位置参数
# p1 = Process(target=task, kwargs={'name': 'superman'}) # 关键字参数
p1.start() # 异步 告诉操作系统创建一个新的进程 并在该进程中执行task函数
print('主进程')
# 面向对象创建进程
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name, age):
super().__init__()
self.name = name
self.age = age
def run(self):
print('run is running', self.name, self.age)
time.sleep(3)
print('run is over', self.name, self.age)
if __name__ == '__main__':
obj = MyProcess('superman', 18)
obj.start()
print('主进程')
进程间数据默认隔离
同一台计算机上的多个进程数据是严格意义上的物理隔离(默认情况下)
# 我们可以把内存看成很对个小房间组成的, 一人一个互不干扰
from multiprocessing import Process
import time
money = 1000
def task():
global money # 局部修改全局
money = 666
print('子进程的task函数查看money', money)
if __name__ == '__main__':
p1 = Process(target=task)
p1.start() # 创建子进程
time.sleep(3) # 主进程代码等待3秒
print(money) # 主进程代码打印money
# 同一台计算机上面,进程和进程之间是数据隔离的
进程的join方法
# join方法就是让主进程等待子进程代码运行完毕之后 再执行
from multiprocessing import Process
import time
def task(name, n):
print('%s is running' % name)
time.sleep(n)
print('%s is over' % name)
if __name__ == '__main__':
p1 = Process(target=task, args=('super xz1', 1))
p2 = Process(target=task, args=('super xz2', 2))
p3 = Process(target=task, args=('super xz3', 3))
'''主进程代码等待子进程代码运行结束再执行'''
start_time = time.time()
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
end_time = time.time() - start_time
print('主进程', f'总耗时:{end_time}')
IPC机制
IPC:进程间通信
消息队列:存储数据的地方 所有人都可以存 也都可以取
from multiprocessing import Queue
q = Queue(3) # 括号内可以指定存储数据的个数
# 往消息队列中存放数据
q.put(111)
# print(q.full()) # 判断队列是否已满
q.put(222)
q.put(333)
# print(q.full()) # 判断队列是否已满
# 从消息队列中取出数据
print(q.get())
print(q.get())
# print(q.empty()) # 判断队列是否为空
print(q.get())
# print(q.empty()) # 判断队列是否为空
# print(q.get())
print(q.get_nowait())
"""
full() empty() 在多进程中都不能使用!!!
"""
生产者消费者模型
生产者
负责产生数据的'人'
消费者
负责处理数据的'人'
该模型除了有生产者和消费者之外还必须有消息队列(只要是能够提供数据保存服务和提取服务的理论上都可以)
进程对象属性和方法
"""
查看进程号的方法:
windows: tasklist 结果集中PID
mac:ps -ef
"""
1.1 查看进程号
courrent_process函数
from multiprocessing import Process, current_process
print(current_process().pid) # 13236 进程号
# 获取进程号的目的就是可以通过代码的方式管理进程
windows系统: taskkill关键字
Mac/linux系统: kill关键字
1.2 os模块
os.getpid() # 获取当前进程的进程号
os.getppid() # 获取到当前进程的父进程号
2.杀死进程
terminate() # 这个过程可能需要一点时间
3.判断子进程是否存活
is_alive() # 返回True或者False
守护进程
"""
守护就是生死全靠守护的对象,对生守生, 对死守死
"""
僵尸进程和孤儿进程
僵尸进程
所有的子进程在运行结束之后都会变成僵尸进程(死了但没死透)
因为还保留着pid和一些运行过程中的记录便于主进程查看(只是短时间保存)
等这些信息被主进程回收,就彻底死了
1.主进程正常结束
2.调用join方法
孤儿进程
子进程存在 父进程死了
子进程会被操作系统自动接管
互斥锁
"""
在多个进程操作同一个数据的时候会造成数据的错乱, 所以我们需要增加一个加锁处理(互斥锁)
将并发变成串行, 效率低了,但安全性高了
互斥锁并不能轻易使用, 容易造成死锁现象
互斥锁旨在处理数据的部分加锁, 不能什么地方都加
"""
"""
行锁: 针对行数加锁, 同一时间只能一个人操作
表锁: 针对表数据加锁, 同一时间只能一个人操作
"""
标签:11,__,20,name,print,time,进程,import,周报
From: https://www.cnblogs.com/super-xz/p/16909515.html