学习python第八周学习总结
设计模式
1.如何理解设计模式
在IT行业有很多前辈针对固定的一些问题设计出了固定的解决套路
2.设计模式(23种)
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
工厂模式(Factory Pattern)
抽象工厂模式(Abstract Factory Pattern)
单例模式(Singleton Pattern)
建造者模式(Builder Pattern)
原型模式(Prototype Pattern)
责任链模式(Chain of Responsibility Pattern)
命令模式(Command Pattern)
解释器模式(Interpreter Pattern)
迭代器模式(Iterator Pattern)
中介者模式(Mediator Pattern)
备忘录模式(Memento Pattern)
观察者模式(Observer Pattern)
状态模式(State Pattern)
空对象模式(Null Object Pattern)
策略模式(Strategy Pattern)
模板模式(Template Pattern)
访问者模式(Visitor Pattern)
MVC 模式(MVC Pattern)
业务代表模式(Business Delegate Pattern)
组合实体模式(Composite Entity Pattern)
数据访问对象模式(Data Access Object Pattern)
前端控制器模式(Front Controller Pattern)
拦截过滤器模式(Intercepting Filter Pattern)
服务定位器模式(Service Locator Pattern)
传输对象模式(Transfer Object Pattern)
参考博客:https://www.cnblogs.com/chenssy/p/3357683.html
3.设计模式的分类
创建型、结构型、行为型
4.设计模式之单例模式
类加括号调用多次只允许产生一个对象
class MyClass:
pass
obj1 = MyClass()
obj2 = MyClass()
obj3 = MyClass()
print(id(obj1), id(obj2), id(obj3)) # 2263190030944 2263190494080 2263189844464
'''
正常情况下 类名只要加括号实例化产生对象 执行几次就会产生几个不同的对象
'''
'''
有时候我们不希望类频繁的产生不同的对象
类中有很多很好用的方法 程序很多地方都需要使用(通过对象调用)
如果产生的地方特别多 那么会浪费一定的内存空间 所以需要使用单例
'''
方式1:使用元类干预对象的创建过程
class MyMeTaClass(type):
# 记录类是否已经创建了对象
instance = None
def __call__(self, *args, **kwargs):
if self.instance:
return self.instance
# 获取空对象
obj = super().__call__(*args, **kwargs)
# 保存对象
self.instance = obj
# 返回空对象
return obj
class Single(metaclass=MyMeTaClass):
def __init__(self, name):
self.name = name
obj1 = Single('jason')
obj2 = Single('kevin')
obj3 = Single('tony')
print(id(obj1), id(obj2), id(obj3))
print(obj1.name)
print(obj2.name)
print(obj3.name)
pickle模块
了解pickle模块:
pickle模块与json模块很像 都是用来做序列化 反序列化的
并且pickle模块支持python所有数据类型
缺点:
pickle不支持跨语言传输 只能python自己跟自己玩 兼容性不好
为了比较方便的编写选课系统 我们需要将对象保存到文件 所以需要用该模块
后续我们不用 因为有更加高级的方法:ORM(对象关系映射)
class MyClass:
def __init__(self, name):
self.name = name
def choice_course(self):
print('%s正在选课' % self.name)
obj = MyClass('jason')
# 需求:将对象保存到文件 并且将来取出之后还可以通过句点符随意调用
with open(r'a.txt','w',encoding='utf8') as f:
f.write(obj)
# 常规的文件操作不行,文件虽然创建了,但是里面并没有写入数据
import json
with open(r'a.txt','w',encoding='utf8') as f:
f.write(json.dumps(obj))
# json模块也不行,对象不再json序列化的范围之内
import pickle
with open(r'a.txt', 'wb') as f:
f.write(pickle.dumps(obj))
pickle.dumps(obj, f)
# 文件内容是二进制形式的,只是用来保存的,不能用肉眼去看懂的,需要用的时候用代码去看
with open(r'a.txt', 'rb') as f:
data = pickle.load(f)
print(data) # 是一个对象
print(data.name) # jason 可以通过点的方式取到数据
data.choice_course() # jason正在选课
系统架构设计
三层架构
功能展示层
src.py
admin_view.py
teacher_view.py
student_view.py
核心逻辑层
admin_interface.py
teacher_interface.py
student_interface.py
数据处理层
db_hanlder.py
model.py
软件开发架构
1.什么是软件开发架构
编写项目之前需要遵循的代码层面上的规范
1.从本质上来看,软件架构是属于一种系统草图。
2.在软件架构所描述的对象就是直接的进行系统抽象组件构成。
3.连接系统的各个组件之间就是做到把组件之间所存在的通讯比较明确与相对细致的实施描述。
4.处于相应的系统实现环节,那么就会使得细化这些抽象组件成为现实的组件,比如可以是具体的某个类或者是对象。
5.从面向对象领域进行分析,那么各个组件之前实施的连接实现往往是接口。
2.软件开发架构
2.1c/s架构
c/s架构:
c:client 客服端(程序需要先安装后,才能运行在用户的电脑上,对用户的电脑的操作系统环境依赖较大)
s:server 服务端
ps:计算机上下载的各个互联网公司的app本质其实都是客户端
下载一个个客户端的目的说白了就是为了体验对应服务端的服务
客户端可以看作是即将去消费的客人
服务端可以看作是给你推荐服务的店
服务端具备的特征:
1.24小时不间断提供服务
2.固定的地址
3.可以同一时间服务很多人
2.2b/s架构
b/s架构:
b:broswer 浏览器(其实也是一种客户端,它不需要安装应用程序,只需要在浏览器上通过HTTP请求服务器端相关的资源)
s:server 服务器
ps:b/s架构本质也是c/s架构
通过浏览器来充当各个服务器的客户端,用于想要体验服务不需要下载指定的客户端
3.架构优劣势
cs架构:
优势:下载对应的客户端,可以在客户端软件内高度制定相关服务
劣势:使用必须先下载客户端比较麻烦
bs架构:
优势:不需要下载客户端 能够快速体验服务
劣势:制定花里胡哨的功能
4.架构发展趋势
发展趋势:统一接口原则
微信:里面有许多小程序 可以直接体验其他服务
支付宝:里面有许多小程序 可以直接体验其他服务
后期就是cs与bs交错使用,互补各自劣势
ps:可以方便用户使用,更重要的是可以给自己圈用户便于后期收割
网络编程简介
一、如何理解网络编程
基于互联网编写代码,程序可以实现远程数据交互
1.本质就是俩个设备之间的数据交换
2.数据传递就是把一个设备中的数据发送给另外一个设备,然后接收另外一个设备反馈的数据,现在网路编程基本上都是基于请求\响应方式的,也就是一个设备发送请求数据给另一个,然后接收另一个设备反馈的数据
二、网络编程的目的
网络编程的本质是为了解决计算机之间远程数据交互
三、网络编程的意义
学习网络编程之后,我们就可以编写一个c/s架构的软件
四、网络编程的起源
网络编程最早由美国军方开发
没有网络编程的时候,如果俩台计算机之间要交互数据,只能使用硬盘拷贝
五、网络编程的要求
1.早期的电话线,必须要有电话线
2.大屁股电脑,必须要有网线
3.笔记本电脑,必须要有网卡
计算机之间想要实现远程数据交互,俩者之间必须要有物理连接介质
OSI七层协议
1.了解知识
规定了计算机涉及到数据远程交互的时候,必须要经过的部件/流程
或者说就是所有的计算机在涉及到网络传输这块必须要有相同的零部件
这七层协议只争对网络协议
应用层、表示层、会话层、传输层、网络层、数据链路层、物理连接层
'''也可以合并成五层:'''
应用层、传输层、网络层、数据链路层、物理连接层
'''也可以合并成四层:'''
应用层、传输层、网络层、网络接口层
ps:记忆小技巧:记开头,,应、表、会、传、网、数、物
2. 各层特征
数据发送出去的时候,是从上往下走
数据该收回来的时候,是从下往上走
2.1物理连接层
保证物理连接介质的条件,传递电信号(主要研究插网线情况),是一堆二进制字符
物理层无法获取具体的数据内容以及分解字符串,因此将电信号传给数据链路层
2.2数据链路层(遵循ARP协议)
1.规定了电信号的分组方式
2.规定每台计算机都必须有一块网卡
网卡上必须有一串记录,可以看作是它的身份证号-->以太网地址\mac地址
以太网地址\mac地址:前6位是生产编号,后6位是生产流水线
ps:有了太网地址\mac地址,我们就可以根据该地址查找计算机(基于mac地址实现数据交互)
2.3网络层
IP协议:规定了任何接入互联网的计算机都必须有一个IP地址(身份的标识)
IP地址的特性:
IPV4:点分十进制,最小是0.0.0.0,最大是255.255.255.255
随着社会的发展,同时上网的人越来越多
IPV6:百度了解,可以表示出地球上每一粒沙子
IP特征:每个IP都自带定位
扩展知识:IP代理:隐藏自己的地址,但是还是可以找得到的
2.4传输层
PORT协议(端口协议):规定了一台计算机上的每一个应用程序都必须有一个端口号
端口号相当于是计算机用来管理多个应用程序的标记
端口号的特征
1.端口号范围:0-65535
2.端口号是动态分配
3.同一时间同一台计算机端口号不能冲突,同一时间一个端口号只能给一个计算机使用
4.固定的使用范围:
0-1024范围是操作系统内部使用的
1024-8000范围是常见的软件已经使用了
我们以后写代码使用8000以后的端口号
扩展知识:
IP+PORT:表示全世界任意一台接入互联网的计算机上的某个具体的应用程序
什么是网址(URL):统一资源定位符,本质:IP+PORT
网络相关设施
1.交换机
能够让接入交换机的多台计算机实现彼此互联
2.以太网通信(mac通信)
原理:有了交换机之后,根据电脑的mac地址就可以实现数据交互
广播:在交换机中喊,所有接入交换机的设备都可以听到
单播:被喊得设备给与回应
缺陷:
mac地址通信仅限于局域网
接入设备过多的时候,会造成广播风暴
3.局域网
有某个固定区域组成的网络
广域网可以看作是一个更大区域的局域网
4.路由器
局域网与局域网直接不能直接跨着访问
路由器作用(功能):将多个局域网连接到一起,不同的局域网可以相互访问
域名解析
1.什么是域名解析
域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务。
IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。
域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。
2.过程
1.当应用过程需要将一个主机域名映射为IP地址时,就调用域名解析函数,解析函数将待转换的域名放在DNS请求中,以UDP报文方式发给本地域名服务器。
2.本地的域名服务器查到域名后,将对应的IP地址放在应答报文中返回。
3.同时域名服务器还必须具有连向其他服务器的信息以支持不能解析时的转发。
4.若域名服务器不能回答该请求,则此域名服务器就暂成为DNS中的另一个客户,向根域名服务器发出请求解析,根域名服务器一定能找到下面的所有二级域名的域名服务器,这样以此类推,一直向下解析,直到查询到所请求的域名。
5.域名解析的流程是:域名-DNS(域名解析服务器)-网站空间。
DNS服务器
1.DNS是什么意思
DNS,全称Domain Name System,中文名域名系统,它是互联网中非常重要且常用的系统,它的主要功能是将人们便于记忆的DomainName(域名)与可由机器识别的IPAddress(IP地址)进行转换,这样可以让人们更方便地访问互联网。这个通过域名获取对应IP地址的过程称为域名解析(或主机名解析)
2.DNS服务器是什么
1.DNS服务器是(Domain Name System或者Domain Name Service)域名系统或者域名服务
2.域名系统为Internet上的主机分配域名地址和IP地址。
3.用户使用域名地址,该系统就会自动把域名地址转为IP地址。
4.域名服务是运行域名系统的Internet工具。
5.执行域名服务的服务器称之为DNS服务器,通过DNS服务器来应答域名服务的查询。
3.DNS解析流程图
传输层
1.TCP与UDP协议
规定了数据传输所遵循的规则
数据传输能够遵循的协议有很多,TCP和UDP是常见的俩个
2.TCP协议
TCP协议
'''
基于TCP传输数据非常的安全,因为有双向通道
基于TCP传输数据,数据不容易丢失,不容易丢失的原因在于二次确认
每次发送数据都需要返回确认消息,否则在一定的时间会反复发送
'''
3.TCP协议--三次握手
P传输数据,数据不容易丢失,不容易丢失的原因在于二次确认
'''三次握手:'''
所谓三次握手,是指建立一个TCP连接时,需要客户端和服务器总共发送3个包
是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。
建立双向通道,数据基于这个通道来回发送
'''洪水攻击:'''
同时让大量的客户端朝服务端发送建立TCP连接的请求
'''三次握手过程'''
(1)客户端向服务器端发送连接请求包SYN(syn=j),等待服务器回应;
(2)服务器端收到客户端连接请求包SYN(syn=j)后,将客户端的请求包SYN(syn=j)放入到自己的未连接队列,此时服务器需要发送两个包给客户端;
(3)客户端收到服务器的ACK(ack=j+1)和SYN(syn=k)包后,知道了服务器同意建立连接,此时需要发送连接已建立的消息给服务器;
向服务器发送连接建立的确认包ACK(ack=k+1),回应服务器的SYN(syn=k)告诉服务器,我们之间已经建立了连接,可以进行数据通信。
ACK(ack=k+1)包发送完毕,服务器收到后,此时服务器与客户端进入ESTABLISHED状态,开始进行数据传送。
4.TCP协议--四次挥手
'''四次握手特点:'''
断开双向通道,中间的俩步是不能合并的需要有检查的时间
'''四次握手过程 '''
(1)当服务端或者客户端不想再与对方进行通信之后,双方任意一方都可以主动发起断开链接的请求,我们还
是以客户端主动发起为例
(2)客户端由于已经没有任何需要发送给服务端的消息了,所以发起断开客户端到服务端的通道请求
(3)服务端收到该请求后同意了 至此客户端到服务端的单项通道断开
(4)服务端这个时候不会立刻朝客户端发器请求说那我也断开到你家的通道吧,同时它也不能拒绝人家断开的请求,服务端会去查看自己还有没有需要给客户端发送的数据,如果还有的话,就不会立马断开,先把数据发完才能断
(5)等服务端检查完毕之后也没有数据要发送给客户端了,这个时候就会朝客户端发起断开服务端到客户端的
通道请求
(6)客户端确认该请求,至此四次挥手完成
(7)挥手必须是四次,'中间的两次不能合并成一次,原因就在于需要检查是否还有数据需要给对方发送'
5.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的连接请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送;
但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;
但未必你所有的数据都全部发送给对方了,所以你可能未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
5.UDP协议
UDP协议
'''
TCP类似于打电话:你一句我一句
UDP类似于发短信:反正我发了,你看不看回不回复没关系
'''
UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。
UDP在传输数据报前不用在客户和服务器之间建立一个连接,没有任何的通道也没有任何的限制
UDP发送数据没有TCP安全,很随意
例如:QQ就是采用的就是UDP
6.TCP协议 VS UDP协议
TCP和UDP的主要区别:
TCP传输数据稳定可靠,适用于对网络通讯质量要求较高的场景,需要准确无误的传输给对方,
比如,传输文件,发送邮件,浏览网页等等
UDP的优点是速度快,但是可能产生丢包,所以适用于对实时性要求较高但是对少量丢包并没有太大要求的场景。
应用层
主要取决于程序员自己采用什么策略和协议
常见协议:HTTP、HTTPS、FTP.....
socket套接字编程
1.基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
2.基于网络类型的套接字家族
套接字家族的名字:AF_INET
AF_INET6被用于ipv6,还有一些其他的地址家族,在网络编程中,大部分时候我们只使用AF_INET
1.简易版本的俩个程序之间的交互
'''服务端'''
import socket
# 1.创建一个socket对象
server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.bind(('127.0.0.1', 8080))
# 3.半连接层
server.listen(5)
# 4.开业,等待接客
sock, address = server.accept()
print(sock, address)
# 5.数据交互
sock.send(b'welcome to shanxi~')
data = sock.recv(1024) # 接收客户端发送的数据 1024bytes
print(data)
# 6.断开连接
sock.close() # 断连接
server.close() # 关机
'''客户端'''
import socket
# 1.创建一个socket对象
client = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.连接服务端
client.connect(('127.0.0.1', 8080))
# 3.数据交互
data = client.recv(1024)
print(data)
client.send(b'so beautiful!!!')
# 6.断开连接
client.close() # 断连接
client.close() # 关机
2.优化版本的俩个程序之间的交互
1.send与recv
客户端与服务端不能同时执行同一个
有一个收 另外一个就是发
有一个发 另外一个就是收
不能同时收或者发!!!
2.消息自定义
input获取用户数据即可(主要编码解码)
3.循环通信
给数据交互环节添加循环即可
4.服务端能够持续提供服务
不会因为客户端断开连接而报错
异常捕获 一旦客户端断开连接 服务端结束通信循环 调到连接处等待
5.消息不能为空
判断是否为空 如果是则重新输入(主要针对客户端)
6.服务端频繁重启可能会报端口被占用的错(主要针对mac电脑)
from socket import SOL_SOCKET,SO_REUSEADDR
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
7.客户端异常退出会发送空消息(针对mac linux)
针对接收的消息加判断处理即可
import socket
# 1.创建一个socket对象
server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.bind(('127.0.0.1', 8080))
# 3.半连接层
server.listen(5)
# 4.开业,等待接客
sock, address = server.accept()
print(sock, address)
# 5.数据交互
while True:
try:
msg = input('请输入要发送给客户端的信息>>>:').strip()
sock.send(msg.encode('utf8'))
data = sock.recv(1024) # 接收客户端发送的数据 1024bytes
print(data.decode('utf8'))
except ConnectionError:
sock.close()
break
import socket
# 1.创建一个socket对象
client = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.连接服务端
client.connect(('127.0.0.1', 8080))
# 3.数据交互
while True:
data = client.recv(1024)
print(data.decode('utf8'))
msg = input('请输入要发送给服务端的信息>>>:').strip()
if len(msg) == 0: continue
client.send(msg.encode('utf8'))
半连接层
当服务器在响应了客户端的第一次请求后会进入等待状态,会等客户端发送的ack信息,这时候这个连接就称之为半连接
半连接池其实就是一个容器,系统会自动将半连接放入这个容器中,可以避免半连接过多而保证资源耗光
产生半连接池的俩种情况:
客户端无法返回ACK信息
服务器来不及处理客户端的连接请求
设置的最大等待人数 >>>: 节省资源 提高效率
server.listen(5) 指定5个等待席位
主要是为了做缓冲 避免太多无效等待
粘包现象
1.粘包现象产生的本质
sock.send(b'jason')
data = client.recv(1024)
print(data)
对方只给了5个字符,但是后面我们返回是要返回1024个
粘包现象的产生是因为
1.TCP的特性产生的
流水协议:所有的数据类似于流水,连接在一起
触发条件是数据量很小 并且时间间隔很多,那么就会自动组织到一起
2.recv
我们不知道即将要接收的数据量多大 如果知道的话不会产生也不会产生黏包
粘包是接收长度没对上导致的
控制recv接收的字节数与之对应(你发多少字节我收多少字节)
2.粘包问题解决方案
struct模块
struct模块无论数据长度是多少 都可以帮你打包成固定长度
然后基于该固定长度 还可以反向解析出真实长度
这里利用struct模块里的
struct.pack() 方法来实现打包(将真实数据长度变为固定长度的数字)
struct.unpack() 方法解包(将该数字解压出打包前真实数据的长度)
pack unpack模式参数对照表(standard size 转换后的长度)
import struct
info = '下午上课 以后可能是常态!'
print(len(info)) # 13 数据原本的长度
res = struct.pack('i', len(info)) # 将数据原本的长度打包
print(len(res)) # 4 打包之后的长度是4
ret = struct.unpack('i', res) # 将打包之后固定长度为4的数据拆包
print(ret[0]) # 13 又得到了原本数据的长度
info1 = '打起精神啊 下午也需要奋斗 也需要认真听 客服困难 你困我也困!!!'
print(len(info1)) # 34
res = struct.pack('i', len(info1)) # 将数据原本的长度打包
print(len(res)) # 4 打包之后的长度是4
ret = struct.unpack('i', res)
print(ret[0]) # 34
struct模块针对数据量特别大的数字没有办法打包
粘包问题解决思路
思路
1.先将真实数据的长度制作成固定长度 4
2.先发送固定长度的报头
3.再发送真实数据
1.先接收固定长度的报头 4
2.再根据报头解压出真实长度
3.根据真实长度接收即可
终极方案
服务器端
1.先制作一个发送给客户端的字典
2.制作字典的报头
3.发送字典的报头
4.发送字典
5.再发真实数据
客户端
1.先接收字典的报头
2.解析拿到字典的数据长度
3.接收字典
4.从字典中获取真实数据的长度
5.循环获取真实数据