subprocess
1.定义
1.可以帮你通过代码执行操作系统的终端命令
2.并返回终端执行命令后的结果
2.方法
用subprocess
模块来运行系统命令.subprocess
模块允许我们创建子进程,连接他们的输入/输出/错误管道,还有获得返回值。
1.subprocess模块中只定义了一个类: Popen。可以使用Popen来创建进程,并与进程进行复杂的交互。
2.如果参数shell设为true,程序将通过shell来执行。
3.subprocess.PIPE
在创建Popen对象时,subprocess.PIPE可以初始化stdin, stdout或stderr参数。表示与子进程通信的标准流。
3.代码
初始版
import subprocess
# 执行系统dir命令,把执行的正确结果放到管道中
obj = subprocess.Popen(
'tasklist', # cmd 命令 /dir/tasklist
shell= True, # Shell=True
stderr=subprocess.PIPE, # 返回错误结果参数 error
stdout=subprocess.PIPE # 返回正确结果参数
)
# 拿到正确结果的管道,读出里面的内容
data = obj.stdout.read() + obj.stderr.read()
# cmd中默认为gkb,解码需要gbk
print(data.decode('gbk'))
客户端与服务端交互,cmd命令在客户端打印
'''服务端'''
import socket
import subprocess
'''客户端输入cmd命令,服务端接受命令并传给cmd,得到正确的数据,利用subprocess返回数据'''
s = socket.socket()
s.bind(
('127.0.0.1',8848)
)
s.listen(5)
print('等待客户端连接')
while True:
conn,addr = s.accept()
print(f'有客户端{addr}成功连接')
while True:
try:
# 1.接受用户输入的cmd命令
cmd = conn.recv(1024).decode('utf-8')
if cmd == 'q':
break
# 2.将用户输入命令利用subprocess得到正确返回
obj = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 3.取出正确和错误结果
data = obj.stdout.read() + obj.stderr.read()
# 将结果发送给客户端
conn.send(data)
except Exception:
break
conn.close()
==========================================================
'''客户端'''
import socket
import subprocess
c = socket.socket()
c.connect(
('127.0.0.1',8848)
)
while True:
data = input('请输入CMD命令:')
# 发送
c.send(data.encode('utf-8'))
if data == 'q':
break
# 接收 进行解码,cmd默认gbk形
msg = c.recv(1024).decode('gbk')
print(msg)
粘包问题
服务端第一次发送的数据,客户端无法精确一次性接受完毕,下一次发送的数据与上一次数据粘在一起了.
- 无法预测对方需要接受的数据大小长度
- 多次连续发送数据量小,并且时间间隔短的数据一次性打包发送
在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小、数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。
对于UDP,不会使用块的合并优化算法,这样,实际上目前认为,是由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。所以UDP不会出现粘包问题。
TCP协议特性
TCP是一个流式协议,会将多次连续发送数据量小,并时间间隔短的数据一次性打包发送.
解决粘包问题
为了避免粘包现象,可采取以下几种措施:
(1)对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;
(2)对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;
(3)由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。
struct模块
可以将发送的数据长度提前发送至服务端,服务端接受到数据长度,自定义接受.
必须先定义报头,发送报头,再发送真实数据.
是一个可以将很长的数据的长度,压缩成固定的长度的一个标记(数据报头)
`i:模式`,会将数据长度压缩成4个bytes
代码
'''服务端'''
import socket
import struct
import subprocess
s= socket.socket()
s.bind(
('127.0.0.1',8848)
)
s.listen(5)
print('等待客户端连接')
while True :
conn,addr= s.accept()
print(f'客户端{addr}已连接')
while True:
try:
# 1.接受用户输入的cmd命令
cmd = conn.recv(1024).decode('utf-8')
if cmd == 'q':
break
# 2.将用户输入命令利用subprocess得到正确返回
obj = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
# 3.取出正确和错误结果
data = obj.stdout.read() + obj.stderr.read()
# 4.打包压缩,成只有4位的报头信息,i 模式
headers = struct.pack('i',len(data))
# 5.将报头先行发送,让客户端准备接受的大小
conn.send(headers)
# 6.再发送真实数据
conn.send(data)
except Exception as e:
print(e)
break
conn.close()
=========================================================
'''客户端'''
import socket
import subprocess
import struct
c = socket.socket()
c.connect(
('127.0.0.1',8848)
)
while True:
msg = input('请输入cmd命令')
if msg == 'q':
break
# 1.将cmd 命令传至服务端处理
c.send(msg.encode('utf-8'))
# 2.接受服务端发送的4位报头
headers = c.recv(4)
# 3.将报头解包(unpack),获得元组,索引取数据长度
data_len = struct.unpack('i',headers)[0]
# 4.接受真实的数据信息
data = c.recv(data_len)
print(data.decode('gbk'))
c.close()
代码2
即想发送文件,又想发送文件的描述信息
'''客户端发送字典给服务端
send_dic:{
file_name : 文件名
file_size : 文件的真实长度}
服务端接受到字典,并接受文件的真实数据'''
服务端
import socket
import struct
import json
s = socket.socket()
s.bind(
('127.0.0.1',8848)
)
s.listen(5)
print('等待客户端连接')
while True:
conn,addr = s.accept()
print(f'用户{addr}已连接')
while True:
try:
# 1.接受到客户端发送的报头
beaders = conn.recv(4)
# print(beaders) # b'Mx00x00x00'
# 2.报头解压缩,索引获得数据长度
data__len = struct.unpack('i',beaders)[0]
# print(data__len) # 77
# 3.接受真实数据(序列化的数据)
bytes_data = conn.recv(data__len)
# print(bytes_data) # b'{"file_name": "abc\u7684\u9017\u6bd4\u4eba\u751f.txt", "file_size": 12345678}'
# 4.反序列化获得数据
dic = json.loads(bytes_data.decode('utf-8'))
# print(dic) # {'file_name': 'abc的逗比人生.txt', 'file_size': 12345678}
except Exception as e :
print(e)
break
conn.close()
=========================================================
'''客户端'''
import socket
import struct
import json
import time
c = socket.socket()
c.connect(
('127.0.0.1',8848)
)
while True:
# 1.用户文件的字典
send_dic = {
'file_name':'abc的逗比人生.txt',
'file_size':12345678
}
# 2.json序列化,并转码成bytes类型数据(为的是struct的len长度)
json_data = json.dumps(send_dic)
bytes_data = json_data.encode('utf-8')
# 3.压缩数据做成报头,发送至服务端
headers = struct.pack('i',len(bytes_data))
# print(bytes_data) # b'{"file_name": "abc\u7684\u9017\u6bd4\u4eba\u751f.txt", "file_size": 12345678}'
# print(len(bytes_data)) # 77
# print(headers) # b'Mx00x00x00'
c.send(headers)
# 4.发送真实数据
c.send(bytes_data)
time.sleep(10)
# {'file_name': 'abc的逗比人生.txt', 'file_size': 12345678}
# 会一直打印,所以手动停止5秒
上传大文件
利用while
循环进行一段一段的上传防止粘包.
服务端
import socket
import struct
import json
s = socket.socket()
s.bind(
('127.0.0.1',8848)
)
s.listen(5)
print('等待客户端连接')
while True:
conn, addr = s.accept()
print(f'客户端{addr}已连接')
try:
# 1.接受客户端传来的字典报头
headers = conn.recv(4)
# 2.解压索引获得字典的长度
data_len = struct.unpack('i',headers)[0]
# 3.接受文件字典的bytes信息
bytes_data = conn.recv(data_len)
# 4.反序列化得到字典数据
data_dic = json.loads(bytes_data.decode('utf-8'))
print(data_dic)
# 5.获得文件字典的名字与大小
file_name = data_dic.get('file_name')
file_size = data_dic.get('file_size')
# 6.以文件名打开文件(循环控制打开资源占用)
size = 0
with open(file_name,'wb') as f:
while size < file_size:
# 每次接受1024大小
data = conn.recv(1024)
# 每次写入data大小
f.write(data)
# 写完进行追加
size += len(data)
print(f'{file_name}接受完毕')
except Exception as e:
print(e)
break
conn.close()
客户端
import socket
import struct
import json
c= socket.socket()
c.connect(
('127.0.0.1',8848)
)
# 1.打开一个视频文件,获取数据大小
with open(r'F:老男孩12期开课视频day 275 上传大文件.mp4','rb') as f :
movie_bytes = f.read() #获得的是二进制流
# 文件自动关闭
# 2.为视频文件组织一个信息字典,字典有名称大小
movie_info = {
'file_name':'大视频.mp4',
'file_size':len(movie_bytes)
}
# 3.打包字典,发送文件字典的报头(客户端获得文件的名字与大小)
json_data = json.dumps(movie_info)
bytes_data = json_data.encode('utf-8')
# 获得字典的报头
headers = struct.pack('i',len(bytes_data))
# 发送报头
c.send(headers)
# 发送真实文件的字典
c.send(bytes_data)
# 4.发送真实的文件数据(大文件循环发送减少占用)
size = 0
num = 1
with open(r'F:老男孩12期开课视频day 275 上传大文件.mp4','rb') as f :
while size < len(movie_bytes):
# 打开时每次读取1024大小数据
send_data = f.read(1024) # 获得的是二进制流
print(send_data,num)
num += 1
# 每次发送都是1024大小数据
c.send(send_data)
# 为初始数据增加发送的大小,控制循环
size += len(send_data)
UDP
UDP是一种传输协议,
1.不需要建立双向通道
2.不会粘包
3.客户端给服务端发送数据,不需要等待服务端返回接受成功
4.数据容易丢失,
- udp就好比是在发短信
- tcp协议好比是在打电话
- UDP是无链接的,先启动哪一端都不会报错
- UDP协议是数据报协议,发空的时候也会自带报头,因此客户端输入空,服务端也能收到
'''服务端'''
import socket
# 1.SOCK_DGRAM:代表UDP
server = socket.socket(type=socket.SOCK_DGRAM)
# 2.服务端要绑定 ip + port
server.bind(
('127.0.0.1',8848)
)
# server.listen(5) UDB不需要建立连接
# conn ,addr= server.accept() 也不需要
# 3.接收服务端发送的消息,及地址
msg ,addr = server.recvfrom(1024)
print(msg)
'''只管发送,不会管有没有接收到,不可靠传输'''
=========================================================
'''客户端'''
import socket
# 1.获取UDB对象
client = socket.socket(type=socket.SOCK_DGRAM)
# 2.获取ip端口地址
IP_port = ('127.0.0.1',8848)
# 3.发送数据至服务端地址
client.sendto(b'hello',IP_port)
QQ聊天室
- UPD协议一般不用于传输大数据。
- UDP套接字虽然没有粘包问题,但是不能替代TCP套接字,因为UPD协议有一个缺陷:如果数据发送的途中,数据丢失,则数据就丢失了,而TCP协议则不会有这种缺陷,因此一般UPD套接字用户无关紧要的数据发送,例如qq聊天。
'''服务端'''
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(
('127.0.0.1',8848)
)
print('等待用户连接...')
while True:
# 接受到客户端发送的消息(元组形式)
msg ,addr = server.recvfrom(1024)
print(f'来自用户{addr}的消息:')
print(msg.decode('utf-8')) # 打印客户端发送的消息
send_msg = input('服务端发送的消息:').encode('utf-8') # 服务端发送消息至客户端
# 服务端向客户端addr发送
server.sendto(send_msg,addr)
====================================================
'''客户端'''
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1',8848)
while True:
send_msg = input('客户端1:').encode('utf-8')
# 客户端发送至服务端的消息
client.sendto(send_msg,ip_port)
# 接受到服务端的消息
msg ,addr= client.recvfrom(1024)
print(f'来自用户{addr}的消息')
print(msg.decode('utf-8'))
SocketServer
python内置模块,可以简化socket套接字服务端的代码。
- 简化TCP与UDP服务端代码
- 必须要创建一个类
基于tcp的套接字,关键就是两个循环,一个链接循环,一个通信循环
socketserver模块中分两大类:server类(解决链接问题)和request类(解决通信问题)
'''服务端'''
import socketserver
# 1.定义类,必须继承BaseRequestHandler类
class Mytcp(socketserver.BaseRequestHandler):
# 2.重写父类handle
def handle(self):
print(self.client_address)
while True:
try:
# 1.服务端接受消息
data = self.request.recv(1024).decode('utf-8') # 等同于conn.recv(1024)
send_msg = data.upper()
# 2.给客户端发送消息
self.request.send(send_msg.encode('utf-8'))
except Exception as e:
print(e)
break
if __name__ == '__main__':
# 定义类的传参得到实例化对象
server = socketserver.ThreadingTCPServer(
('127.0.0.1',8848),Mytcp
)
server.serve_forever()
==========================================================
'''客户端'''
import socket
client = socket.socket()
client.connect(
('127.0.0.1',8848)
)
while True:
msg = input('客户端向服务端发送:')
client.send(msg.encode('utf-8'))
if msg == 'q':
break
data = client.recv(1024)
print(data.decode('utf-8'))