周总结第八周
周总结(8)
软件开发架构
C/S架构
C客户端,S服务端
优点:针对客户端可以高度定制
B/S架构
B浏览器,S服务端
优点:不需要下载
通过前者与后者进行数据交互,基于网络
网络编程
- 基于网络编写代码,能够实现数据的远程交互。
目的: 开发C/S架构的软件
必备条件
必须具备一个物理链接介质。
OSI七层协议
1.应用层
2.表示层
3.会话层
4.传输层
5.网络层
6.数据链路层
7.物理链接层
物理链接层
- 计算机之间的物理连接介质,接收数据
数据链路层
- 基于物理链接层收到的信号
1.规定了 信号的分组方式
2.以太网协议
MAC地址,由12位16进制数组成,前六位位厂商编号,后六位流水线号。 每台计算机都必须有网卡。
网络层
IP地址 规定了计算机接入网络必须要拥有IP地址
与MAC地址区别: MAC地址是物理地址无法修改
IPV4 点分十进制,现如今地址池已不够用
IPV6 可以表示地球上每一粒沙子,地址池非常大
IP地址分为公网与私网,公网才可以直接被浏览器访问到
传输层
PORT端口协议
每个应用软件都有属于他的独立端口
端口范围:
0-65535 动态分配
0 -1024 系统默认使用的端口
1024-8000 常用软件使用的端口号
我们以后使用8000往后
同一台计算机上面的端口不能重合。每次关闭应用重新打开便会重新分配一个端口。有些软件也可以设置固定端口
IP地址:用来标识全世界独一无二的一台接入互联网的计算机
PORT号:用来标识一台计算机上面的某一个应用程序
那么IP + PORT :就是定位全世界独一无二的计算机上面的某一个应用程序
所以我们在日常中使用的网址其本质就是""" IP地址+端口号,通过域名解析将网址解析IP+PORT 两者之间使用冒号:连接 """
TCP协议
可靠协议,流式协议
"三次握手键连接"
彼此之间互相开通道传输数据。
1.客户端向服务端发出开通道需求并附上标识(作为此通道唯一标识)
2. 服务端允许建立通道(+上此通道标识),但此时通道是单向的,服务端也想向 客户端开通通信通道, 发出请求给客户端建立通信通道。 并附上标识。
3. 客户端允许建立 通道。附上标识。
# 上述步骤完成后 就建立了双向通道
"四次挥手断链接"
1.客户端请求断开单向通道。(客户端通向服务端的通道)
2. 服务端确认断开单向通道。
3.服务端请求断开单向通道(服务端通向客户端的通道)
4.客户端确认断开单向通道
# 不能向 3次握手建链接那样 中间2步合并,因为中间需要确认消息是否发完,要思考。
称为: "TIME_WAIT 状态 "(时间等待)
比如说: 你发分手短信给女朋友, 女朋友不会立刻给你回复消息。他需要思考思考。(海后除外)
====================================================================================
"洪水攻击" "SYN_RCVD 状态" (同步收到)
是指在同一时间大量的客户端向服务端申请建立链接。 导致服务端处于 SYN_RCVD状态
解决方案:
设定缓冲池: 设定数量同时只接待规定的数量的客户端,超出部分进入缓冲中排队。当接待中有客户端退出,顺位补位 # 类似于咱们玩游戏排队
UDP协议
丢包协议、不可靠协议
不需要建立双向通道,数据的传输的速度快,但是传输的数据可能会发生丢失
QQ使用的就是UDP协议,以前我们使用QQ得时候经常会发现短信发了对方收不到,所以就存在产生数据的丢失情况。
但如果在UDP协议的基础上做扩展来保证数据的安全就两全其美。
应用层
程序员自己写的应用程序。协议狠多
常见的 HTTP , HTTPS, FTP
网络相关专业名词
计算机之间想要实现数据交互 必须 要 '连接'在一起
1.交换机
能够将所有接在交换机设备上的计算机彼此互联
2.广播
首次查找在同一台交换机上的其他计算机,需要向交换机发出广播 ,首次"群发"
3.单播
广播目标计算机回复消息时称为 "单播"并传输 mac地址
4.广播风暴
同一台交换机上的计算机同时 发出广播
5.局域网
单个交换机组成的网络。一个小区域内的网络。
6.广域网
区域更大的局域网
7.互联网
所有局域网,广域网连接到一起
8.路由器
不同局域网之间的计算机无法实现数据交互,需要路由器连接
socket模块
封装七层协议复杂端口提供了简单快捷端口
套接字间接:
AF_UNIX 基于文件类型的套接字家族
AF_INET 基于网络类型的套接字家族
socket基本使用
bind() 绑定地址
listen() 半连接池
accept() 等待客户端链接
send() 发送消息
recv() 接收消息
connect() 链接服务端
黏包现象
我们在使用socket 模块进行数据交互时,服务端连续执行了3次recv, 客户端连续执行了 3次send
此时如果我们的recv 填写的1024字节 如果send 传输的字节不超过1024,那么我们只第一次recv就会得到3条信息合并成一条。
如果我们填写的字节大写跟我们传输的一样,那么就会一次性连续得到3条消息
此现象就称为黏包现象。
"黏包现象产生的原因"
1.不知道每次的数据到底多大
2.TCP也称为流式协议:数据像水流一样绵绵不绝没有间隔(TCP会针对数据量较小且发送间隔较短的多条数据一次性合并打包发送)
"解决方法:"
明确的得知即将获取的数据具体有多大。使用 "struct模块"
黏包终极解决思路
# 发送方
1.先构造一个字典,里面放真实数据的信息
eg: 数据大小、名称、简介、、、
2、对字典做打包处理pack
3、发送固定长度的报头 (字典的长度)
4、发送真实的字典数据
5、发送真实的真正数据
# 接收方
1、先接收打包好固定长度的字典
2、解析出字典的真实长度unpack
3、接收字典数据
4、从字典数据中解析出各种信息
5、接收真实的数据
服务端:
import socket
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 14334))
server.listen(5)
sock, addr = server.accept()
# 接收固定长度的报头
date_dict_head = sock.recv(4)
# 根据报头解析出字典数据的长度
data_dict_len = struct.unpack('i',date_dict_head)[0]
# 接收字典数据
data_dict_bytes = sock.recv(data_dict_len)
# 利用jason 模块 解码并反序列化
data_dict = json.loads(data_dict_bytes)
# 获取真实字典数据
print(data_dict)
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)
第二种获取真实数据的方法:
total_size = data_dict.get('file_size')
with open(data_dict.get('file_name'), 'wb') as f:
f.write(sock.recv(total_size))
客户端:
import socket
import os
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1',14334))
# 获取真实数据大小
file_size = os.path.getsize(r'D:\pythonProject\11.16\1.txt')
# 制作真实数据的字典数据
data_dict = {
'file_name': '我爱你.txt',
'file_size': file_size,
'file_desc': '很带劲',
'file_info': '我的心里话'
}
# 制作字典报头
data_dict_bytes = json.dumps(data_dict).encode('utf8')
# 获取字典报头长度 打包
data_dict_len = struct.pack('i',len(data_dict_bytes))
# 发送字典报头 (i模式报头固定长度是4)
client.send(data_dict_len)
# 发送字典
client.send(data_dict_bytes)
# 发送真实数据
with open(r'D:\pythonProject\11.16\1.txt','rb') as f:
for line in f:
client.send(line)
并发编程理论
发展史:
1.穿孔卡片,一次只能给一个人使用电脑.cpu利用率极低。
2.联机批处理 使用磁盘一次性录入多个程序,缩短了cpu的等待时间,提高了cpu的利用率
3.脱机批处理 类似于现在的计算机核心部件雏形,极大提升了cpu的利用率。
单道技术
-
所有程序排队执行,执行完一个才会去执行下一个,过程中不能重合。
缺点: 浪费了资源。耗时长
多道技术
-
计算机利用等待空闲 提前准备其他数据。最大化压榨CPU性能提高性能
两种状态:
1.切换
运行中遇到IO操作,cpu会利用空档去执行别的程序
2.保存
每次切换都会保存程序运行的当前状态。
进程理论
1.程序
程序员编写的代码还没有运行。
2.进程
系统当前正在运行的程序
进程的调度算法
1. FCFS 先来先服务
顾名思义谁先来先服务谁
2. 短作业优先调度
优先处理代码短的程序
3. 时间片轮转法+多级反馈队列
将时间平均分给每个程序,如果没执行完便会根据进程时间长短进行分级 分配更多的时间
等级越靠下表示耗时越长(代码很多),每次分级分到的时间就会越多优先级越低。 如果有新代码来了优先执行新代码
并行与并发
并行:
多个程序同时执行,需要多个CPU进行工作
并发:
一个CPU工作看起来运行了多个程序
进程的三状态
就绪态
所有程序再被CPU执行之后都必须先进入就绪态等待
运行态
CPU正在执行
阻塞态
进程运行过程中出现了IO操作 阻塞态无法直接进入运行态 需要先进入就绪态。
同步与异步
表达任务的提交方式
同步:
提交完任务之后会在原地等待任务的返回结果,在等待的过程不会做任何事。
异步:
提交完任务之后 不原地等待,去干别的事情,有结果自动通知。
阻塞与非阻塞
任务的执行状态
阻塞态
进程的三状态中的阻塞态。任务有IO操作时就会进入
非阻塞态
就绪态和运行态
"""
如果想要提高程序被执行效率
就要程序一直处于就绪态和运行态
"""
同步异步和阻塞费阻塞结合使用
同步异步:用来描述任务的提交方式
阻塞非阻塞: 用来描述任务的执行状态
同步+阻塞:
银行排队办理业务,期间不做任何事,就等着
同步+非阻塞:
银行排队办理业务,期间可以去做一些其他的事,但是人还在办理业务的队列中
异步阻塞:
在椅子上坐着,不做任何事
异步非阻塞:
在椅子上面坐着,在期间喝水、吃东西、工作、玩手机、、(这个过程就是把程序运行到了极致)
创建进程的多种方式
我们常用的就是直接双击软件图标。
但是python代码也给我们提供了创建进程的方式
第一种方法:
def task(name):
print('子程序开始', name)
time.sleep(3)
print('子程序结束', name)
if __name__ == '__main__':
p1 = Process(target=task, args=('tank',))
p1.start() # 异步
task() # 同步
print('这是主程序')
===================================================================================
第二种方法:
class MyProcess(Process):
import time
from multiprocessing import Process
class MyProcess(Process):
def __init__(self,name,age):
super().__init__()
self.name = name
self.age = age
def run(self):
print('子程序开始',self.name)
time.sleep(3)
print('子程序结束',self.age)
if __name__ == '__main__':
obj = MyProcess('tank',28)
obj.start()
print('主程序')
进程间的数据隔离
一台计算机上的多个进程 数据在默认情况下是无法访问的。也称为物理隔离,但是我们也可以通过 global 来修改 外部数据实现
"""
在不同的操作系统中创建进程底层原理不一样
windows
以导入模块的形式创建进程
linux/mac
以拷贝代码的形式创建进程
"""
在子程序中复制了同样的代码形成了一个新的内存空间,在此内存空间的task函数类利用global修改了外部name的值
进程的join方法
"join方法就是让主进程等待子进程代码运行完毕之后 再执行后续的代码"
想要p1.start()之后的代码, 等待子进程全部运行结束之后再打印
1.直接sleep,但是这个方法肯定不可行,因为子进程运行时间不可控
2.join方法
针对多个子进程的等待
IPC机制与消息队列
IPC : 进程间相互通信
消息队列:存储数据的地方,谁都能存,谁都能取
Queue 模块
先进先出,使用put方法增加数据
进程对象的多种方法
1.查看进程号:
current_process() # 查看父进程号
current_process().pid # 查看子进程号
os查看进程号:
import os
os.getpid() # 查看子进程号
os.getppid() # 查看父进程号
代码:
def start():
print(f'我是子进程1我的进程号:', os.getpid())
print(f'我是子进程1我的父进程号:', os.getppid())
def end():
print(f'我是子进程2我的进程号:', os.getpid())
print(f'我是子进程2我的父进程号:', os.getppid())
if __name__ == '__main__':
p1 = Process(target=start)
p2 = Process(target=end)
p1.start()
p2.start()
----------------------------------------
我是子进程1我的进程号: 45084
我是子进程2我的进程号: 22744
我是子进程1我的父进程号: 28576
我是子进程2我的父进程号: 28576
----------------------------------------
===================================================================================
2.中止进程:
p1.terminate()
代码:
def start():
print(f'我是子进程1')
def end():
print(f'我是子进程2')
if __name__ == '__main__':
p1 = Process(target=start)
p2 = Process(target=end)
p1.start()
p1.terminate()
print('我p1被杀死了')
p2.start()
----------------------------------------
我p1被杀死了
我是子进程2
----------------------------------------
===================================================================================
3.判断进程是否存活
p1.is_alive() # 判断过程中会经历主进程回收,如果中间不加 sleep 还是 为True 还没死透,也称为假死
4.start() # 创建子进程
5.join() # 等待子进程代码执行完毕后在执行主进程代码
守护进程
守护进程会随着守护的 进程结束(主进程)而立刻结束
使用 .daemon= True来设置 守护进程
僵尸进程和孤儿进程
僵尸进程
所有的子进程在运行结束之后都会变成僵尸进程(死了没死透)
因为还保留着pid和一些运行过程的中的记录便于主进程查看(只是短时间保存)
等这些信息被主进程回收,就彻底死了
1.主进程正常结束
2.调用join方法 等待子进程 死透
# 僵尸进程是无害的
孤儿进程
# 子进程存在,但是父进程毙了
子进程会被操作系统自动接管 (类似于福利院收养了)
多进程数据错乱与互斥锁
"""
在多个进程操作同一个数据的时候会造成数据的错乱, 所以我们需要增加一个加锁处理(互斥锁)
将并发变成串行, 效率低了,但安全性高了
互斥锁并不能轻易使用, 容易造成死锁现象
互斥锁旨在处理数据的部分加锁, 不能什么地方都加
"""
"""
行锁: 针对行数加锁, 同一时间只能一个人操作
表锁: 针对表数据加锁, 同一时间只能一个人操作
"""