1.C/S B/S架构
C: client端,客户端
B: Browser,浏览器
S: server 服务端
C/S 客户端与服务器之间的架构: QQ,微信,游戏,App的都属于C/S架构.
优点: 安全性高,个性化设置,功能全面.响应速度快.
缺点: 开发成本高,维护成本高.(基于App),面向的客户固定.
B/S 浏览器与服务器之间的架构:它属于C/S架构,最近几年比较流行的特殊的C/S架构.
优点: 开发维护成本低,,面向用户广泛.
缺点: 安全性相对低,响应速度相对慢,个性化的设置单一.
2.互联网通信的原理
打电话示例:
穿越时空: 80年代初期,固定电话,座机.
- 一堆物理连接介质将两个部电话连接起来.
- 拨号.
- 交流.
那时候没有普通话,河南,山西,广西,广东,福建等等.....
推广了普通话.
与国外一些国家去交流,统一英语.
互联网通信:
- 一堆物理连接介质将两个部电话连接起来.
- 拨号.
- 统一的通信标准. 一揽子协议,
这些互联网协议: 就是一个一个标准,最终就可以通信.
3.osi 七层协议(五层协议)
1.物理层:
一系列的物理连接介质: 网线,光纤,电缆等等等.
发送的数据就是010101010110比特数据流,这些数据连续不断地收发,010110,拿到010101没有用,你不知道数据代表的意义, 数据要进行分组(按照一定规则), 数据分组这件事物理层做不了.
2.数据链路层: 以太网协议
是按照一定的协议对比特流数据进行分组.
以太网协议.:就是对数据进行分组.
写信: 数据交流. 收件人地址, 发件人的地址.信件类型(加急,普通,工作上信封等等.....).
以太网协议:
head(源mac地址,目标mac地址) 数据类型 | data
head 固定18个字节. 46<data<1500
-
一组电信号构成一个数据报,叫做‘帧’
-
每一数据帧分成:报头head和数据data两部分
数据头(head) | data数据
数据头: 固定长度18个字节.
源地址,目的地址,数据类型.
data数据: 46字节 <= data <=1500字节
-
问题一: 为什么数据头要固定?
固定就是一个标准,统一,为了提取源地址以及目标地址.
-
问题2: 以太网协议中源目标地址如何设置唯一?
网线直接接触的硬件就是网卡.网卡上有一个地址,mac地址,确定计算机的唯一性的物理地址.
网卡上: 12位 16进制组成的一串数字: 前六位 厂商编号: 后六位:流水线号.
-
只做好了信件: head(源地址,目标地址,数据类型) | data(今晚你请我吃饭)
广播: 计算机最原始的通信方式就是吼.
数据的分组(源地址目标地址) + 广播: 理论上我的计算机就可以通信了.效率太低,每台计算机都需要接收广播的消息,查看是否是给自己的数据.比广播风暴还要严重.
所以: 广播它是有范围的,在同一子网,局域网内是通过广播的方式,发消息.
3.网络层: IP协议:确定对方的局域网的位置.
广播,mac地址,+ ip == 可以找到世界上任意一台计算机.
计算机的通信: 计算机的软件与服务器的软件进行的通信.
4.传输层: 端口协议.
tcp协议:
三次握手和三次挥手
udp协议
广播,mac地址,+ ip + 端口 == 可以找到世界上任意一台计算机对应的软件.
5.应用层: 软件自己定义的协议.
qq : 发送数据; '今晚请我吃饭...' ---> {id: '念', 'content': '今晚请我吃饭...'}
将数据按照自己定义的协议进行分装, http FTP协议.
五层协议重新梳理:
服务器: 大黑盒子, 机房声音很大,对温度,湿度,等环境都有要求,双电源,双网卡,系统linux.
详细解释中间环节一些特殊的功能:
数据经过以太网协议封装后,先要从局域网内进行吼.每次发消息,每次都要吼,这样效率也是很低的.(数据给交换机,交换机在分发出去.)
交换机的自主学习功能:
广播: 吼.
单播: 单线直接联系.
物理层---> 数据链路层(以太网协议(mac地址)) ---->网络层(IP协议) ----> 传输层(端口协议(TCP,UDP协议)) ---> 应用层:
mac地址 + 广播形式 + ip地址 + 端口 == 锁定全世界范围内的任意一个计算机的某软件的位置.
ip地址 + 端口 == 锁定全世界范围内的任意一个计算机的某软件的位置.
传输层: 端口协议.
TCP协议,UDP协议.
端口: 0~65535端口号.
1~1023系统占用的端口号.
1024~8000之内:一般的是有软件占用.
4.TCP的三次握手四次挥手
建立的链接不能一直连接着.
TCP协议: 好人协议,不会拒绝别人.
syn洪水攻击: 黑客会虚拟很多的假IP,然后访问你的服务器,半连接池,缓冲效果.
四次挥手:
5.udp与tcp
tcp协议:
优点:好人协议,流式协议.稳定,安全,
缺点: 效率低,
使用TCP的应用:Web浏览器;文件传输程序。
udp协议:
优点: 效率高,传输快.
缺点: 不安全,不是面向连接的,不可靠
使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP),微信qq。
6.socket套接字.
五层协议: 从传输层包括传输层一下,都是操作系统帮助我们封装的各种head.你不用去关心.
# content = input('>>>')
# print(content)
# 怎么交给操作系统( ⊙o⊙ )?
# 模块,或者内置函数 必须有方法内置的一些代码接收数据,然后在底层交由操作系统.
# socket套接字充当的就是内置模块的角色.
# 你说一下socket的套接字?
'''
socket 套接字,它存在于传输层与应用层之间的抽象层,
1. 避免你学习各层的接口,以及协议的使用, socket已经封装好了所有的接口.
直接使用这些接口或者方法即可,使用起来方便,提升开发效率.
2. socket就是一个模块.通过使用学习模块提供的功能,
建立客户端与服务端的通信,使用方便.
'''
1.基于TCP协议的socket通信.
2.单个客户与服务端通信.
服务端:
import socket
phone = socket.socket()
phone.bind(('127.0.0.1', 8888))
phone.listen(5)
# 4. 接收连接
while 1:
print('start')
conn, addr = phone.accept() # 程序夯住
print(conn,addr)
while 1:
try:
from_client_data = conn.recv(1024) # 至多接收1024个字节
if from_client_data == b'q':
break
print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
to_client = input('>>>')
conn.send(to_client.encode('utf-8'))
except ConnectionResetError:
break
conn.close()
phone.close()
客户端:
import socket
# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写
# 连接服务器ip地址与端口
phone.connect(('127.0.0.1', 8848))
# 发消息
to_server = input('>>>').strip()
phone.send(to_server.encode('utf-8'))
# 接收消息
from_server_data = phone.recv(1024) # 夯住,等待服务端的数据传过来
print(f'来自服务端消息:{from_server_data.decode("utf-8")}')
# 关机
phone.close()
3.通信循环.
服务端:
import socket
phone = socket.socket()
phone.bind(('127.0.0.1', 8888))
phone.listen(5)
# 4. 接收连接
while 1:
print('start')
conn, addr = phone.accept() # 程序夯住
print(conn,addr)
while 1:
try:
from_client_data = conn.recv(1024) # 至多接收1024个字节
if from_client_data == b'q':
break
print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
to_client = input('>>>')
conn.send(to_client.encode('utf-8'))
except ConnectionResetError:
break
conn.close()
phone.close()
客户端:
import socket
# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写
# 连接服务器ip地址与端口
phone.connect(('127.0.0.1', 8888))
# 发消息
while 1:
to_server = input('>>>').strip()
if to_server.upper() == 'Q':
phone.send('q'.encode('utf-8'))
break
phone.send(to_server.encode('utf-8'))
# 接收消息
from_server_data = phone.recv(1024) # 夯住,等待服务端的数据传过来
print(f'来自服务端消息:{from_server_data.decode("utf-8")}')
# 关机
phone.close()
4.通信,连接循环.
服务端:
# import socket
#
# # phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# # 1. 创建socket对象(买手机)
# phone = socket.socket() # 可以默认不写
#
# # 2. 绑定ip地址和端口(办卡)
# phone.bind(('127.0.0.1', 8848)) # 本地回环地址
#
# # 3. 监听.(开机状态)
# phone.listen(5)
#
# # 4. 接收连接
# print('start')
# conn, addr = phone.accept() # 程序夯住
# # print(conn,addr)
# while 1:
# from_client_data = conn.recv(1024) # 至多接收1024个字节
# print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
# to_client = input('>>>')
# conn.send(to_client.encode('utf-8'))
#
# conn.close()
# phone.close()
# 无论你的客户端是否正常关闭,服务端都应该正常关闭,而不是报错.
import socket
# phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写
# 2. 绑定ip地址和端口(办卡)
phone.bind(('127.0.0.1', 8888)) # 本地回环地址
# 3. 监听.(开机状态)
phone.listen(5)
# 4. 接收连接
print('start')
conn, addr = phone.accept() # 程序夯住
# print(conn,addr)
while 1:
try:
from_client_data = conn.recv(1024) # 至多接收1024个字节
if from_client_data == b'q':
break
print(f'来自客户端{addr}消息{from_client_data.decode("utf-8")}')
to_client = input('>>>')
conn.send(to_client.encode('utf-8'))
except ConnectionResetError:
break
conn.close()
phone.close()
客户端:
import socket
# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写
# 连接服务器ip地址与端口
phone.connect(('127.0.0.1', 8888))
# 发消息
while 1:
to_server = input('>>>').strip()
if to_server.upper() == 'Q':
phone.send('q'.encode('utf-8'))
break
phone.send(to_server.encode('utf-8'))
# 接收消息
from_server_data = phone.recv(1024) # 夯住,等待服务端的数据传过来
print(f'来自服务端消息:{from_server_data.decode("utf-8")}')
# 关机
phone.close()
5.利用socket完成获取远端命令的示例.
服务端
import socket
import subprocess
phone=socket.socket()
phone.bind(("127.0.0.1",8848))
phone.listen(5)
print("start")
conn, addr = phone.accept()
print(conn)
while 1:
from_client = conn.recv(1024)
print(from_client)
obj = subprocess.Popen(from_client.decode("utf-8"),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
ret=obj.stdout.read()+obj.stderr.read()
conn.send(ret)
conn.close()
phone.close()
客户端
import socket
phone=socket.socket()
phone.connect(("127.0.0.1", 8848))
while 1:
info=input(">>>>>")
phone.send(info.encode("utf-8"))
from_server=phone.recv(1024)
print(from_server.decode("gbk"))
phone.close()
6.黏包现象.
1.黏包现象.
服务端: 客户端:
第一次 dir 数据418 < 1024 接收418数据
第二次 ipconfig 数据1517 > 1024 接收1024个字节
第三次 dir 数据418 < 1024 接收493个字节
黏包现象的根本原因:缓冲区.
recv是一旦发送就会在接收数据缓存区等待抓取,一旦上次发的数据过大,一次抓取不完,就会剩余在接收数据缓冲区,
那么在第二次发送后继续抓取时就会发现还有数据,那就会直接抓取,其实是剩余的而不是新发送来的
send同理
2.系统缓冲区.
缓冲区的作用?
没有缓冲区:如果你的网络出现短暂的异常或者波动,接收数据就会出现短暂的中断,影响你的下载或者上传的效率.
但是 凡是都是双刃剑,缓冲区解决了上传下载的传输效率的问题,带来了黏包问题.
减少与磁盘的交互
3.什么情况下产生黏包
- recv会产生黏包(如果recv接受的数据量(1024)小于发送的数据量,第一次只能接收规定的数据量1024,第二次接收剩余的数据量)
- send 也可能发生粘包现象.(连续send少量的数据发到输出缓冲区,由于缓冲区的机制,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络)
4.解决黏包的方案
1.错误实例:
- 可以扩大recv的上限. recv(10240000000000000000000000000) 不是解决这个问题的根本原因, 8g, 10G,这些都会直接放在内存 中.
- 故意延长recv的时间. sleep 这样会非常影响效率.
2.思路:
分析一下功能:
send多次,recv一次.(不是一收一发制)
recv的工作原理:pass
3.解决粘包现象的思路分析:
-
当我第二次给服务器发送命令之前,我应该循环recv直至将所有的数据全部取完.
问题:
result 3000bytes recv 3次
result 5000bytes recv 5次
result 30000bytes recv ?次 ---> 循环次数相关
- 如何限制循环次数?
当你发送的总bytes个数,与接受的总bytes个数相等时,循环结束.
-
如何获取发送的总bytes个数: len() --- > 3400个字节 int
总数据 result = b'fdsafdskfdskfdsflksdfsdlksdlkfsdjf'
所以:
服务端:
send(总个数)
send(总数据)
-
总个数是什么类型? int() 3400,send需要发送 bytes类型.
send(总个数)
将int 转化成bytes 即可. b'3400'
方案一:
str(3400) -- > '3400' -----> bytes('3400') -----> b'3400' ---> 几个字节? 4个字节
send(总数据)
你现在继续解决的问题!!!!!
无论总字节个数是多多少? 129 3400 10000 30000 转化成固定长度的bytes.
将不固定长度的int类型,转化成固定长度bytes类型.方便获取头部信息.
(recv的工作原理)
第二种优化版
用一个字典承载
#自定义字典的数据头
import json
import struct
head_dict = {
'MD5': 'fdsaf2345544324dfs',
'file_name': '婚前视频',
'file_size': 15436540006555555555555555556556546546565654654654654645555555555555555555555555555555555555,
}
head_len = len(json.dumps(head_dict).encode('utf-8'))
print(head_len)
ret = struct.pack('i', head_len)
print(ret)
示例:
服务端
import socket
import subprocess
import struct
import json
phone = socket.socket()
phone.bind(('127.0.0.1', 8888))
phone.listen(5)
# 4. 接收连接
print('start')
conn, addr = phone.accept()
while 1:
try:
cmd = conn.recv(1024)
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
result = obj.stdout.read() + obj.stderr.read()
result = result.decode('gbk').encode('utf-8')
# print(f'服务端发送的总字节数{len(result)}')
# 1. 制作报头
head_dict = {
'MD5': 'fdsaf2345544324dfs',
'file_name': '婚前视频',
'file_size': len(result),
}
# 2. 将报头字典转化成json字符串
head_dict_json = json.dumps(head_dict)
# 3. 将json字符串 转化成bytes
head_dict_json_bytes = head_dict_json.encode('utf-8')
# 4. 获取报头的长度
head_len = len(head_dict_json_bytes)
# 5.将长度转化成固定的4个字节
head_len_bytes = struct.pack('i',head_len)
# 6. 发送固定的4个字节
conn.send(head_len_bytes)
# 7. 发送报头
conn.send(head_dict_json_bytes)
# 8. 发送原数据
conn.send(result)
except ConnectionResetError:
break
conn.close()
phone.close()
客户端
import socket
import struct
import json
phone=socket.socket()
phone.connect(('127.0.0.1', 8888))
while 1:
cmd=input(">>>")
phone.send(cmd.encode("utf-8"))
head=phone.recv(4)
head_len=struct.unpack("i",head)[0]
print(head_len)
dic_josn=phone.recv(head_len).decode("utf-8")
dic=json.loads(dic_josn)
print(dic)
a=b""
while dic["file_size"] > len(a):
a+=phone.recv(1024)
print(a.decode("utf-8"))
phone.close()
7.udp
服务端
import socket
udp_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 基于网络,udp协议的socket
udp_server.bind(('127.0.0.1', 9000))
while 1:
from_client_data = udp_server.recvfrom(1024)
print(f'来自{from_client_data[1]}的消息:{from_client_data[0].decode("utf-8")}')
to_client_data = input('>>>').strip()
udp_server.sendto(to_client_data.encode('utf-8'),from_client_data[1])
客户端
import socket
udp_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) # 基于网络,udp协议的socket
while 1:
to_server_data = input('>>>').strip()
udp_client.sendto(to_server_data.encode('utf-8'),('127.0.0.1', 9000))
from_server_data = udp_client.recvfrom(1024)
print(f'来自{from_server_data[1]}的消息:{from_server_data[0].decode("utf-8")}')
多用户连接服务器
import socketserver
class MyServer(socketserver.BaseRequestHandler): # 继承的类固定的
def handle(self): # 必须是这个handle名字.
while 1:
from_client_data = self.request.recv(1024).decode('utf-8') # self.request == conn管道
print(from_client_data)
to_client_data = input('>>>').strip()
self.request.send(to_client_data.encode('utf-8'))
if __name__ == '__main__':
ip_port = ('127.0.0.1',8848)
server = socketserver.ThreadingTCPServer(ip_port,MyServer)
# server.allow_reuse_address = True
# print(socketserver.ThreadingTCPServer.mro())
# [ThreadingTCPServer, ThreadingMixIn,TCPServer, BaseServer]
server.serve_forever()
# 1. 入口点:ThreadingTCPServer()
客户端
import socket
# 1. 创建socket对象(买手机)
phone = socket.socket() # 可以默认不写
# 连接服务器ip地址与端口
phone.connect(('127.0.0.1', 8848))
# 发消息
while 1:
content = input('>>>').strip()
phone.send(f'MC骚强:{content}'.encode('utf-8'))
# 接收消息
from_server_data = phone.recv(1024) # 夯住,等待服务端的数据传过来
print(f'来自服务端消息:{from_server_data.decode("utf-8")}')
# 关机
phone.close()