zoukankan      html  css  js  c++  java
  • python paramiko登陆设备

    一,单线程 - shell交互

    def chan_recv(chan):
        data = chan.recv(1024)            # 收1024数据
        sys.stdout.write(data.decode())   # 输出
        sys.stdout.flush()
    
    if __name__ == '__main__':
        ssh = paramiko.SSHClient()
        ssh.load_system_host_keys()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect('10.10.10.2', port=22, username='cisco', password='cisco', timeout=3)  # 3秒超时
        channel = ssh.invoke_shell()
    
        chan_recv(channel)       # 开始前先收一下数据
        while True:              # 监听输入
            d = input()
            if d == 'quit':      # 如果输入quit,就退出
                break
            channel.send(d + '
    ')
            chan_recv(channel)
    
        channel.close()
        ssh.close()

    问题:接收数据时的不规则性,chan.recv(1024)每次只收1024数据:

    1,如果发送方的数据大于1024,就导致一次就取不完,需要分多次取

    2,粘包问题: 即使发送方数据小于1024,但是如果去缓存取数据的时候数据还没到达,也会导致一次取不完;而且也可能会取到下一次命令的返回数据,即如果交互多次,此时输入命令和拿到的结果无法一一对应,

    以上代码在执行时,获取不到预期结果时,多敲几个回车就会出结果

    解决方法:

    1,第二个粘包问题可以通过sleep粗暴解决

    2,如果想把两个问题同时解决,主要有三个方法:

      a)发送实际数据前,server端先发数据大小,client端持续接收,并且最后一次不收1024,而收实际大小,但是像paramiko这种server端无法改造的不适用(老男孩python socket编程就是这种解决思路)

      b)明确结尾标示符,即做回显判断,每输入一条命令,都接收到“结尾标示符”为止,参考“python paramiko自动登录网络设备抓取配置信息”

      c)双线程,主线程做输入,子线程持续不断接收


    二,双线程 - shell交互

    def chan_recv(chan):
        while True:
            data = chan.recv(1024)      # data是收到的数据,每次收1024
            if not data:    # 客户端输入了断开socket的命令(例如exit),会导致子线程循环结束
                break
            sys.stdout.write(data.decode())
            sys.stdout.flush()
    
    
    if __name__ == '__main__':
        ssh = paramiko.SSHClient()
        ssh.load_system_host_keys()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect('10.10.10.2', port=22, username='cisco', password='cisco', timeout=3)  # 3秒超时
        channel = ssh.invoke_shell()
        
        # 这里如果不设置daemon进程,即使主线程关闭退出,子线程也不会结束 
        writer = threading.Thread(target=chan_recv, args=(channel,), daemon=True)  
        writer.start()
        while True:
            d = input()
            if d == 'quit':
                break
            channel.send(d + '
    ')
        # writer.join()  # 这里不用join了,线程已经设置为守护线程,主线程结束就会自动关闭守护线程了
    
        channel.close()
        ssh.close()

    备注:

    1,可以看到相比单线程-shell交互,双线程版的子线程可以通过while循环接收server,也就不用去关心粘包、一次收1024能不能收完这类问题了

    2,server端可能会有交互要求输入,例如server端可能进行了分屏,返回--More--,要求输入空格后,才继续显示,此时需要先取消分屏

    3,输入一条命令,界面上会显示两遍,第一遍是自己客户端输入的,第二遍是server端的回显

    三,双线程 - 执行预先定义的命令

    本例是对“python paramiko自动登录网络设备抓取配置信息”的改进,无需事先确定回显内容

    import paramiko
    import threading
    
    
    class MyThread(threading.Thread):
    
        def __init__(self, func, args=()):
            super(MyThread, self).__init__()
            self.func = func
            self.args = args
    
        def run(self):
            self.result = self.func(*self.args)
    
        def get_result(self):
            try:
                return self.result
            except Exception:
                return None
    
    
    def chan_recv(chan):
        resp = ''
        while True:
            data = chan.recv(1024)
            if not data:
                break
            resp += data.decode()
        return resp
    
    
    def shell(commands, host, port, username, password, timeout=3):
        ssh = paramiko.SSHClient()
        ssh.load_system_host_keys()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(host, port=port, username=username, password=password, timeout=timeout)
        channel = ssh.invoke_shell()
        writer = MyThread(chan_recv, args=(channel,))
        writer.start()
        for cmd in commands:
            channel.send(cmd)
        writer.join()
        channel.close()
        ssh.close()
        return writer.get_result()
    
    
    if __name__ == '__main__':
        cmds = ['enable
    ', 'cisco
    ', 'terminal length 0
    ', 'show ip int br
    ',
                'show run
    ', 'show version
    ', 'show inventory
    ',
                'sh cdp nei
    ', 'conf t
    ', 'router ospf 110
    ', 
                'network 10.10.10.2 0.0.0.0 area 0
    ',
                'end
    ', 'exit
    ']
        res = shell(cmds, '10.10.10.2', '22', 'cisco', 'cisco')
        print(res) 

    执行时,有时会遇到EOFerror报错,需要在接收数据的时候做下改造:

        while True:
            try:
                data = channel.recv(1024) 
                if not data:  
                    break
                resp += data.decode()
            except EOFError:   
                pass 
    

      

    四,单线程 - 执行预先定义的命令

    其实执行预先定义的命令,不存在交互,无需另起线程,可以用串行方式,先把命令发过去,再不停接收数据即可。

    import paramiko
    import os
    
    
    def shell(commands, host, port, username, password, timeout=3):
        ssh = paramiko.SSHClient()
        ssh.load_system_host_keys()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        ssh.connect(host, port=port, username=username, password=password, timeout=timeout)
        channel = ssh.invoke_shell()
    
        for cmd in commands:
            channel.send(cmd)
    
        resp = ''
        while True:
            try:
                data = channel.recv(1024)  # 如果对方没有发东西过来,就会一直阻塞在recv
                if not data:    # 如果发过来是b'',退出
                    break
                resp += data.decode()
            except EOFError:    # 如果发过来了其他终止符,导致EOF
                pass  # 只能用pass,不能用break
    
        channel.close()
        ssh.close()
        return resp
    
    
    if __name__ == '__main__':
        cmds = ['enable
    ', 'cisco
    ', 'terminal length 0
    ', 'show ip int br
    ',
                'show run
    ', 'show version
    ', 'show inventory
    ',
                'sh cdp nei
    ', 'conf t
    ', 'router ospf 110
    ', 
                'network 10.10.10.2 0.0.0.0 area 0
    ',
                'end
    ', 'exit
    ']
        res = shell(cmds, '10.10.10.2', '22', 'cisco', 'cisco')
        print(res) 
    

      

    总结:

    结束的判断主要有三种方法:(参考https://www.cnblogs.com/litaozijin/p/6624029.html)

    1,服务器端预先发送数据长度,每次接收时判断(老男孩python课件中socket编程也是这个方法)

    2,结尾标示符

    3,终止socket

    收数据时的方法有两种:

    1,单线程

    2,双线程:主线程发送命令,子线程负责持续接收

  • 相关阅读:
    shell 编写简单的整数计算器
    信号控制
    MySQL-索引及优化整理
    Java面试-Java容器有哪些
    C语言宏定义
    值类型与引用类型的区别
    C++虚函数简介
    DNS-域名解析
    扇区,簇,块区分
    Java合并两个数组为一个新数组
  • 原文地址:https://www.cnblogs.com/guxh/p/12375801.html
Copyright © 2011-2022 走看看