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 + '\n')
        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 + '\n')
    # 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\n', 'cisco\n', 'terminal length 0\n', 'show ip int br\n',
            'show run\n', 'show version\n', 'show inventory\n',
            'sh cdp nei\n', 'conf t\n', 'router ospf 110\n', 
            'network 10.10.10.2 0.0.0.0 area 0\n',
            'end\n', 'exit\n']
    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\n', 'cisco\n', 'terminal length 0\n', 'show ip int br\n',
            'show run\n', 'show version\n', 'show inventory\n',
            'sh cdp nei\n', 'conf t\n', 'router ospf 110\n', 
            'network 10.10.10.2 0.0.0.0 area 0\n',
            'end\n', 'exit\n']
    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,雙線程:主線程發送命令,子線程負責持續接收


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM