网络编程学习笔记
昨日内容回顾
- 软件开发架构
1.cs架构
2.bs架构
发展趋势:统一接口
- 网络编程简介
1.远程数据交互
2.物理连接介质是远程数据交互必备的条件
- OSI七层协议
1.七层:应表会传网数物
应用层
表示层
会话层
传输层
网络层
数据链路层
物理连接层
2.以太网协议
mac地址
3.IP协议
ip地址
公网IP:基于互联网就可以直接访问
需要花钱购买(实名注册)
私网IP:不能直接被互联网访问
正常情况下我们都是私网IP
4.PORT协议
端口号
- 网络相关名词
1.局域网
2.交换机
3.路由器
4.广播与单播
5.广播风暴
6.网址(URL)
域名(www.baidu.com) ip:port
今日内容概要
- TCP与UDP协议
- socket套接字编程
- 代码优化
- 半连接池
- TCP黏包问题及解决思路
今日内容详细
作业讲解
域名解析
域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。
DNS服务器
进行域名(domain name)和与之相对应的IP地址 (IP address)转换的服务器。
TCP与UDP协议
TCP和UDP都属于传输层,规定了数据传输所遵循的规则
ps:数据传输能够遵循的协议有很多 TCP和UDP是较为常见的两个
TCP协议
TCP类似于打电话:你一句我一句 有来有往
三次握手
建立双向通道
ps:洪水攻击
同时让大量的客户端朝服务端发送建立TCP连接的请求
四次挥手
断开双向通道
中间的两步不能合并(需要有检查的时间)
"""
基于TCP传输数据非常的安全 因为有双向通道(描述不够准确)
基于TCP传输数据,数据不容易丢失!!! 不容易丢失的原因在于二次确认机制
基于TCP发送的消息会在本地先保存该消息,确认对方收到然后接收反馈信息才会删除,否则在一定的时间内会频繁的多次发送直到对方确认或者超时为止
"""
UDP协议
UDP类似于发短信:只要发送了 不管别人看没看到 也不管回不回复
是不可靠的、不需要建立双下通道,数据传输效率高,但是可能会丢失
基于UDP协议发送数据 没有任何的通道也没有任何的限制
UDP发送数据没有TCP安全(没有二次确认机制)
应用层
主要取决于程序员自己采用什么策略和协议
常见协议有:HTTP HTTPS FTP...
socket套接字编程
socket套接字简介
编写一个cs架构的程序,实现数据交互,需要编写代码操作OSI七层,相当的复杂。由于操作OSI七层是所有cs架构的程序都需要经历的过程,所以有固定的模块。socket套接字是一门技术,socket模块提供了快捷方式,不需要自己处理每一层。
基于文件类型的套接字家族
套接字家族的名字:AF_UNIX
基于网络类型的套接字家族
套接字家族的名字:AF_INET
socket模块应用
服务端
"""运行程序的时候 肯定是先确保服务端运行 之后才是客户端"""
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
"""
recv和send接收和发送的都是bytes类型的数据
"""
print(data)
# 6.断开连接
sock.close() # 断链接
server.close() # 关机
客户端
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()
代码优化
1.send与recv
客户端与服务端不能同时执行同一个
有一个收 另外一个就是发
有一个发 另外一个就是收
不能同时收或者发!!!
2.消息自定义
input获取用户数据即可(主要编码解码)
3.循环通信
给数据交互环节添加循环即可
# 服务端
while True:
data = sock.recv(1024) # 听别人说话
print(data.decode('utf8'))
msg = input('请回复消息>>>:').strip()
sock.send(msg.encode('utf8')) # 回复别人说的话
# 客户端
while True:
msg = input('请输入你需要发送的消息>>>:').strip()
client.send(msg.encode('utf8')) # 给服务端发送消息
data = client.recv(1024) # 接收服务端回复的消息
print(data.decode('utf8'))
4.服务端能够持续提供服务
不会因为客户端断开连接而报错
异常捕获 一旦客户端断开连接 服务端结束通信循环 调到连接处等待
while True:
try:
data = sock.recv(1024) # 听别人说话
if len(data) == 0:
break
print(data.decode('utf8'))
msg = input('请回复消息>>>:').strip()
if len(msg) == 0:
msg = '太忙 暂无消息'
sock.send(msg.encode('utf8')) # 回复别人说的话
except Exception:
break
'''
客户端如果异常断开 服务端代码应该重新回到accept等待新的客人
'''
5.消息不能为空
判断是否为空 如果是则重新输入(主要针对客户端)
# 客户端
while True:
msg = input('请输入你需要发送的消息>>>:').strip()
# 用户如果什么都不输 直接回车 那么不该往下走
if len(msg) == 0:
continue
client.send(msg.encode('utf8')) # 给服务端发送消息
# 服务端
msg = input('请回复消息>>>:').strip()
if len(msg) == 0:
msg = '太忙 暂无消息'
6.服务端频繁重启可能会报端口被占用的错(主要针对mac电脑)
from socket import SOL_SOCKET,SO_REUSEADDR
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
7.客户端异常退出会发送空消息(针对mac linux)
针对接收的消息加判断处理即可
半连接池
在py文件默认同一时间只能运行一次,如果想单独分开运行多次 就需要用半连接池。
作用:设置的最大等待人数可以节省资源,提高效率。
server.listen(5)
TCP黏包问题
服务端代码
print(sock.recv(1024))
print(sock.recv(1024))
print(sock.recv(1024))
客户端代码
client.send(b'jason')
client.send(b'kevin')
client.send(b'tony')
"""
三次打印的结果
b'hellojasonkevin'
b''
b''
"""
# 问题:传入的3次数据被合成一个
1.TCP特性
流式协议:所有的数据类似于水流 连接在一起的
ps:数据量很小 并且时间间隔很多 那么就会自动组织到一起
2.recv
我们不知道即将要接收的数据量多大 如果知道的话不会产生也不会产生黏包
解决黏包问题思路
可以利用struct模块精准的获取数据的大小
import struct
info = '下午上课 以后可能是常态!'
print(len(info)) # 13 数据原本的长度
# pack可以将任意长度的数字打包成固定长度
res = struct.pack('i', len(info)) # 将数据原本的长度打包 第一个参数是格式 写i就可以了
print(len(res)) # 4 打包之后的长度是4
# unpack可以将固定长度的数字解包成打包之前数据真实的长度
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.根据真实长度接收即可
"""
struct模块针对数据量特别大的数字没有办法打包!!!
终极解决方案:利用字典存储数据,不但有数据的长度,还可以携带额外的数据。
服务端
1.先构造一个数据的详细字典
2.对字典数据进行打包处理 得到一个固定长度的数据 4
3.将上述打包之后的数据发送给客户端
4.将字典数据发送给客户端
5.将真实数据发送给客户端
import socket
import os
import struct
import json
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
sock, address = server.accept()
while True:
# 1.先构造数据文件的字典
file_dict = {
'file_name': 'Jason合集.txt',
'file_size': os.path.getsize(r'../XXX视频合集.txt'),
'file_desc': '内容很精彩 一定不要错过',
'file_root': 'jason'
}
# 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'../XXX视频合集.txt', 'rb') as f:
for line in f:
sock.send(line)
break
客户端
1.先接收固定长度的数据 4
2.根据固定长度解析出即将要接收的字典真实长度
3.接收字典数据
4.根据字典数据 获取出真实数据的长度
5.接收真实数据长度
import socket
import struct
import json
client = socket.socket()
client.connect(('127.0.0.1', 8080))
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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· DeepSeek 开源周回顾「GitHub 热点速览」
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了