1. subprocess模块
-
描述:创建一个新的进程让其执行另外的程序,并与它进行通信,获取标准的输入、标准输出、标准错误以及返回码等
-
subprocess模块中定义了一个Popen类,通过它可以来创建进程,并与其进行复杂的交互,输出的结果需要使用read()来读取,并返回bytes类型
import subprocess
subprocess.Popen(arg, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
-
args:需要执行的shell命令
-
shell:设置位True代表程序通过shell执行命令
-
stdin:标准输入
-
stdout:标准输出
-
stderr:标准错误输出
-
PIPE:管道(输入输出的对象)
import subprocess
for i in range(2):
shell = input('>>>:').strip()
data = subprocess.Popen(shell, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
print('正确输出是:')
print(data.stdout.read())
print('错误输出是:')
print(data.stderr.read())
>>>
>>>:hostname
正确输出是:
b'Yan\r\n'
错误输出是:
b''
>>>:aaa
正确输出是:
b''
错误输出是:
b"'aaa' \xb2\xbb\xca\xc7\xc4\xda\xb2\xbf\xbb\xf2\xcd\xe2\xb2\xbf\xc3\xfc\xc1\xee\xa3\xac\xd2\xb2\xb2\xbb\xca\xc7\xbf\xc9\xd4\xcb\xd0\xd0\xb5\xc4\xb3\xcc\xd0\xf2\r\n\xbb\xf2\xc5\xfa\xb4\xa6\xc0\xed\xce\xc4\xbc\xfe\xa1\xa3\r\n"
2. struct模块
-
描述:把一个数据类型转为固定长度的bytes类型,可以用于网络传输
-
struct.pack:把数据类型根据指定的格式符转为字节流,返回bytes类型
-
struct.unpack:与pack相反,把字节流转为数据类型,返回元组形式,第0个值是转换位的数据类型的值
import struct
# 'i'代表int(整型),标准字节长度是:4;int的取值范围是: -2147483648 <= i(number) <= 2147483647
# 'q'代表long(长整型),标准字节长度是:8;'i'范围内的值也可以使用'q'
data1 = struct.pack('i', 2147483647)
print(data1)
print(len(data1))
print(struct.unpack('i', data1))
print('-' * 38)
data2 = struct.pack('q', 2147483648)
print(data2)
print(len(data2))
print(struct.unpack('q', data2))
print('-' * 38)
data3 = struct.pack('q', 1)
print(data3)
print(len(data3))
print(struct.unpack('q', data3)[0])
>>>
b'\xff\xff\xff\x7f'
4
(2147483647,)
--------------------------------------
b'\x00\x00\x00\x80\x00\x00\x00\x00'
8
(2147483648,)
--------------------------------------
b'\x01\x00\x00\x00\x00\x00\x00\x00'
8
1
3. 粘包现象
-
功能描述:如下服务端和客户端代码,实现的功能是客户端成功与服务端建立连接,客户端可以远程执行服务端的系统命令(windows平台默认编码是'gbk'),并把执行结果返回给客户端
-
问题描述:指定的字节大小是1024,如果接收的字节大于1024,剩余的数据会随着下一次执行的命令而接收,这样所有的命令和接收数据的结果会完全的混乱。如果把字节大小配置为无穷大,但是超过了内存的物理大小,会把程序搞崩溃,扔是无法从根本解决问题。这种不知道接收的数据大小而手动指定具体的字节大小,最终造成的不可预知的异常结果就是粘包现象
# 服务端
import subprocess
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, add = server.accept()
print('[%s]已成功连接,端口是:[%s]' % (add[0], add[1]))
while True:
try:
shell = conn.recv(1024)
if not shell:
break
data = subprocess.Popen(shell.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = data.stdout.read()
stderr = data.stderr.read()
conn.send(stdout + stderr)
except ConnectionResetError:
break
conn.close()
server.close()
# 客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
shell = input('请输入运行命令:').strip()
if not shell:
continue
client.send(shell.encode('utf-8'))
data = client.recv(1024)
print(data.decode('gbk'))
client.close()
-
只有TCP有粘包现象,UDP永远不会粘包
-
发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区
-
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据
-
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的
-
Nagle算法:把数据量较小并且时间间隔比较短的包合成一个包发送
-
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的
-
tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头
-
udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠
-
tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包
-
两种情况下会发生粘包:
-
发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
-
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
4. 解决粘包问题
-
普通版:自定义报头
-
发送:
-
1. 先制作报头
-
2. 再发送报头
-
3. 最后再发送真实的数据
-
接收:
# 服务端
import subprocess
import struct
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, add = server.accept()
print('[%s]已成功连接,端口是:[%s]' % (add[0], add[1]))
while True:
try:
shell = conn.recv(1024)
if not shell:
break
data = subprocess.Popen(shell.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = data.stdout.read()
stderr = data.stderr.read()
# 第一步:制作报头
total_size = len(stdout) + len(stderr)
header = struct.pack('i', total_size)
# 第二步:发送报头
conn.send(header)
# 第三步:发送真实数据
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
server.close()
# 客户端
import struct
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
shell = input('请输入运行命令:').strip()
if not shell:
continue
client.send(shell.encode('utf-8'))
# 第一步:接收报头
head = client.recv(4)
total_size = struct.unpack('i', head)[0]
# 第二步:循环收取完整的数据
recv_size = 0
data = b''
while recv_size < total_size:
recv_data = client.recv(1024)
data += recv_data
recv_size += len(recv_data)
print(data.decode('gbk'))
client.close()
-
高级版:把报头做成字典形式,字典里包含需要发送的真实数据的详细信息,然后再序列化成字符串,最后再转换成固定长度的字节流(使用'i'的方式,4个字节足够)
-
发送:
-
1. 先制作报头(使用字典格式存储)
-
2. 把字典序列化为字符串
-
3. 把字符串编码为bytes类型
-
4. 把butes类型的长度转为字节流
-
5. 再发送报头长度
-
6. 然后发送报头
-
7. 最后再发送真实的数据
-
接收:
-
1. 先接收报头长度
-
2. 接收报头,解出报头内容
-
3. 解码为字符串类型
-
4. 反序列化位字典类型
-
5. 在字典中获取报头内容
-
6. 循环接收完整的数据
# 服务端
import subprocess
import struct
import json
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('127.0.0.1', 8080))
server.listen(5)
while True:
conn, add = server.accept()
print('[%s]已成功连接,端口是:[%s]' % (add[0], add[1]))
while True:
try:
shell = conn.recv(1024)
if not shell:
break
data = subprocess.Popen(shell.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = data.stdout.read()
stderr = data.stderr.read()
# 第一步:制作报头
# 1. 使用字典存储报头中需要发送数据的详细信息
header_dict = {
'total_size': len(stdout) + len(stderr),
'MD5': 'md5值',
'file_name': '文件路径和文件名',
'file_size': '文件大小',
'file_pwd': '文件加密认证'
}
# 2. 序列化成字符串类型
header_json = json.dumps(header_dict)
# 3. 编码为bytes类型
header_bytes = header_json.encode('utf-8')
# 4. 转换位字节流
header_size = struct.pack('i', len(header_bytes))
# 第二步:发送报头的长度
conn.send(header_size)
# 第三步:发送报头
conn.send(header_bytes)
# 第四步:发送真实数据
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
server.close()
# 客户端
import struct
import json
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
shell = input('请输入运行命令:').strip()
if not shell:
continue
client.send(shell.encode('utf-8'))
# 第一步:接收报头长度
header_len = client.recv(4)
header_size = struct.unpack('i', header_len)[0]
# 第二步:接收报头,解出报头内容
# 1. 接收报头
header_bytes = client.recv(header_size)
# 2. 解码为字符串类型
header_json = header_bytes.decode('utf-8')
# 3. 反序列化为字典类型
header_dict = json.loads(header_json)
# 4. 在字典中获取报头内容
total_size = header_dict['total_size']
# 第三步:循环接收完整的数据
recv_size = 0
data = b''
while recv_size < total_size:
recv_data = client.recv(1024)
data += recv_data
recv_size += len(recv_data)
print(data.decode('gbk'))
client.close()
5. 应用
# 服务端
import os
import struct
import json
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
def get(filename):
header_dict = {
'file_size': os.path.getsize(filename),
'file_name': filename
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
header_size = struct.pack('i', len(header_bytes))
conn.send(header_size)
conn.send(header_bytes)
with open(filename, 'rb') as f:
for i in f:
conn.send(i)
while True:
print('等待客户端连接中...')
conn, add = server.accept()
print('客户端[%s]连接成功,端口是[%s]' % (add[0], add[1]))
while True:
try:
shell = conn.recv(1024)
if not shell:
break
cmd, filename = shell.decode('utf-8').split()
if cmd == 'get':
get(filename)
except ConnectionResetError:
break
conn.close()
server.close()
# 客户端
import struct
import json
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
def get():
header_len = client.recv(4)
header_size = struct.unpack('i', header_len)[0]
header_bytes = client.recv(header_size)
header_json = header_bytes.decode('utf-8')
header_dict = json.loads(header_json)
file_name = header_dict['file_name']
file_size = header_dict['file_size']
recv_size = 0
with open(file_name, 'wb') as f:
while recv_size < file_size:
recv_data = client.recv(1024)
f.write(recv_data)
recv_size += len(recv_data)
while True:
shell = input('下载请输入[get 文件名]:').strip()
if not shell:
continue
client.send(shell.encode('utf-8'))
cmd, filename = shell.split()
if cmd == 'get':
get()
client.close()
# 服务端
import struct
import json
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
def put():
header_len = conn.recv(4)
header_size = struct.unpack('i', header_len)[0]
header_bytes = conn.recv(header_size)
header_json = header_bytes.decode('utf-8')
header_dict = json.loads(header_json)
file_name = header_dict['file_name']
file_size = header_dict['file_size']
recv_size = 0
with open(file_name, 'wb') as f:
while recv_size < file_size:
recv_data = conn.recv(1024)
f.write(recv_data)
recv_size += len(recv_data)
while True:
print('等待客户端连接中...')
conn, add = server.accept()
print('客户端[%s]连接成功,端口是[%s]' % (add[0], add[1]))
while True:
try:
shell = conn.recv(1024)
if not shell:
break
cmd, filename = shell.decode('utf-8').split()
if cmd == 'put':
put()
except ConnectionResetError:
break
conn.close()
server.close()
# 客户端
import os
import struct
import json
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
def put(filename):
header_dict = {
'file_size': os.path.getsize(filename),
'file_name': filename
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
header_size = struct.pack('i', len(header_bytes))
client.send(header_size)
client.send(header_bytes)
with open(filename, 'rb') as f:
for i in f:
client.send(i)
while True:
shell = input('上传请输入[put 文件名]:').strip()
if not shell:
continue
client.send(shell.encode('utf-8'))
cmd, filename = shell.split()
put(filename)
client.close()
# 服务端
import os
import struct
import json
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
def get(filename):
header_dict = {
'file_size': os.path.getsize(filename),
'file_name': filename
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
header_size = struct.pack('i', len(header_bytes))
conn.send(header_size)
conn.send(header_bytes)
with open(filename, 'rb') as f:
for i in f:
conn.send(i)
def put():
header_len = conn.recv(4)
header_size = struct.unpack('i', header_len)[0]
header_bytes = conn.recv(header_size)
header_json = header_bytes.decode('utf-8')
header_dict = json.loads(header_json)
file_name = header_dict['file_name']
file_size = header_dict['file_size']
recv_size = 0
with open(file_name, 'wb') as f:
while recv_size < file_size:
recv_data = conn.recv(1024)
f.write(recv_data)
recv_size += len(recv_data)
while True:
print('等待客户端连接中...')
conn, add = server.accept()
print('客户端[%s]连接成功,端口是[%s]' % (add[0], add[1]))
while True:
try:
shell = conn.recv(1024)
if not shell:
break
cmd, filename = shell.decode('utf-8').split()
if cmd == 'put':
put()
elif cmd == 'get':
get(filename)
except ConnectionResetError:
break
conn.close()
server.close()
# 客户端
import os
import struct
import json
import socket
client = socket.socket()
client.connect(('127.0.0.1', 8080))
def get():
header_len = client.recv(4)
header_size = struct.unpack('i', header_len)[0]
header_bytes = client.recv(header_size)
header_json = header_bytes.decode('utf-8')
header_dict = json.loads(header_json)
file_name = header_dict['file_name']
file_size = header_dict['file_size']
recv_size = 0
with open(file_name, 'wb') as f:
while recv_size < file_size:
recv_data = client.recv(1024)
f.write(recv_data)
recv_size += len(recv_data)
def put(filename):
header_dict = {
'file_size': os.path.getsize(filename),
'file_name': filename
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
header_size = struct.pack('i', len(header_bytes))
client.send(header_size)
client.send(header_bytes)
with open(filename, 'rb') as f:
for i in f:
client.send(i)
while True:
shell = input('请选择上传或下载[get/put 文件名]:').strip()
if not shell:
continue
if len(shell.split()) == 2:
client.send(shell.encode('utf-8'))
cmd, filename = shell.split()
if cmd == 'put':
print('上传文件[%s]' % filename)
put(filename)
elif cmd == 'get':
print('下载文件[%s]' % filename)
get()
else:
print('输入有误,请重新输入!')
else:
print('输入有误,请重新输入!')
client.close()
# 服务端
import os
import struct
import json
import socket
server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)
def get(filename):
header_dict = {
'file_size': os.path.getsize(filename),
'file_name': filename
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
header_size = struct.pack('i', len(header_bytes))
conn.send(header_size)
conn.send(header_bytes)
with open(filename, 'rb') as f:
for i in f:
conn.send(i)
def put():
header_len = conn.recv(4)
header_size = struct.unpack('i', header_len)[0]
header_bytes = conn.recv(header_size)
header_json = header_bytes.decode('utf-8')
header_dict = json.loads(header_json)
file_name = header_dict['file_name']
file_size = header_dict['file_size']
recv_size = 0
with open(file_name, 'wb') as f:
while recv_size < file_size:
recv_data = conn.recv(1024)
f.write(recv_data)
recv_size += len(recv_data)
while True:
print('等待客户端连接中...')
conn, add = server.accept()
print('客户端[%s]连接成功,端口是[%s]' % (add[0], add[1]))
while True:
try:
shell = conn.recv(1024)
if not shell:
break
cmd, filename = shell.decode('utf-8').split()
if cmd == 'put':
put()
elif cmd == 'get':
get(filename)
except ConnectionResetError:
break
conn.close()
server.close()
#客户端
import os
import struct
import json
import socket
def progress(percent, mode, description=None):
if mode == 'put':
description = '上传'
elif mode == 'get':
description = '下载'
if percent == 100:
print('\r[%s完成]%s%% : %s\n' % (description, percent, '#' * percent), end='')
else:
print('\r[正在%s]%s%% : %s' % (description, percent, '#' * percent), end='')
client = socket.socket()
client.connect(('127.0.0.1', 8080))
def get():
header_len = client.recv(4)
header_size = struct.unpack('i', header_len)[0]
header_bytes = client.recv(header_size)
header_json = header_bytes.decode('utf-8')
header_dict = json.loads(header_json)
file_name = header_dict['file_name']
file_size = header_dict['file_size']
recv_size = 0
with open(file_name, 'wb') as f:
while recv_size < file_size:
recv_data = client.recv(1024)
f.write(recv_data)
recv_size += len(recv_data)
percent = int(100 * (recv_size / file_size))
progress(percent, cmd)
def put(filename):
header_dict = {
'file_size': os.path.getsize(filename),
'file_name': filename
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
header_size = struct.pack('i', len(header_bytes))
client.send(header_size)
client.send(header_bytes)
send_size = 0
with open(filename, 'rb') as f:
for i in f:
client.send(i)
send_size += len(i)
percent = int(100 * (send_size / header_dict['file_size']))
progress(percent, cmd)
while True:
shell = input('请选择上传或下载[get/put 文件名]:').strip()
if not shell:
continue
if len(shell.split()) == 2:
client.send(shell.encode('utf-8'))
cmd, filename = shell.split()
if cmd == 'put':
print('上传文件[%s]' % filename)
put(filename)
elif cmd == 'get':
print('下载文件[%s]' % filename)
get()
else:
print('输入有误,请重新输入!')
else:
print('输入有误,请重新输入!')
client.close()