一、软件开发架构
什么是软件开发架构?
编写项目前需要遵循代码层面上的规范
代码运行的流程 环节以及步骤
软件开发结构
总共分为两层架构 CS架构 BS架构(C客户端(Client) S服务端(Server) B浏览器(Broswer))
客服端 就是在网络上下载的各个互联网公司的app软件
服务端 可以看成是给你提供服务的软件(什么软件给你提供什么服务)
浏览器 通过浏览器来充当服务端的客服端 想体验服务 但是可以不用下载客户端
服务端的特征
1. 24小时不间断的提供服务(打游戏想什么时候玩就什么时候玩)
2. 固定的地址(固定的软件或者RLl 要是一天更新一次天天还去找怎么行)
3. 可以同一时间服务多人(总不能微信我用了你就不能用了吧)
架构优劣势
CS架构
优势: 下载对应的客户端 可以在客户端软件内定制或更新更多优质的服务
劣势: 使用必须下载客户端比较繁琐(有的软件好几个G)
BS架构
优势: 不需要下载客户端就能够直接体验服务例如网页版的什么软件
劣势: 不能高度定制想要的功能 需要遵循浏览器的模式
架构发展趋势
统一接口原则(微信 支付宝 里面的小程序可以直接体验其他软件的服务)
二、网络编程简介
如何理解网络编程
基于互联网编写代码 可以实现程序间的远程数据交互
网络编程的目的
网络编程的本质就是为了解决计算机之间的远程数据交互
网络编程的意义
学习完网络编程之后 我们就可以编写一个CS架构的软件
网络编程的起源
所有先进的技术一般都是源于军事
网络编程由美国军方开发 没有网络编程两台计算机需要交互数据 只能使用硬盘拷贝(相隔十万八千里太麻烦了)
网络编程的要求
早期电话电脑 都需要有电话线网线网卡
计算机之间想要实现远程数据交互 首要条件就是要有物理连接的介质否则无法相通
三、OSI七层协议
七层协议规定了计算机涉及到远程交互的时候 必须要有的部件和流程
七层 应用层>表示层>会话层>传输层>网络层>数据链路层>物理连接层>
五层 应用层>传输层>网络层>数据链路层>物理连接层 (每一层都有各自的功能和规范)
各层特征
物理连接层
保证物理连接介质的条件 传递电信号(主要研究插网线的情况)
数据链路层
规定了电信号的分组方式
每台电脑必须有一块网卡(电脑的以太网地址相当于身份证)简称MAC地址
以太网地址由12位至16进制数组成的 前6位商品编号 后六位生产线水流号
网络层
IP协议
规定了任何接入互联网的计算机都必须有一个IP地址
IP地址
IPV4:点分十进制
最早0.0.0.0 最大255.255.255.255
随着社会的发展 同时能够上网的人越来越多了 ip地址不够用了
IPV6:
能够给地球上的每一粒沙子分一个IP地址(现在慢慢普及)
IP特征
每个IP都自带定位(这就是网警为什么可以找到你的原因)
传输层
PORT协议
端口协议
规定了一台计算机上正在运行的每一个运用都必须有一个端口号
端口号相当于是计算机用来管理多个应用程序的标记
端口号特征
端口号范围: 0-65535
端口号是动态分配
同一时间同一计算机端口号不能冲突
0-1024:一般是操作系统内部需要使用的
1024-8000:一般是常见的软件已经使用了
8000+:我们平时写代码可以使用8000之后的端口号
传输层
PORT协议
TCP协议与UDP协议
数据传输能够遵循的协议有很多 TCP和UDP是较为常见的两个
TCP协议
三次握手 四次挥手
建立双向通道(图1) 断开双向通道(图2)
TCP传输数据非常安全 因为有双向通道 不容易丢失(每次发送之前保存一份)
每次发送数据都需要返回确认消息是否送达 否则在一定的时间会反复发送
TCP类似于打电话:你一句我一句 有来有往
UDP协议
基于UDP协议发送数据 没有任何的通道也没有任何的限制
UDP发送数据没有TCP安全(没有二次确认机制)
UDP类似于发短信:只要发送了 不管别人看没看到 也不管回不回复
应用层
主要取决于程序员自己采用什么策略和协议
常见协议有:HTTP HTTPS FTP...
IP+PORT
IP 用于标识全世界任意一台接入互联网的计算机
PORT 用于标识一台计算机上的某个应用程序
IP+PORT 用于标识全世界任意一台接入互联网的计算机上的某一个具体的程序
网址
网址RUL 统称统一资源定位符
RUL本质
就是IP+PORT
五、网络相关名词
交换机
能够让接入交换机的多台计算机实现彼此互联
以太网
原理
有了交换机之后 根据电脑的MAC地址就可以实现数据交互
广播:先有了交换机 所有接入交换机的设备都能收到
单播:只有被查找的设备 才会回复相应信息
缺陷
MAC地址通信仅限于局域网
接入交换机的设备过多 可能会造成广播风暴(很多设备一起广播被查找的设备接收不到)
局域网
由某个固定区域组成的网络
广域网
由更大的区域组成的局域网
路由器
将多个局域网连接到一起的设备
八、Socket套接字
基于文件的类型套接字家族 AF_UNIX
基于网络类型的套接字家族 AF_INET
基本格式:
服务端
"""运行程序的时候 肯定是先确保服务端运行 之后才是客户端"""
import socket
server = socket.socket() # 1.创建一个socket对象 # 括号内什么都不写 默认就是基于网络的TCP套接字
server.bind(('127.0.0.1', 8080)) # 2.绑定一个固定的地址(ip\port) # 127.0.0.1本地回环地址(只允许自己的机器访问)
server.listen(5) # 3.半连接池(暂且忽略)
sock, address = server.accept() # 4.开业 等待接客
print(sock, address) # sock是双向通道 address是客户端地址
sock.send(b'hello big baby~') # 5.数据交互 # 朝客户端发送数据
data = sock.recv(1024) # 接收客户端发送的数据 1024bytes
print(data)
sock.close() # 断开链接
server.close() # 关闭服务器
客户端
'''服务端关机了 客户端也会关机'''
client = socket.socket() # 1.产生一个socket对象
client.connect(('127.0.0.1', 8080)) # 2.连接服务端(拼接服务端的ip和port)
data = client.recv(1024) # 3.数据交互# 接收服务端发送的数据
print(data)
client.send(b'hello sweet server') # 朝服务端发送数据
client.close() # 4.关闭
简单模拟一下聊天
"""运行程序的时候 肯定是先确保服务端运行 之后才是客户端"""
import socket
server = socket.socket() # 1.创建一个socket对象 # 括号内什么都不写 默认就是基于网络的TCP套接字
server.bind(('127.0.0.1', 8081)) # 2.绑定一个固定的地址(ip\port) # 127.0.0.1本地回环地址(只允许自己的机器访问)
server.listen(5) # 3.半连接池(暂且忽略)
sock, address = server.accept() # 4.开业 等待接客
print(sock, address) # sock是双向通道 address是客户端地址
while True:
msg = input('请输入发给客户端的消息>>>:').strip()
sock.send(msg.encode('utf8'))
data = sock.recv(1024)
print(data.decode('utf8'))
import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('127.0.0.1', 8081))
while True: # 3.数据交互
data = client.recv(1024) # 接收服务端发送的数据
print(data.decode('utf_8'))
msg = input('请输入你要发给服务端的消息>>>:').strip()
client.send(msg.encode('utf8')) # 朝服务端发送数据
九、半连接池
半连连接池server.listen(5)
意思就是同时可以运行五个客户端 如果多于五个则会报错
需要等待前面五个客户端其中一个结束 则会有多余的位置给到第六个
十、黏包问题
1.TCP特性
流式协议:所有的数据类似于水流 连接在一起的
ps:数据量很小 并且时间间隔很多 那么就会自动组织到一起
2.recv
我们不知道即将要接收的数据量多大 如果知道的话不会产生也不会产生黏包
server
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
sock, address = server.accept()
sock.send(b'LebronJames')
sock.send(b'hahaha')
sock.send(b'shiwoa')
client
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
data = client.recv(1024)
print(data)
data = client.recv(1024)
print(data)
data = client.recv(1024)
print(data)
reseult: b'LebronJameshahahashiwoa' b'' b''
'''会发现我们的结果会黏到一起 它的最大内存是1024bytes所以我们计算不到它是多大'''
'''所以我们需要学习一个新的模块 struct'''
import struct
info = '啊对对对 我的偶像就是LebronJames!!!'
print(len(info)) # 25获取内容数据个数
res = struct.pack('i', len(info)) # 将数据原本的长度打包 i 是一个模式
print(len(res)) # 4 打包之后的长度是4
ret = struct.unpack('i', res) # 将打包之后固定长度为4的数据拆包
print(ret[0]) # 25 又得到了原本数据的长度
'''
struct模块无论数据长度是多少 都可以帮你打包成固定长度
然后基于该固定长度 还可以反向解析出真实长度
struct模块针对数据量特别大的数字没有办法打包!!!
'''
思路
1.先将真实数据的长度制作成固定长度 4
2.先发送固定长度的报头
3.再发送真实数据
1.先接收固定长度的报头 4
2.再根据报头解压出真实长度
3.根据真实长度接收即可
server
import socket
import os
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 8081))
server.listen(5)
while True:
sock, address = server.accept()
while True:
# 1.先构造数据文件的字典
file_dict = {
'file_name': 'LebronJames.txt',
'file_size': os.path.getsize(r'LebronJames.txt'),
'file_desc': '内容很精彩 一定不要错过',
'file_root': 'LebronJames'
}
# 2.将字典打包成固定长度的数据
dict_json = json.dumps(file_dict)
file_bytes_dict = len(dict_json.encode('utf8'))
dict_len = struct.pack('i', file_bytes_dict)
# 3.发送固定长度的字典报头
sock.send(dict_len)
# 4.发送真实字典数据
sock.send(dict_json.encode('utf8'))
# 5.发送真实数据
with open(r'LebronJames.txt', 'rb') as f:
for line in f:
sock.send(line)
break
Client
import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 8081))
while True:
# 1.先接收长度为4的报头数据
header_len = client.recv(4)
# 2.根据报头解包出字典的长度
dict_len = struct.unpack('i', header_len)[0]
# 3.直接接收字典数据
dict_data = client.recv(dict_len) # b'{"file_name":123123123}'
# 4.解码并反序列化出字典
real_dict = json.loads(dict_data)
print(real_dict)
# 5.从数据字典中获取真实数据的各项信息
total_size = real_dict.get('file_size') # 32423423423
file_size = 0
with open(r'%s' % real_dict.get('file_name'), 'wb') as f:
while file_size < total_size:
data = client.recv(1024)
f.write(data)
file_size += len(data)
print('文件接收完毕')
break