Day 05
远程执行命令和粘包
client客户端
import socket
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM=》TCP协议
# 2、拨电话
phone.connect(("127.0.0.1", 8080))
# 3、发/收消息=>通信循环
while True:
cmd = input("[root@localhost]# ").strip()
phone.send(cmd.encode('utf-8'))
data = phone.recv(1024) # 大于1024
print(data.decode('gbk'))
# 4、关闭
phone.close()
server服务端
"""
服务端应该满足的特性:
1、一直对外提供服务
2、并发地提供服务
"""
import socket
import subprocess
# 1、买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM=》TCP协议
# 2、插手机卡
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 就是它,在bind前加
phone.bind(("127.0.0.1", 8080)) # 本地回环
# 3、开机
phone.listen(5)
print('starting %s:%s' % ("127.0.0.1", 8080))
# 4、等电话链接=>链接循环
while True:
conn, client_addr = phone.accept()
print(client_addr)
# 5、收/发消息=>通信循环
while True:
try:
cmd = conn.recv(1024) # 最大接收的字节个数
if len(cmd) == 0: # 针对linux系统
break
obj = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
res=obj.stdout.read()+obj.stderr.read() # + 会申请新的内存空间
# 可以根据流式协议 连续发送out 和 err
print(res)
conn.send(res)
except Exception: # 针对windows系统
break
# 6、关闭
conn.close() # 挂电话
phone.close() # 关机
流式协议 = 粘包问题
将短时间内多个数据打包一起发送,当等待时间过长时短数据也会发送出去。
我们知道TCP协议也叫流式协议,那么只有TCP有粘包现象,UDP永远不会粘包,因为服务端发数据一次只能发送1k数据,但是接收端的应用程序能够一次提取多个数据3k或者更多,应用程序看到的数据是一个整体,或者说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。
所谓的粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据造成的。
此外,发送引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往哟啊收集到足够多数据后才发送一个TCP段。连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方接收到了粘包数据。
-
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
-
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
-
tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略
即服务端不能接收空字符,收空相当于什么也没收到自然也什么都不会发送出去。
两种情况会发生粘包
- 发送端要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据量很小,会合到一起,产生粘包)
- 接收方不及时接收缓冲区的包,造成多个包接受(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
解决粘包的问题
方法一、
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
from socket import *
import subprocess
# 创建对象
server = socket(AF_INET, SOCK_STREAM)
# 绑定服务
server.bind(("192.168.11.77", 8080))
# 服务
server.listen(5) # 最大监听数量
while True:
common, address = server.accept() # 准备接收
print("我已开机感觉良好!")
while True:
try:
message = common.recv(1024) # 接收客户端最大字节流
if len(message) == 0: break # 如果接收为空
obj = subprocess.Popen(
message.decode("utf8"),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
err = obj.stderr.read() # 读取错误
if err: # 如果存在错误
result = err # 结果为错误
else: # 没有错误1
result = obj.stdout.read() # 正常打印
data_length = len(result) # 判断结果的长度
common.send(str(data_length)).encode("utf8") # 蠢在这里!! str
except Exception:
break
这里我们发送只能发送字符串(str)形式,但是我们得到的长度是整形(int)类型,所以我们要把他变成字符串类型。
其实我们可以把它用struct
加密一下
这是struct
的加密参数范围
接下里我们将演示一下如何通过报头来传输文件或者图片
server
import subprocess
import os
import struct
import json
from socket import *
server = socket(AF_INET, SOCK_STREAM)
# print(server)
server.bind(('127.0.0.1', 8082))
server.listen(5)
while True:
conn, client_addr = server.accept()
print(conn)
print(client_addr)
while True:
try:
msg = conn.recv(1024).decode('utf-8')
cmd,file_path=msg.split()
if cmd == "get":
# 一、制作报头
header_dic={
"total_size":os.path.getsize(file_path),
"filename":os.path.basename(file_path),
"md5":"1231231231232132131232311"
}
header_json=json.dumps(header_dic)
header_json_bytes=header_json.encode('utf-8')
# 二、发送数据
# 1、先发送报头的长度
header_size=len(header_json_bytes)
conn.send(struct.pack('i',header_size))
# 2、再发送报头
conn.send(header_json_bytes)
# 3、最后发送真实的数据
with open(r'%s' %file_path,mode='rb') as f:
for line in f:
conn.send(line)
except Exception:
break
conn.close()
server.close()
client
import struct
import json
from socket import *
client = socket(AF_INET, SOCK_STREAM)
# print(client)
client.connect(('127.0.0.1', 8082))
while True:
cmd = input(">>: ").strip() # get 文件路径
if len(cmd) == 0:
continue
client.send(cmd.encode('utf-8'))
# 1、先接收报头的长度
res=client.recv(4)
header_size=struct.unpack('i',res)[0]
# 2、再接收报头
header_json_bytes=client.recv(header_size)
header_json=header_json_bytes.decode('utf-8')
header_dic=json.loads(header_json)
print(header_dic)
# 3、最后接收真实的数据
total_size=header_dic['total_size']
filename=header_dic['filename']
recv_size = 0
with open(r"D:\python全栈15期\day32\代码\03 定制复杂的报头\版本2\download\%s" %filename, mode='wb') as f:
while recv_size < total_size:
data = client.recv(1024)
f.write(data)
recv_size += len(data)
client.close()