python获取交互式ssh shell

来源:互联网 发布:co网络用语是什么意思 编辑:程序博客网 时间:2024/06/05 03:09

最近在做一个项目,需要在客户端集成一个交互式ssh功能,大概就是客户端跟服务器申请个可用的机器,服务端返回个ip,端口,密码, 然后客户端就可以直接登录到机器上操做了。该程序基于paramiko模块。

经查找,从paramiko的源码包demos目录下,可以看到交互式shell的实现,就是那个demo.py。但是用起来有些bug,于是我给修改了一下interactive.py(我把windows的代码删掉了,剩下的只能在linux下用)。代码如下:


#coding=utf-8import socketimport sysimport osimport termiosimport ttyimport fcntlimport signalimport structimport selectnow_channel = Nonedef interactive_shell(chan):    posix_shell(chan)def ioctl_GWINSZ(fd):    try:        cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ,'aaaa'))    except:        return    return crdef getTerminalSize():    cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)    return int(cr[1]), int(cr[0])    def resize_pty(signum=0, frame=0):    width, height = getTerminalSize()    if now_channel is not None:        now_channel.resize_pty(width=width, height=height)def posix_shell(chan):    global now_channel    now_channel = chan    resize_pty()    signal.signal(signal.SIGWINCH, resize_pty) # 终端大小改变时,修改pty终端大小    stdin = os.fdopen(sys.stdin.fileno(), 'r', 0) # stdin buff置为空,否则粘贴多字节或者按方向键的时候显示不正确    fd = stdin.fileno()    oldtty = termios.tcgetattr(fd)    newtty = termios.tcgetattr(fd)    newtty[3] = newtty[3] | termios.ICANON    try:        termios.tcsetattr(fd, termios.TCSANOW, newtty)        tty.setraw(fd)        tty.setcbreak(fd)        chan.settimeout(0.0)        while True:            try:                r, w, e = select.select([chan, stdin], [], [])            except:                # 解决SIGWINCH信号将休眠的select系统调用唤醒引发的系统中断,忽略中断重新调用解决。                continue            if chan in r:                try:                    x = chan.recv(1024)                    if len(x) == 0:                        print 'rn*** EOFrn',                        break                    sys.stdout.write(x)                    sys.stdout.flush()                except socket.timeout:                    pass            if stdin in r:                x = stdin.read(1)                if len(x) == 0:                    break                chan.send(x)    finally:        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, oldtty)


使用示例:

#coding=utf8import paramikoimport interactive#记录日志paramiko.util.log_to_file('/tmp/aaa')#建立ssh连接ssh=paramiko.SSHClient()ssh.load_system_host_keys()ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())ssh.connect('192.168.1.11',port=22,username='hahaha',password='********',compress=True)#建立交互式shell连接channel=ssh.invoke_shell()#建立交互式管道interactive.interactive_shell(channel)#关闭连接channel.close()ssh.close()


interactive.py代码中主要修复了几个问题:

1、当读取键盘输入时,方向键会有问题,因为按一次方向键会产生3个字节数据,我的理解是按键一次会被select捕捉一次标准输入有变化,但是我每次只处理1个字节的数据,其他的数据会存放在输入缓冲区中,等待下次按键的时候一起发过去。这就导致了本来3个字节才能完整定义一个方向键的行为,但是我只发过去一个字节,所以终端并不知道我要干什么。所以没有变化,当下次触发按键,才会把上一次的信息完整发过去,看起来就是按一下方向键有延迟。多字节的粘贴也是一个原理。解决办法是将输入缓冲区置为0,这样就没有缓冲,有多少发过去多少,这样就不会有那种显示的延迟问题了。

2、终端大小适应。paramiko.channel会创建一个pty(伪终端),有个默认的大小(width=80, height=24),所以登录过去会发现能显示的区域很小,并且是固定的。编辑vim的时候尤其痛苦。channel中有resize_pty方法,但是需要获取到当前终端的大小。经查找,当终端窗口发生变化时,系统会给前台进程组发送SIGWINCH信号,也就是当进程收到该信号时,获取一下当前size,然后再同步到pty中,那pty中的进程等于也感受到了窗口变化,也会收到SIGWINCH信号。

3、读写‘慢’设备(包括pipe,终端设备,网络连接等)。读时,数据不存在,需要等待;写时,缓冲区满或其他原因,需要等待。ssh通道属于这一类的。本来进程因为网络没有通信,select调用为阻塞中的状态,但是当终端窗口大小变化,接收到SIGWINCH信号被唤醒。此时select会出现异常,触发系统中断(4, 'Interrupted system call'),但是这种情况只会出现一次,当重新调用select方法又会恢复正常。所以捕获到select异常后重新进行select可以解决该问题。

0 0
原创粉丝点击