Day38网络编程扩展
1、传输层
PORT协议
TCP协议 与UDP协议
规定了数据传输所遵循的规则
数据传输能够遵循的协议有很多 TCP和UDP只是比较常见的两个
1.1、TCP协议
三次握手:建立双向通道
洪水攻击:就是同时让大量的客户端朝服务端发送建立TCP连接请求
四次挥手:断开双向通道,中间两部不能合并(需要检查的时间停顿,检查是否还有数据没有发送完)
基于TCP传输非常安全 因为有双向通道
- 基于TCP传输数据,数据容易丢失!,不容易丢失的原因在于二次确认机制
- 每次发送数据都需要返回确认消息 否则在一定时间就会反复发送
TCP类似于打电话:你一句 我一句 有来有往
UDP 类似于发短信:只要发送了 不管别人看没看到也不管回不回复
2、应用层
主要取决于程序员自己采用什么策略与协议
常见的协议有:HTTP
、 HTTPS
、 FTP
…
3、socket 套接字
基于文件类型的套接字家族
- 套接字家族的名字:AF_UNIX
基于网络类型的套接字家族
- 套接字家族的名字:AF_INET
服务端
import socket
# 创建一个socket 对象
server = socket.socket()
# 绑定一个固定的地址(ip\port)
# 127.0.0.1 为本地回环地址(只允许自己的机器访问)
server.bind(('127.0.0.1', 8080))
# 半连接池(暂且忽略)
server.listen(5)
# 准备就绪 等待客户端 来访问(开业,等待接客)
sock, address = server.accept()
# sock 是双向通道 address是客户端地址
print(sock, address)
# 数据交互
# 朝客户端发送数据
sock.send(b'hell big sb')
# 接收 客户端发送的数据 1024bytes
data = sock.recv(1024)
print(data)
#
# 断开连接
sock.close()
客户端
import socket
# 产生一个socke对象
client = socket.socket()
#连接服务器 (拼接服务端的IP和port)
client.connect(('127.0.0.1',8080))
#数据交互
#接收服务端 发送的数据
data = client.recv(1024)
#打印
print(data)
# 朝服务端发送数据
client.send(b'hello small sb')
# 关闭
client.close()
3.1、代码优化
消息自定义
利用 input 接收用户数据
循环通信
给数据交互环节添加循环即可
循环通信
给数据交互环节添加循环即可
服务端能够持续提高服务
不会因为客户端 断开连接而报错
异常捕获 一旦客户端断开连接 服务端结束通信循环 遇到连接处等待
服务端
import socket
import json
# 创建一个socket 对象
server = socket.socket()
# 绑定一个固定的地址(ip\port)
# 127.0.0.1 为本地回环地址(只允许自己的机器访问)
server.bind(('127.0.0.1', 8080))
# 半连接池(暂且忽略)
server.listen(5)
# 准备就绪 等待客户端 来访问(开业,等待接客)
#添加循环 让服务端 每次结束客户端连接接触都能回归为等待访问的状态
while True :
sock, address = server.accept()
# sock 是双向通道 address是客户端地址
print(sock, address)
# 数据交互
# 朝客户端发送数据
# 添加循环 让服务端 能够自定义返回的数据
while True:
try:
sent_data=input('请输入发送的数据>>>').strip()
if len(sent_data)==0:
print('消息不能为空')
continue
sent_data=bytes(sent_data,'utf')
sock.send(sent_data)
# 接收 客户端发送的数据 1024bytes
data = sock.recv(1024)
data=str(data,'utf')
print(data)
#当客户端断开过后 捕获异常
except Exception:
break
#
# 断开连接
sock.close()
客户端
import socket
# 产生一个socke对象
client = socket.socket()
#连接服务器 (拼接服务端的IP和port)
client.connect(('127.0.0.1',8080))
#数据交互
#接收服务端 发送的数据
#添加循环 能够让客户端 重复的发送数据
while True:
data = client.recv(1024)
#打印
data=str(data,'utf')
print(data)
# 朝服务端发送数据
sent_data=input('请输入要传入的数据>>>').strip()
sent_data=bytes(sent_data,'utf')
client.send(sent_data)
# 关闭
client.close()
3.2、半连接池
server.listen(5) #参数 5 表示 等待访问的客户端 数 超过这个数 那么就会使那个客户端报错
主要为了做缓冲 避免太多无效等待
3.3、黏包问题
TCP特性
流逝协议:所有的数据类似于水流 连接在一起的
数据量很小 并且时间间隔很多 那么就会自动组织到一起
recv
我们不知道即将要接收的数据量很大 如果 知道的话 不会产生也不会产生黏包
通过struct
import struct
info = '你好啊 大帅比'
print(len(info))
# 将数据原本的长度打包
res = struct.pack('i', len(info))
# 打包之后的长度为4
print(len(res))
# 将打包之后 固定长度为 4的数据拆包
ret = struct.unpack('i', res)
# 又得到了原本数据的长度
print(ret[0])
struct模块 无论数据长度 是多少 都可以帮你打包成固定长度
然后基于该长度 还可以反向解析出真实长度
3.4、实操
服务端
import socket
import json
# 创建一个socket 对象
import struct
server = socket.socket()
# 绑定一个固定的地址(ip\port)
# 127.0.0.1 为本地回环地址(只允许自己的机器访问)
server.bind(('127.0.0.1', 8080))
# 半连接池(暂且忽略)
server.listen(5)
# 准备就绪 等待客户端 来访问(开业,等待接客)
# 添加循环 让服务端 每次结束客户端连接接触都能回归为等待访问的状态
while True:
sock, address = server.accept()
# sock 是双向通道 address是客户端地址
print(sock, address)
# 数据交互
# 朝客户端发送数据
# 添加循环 让服务端 能够自定义返回的数据
while True:
try:
sent_data = input('请输入发送的数据>>>').strip()
if len(sent_data) == 0:
print('消息不能为空')
continue
# 将获取的数据 转为bytes类型
sent_data = bytes(sent_data, 'utf')
# 计算字节个数
res_w = len(sent_data)
# 然后进行打包
res_w = struct.pack('i', res_w)
# 将打包后的数据发送过去
sock.send(res_w)
# 再将真正的数据发送过去
sock.send(sent_data)
# 接收 客户端发送的数据
#首先获取 打包后的 数据
data = sock.recv(4)
# 将打包后的数据解包获取真实数据字节数
data = struct.unpack('i', data)[0]
#获取真实数据
b_data = sock.recv(data)
#解码
data = str(b_data, 'utf')
# 打印
print(data)
# 当客户端断开过后 捕获异常
except Exception:
break
#
# 断开连接
sock.close()
客户端
import socket
import struct
# 产生一个socke对象
client = socket.socket()
# 连接服务器 (拼接服务端的IP和port)
client.connect(('127.0.0.1', 8080))
# 数据交互
# 接收服务端 发送的数据
# 添加循环 能够让客户端 重复的发送数据
while True:
#获取真实数据长度打包后的数据
res = client.recv(4)
#数据解包获取真实的数据长度
res_r = struct.unpack('i', res)[0]
# 通过真实数据长度 获取真实数
data = client.recv(res_r)
# 打印
#解码
data = str(data, 'utf')
print(data)
# 朝服务端发送数据
sent_data = input('请输入要传入的数据>>>').strip()
#编码计算 数据长度
sent_data = bytes(sent_data, 'utf')
res_w = len(sent_data)
#将数据长度打包
res_w = struct.pack('i', res_w)
# sent_res = bytes(res_w, 'utf')
# 发送 输出长度打包后的数据
client.send(res_w)
#发送真实数据
client.send(sent_data)
# 关闭
client.close()
作业
服务器
import socket
import struct
import json
import os
# 创建一个socket 对象
import struct
server = socket.socket()
# 绑定一个固定的地址 本地回环地址(ip/port)
server.bind(('127.0.0.1', 8080))
# 半连接池
server.listen(5)
while True:
# 准备就绪 等待客户端访问阶段
sock, address = server.accept()
# 是双向通道 address是客户端地址
print(sock, address)
# 数据交互
while True:
try:
picture_address = r'C:\Users\82576\Desktop\jason.mp4'
jason_dict = {'address': picture_address,
'png_number': os.path.getsize(picture_address)}
json_dict = json.dumps(jason_dict)
b_dict = bytes(json_dict, 'utf')
dict_bytes_number = len(b_dict)
res_w = struct.pack('i', dict_bytes_number)
# 发送字典报头
sock.send(res_w)
# 发送字典
sock.send(b_dict)
#发送真实数据
with open(r'%s'%picture_address,'rb')as p_w:
sock.send(p_w.read())
except Exception:
break
客户端
import socket
import struct
import json
# 产生一个socket对象
client = socket.socket()
# 连接服务器 (拼接服务端ip和port)
client.connect(('127.0.0.1', 8080))
# 获取字典的抱头
res_dict = client.recv(4)
# 解包字典报头
new_res_dict = struct.unpack('i', res_dict)[0]
# 获取真实的字典
b_jason_dict = client.recv(new_res_dict)
# 反序列化 解码
jason_dict = json.loads(b_jason_dict)
# 获取真实数据的报头
png_res = jason_dict.get('png_number')
# 获取真实数据
jason_png = client.recv(png_res)
with open('jason.mp4', 'wb')as jason_w:
jason_w.write(jason_png)