网络编程
软件开发架构
什么是架构?
编写项目之前需要遵循的代码层面上的一些规范(例如,运行的步骤,流程等)
c/s架构
c:client 客户端
s:server 服务端
计算机或手机下载的各类app本质上就是客户端
优势是:下载对应的客户端,可以在客户端体验高度定制服务
劣势是:使用必须下载!!
b/s架构
b:broswer 浏览器
s:server 服务器
通过浏览器来充当各个服务器的客户端,用于想要体验服务又不用下载客户端
劣势是:网页的各种功能花里胡哨,观看效果不佳
架构发展趋势
1.统一接口原则
(可以通过一个接口去往其他程序)
2.cs和bs交叉使用,避免各自的劣势
网络编程
网络编程简介
基于网络编写代码,程序可以实现远程数据交互
网络编程的起源
目的:为了解决计算机之间远程数据交互
网络编程的意义:学习完就可以编写cs架构
网络编程的起源:任何先进的技术一般都来源于军事
网络编程的要求:计算机之间想要实现远程数据交互,首要条件就是要有物理连接介质
OSI七层协议
规定了计算机涉及到远程交互的时候必须要经过相同的流程
数据发送出去的时候 是从上往下走
数据接收回来的时候 是从下往上走
应用层
主要是程序员自己编写代码的地方,有什么协议和规范取决于程序员自己
常见协议有:HTTP,HTTPS等等
表示层
会话层
传输层
1.port协议
端口协议:规定了一台计算机上的每一个正在运行的app都必须要有一个端口号
端口号相当于计算机用于管理多个app的标记
端口号特征:
1.端口号范围:0-65535
2.端口号是动态分配的
3.同一时间同一台计算机的端口号是不能冲突的
4.
0-1024:一般是操作系统内部需要使用的
1024-8000:一般是常见的软件已经使用了
8000+:我们平时写代码可以使用8000之后的端口号
IP:用于标识全世界任意一台接入互联网的计算机
PORT:用于标识一台计算机上的某个app
IP+PORT:用于标识任意一台接入互联网的计算机上的某个app、
网址(URL):统一资源定位符
URL的本质就是IP+PORT
2.TCP协议与UDP协议
规定了数据传输所遵循的规则
ps:数据传输能够遵循的协议有很多,TCP协议与UDP协议是比较常见的两个
2.TCP协议
1.三次握手 (建连接)
1.三次握手 (建连接)建立双向通道
在发数据前必须和对方建立可靠的连接,连接必须要经过三次,对话才能建立起来
eg图1:
c(表示客户端)s(表示服务端)
第一次握手:是c朝s发出请求,请求可以建立连接
第二次握手:是s收到c的请求,并发出可以建立连接的信号
(此时的数据通道是单向的,只有c可以向s发送数据)
第三次握手:是c朝s发送确认,并允许s和自己建立连接,双方建立连接,c和s可以传输数据了
(此时双方都可以基于彼此建立的连接互相发送数据了)
ps:基于tcp传输数据很安全的原因是TCP会二次确认,数据不容易丢失。每次发送出去的消息都必须要返回确认消息,否则的话再短时间内会反复发送
洪水攻击:同时有多个客户端向服务端发送TCP连接请求!
2.四次挥手 (断连接)
基于三次挥手之后,想要断开连接
eg下图:
第一次挥手:c朝s发起请求,想要断开连接
第二次挥手:s向c发起确认,允许断开连接
(此时的c已经不可以向s发送数据了 ,但是s还是可以给c发送数据)
从第二次到第三次挥手之间有一个时间间隔,s可能还需要有接受数据的时间
第三次挥手:s朝c发起请求,想要断开连接
第四次挥手:c向s发起确认,允许断开连接
(此时的双方都不可以再互相发送数据了)
3.UDP协议
基于UDP发送的数据没有任何通道也没有任何的限制,缺点是数据容易丢失
(可以基于发送过程做一些优化操作)
服务端代码
import socket
res = socket.socket(type=socket.SOCK_DGRAM)
res.bind(('127.0.0.1',8080))
msg,address = res.recvfrom(1024)
# 接收客户端发送过来的消息
print('msg>>>%s'%msg.decode('utf8'))
# 由于UDP没有双向通道 所以每次发送消息都会带着它的地址
print('address>>>>:',address)
res.sendto('服务端'.encode('utf8'),address)
客户端代码
import socket
c = socket.socket(type=socket.SOCK_DGRAM)
server_address = ('127.0.0.1',8080)
c.sendto('客户端'.encode('utf8'),server_address)
msg,address = c.recvfrom(1024)
print('msg>>>"%s'%msg.decode('utf8'))
print('address>>>>>:',address)
网络层
ip协议
:规定了任何接入互联网的计算机都必须有IP地址
每个ip地址都自带定位
IP地址:
IPV4:点分十进制
最小:0.0.0.0
最大:255.255.255.255
IPV6:
无限大了 能够给地球上的每一粒沙子都分一个ip地址
数据链路层
1.规定了电信号的分组方式
2.规定了每台计算机都必须有一块网卡,网卡上必须有一串记录(电脑的以太网址也称mac地址,身份证号)
3.mac地址:由12位16进制数组成
前6位:厂商编号
后6位:生产流水线号
可以根据该地址查找到计算机,基于mac地址实现数据交互
物理连接层
保证物理连接介质的条件,传递电信号(主要研究插网线情况)
网络相关设备
1.交换机
能够让接入交换机的多台计算机实现彼此互联
2.以太网通信(mac通信)
有了交换机以后,根据电脑的mac地址就可以实现数据交互
广播:就是在交换机中发出请求,所有接入交换机的设备都能收到
单播:只有被查找设备才会回复相应信息
缺陷:1.mac地址通信仅限于局域网
2.接入交换机的设备过多的话 可能会造成广播风暴
3.局域网
有某个固定的区域组成的网络
4.路由器
将多个局域网连接到一起的设备
socket套接字
主要作用于软件层和应用层中间 ,作为一个接口。如果没有socket,那么我们就需要手动操作各个层之间的代码了!!
发展史
1.一开始,套接字被设计用在同一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或IPC。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
2.基于文件类型的套接字家族:套接字家族的名字:AF_UNIX
3.基于网络类型的套接字家族:套接字家族的名字:AF_INET,所有地址家族中,AF_INET是使用最广泛的一个
socket实例
先运行服务端再运行客户端
1.服务端
import socket
# 1.先创建一个socket对象
server = socket.socket()
# 绑定一个固定的地址IP和port
server.bind(('192.168.1.169',8080))
# 半连接池
server.listen(5)
# 开业等待接口
sock,address = server.accept()
print(sock,address) # sock是双向通道 address是客户端地址65459
# 数据交互
sock.send(b'hello') # 朝客户端发送数据
res=sock.recv(1024) # 接收客户端发送的数据
print(res)
# 断开连接
sock.close() # 断连接
server.close() # 关机
------------
2.客户端
import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('192.168.1.169', 8080))
# 3.数据交互
res=client.recv(1024) # 接收服务端发送的数据
print(res)
client.send(b'hei') # 朝服务端发送数据
# 关闭
client.close() # 直接把双向通道关闭
代码优化
1.send与recv
客户端与服务端不能同时执行同一个
有一个收 另外一个就是发
有一个发 另外一个就是收
不能同时收或者发!!!
2.消息自定义
input获取用户数据即可(主要编码解码)
3.循环通信
给数据交互环节添加循环即可
4.服务端能够持续提供服务
不会因为客户端断开连接而报错
异常捕获 一旦客户端断开连接 服务端结束通信循环 调到连接处等待
5.消息不能为空
判断是否为空 如果是则重新输入(主要针对客户端)
# 服务端
import socket
# 1.先创建一个socket对象
server = socket.socket()
# 绑定一个固定的地址IP和port
server.bind(('192.168.1.169',8080))
# 半连接池
server.listen(5)
# 开业等待接口
while True:
sock,address = server.accept()
print(sock,address) # sock是双向通道 address是客户端地址65459
# 数据交互
while True:
try:
msg = input('请输入你要发送的内容》》》:').strip()
if len(msg) == 0:
continue
sock.send(msg.encode('utf8')) # 朝客户端发送数据
res = sock.recv(1024) # 接收客户端发送的数据
print(res.decode('utf8'))
except ConnectionResetError:
break
# 客户端
import socket
# 1.产生一个socket对象
client = socket.socket()
# 2.连接服务端(拼接服务端的ip和port)
client.connect(('192.168.1.169', 8080))
# 3.数据交互
while True:
res = client.recv(1024) # 接收服务端发送的数据
print(res.decode('utf8'))
msg = input('请输入你要发送的内容》》》》:').strip()
if len(msg) == 0:
msg = '重来重来!!!'
client.send(msg.encode('utf8')) # 朝服务端发送数据
半连接池
server.listen(5)
# 括号里的数字就是等待和服务端连接数据的客户端
这个功能主要是为了优化代码,避免无效等待
假设 现在有数不清的客户端想向服务端发数据 ,如果没有这个半连接池的话,是不是所有的用户都要等在外面 让服务端和上一个客户端交互完成再接着下一个,那么这个半连接池就是再告诉后面排队等待的用户 你的前面还有几个人再等着 你可以现在干别的事情,避免无效等待!!
粘包现象
粘包问题的产生
1.TCP特性
流式协议:所有的数据类似于水流 连接在一起的
ps:数据量很小 并且时间间隔很多 那么就会自动组织到一起
2.recv
我们不知道即将要接收的数据量多大 如果知道的话不会产生黏包
代码实例
# 服务端
import socket
s = socket.socket()
s.bind(('192.168.1.169',8080))
s.listen(5)
sock,address =s.accept()
sock.send(b'hello')
sock.send(b'hai')
sock.send(b'hahaha')
------------
# 客户端
import socket
res = socket.socket()
res.connect(('192.168.1.169', 8080))
c = res.recv(1024)
print(c.decode('utf8'))
c = res.recv(1024)
print(c.decode('utf8'))
c = res.recv(1024)
print(c.decode('utf8'))
打印结果:
hello
haihahaha # 出现了粘包现象
struct模块
简介
struct模块无论数据长度是多少 都可以帮你打包成固定长度
然后基于该固定长度 还可以反向解析出真实长度
ps : 对于数据量特别大的模块,会直接报错
粘包问题的解决方案
# 服务端
1.先将真实数据的长度制作成固定长度 4
2.先发送固定长度的报头
3.再发送真实数据
# 客户端
1.先接收固定长度的报头 4
2.再根据报头解压出真实长度
3.根据真实长度接收即可
代码实操
服务端
1.先构造一个详细的数据字典
2.对字典数据进行打包,获得一个打包之后的固定长度
3.发送打包以后的数据给客户端
4.将字典数据发送给客户端
4.发送真实数据给客户端
import os
import socket
import struct
import json
res = socket.socket()
res.bind(('192.168.1.169', 8080))
res.listen(5)
while True:
sock, address = res.accept()
while True:
# 1.先构建数据文件的字典
file_dict = {
'file_name': '视频合集',
'file_size': os.path.getsize(r'视频合集'),
'file_content': 'python课程',
'file_root': 'summer'
}
# 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.发送真实字典数据 (先把字典转为json格式 再发送 )
res = json.dumps(file_dict)
sock.send(res.encode('utf8'))
# 5.发送真实数据
with open(r'视频合集','rb')as f:
for line in f:
sock.send(line)
break
客户端
1.先接收固定长度的数据
2.根据固定长度解析出即将要接收的字典的真实长度
3.接收字典数据
4.根据字典长度获取真实字典的数据长度
5.接收真实长度
import json
import socket
import struct
s = socket.socket()
s.connect(('192.168.1.169', 8080))
# 1.先接收固定长度的数据报头
res = s.recv(4)
# 2.根据报头解析出字典的长度
dict_len = struct.unpack('i',res)[0]
# 3.接收字典数据
dict_data = s.recv(dict_len)
# 4.解码并反序列化出字典
real_dict = json.loads(dict_data)
print(real_dict)
# 5.从数据字典中获取真实数据的各项信息
total_size = real_dict.get('file_size')
# 获取数据 由于获取到的是一个文件 必须先读出来再循环打印
# file_size = 0
# with open(r'%s'%real_dict.get('file_name'),'wb')as f:
# while file_size < total_size:
# data = s.recv(1024)
# f.write(data)
# file_size += len(data)
# print('文件接收完毕')
# break
# 这里也可以直接接收具体字节数 因为我们前面已经知道他这个文件到底有多大了嘛 就不需要写的这么复杂
dict1 = s.recv(75)
print(dict1.decode('utf8'))
dict1 = s.recv(75)
print(dict1.decode('utf8'))
dict1 = s.recv(75)
print(dict1.decode('utf8'))
代码运行结果:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现