【网络编程】第3回 周总结
1. 软件开发架构
1.1 什么是软件开发架构
- 编写项目之前需要遵循的代码层面上的规范,代码运行的流程,环节步骤
1.2 软件开发架构
1.2.1 C/S 架构
- c:client 客户端 s:server 服务端
- 计算机计算机上下载的各个互联网公司的app软件 本质其实是客户端
- 客户端可以看成是即将去消费的客人,客户端可以看成是即将去消费的客人
1.2.2 B/S 架构
- b:broswer 浏览器 s:server 服务器
- b/s架构本质也是c/s架构,通过浏览器来充当各个服务端的客户端,用于想要体验服务不需要下载指定的客户端
1.2.3 服务端特征
- 24小时不间断提供服务
- 固定的地址(不能经常改变)
- 可以同一时间服务多人
1.3 架构优劣势
1.3.1 cs架构
- 优势:下载对应的客户端,可以再客户端软件内高度定制相关服务
- 劣势:使用必须先下载客户端,比较繁琐
1.3.2 bs架构
- 优势:不需要下载客户端,避免快速体验服务
- 劣势:定制花里胡哨的功能较为繁琐
1.4 架构发展趋势
- 统一接口原则,后面就是cs和bs交错使用,避免各自的劣势(可以方便用户使用,更重要的是可以给自己圈用户便于后期收割)
2. 网路编程简介
2.1 如何理解网络编程
- 基于互联网写代码,程序可以实现远程数据交互
2.2 网络编程的目的
- 网络编程的本质是为了解决计算机之间远程数据交互
2.3 网络编程的意义
- 学习完网络编程之后,我们就可以编写一个cs架构的软件
2.4 网络编程的起源
- 任何先进的技术一般都是来源军事(网络编程由美国军方开发,没有网络编程的时候,如果两台计算机之间要交互数据只能使用硬盘拷贝)
2.5 网络编程的要求
- 早期的电话:必须有电话线
- 大屁股电脑:必须要有网线
- 笔记本电脑:必须要有网卡
- 计算机之间要实现远程数据交互,首先条件就是要有物理连接介质
3. OSI七层协议
3.1 七层合并优化
- 规定了计算机涉及到数据远程交互的时候,必须要经过的部件/流程,所有的计算机在涉及到网络传输这块必须要有相同的零部件
- 七层:应用层,表示层,会话层,传输层,网络层,数据链路层,物料连接层(应-表-会-传-网-数-物)
- 合并成五层:应用层,传输层,网络层,数据链路层,物理连接层
- 优化成四层:应用层,传输层,网络层,网络接口层
- 图表
3.2 各层特征
3.2.1 数据顺序
- 数据发送出去的时候,是从上往下走
- 数据接受回来的时候,是从下往上走
3.2.2 物料连接层
- 保证物理连接介质的条件,传入电信号(主要研究插线网线的情况)
3.2.3 数据链路层
- 规定了电信号的分组方式
- 规定每台计算机都必须有一块网卡
- 网卡上必须有一串>>>:电脑的以太网址址(身份证号)mac地址以太网地址\mac地址:由12位16进制数组成的,前6位:产品编号 后6位:生产流水线号,既然mac地址相当于电脑的身份证号,也就意味着可以根据该地址查找计算机(可以基于mac地址实现数据交互)
3.2.4 网络层
- IP协议:规定了任何接入互联网的计算机都必须有一个IP地址
- IP地址:IPV4:点分十进制 最小:0.0.0.0 最大:255.255.255.255
- 1PV6 :能够给地球上每一粒沙分一个IP地址
- IP特征:每个IP都自带定位。ps:ip代理
3.2.5 传输层
- PORT协议:端口协议:规定了一台计算机上的每一个正在运行的应用程序都必须有一个端口号,端口号相当于是计算机用来管理多个应用程序的标记
- 特征
1.端口号范围:0-65535
2.端口号是动态分配的
3.同一时间同一台计算机端口号不能冲突
4.
0-1024:一般是操作系统内部需要使用的
1024-8000:一般是常见的软件已经使用了
8000+:我们平时写代码可以使用8000之后的端口号
- IP+PORT:IP:用于标识全世界任意一台接入互联网的计算机,PORT:用于标识一台计算机上的某个应用程序,IP+PORT:用于标识全世界任意一台接入互联网的计算机上的某一个具体的程序
- 什么是网址:(urL)统一定位符,url的本质:其实就是IP+PORT
3.2.6 传输层
- PORT协议:端口协议:规定了一台计算机上的每一个正在运行的应用程序都必须有一个端口号,端口号相当于是计算机用来管理多个应用程序的标记
- 本质:规定了数据传输所遵循的规则,数据传输能够遵循的协议有很多,TCP和UDP是较为常见的两个
- TCP协议:TCP--传输控制协议,提供是面向连接、可靠的字节流服务。当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
- UDP协议: UDP--用户数据报协议,是一个简单的面向数据报的运输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快
3.2.7 应用层
- 主要取决于程序员自己采用什么策略和协议,常见协议有:HTTP HTTPS FTP...
3.2 三层握手
- 建立双向通道
- 洪水攻击:同时让大量客户端朝服务端发送建立TCP连接的请求
3.3 四次挥手
- 断开双向通道,中间的两步不能合并(需要有检查的时间)
3.4 整理
3.4.1 判断
基于TCP传输数据非常的安全 因为有双向通道?
- 前半句对,后半句错
- 基于TCP传输数据,数据不容易丢失,不容易丢失的原因在于二次确认机制,每次发送数据都需要返回确认消息 否则在一定的时间会反复发送
3.4.2 总结
- 基于UDP协议发送数据,没有任何的通道也没有任何的限制,UDP发送数据没有TCP安全(没有二次确认机制)
3.4.3 对比
- TCP类似于打电话:你一句我一句 有来有往
- UDP类似于发短信:只要发送了 不管别人看没看到 也不管回不回复
5. 网路相关名词
5.1 交换机
- 能够让交换机的多台计算机实现彼此互联
5.2 以太网通信(mac通信)
- 有了交互机之后,根据电脑的mac地址就可以实现数据交互
- 广播:先在交换机中吼,所以接入交换机的设备都能收到
- 单播:只有被查找设备,才会回复相应信息
- 缺陷:1.mac地址通信仅限于局域网,2.接入交换机的设备过多,可能会造成广播风暴(类似于所以人同时吼)
- 图解
5.3 局域网
- 某个固定区域组成的网络,广域网可以看成是更大区域的局域网
5.4 路由器
- 将多个局域网连接到一起的设备
- 图解
6. 域名解析,DNS服务器
6.1 域名解析
- 域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务。IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。
- 域名解析也叫域名指向、服务器设置、域名配置以及反向IP登记等等。说得简单点就是将好记的域名解析成IP,服务由DNS服务器完成,是把域名解析到一个IP地址,然后在此IP地址的主机上将一个子目录与域名绑定。
6.2 DNS服务器
- DNS(Domain Name Server,域名服务器)是进行域名(domain name)和与之相对应的IP地址 (IP address)转换的服务器。DNS中保存了一张域名(domain name)和与之相对应的IP地址 (IP address)的表,以解析消息的域名。 域名是Internet上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。域名是由一串用点分隔的名字组成的,通常包含组织名,而且始终包括两到三个字母的后缀,以指明组织的类型或该域所在的国家或地区。
7. socket套接字
7.1 基于文件类型的套接字家族
- 套接字家族的名字:AF_UNIX
- unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信
7.2 基于网络类型的套接字家族
- 套接字家族的名字:AF_INET
- 所有地址家族中,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET
7.3 信息交互
7.3.1 服务端代码
import socket
# 1.创捷一个socket对象
server = socket.socket() # 括号内什么都不写,默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.bind(('127.0.0.1', 8080)) # 127.0.0.1本地回环地址(只允许自己的机器访问)
# 3.半连接池
server.listen(5)
# 4.开业 等待接客
sock, address = server.accept()
print(sock, address) # sock是双向通道,address是客户端地址
# 5.数据交互
sock.send(b'hello big baby') # 朝客户端发送数据
data = sock.recv(1024) # 接收客户端发送的数据 1024bytes
print(data)
# 断开连接
sock.close() # 断链接
server.close() # 关机
7.3.2 客户端代码
import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('127.0.0.1', 8080))
# 3.数据交互
data = client.recv(1024) # 接收服务端发送的数据
print(data)
client.send(b'hello sweet server') # 朝服务端发送数据
# 4. 关闭
client.close()
7.3.3 运行顺序
- 先运行服务端
- 在运行客户端
- 传信息再到服务端查看
7.4 代码优化
7.4.1 send与recy
- 客户端与服务端不能同时执行同一个
- 有一个收,另外一个就是发
- 有一个发,另外一个就是收
- 不能同时收或者发
7.4.2 消息自定义
- input获取用户数据即可(主要编码解码)
7.4.3 循环通信
1.给数据交互环节添加循环即可
7.4.4 服务端能够持续提供服务
- 不会因为客户端断开连接而报错,异常捕获 一旦客户端断开连接 服务端结束通信循环
7.4.5 消息不能为空
- 判断是否为空 如果是则重新输入(主要针对客户端)
7.4.6 服务端频繁重启可能会被占用的错(主要针对mac电脑)
- from socket import SOL_SOCKET,SO_REUSEADDR
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
7.4.7 客户端异常退出会发送空消息(针对mac linux)
- 针对接收的消息加判断处理即可
7.5 优化代码展示
7.5.1 服务端
import socket
from socket import SOL_SOCKET, SO_REUSEADDR
# 1.创建一个socket对象
server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 就是它,在bind前加
server.bind(('127.0.0.1', 8080)) # 127.0.0.1本地回环地址(只允许自己的机器访问)
# 3.半连接池(暂且忽略)
server.listen(5)
# 4.开业 等待接客
while True:
sock, address = server.accept()
print(sock, address) # sock是双向通道 address是客户端地址
# 5.数据交互
while True:
try:
msg = input('请输入发送给客户端的消息>>>:').strip()
if len(msg) == 0: continue
sock.send(msg.encode('utf8')) # 朝客户端发送数据
data = sock.recv(1024) # 接收客户端发送的数据 1024bytes
if len(data) == 0: #
break
print(data.decode('utf8'))
except ConnectionResetError:
sock.close()
break
7.5.2 客户端
import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
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:
msg = '手抖了一下 暂无消息'
client.send(msg.encode('utf8')) # 朝服务端发送数据
7.5.3 运行结果展示
7.6 半连接池
server.listen(5):主要是为了做缓冲 避免太多无效等待
8. 黏包
8.1 黏包现象
8.1.1 服务端代码
import socket
# 1.创建一个socket对象
server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.bind(('127.0.0.1', 8080)) # 127.0.0.1本地回环地址(只允许自己的机器访问)
server.listen(5)
sock, address = server.accept()
print(sock.recv(5))
print(sock.recv(5))
print(sock.recv(5))
8.1.2 客户端代码
import socket
# 1.创建一个socket对象
server = socket.socket() # 括号内什么都不写 默认就是基于网络的TCP套接字
# 2.绑定一个固定的地址(ip\port)
server.bind(('127.0.0.1', 8080)) # 127.0.0.1本地回环地址(只允许自己的机器访问)
server.listen(5)
sock, address = server.accept()
print(sock.recv(5))
print(sock.recv(5))
print(sock.recv(5))
8.1.3 结果展示
8.1.4 结论
- 只有TCP有黏包现象,UDP永远不会黏包
- TCP特性>>流式协议:所有的数据类似于水流,连接在一起的。ps:数据量很小,并且时间间隔很多,那么就会自动组织到一起
- recv:我们不知道即将要接收的数据量多大,如果知道的话不会产生也不产生黏包
8.2 struct 模块
8.2.1 代码讲解
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
8.2.2 总结
- struct模块无论数据长度是多少,都可以帮你打包成固定长度,然后基于该固定长度 还可以反向解析出真实长度
- struct模块针对数据量特别打的数字没有办法打包
8.2.3 思路
1. 先将真实数据的长度制作成固定长度
2. 发送送固定长度的报头
3. 再发送真实数据
1. 先接收固定长度的报头
2. 再根据报头解压出真实长度
3. 根据真实长度接收即可
8.2.4 图表
8.3 解决黏包问题的终极方案
8.3.1 服务端
- 先构造一个数据的详细字典
- 对字典数据进行打包处理,得到一个客户端固定长度的数据
- 将上述打包之后的数据发送给客户端
- 将字典数据发给客户端
- 将真实数据发送给客户端
8.3.2 客户端
- 先接收固定长度的数据
- 根据固定长度解析出即将要接收的字典真实长度
- 接收字典数据
- 根据字典数据,获取出真实的数据的长度
- 接收真实数据长度
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?