C/S B/S架构
C: client端
B: browse 浏览器
S: server端
C/S架构: 基于客户端与服务端之间的通信
QQ, 游戏,皮皮虾, 快手,抖音.
优点: 个性化设置,响应速度快,
缺点: 开发成本,维护成本高,占用空间,用户固定.
B/S架构: 基于浏览器与服务端之间的通信
谷歌浏览器,360浏览器,火狐浏览器等等.
优点: 开发维护成本低,占用空间相对低,用户不固定.
缺点: 功能单一,没有个性化设置,响应速度相对慢一些.
80年代,固定电话联系,(还没有推广普通话)
1. 两台电话之间一堆物理连接介质连接.
2. 拨号,锁定对方电话的位置.
由于当时没有统一普通话,所以你如果和河南,山西,广西,福建等朋友进行友好的沟通交流,你必须学当地的方言.
推广普通话,统一交流方式.
1. 两台电话之间一堆物理连接介质连接.
2. 拨号,锁定对方电话的位置.
3. 统一交流方式.
全球范围内交流:
1. 两台电话之间一堆物理连接介质连接.
2. 拨号,锁定对方电话的位置.
3. 统一交流方式.(英语)
话题转回互联网通信:
我现在想和美国的一个girl联系.你如何利用计算机联系???
1. 两台计算机要有一堆物理连接介质连接.
2. 找到对方计算机软件位置.
3. 遵循一揽子互联网通信协议.
TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、流式协议, 传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;文件传输程序。
UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文(数据包),尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。
syn洪水攻击:制造大量的假的无效的IP请求服务器.致使正常的IP访问不了服务器.
socket套接字:
1.socket是处于应用层与传输层之间的抽象层,他是一组操作起来非常简单的接口(接受数据)此接口接受数据之后,交由操作系统.
为什么存在socket抽象层?
如果直接与操作系统数据交互非常麻烦,繁琐,socket对这些繁琐的的操作高度的封装,简化.
2.socket在python中就是一个模块.
# 服务端
import socket
phone = socket.socket()
phone.bind(('192.168.14.230', 8849))
phone.listen(2) # listen 允许几个人链接,剩下的链接等待
conn, addr = phone.accept() # 等待客户端连接我,阻塞的状态中
print(f'链接来了{conn,addr}')
from_client_data = conn.recv(1024)
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
to_client_data = input('>>>').strip().encode('utf-8')
conn.send(to_client_data)
conn.close()
phone.close()
# 客户端
import socket
phone = socket.socket()
phone.connect(('192.168.14.230', 8849))
to_server_data = input('>>>').strip().encode('utf-8')
phone.send(to_server_data)
from_server_data = phone.recv(1024)
print(f'来自服务器的消息:{from_server_data}')
总结:
服务端和客户端都加循环,如果正常退出双方都直接break,设置判断信息
服务端在客户等待连接的后面加while循环,客户端在链接地址之后加循环
服务端需要加一个异常退出的异常处理,提示异常退出
# 服务端
import socket
phone = socket.socket()
phone.bind(('192.168.14.230', 8849))
phone.listen(2) # listen 允许几个人链接,剩下的链接等待
conn, addr = phone.accept() # 等待客户端连接我,阻塞的状态中
print(f'链接来了{conn,addr}')
while 1:
try:
from_client_data = conn.recv(1024)
if from_client_data.upper() == b'Q': # 正常退出 服务端跟着关闭
print('客户正常退出聊天了')
break
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
to_client_data = input('>>>').strip().encode('utf-8')
conn.send(to_client_data)
except ConnectionResetError: # 异常退出 会报错 写提示内容
print('客户端链接中断了')
break
conn.close()
phone.close()
# 客户端
import socket
phone = socket.socket()
phone.connect(('192.168.14.230', 8849))
while 1:
to_server_data = input('>>>').strip().encode('utf-8')
if not to_server_data: # 服务端如果收到了空的内容,服务端就会一直阻塞中.无论是那一端发送,都不能为空
print('发送内容不能为空')
continue
phone.send(to_server_data)
if to_server_data.upper() == b'Q': # 判断如果是Q的话就退出,正常退出
break
from_server_data = phone.recv(1024)
print(f'来自服务器的消息:{from_server_data}')
phone.close()
总结:
服务端在客户端链接之前再加一层while循环,并且把关闭此次通话加到循环最下面
listen(2) 允许2个人链接,剩下的链接等待 (实际上三个人链接),超过就会报错
如果第一个链接时,第二个发了信息,当第一个关闭的时候自动接收第二个发送的信息
# 服务端
import socket
phone = socket.socket() # 买电话
phone.bind(('192.168.14.230', 8849)) # 0-65535 1024之前系统分配好的端口 绑定电话卡
phone.listen(2) # listen 允许2个人链接,剩下的链接等待 (实际上三个人链接)
while 1:
conn, addr = phone.accept() # 等待客户端连接我,阻塞的状态中
print(f'链接来了{conn,addr}')
while 1:
try:
from_client_data = conn.recv(1024)
if from_client_data.upper() == b'Q': # 正常退出 客户端通道跟着关闭
print('客户正常退出聊天了')
break
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
to_client_data = input('>>>').strip().encode('utf-8')
conn.send(to_client_data)
except ConnectionResetError: # 异常退出 会报错 写提示内容
print('客户端链接中断了')
break
conn.close()
phone.close()
# 客户端
import socket
phone = socket.socket()
phone.connect(('192.168.14.230', 8849))
while 1:
to_server_data = input('>>>').strip().encode('utf-8')
if not to_server_data: # 服务端如果收到了空的内容,服务端就会一直阻塞中.无论是那一端发送,都不能为空
print('发送内容不能为空')
continue
phone.send(to_server_data)
if to_server_data.upper() == b'Q': # 判断如果是Q的话就退出,正常退出
break
from_server_data = phone.recv(1024) # 最多接受的字节数量
print(f'来自服务器的消息:{from_server_data}')
phone.close()
总结:
服务端先导入subprocess模块,作用是可以执行命令,
然后修改接收内容,改成操作命令的固定代码
客户端接收内容需要改成gbk编码,因为windows操作系统的默认编码是gbk编码,苹果系统不需要改
"""
shell: 命令解释器,相当于调用cmd 执行指定的命令。
stdout:正确结果丢到管道中。
stderr:错了丢到另一个管道中。
windows操作系统的默认编码是gbk编码。
"""
# 服务端
import socket
import subprocess
phone = socket.socket()
phone.bind(('192.168.14.230', 8849))
phone.listen(2) # listen 允许2个人链接,剩下的链接等待
while 1:
conn, addr = phone.accept() # 等待客户端连接我,阻塞的状态中
print(f'链接来了{conn,addr}')
while 1:
try:
from_client_data = conn.recv(1024)
if from_client_data.upper() == b'Q': # 正常退出 客户端通道跟着关闭
print('客户正常退出聊天了')
break
obj = subprocess.Popen(from_client_data.decode('utf-8'),
shell=True, # shell: 命令解释器,相当于调用cmd 执行指定的命令。
stdout=subprocess.PIPE, # stdout:正确结果丢到管道中。
stderr=subprocess.PIPE, # stderr:错了丢到另一个管道中。
)
result = obj.stdout.read() + obj.stderr.read()
conn.send(result)
except ConnectionResetError: # 异常退出 会报错 写提示内容
print('客户端链接中断了')
break
conn.close()
phone.close()
# 客户端
import socket
phone = socket.socket()
phone.connect(('192.168.14.230', 8849))
while 1:
to_server_data = input('>>>').strip().encode('utf-8')
if not to_server_data: # 服务端如果收到了空的内容,服务端就会一直阻塞中.无论是那一端发送,都不能为空
print('发送内容不能为空')
continue
phone.send(to_server_data)
if to_server_data.upper() == b'Q': # 判断如果是Q的话就退出,正常退出
break
from_server_data = phone.recv(1024) # 最多接受的字节数量
print(f'来自服务器的消息:{from_server_data.decode("gbk")}')
phone.close()
操作系统的缓存区:
1. 为什么存在缓冲区??
1. 暂时存储一些数据.
2. 缓冲区存在如果你的网络波动,保证数据的收发稳定,匀速.
缺点: 造成了粘包现象之一.
第一个粘包现象:
同时多次接收send每次数据太少会形成粘包现象,因为太快多次合并成一次发送
连续短暂的send多次(数据量很小),你的数据会统一发送出去,
第二个粘包现象:
一次接收send数据量太大,导致一次接收不完,第二次再次接收还是第一次剩余内容.
深入研究收发解决方法
如何解决粘包现象:
解决粘包现象的思路:
服务端发一次数据 10000字节,
客户端接收数据时,循环接收,每次(至多)接收1024个字节,直至将所有的字节全部接收完毕,将接收的数据拼接在一起,最后解码.
1. 遇到的问题: recv的次数无法确定
你发送总具体数据之前,先给我发一个总数据的长
度:5000个字节。然后在发送总数据。
客户端: 先接收一个长度。 5000个字节。
然后我再循环recv 控制循环的条件就是只要你接受的数据< 5000 一直接收。
2. 遇到的问题: 总数据的长度转化成的字节数不固定
>>>服务端:
conn.send(total_size)
conn.send(result)
total_size int类型
>>>客户端:
total_size_bytes = phone.recv(4)
total_size
data = b''
while len(data) < total_size:
data = data + phone.recv(1024)
你要将total_size int类型转化成bytes类型才可以发送
387 ---- > str(387) '387' ---->bytes b'387' 长度 3bytes
4185 ----> str(4185) '4185' ---->bytes b'4185' 长度 4bytes
18000------------------------------------------------------> 长度 5bytes
我们要解决:
将不固定长度的int类型转化成固定长度的bytes并且还可以翻转回来。
多次接收解决粘包现象,但不是根本解决:
from_client_data = conn.recv(3) # 最多接受1024字节
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
from_client_data = conn.recv(3) # 最多接受1024字节
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
from_client_data = conn.recv(3) # 最多接受1024字节
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
from_client_data = conn.recv(3) # 最多接受1024字节
print(f'来自客户端{addr}消息:{from_client_data.decode("utf-8")}')
导入struct模块:
服务端制作固定长度的报头使用
客户端反解报头使用
代码实验有效作用:
服务端:
total_size = len(result) # 查看字节
print(f'总字节数:{total_size}')
head_bytes = struct.pack('i', total_size) # 1. 制作固定长度的报头 'i'固定四个报头
conn.send(head_bytes) # 2. 发送固定长度的报头
conn.send(result) # 3. 发送总数据
客户端:
head_bytes = phone.recv(4) # 1. 接收报头
total_size = struct.unpack('i', head_bytes)[0] # 2. 反解报头 'i'固定四个报头
total_data = b'' # 接收内容,依次相加bytes类型,如果只是英文可以不加ASCII码
while len(total_data) < total_size: # 接收的内容长度不会超过反解包头的长度,所以用判断
total_data += phone.recv(1024) # 本来就是反解报头,然后直接全部接收,然后每1024处理一次,直到结束
# 服务端
import socket
import subprocess
import struct
phone = socket.socket()
phone.bind(('192.168.14.230', 8849))
phone.listen(2) # listen 允许2个人链接,剩下的链接等待
while 1:
conn, addr = phone.accept() # 等待客户端连接我,阻塞的状态中
# print(f'链接来了{conn,addr}')
while 1:
try:
from_client_data = conn.recv(1024)
if from_client_data.upper() == b'Q': # 正常退出 服务端跟着关闭
print('客户正常退出聊天了')
break
obj = subprocess.Popen(from_client_data.decode('utf-8'),
shell=True, # shell: 命令解释器,相当于调用cmd 执行指定的命令。
stdout=subprocess.PIPE, # stdout:正确结果丢到管道中。
stderr=subprocess.PIPE, # stderr:错了丢到另一个管道中。
)
result = obj.stdout.read() + obj.stderr.read() # 接收正确或者错误的命令
total_size = len(result) # 查看字节
print(f'总字节数:{total_size}')
head_bytes = struct.pack('i', total_size) # 1. 制作固定长度的报头 'i'固定四个报头
conn.send(head_bytes) # 2. 发送固定长度的报头
conn.send(result) # 3. 发送总数据
except ConnectionResetError: # 异常退出 会报错 写提示内容
print('客户端链接中断了')
break
conn.close()
phone.close()
# 客户端
import socket
import struct
phone = socket.socket()
phone.connect(('192.168.14.230', 8849))
while 1:
to_server_data = input('>>>').strip().encode('utf-8')
if not to_server_data: # 服务端如果收到了空的内容,服务端就会一直阻塞中.无论是那一端发送,都不能为空
print('发送内容不能为空')
continue
phone.send(to_server_data)
if to_server_data.upper() == b'Q': # 判断如果是Q的话就退出,正常退出
break
head_bytes = phone.recv(4) # 1. 接收报头
total_size = struct.unpack('i', head_bytes)[0] # 2. 反解报头 'i'固定四个报头
total_data = b'' # 接收内容,依次相加bytes类型,如果只是英文可以不加ASCII码
while len(total_data) < total_size:
total_data += phone.recv(1024)
print(len(total_data))
print(total_data.decode('gbk'))
phone.close()
源码解释:
Receive up to buffersize bytes from the socket.接收来自socket缓冲区的字节数据,
For the optional flags argument, see the Unix manual.对于这些设置的参数,可以查看Unix手册。
When no data is available, block untilat least one byte is available or until the remote end is closed.当缓冲区没有数据可取时,recv会一直处于阻塞状态,直到缓冲区至少有一个字节数据可取,或者远程端关闭。
When the remote end is closed and all data is read, return the empty string.关闭远程端并读取所有数据后,返回空字符串。
理解:
recv空字符串: 对方客户端关闭了,且服务端的缓冲区没有数据了,我再recv取到空bytes.
1 验证服务端缓冲区数据没有取完,又执行了recv执行,recv会继续取值。
2 验证服务端缓冲区取完了,又执行了recv执行,此时客户端20秒内不关闭的前提下,recv处于阻塞状态。
3 验证服务端缓冲区取完了,又执行了recv执行,此时客户端处于关闭状态,则recv会取到空字符串。
服务端:
1.自定制报头
head_dic = {
'file_name': 'test1', # 需要操作的文件名.使用变量
'md5': 987654321, # 文件字节的md5加密,校验使用.变量
'total_size': total_size, # 字节总长度
}
2.json形式的报头
head_dic_json = json.dumps(head_dic)
3.bytes形式报头
head_dic_json_bytes = head_dic_json.encode('utf-8')
4.获取bytes形式的报头的总字节数
len_head_dic_json_bytes = len(head_dic_json_bytes)
5.将不固定的int总字节数编程固定长度的4个字节
four_head_bytes = struct.pack('i', len_head_dic_json_bytes)
6.发送固定的4个字节
conn.send(four_head_bytes)
7.发送报头数据
conn.send(head_dic_json_bytes)
8.发送总数据
conn.send(result)
客户端:
1.接收报头
head_bytes = phone.recv(4)
2.获得bytes类型字典的总字节数
len_head_dic_json_bytes = struct.unpack('i', head_bytes)[0]
3.接收bytes类型的dic数据
head_dic_json_bytes = phone.recv(len_head_dic_json_bytes)
4.转化成json类型dic
head_dic_json = head_dic_json_bytes.decode('utf-8')
5.转化成字典形式的报头
head_dic = json.loads(head_dic_json)
# 服务端
import socket
import subprocess
import struct
import json
phone = socket.socket()
phone.bind(('192.168.14.230', 8849))
phone.listen(2) # listen 允许2个人链接,剩下的链接等待
while 1:
conn, addr = phone.accept() # 等待客户端连接我,阻塞的状态中
# print(f'链接来了{conn,addr}')
while 1:
try:
from_client_data = conn.recv(1024)
if from_client_data.upper() == b'Q': # 正常退出 服务端跟着关闭
print('客户正常退出聊天了')
break
obj = subprocess.Popen(from_client_data.decode('utf-8'),
shell=True, # shell: 命令解释器,相当于调用cmd 执行指定的命令。
stdout=subprocess.PIPE, # stdout:正确结果丢到管道中。
stderr=subprocess.PIPE, # stderr:错误丢到另一个管道中。
)
result = obj.stdout.read() + obj.stderr.read() # 接收正确或者错误的命令
total_size = len(result) # 字节
print(f'总字节数:{total_size}') # 查看字节
head_dic = { # 1 自定义报头
'file_name': 'test1', # 需要操作的文件名.使用变量
'md5': 987654321, # 文件字节的md5加密,校验使用.变量
'total_size': total_size, # 字节总长度
}
head_dic_json = json.dumps(head_dic) # 2 json形式的报头
head_dic_json_bytes = head_dic_json.encode('utf-8') # 3 bytes形式报头
len_head_dic_json_bytes = len(head_dic_json_bytes) # 4 获取bytes形式的报头的总字节数
four_head_bytes = struct.pack('i', len_head_dic_json_bytes) # 5 将不固定的int总字节数编程固定长度的4个字节
conn.send(four_head_bytes) # 6 发送固定的4个字节
conn.send(head_dic_json_bytes) # 7 发送报头数据
conn.send(result) # 8 发送总数据
except ConnectionResetError: # 异常退出 会报错 写提示内容
print('客户端链接中断了')
break
conn.close()
phone.close()
# 客户端
import socket
import struct
import json
phone = socket.socket()
phone.connect(('192.168.14.230', 8849))
while 1:
to_server_data = input('>>>').strip().encode('utf-8')
if not to_server_data: # 服务端如果收到了空的内容,服务端就会一直阻塞中.无论是那一端发送,都不能为空
print('发送内容不能为空')
continue
phone.send(to_server_data)
if to_server_data.upper() == b'Q': # 判断如果是Q的话就退出,正常退出
break
head_bytes = phone.recv(4) # 1. 接收报头
len_head_dic_json_bytes = struct.unpack('i', head_bytes)[0] # 2 获得bytes类型字典的总字节数
head_dic_json_bytes = phone.recv(len_head_dic_json_bytes) # 3 接收bytes类型的dic数据
head_dic_json = head_dic_json_bytes.decode('utf-8') # 4 转化成json类型dic
head_dic = json.loads(head_dic_json) # 5 转化成字典形式的报头
'''
head_dic = {
head_dic = { # 1 自定义报头
'file_name': 'test1', # 需要操作的文件名.使用变量
'md5': 987654321, # 文件字节的md5加密,校验使用.变量
'total_size': total_size, # 字节总长度
}
'''
total_data = b'' # 接收内容,依次相加bytes类型,如果只是英文可以不加ASCII码
while len(total_data) < head_dic['total_size']: # 接收的内容长度不会超过反解包头的长度,所以用判断
total_data += phone.recv(1024) # 本来就是反解报头,然后直接全部接收,然后每1024处理一次,直到结束
print(len(total_data))
print(total_data.decode('gbk'))
phone.close()
1. 基于udp协议的socket无须建立管道,先开启服务端或者客户端都行.
2. 基于udp协议的socket接收一个消息,与发送一个消息都是无连接的.
3. 只要拿到我的ip地址和端口就都可以给我发消息,我按照顺序接收消息.
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
基于网络的UDP协议的socket socket.SOCK_DGRAM
# 服务端
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 基于网络的UDP协议的socket
server.bind(('192.168.14.198', 9000))
while 1:
from_client_data = server.recvfrom(1024) # 阻塞,等待客户来消息
print(f'\033[1;35;0m来自客户端{from_client_data[1]}: {from_client_data[0].decode("utf-8")} \033[0m')
to_client_data = input('>>>').strip()
server.sendto(to_client_data.encode('utf-8'), from_client_data[1])
最后如果不注释,接收一次必须回复一次才能继续接收
两行如果注释,只接受不发送,可以无限接收.
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 基于网络的UDP协议的socket
while 1:
to_server_data = input('>>>:').strip()
client.sendto(to_server_data.encode('utf-8'), ('127.0.0.1', 9000))
data,addr = client.recvfrom(1024)
print(f'来自服务端{addr}消息:{data.decode("utf-8")}')
最后如果不注释,回复一次必须接收一次才能再次回复
两行如果注释,只发送不接收,可以无限发送.