2022年 11月 8日

Python网络编程(远程命令执行)

远程执行命令

描述:相当于SSH的实现,client发送命令,server端接收并执行后返回结果给client,使用subprocess模块实

实现过程

1.调用subprocess模块

[root@node2 socket]# cat test.py
#!/usr/local/python3/bin/python3
import subprocess
a=subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE)
print(a)
[root@node2 socket]# python3 test.py
<subprocess.Popen object at 0x7fa5f6cae358>         
#对象,subprocess实际是自身开了一个进程,与主进程没关系,它有自己的一块区域,与print(a)类似于并行操作,
 结果直接显示在屏幕上,但是同时也保存在它的子进程中,需要把它的结果放在能够传输的主进程中,主要使用
 管道stdout来实现,stdout=subprocess.PIPE标准输出通过管道,由子进程转到主进程
client.py  remote_command.py  server.py  test.py
 
[root@node2 socket]# python3 test.py
<subprocess.Popen object at 0x7fd51f307358>  #使用stdout就会有输出,把结果转到主进程
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

2.再使用主进程调用

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
[root@node2 socket]# cat test.py
#!/usr/local/python3/bin/python3
import subprocess
a=subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE)
print(a.stdout.read())
[root@node2 socket]# python3 test.py
b'client.py  remote_command.py  server.py  test.py\n'  ##输出的结果是bytes类型要转换
 
[root@node2 socket]# cat test.py
#!/usr/local/python3/bin/python3
import subprocess
a=subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE)
print(str(a.stdout.read(),'utf8'))                  ##转换字符串
[root@node2 socket]# python3 test.py
client.py  remote_command.py  server.py  test.py
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

3.远程命令操作实现

[root@node2 socket]# cat remote_command.py
#!/usr/local/python3/bin/python3
import socket
import subprocess            ###
# family type
sk = socket.socket()
address=('127.0.0.1',8888)
sk.bind(address)
sk.listen(3)
 
while True:
    conn, addr = sk.accept()
    while True:
        try:
            data=conn.recv(1024)
        except Exception:
            break
        print(str(data,'utf-8'))
        if not data:break
        obj=subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE) 
        ##Popen是一个类,后面的是实例化的参数,实例出来的一个对象,而且data接收的是bytes类型要转换
        # obj.stdout.read()            #得到执行obj的结果
        cmd_result=obj.stdout.read()
        conn.send(cmd_result)
sk.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

4.测试:存在问题,当重复操作时会发现命令结果不对

[root@node2 socket]# python3 remote_command.py
dir
[root@node2 socket]# python3 client.py
>>> dir
client.py  remote_command.py  server.py  test.py
  • 1
  • 2
  • 3
  • 4
  • 5

5.接收传送的大小优化

描述:运行命令的结果有限制,如果超过1024后有问题

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
[root@node2 socket]# cat client.py
#!/usr/local/python3/bin/python3
import socket
sk=socket.socket()
address=('127.0.0.1',8888)
sk.connect(address)
while True:
    inp=input('>>> ')
    if inp == 'exit':
        break
    sk.send(bytes(inp,'utf8'))
    data=sk.recv(4096)      #把接收的值调大,但是接收是有范围的限制的,如果更大超过了还会有问题
    print(str(data,'utf-8'))
sk.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

6.issue: 服务器可以把数据发送完,只是client数据没有真正把它接收完

解决的方法: 初始化一个变量,收到的数据加入到变量中,先使用length取得该数据的大小,与之做对比

细化: 客户端设置一次只接收1024个,但是并不知道需要接收多少次,取决于com_result发送的到底有多长,如果是1025发送两次就可以了,如果是2049就要发3次,服务端先通知client这个com_result的大小,再进行判断,如有1048个字节,最大1024,需要接收两次1024+24,再初始一个变量,类似于求和sum=0,开始接收,再把数据添加给变量,接收一次做一次判断这个文件是否等于1048的长度,再接收剩下的24个字节,回到while中判断

[root@node2 socket]# cat remote_command.py
#!/usr/local/python3/bin/python3
import socket
import subprocess
# family type
sk = socket.socket()
address=('127.0.0.1',8888)
sk.bind(address)
sk.listen(3)
while True:
    conn, addr = sk.accept()
    while True:
        try:
            data=conn.recv(1024)
        except Exception:
            break
        print(str(data,'utf-8'))
        if not data:break
        obj=subprocess.Popen(data,shell=True,stdout=subprocess.PIPE)
        cmd_result=obj.stdout.read()
        result_len=bytes(str(len(cmd_result)),'utf8')
        #len(cmd_result)是int类型,再转换成str,再到bytes,注意:str可以与int互转,bytes与str互转,int不能直接转换成bytes
        conn.sendall(result_len)
        conn.sendall(cmd_result)
 
sk.close()
 
[root@node2 socket]# cat client.py
#!/usr/local/python3/bin/python3
import socket
sk=socket.socket()
address=('127.0.0.1',8888)
sk.connect(address)
while True:
    inp=input('>>> ')
    if inp == 'exit':
        break
    sk.send(bytes(inp,'utf8'))
    result_len=str(sk.recv(1024))        #linux中不用int强转
    #result_len=int(str(sk.recv(1024)))  #传输的是一个数字int,过来的是bytes类型,数字怎么也不会超过1024个字节
    print(result_len)
     
    data=bytes()              #过来内容做一个初始化,bytes()是一个空的bytes
    while len(data)!=result_len:  #只要result_len不等于这个长度,就一直接收
        recv=sk.recv(1024)  
        data+=recv            #对接收的累加,加一次后在while中判断是不是等于原来的文件大小,不等于继续接收
    print(str(data,'utf-8'))  #data是bytes类型,需要转换
sk.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

7.粘包现象以及解决办法

粘包现象错误

[root@node2 socket]# python3 client.py
>>> ifconfig
Traceback (most recent call last):
  File "client.py", line 12, in <module>
    result_len=int(str(sk.recv(1024),'utf8'))
ValueError: invalid literal for int() with base 10: '900eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500\n  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

问题: 当进行连续的两次很快的send时,它不会发送完第一次,再发第二次,而且连着第二次的一起发,第一次是数字,client端中使用int,是正常的,但是第二次时,是一堆字符串,使用int会报错,result_len=int(str(sk.recv(1024)))在int转换时,会造成有错误

分析: 在server中的conn.sendall的两行代码,首先第一行是发送一行数字(result_len),数字无论多大,占用的空间都很小,如5000多,可能占一个字节就可以,当conn.sendall(result_len)时,并不会直接发送过去,在很短的时间内会等待一下,看看后面是否还有连续发送的,但是在conn.sendall(cmd_result)中,又发送一堆的字符串,相当于一艘小船开始时,装了一点小内容,它不会开,它会等待一小段时间,然后再装了一堆的东西,一起发送过去了,这个现象是偶尔出现

conn.sendall(result_len)
conn.sendall(cmd_result)
  • 1
  • 2

**解决方法1:**在两个之间小停一下,但是在这始终造成一小段时间的阻塞

'''
遇到问题没人解答?小编创建了一个Python学习交流QQ群:579817333 
寻找有志同道合的小伙伴,互帮互助,群里还有不错的视频学习教程和PDF电子书!
'''
[root@node2 socket]# cat remote_command.py
#!/usr/local/python3/bin/python3
import socket
import subprocess
import time                     #########
# family type
sk = socket.socket()
address=('127.0.0.1',8888)
sk.bind(address)
sk.listen(3)
 
while True:
    conn, addr = sk.accept()
    while True:
        try:
            data=conn.recv(1024)
        except Exception:
            break
        print(str(data,'utf-8'))
        if not data:break
        obj=subprocess.Popen(data,shell=True,stdout=subprocess.PIPE)
        cmd_result=obj.stdout.read()
        result_len=bytes(str(len(cmd_result)),'utf8')
        conn.sendall(result_len)   #当发送数字时,会停留一小点时间,会问下是否还有数据要发送,没有就发送了
        time.sleep(1)              #当加下time.sleep时会超过它等待的时间,这样时间就会变得正常
        conn.sendall(cmd_result)
 
sk.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

**解决方法2:**在两次发送之间,加入一个recv,把它们隔断开

[root@node2 socket]# cat remote_command.py
#!/usr/local/python3/bin/python3
import socket
import subprocess
# family type
sk = socket.socket()
address=('127.0.0.1',8888)
sk.bind(address)
sk.listen(3)
 
while True:
    conn, addr = sk.accept()
    while True:
        try:
            data=conn.recv(1024)
        except Exception:
            break
        print(str(data,'utf-8'))
        if not data:break
        obj=subprocess.Popen(data,shell=True,stdout=subprocess.PIPE)
        cmd_result=obj.stdout.read()
        result_len=bytes(str(len(cmd_result)),'utf8')
        conn.sendall(result_len)
        conn.recv(1024)            #####中间把它隔开,因为recv会阻塞不有问题,只有同时两个send时造成的
        conn.sendall(cmd_result)
 
sk.close()
 
[root@node2 socket]# cat client.py
#!/usr/local/python3/bin/python3
import socket
sk=socket.socket()
address=('127.0.0.1',8888)
sk.connect(address)
while True:
    inp=input('>>> ')
    if inp == 'exit':
        break
    sk.send(bytes(inp,'utf8'))
    #result_len=str(sk.recv(1024),'utf8')
    result_len=int(str(sk.recv(1024),'utf8'))
    sk.sendall('ok')           ###server加了一个recv,client对应加上一个发送
    print(result_len)
     
    data=bytes()
    while len(data)!=result_len:
        recv= sk.recv(1024)
        data+=recv
    print(str(data,'utf-8'))
sk.close()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50