服务端
import socket
import subprocess
import struct
IP = '127.0.0.1'
PORT = 8080
ADD = (IP, PORT)
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(ADD)
server.listen(5)
while True:
conn, addr = server.accept()
while True:
try: # 针对win系统
#接受客户端指令
date = conn.recv(1024)
if not date: break # 针对linux系统
date = date.decode('utf-8')
if date == 'q':
conn.send('已退出'.encode('gbk'))
break
#系统执行命令,并保存显示用于返回客户端
obj = subprocess.Popen(date, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, )
print(obj)
#命令显示
out = obj.stdout.read()
#错误显示
err = obj.stderr.read()
msg = err + out
msg_size = len(out) + len(err)
#固定长度(4)打包作为报头传输
size = struct.pack('i', msg_size)
conn.send(size + msg)
# conn.send(msg) # 以gbk 的bytes进行发送
# conn.send(err)
except ConnectionResetError:
break
conn.close()
客户端
import socket
import struct
IP = '127.0.0.1'
PORT = 8080
ADD = (IP, PORT)
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(ADD)
while True:
msg = input('请输入命令(q退出)>>>>').strip()
if not msg:continue #判断输入是否为空,确保不存在空传输等待
client.send(msg.encode('utf-8'))
# 以固定长度获取报头,即获取命令显示长度,以元组返回
header = client.recv(4)
msg_size = struct.unpack('i',header)[0]
#循环在客户端上打印服务器命令显示
res = b''
recv_size = 0
while recv_size < msg_size:
data = client.recv(1024)
recv_size += len(data)
res += data
print(res.decode('gbk'))
# date = client.recv(1024)
# print(date.decode('gbk'))
if msg == 'q':
break
client.close()
注:
1)粘包问题:连续的send发送,则会出现粘包,即多类数据组合成一个数据块。例:1,‘aaa’,9.9 ——> 1aaa9.9
原因:TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
解决:struct模块,将数据长度封装为一个统一长度的报头,首先进行发送。另一端进行recv时,先对报头进行解码,再以解码后的数字,对剩下的数据进行解码。
2)空传输:
当输入空值返回,则会进行无限期等待,造成程序卡死
原因:
tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住。另:而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。
解决:在传输前进行空值判断
3)ConnectionResetError(连接重置错误):
一端强制关闭
解决:win-异常捕捉,liunx-判断传输空值
4)使用循环取数据:防止数据过大而报错