python – Paramiko SSH隧道关闭问题

我正在研究一个
python脚本,经常在已建立的ssh隧道上查询一些远程数据库.我对paramiko库非常熟悉,所以这是我选择的路线.我更喜欢将它保留在完整的python中,因此我可以使用paramiko来处理关键问题,以及使用python来启动,控制和关闭ssh隧道.

这里有关于这个主题的一些相关问题,但大多数问题似乎都不完整.我下面的解决方案是我迄今为止找到的解决方案.

现在问题是:我能够很容易地创建第一个隧道(在一个单独的线程中)并执行我的DB / python,但是当试图关闭隧道时,localhost将不会释放我绑定到的本地端口.下面,我通过流程的每个步骤包含了我的源代码和相关的netstat数据.

#!/usr/bin/python

import select
import SocketServer
import sys
import paramiko
from threading import Thread
import time



class ForwardServer(SocketServer.ThreadingTCPServer):
    daemon_threads = True
    allow_reuse_address = True

class Handler (SocketServer.BaseRequestHandler):
    def handle(self):
        try:
            chan = self.ssh_transport.open_channel('direct-tcpip', (self.chain_host, self.chain_port), self.request.getpeername())
        except Exception, e:
            print('Incoming request to %s:%d failed: %s' % (self.chain_host, self.chain_port, repr(e)))
            return
        if chan is None:
            print('Incoming request to %s:%d was rejected by the SSH server.' % (self.chain_host, self.chain_port))
            return
        print('Connected!  Tunnel open %r -> %r -> %r' % (self.request.getpeername(), chan.getpeername(), (self.chain_host, self.chain_port)))
        while True:
            r, w, x = select.select([self.request, chan], [], [])
            if self.request in r:
                data = self.request.recv(1024)
                if len(data) == 0:
                    break
                chan.send(data)
            if chan in r:
                data = chan.recv(1024)
                if len(data) == 0:
                    break
                self.request.send(data)
        chan.close()
        self.request.close()
        print('Tunnel closed from %r' % (self.request.getpeername(),))

class DBTunnel():

    def __init__(self,ip):
        self.c = paramiko.SSHClient()
        self.c.load_system_host_keys()
        self.c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        self.c.connect(ip, username='someuser')
        self.trans = self.c.get_transport()

    def startTunnel(self):
        class SubHandler(Handler):
            chain_host = '127.0.0.1'
            chain_port = 5432
            ssh_transport = self.c.get_transport()
        def ThreadTunnel():
            global t
            t = ForwardServer(('', 3333), SubHandler)
            t.serve_forever()
        Thread(target=ThreadTunnel).start()

    def stopTunnel(self):
        t.shutdown()
        self.trans.close()
        self.c.close()

虽然我最终会使用stopTunnel()类型方法,但我已经意识到代码并不完全正确,但更多的是试图让隧道正常关闭并测试我的结果的实验​​.

当我第一次调用create DBTunnel对象并调用startTunnel()时,netstat会产生以下结果:

tcp4       0      0 *.3333                 *.*                    LISTEN
tcp4       0      0 MYIP.36316      REMOTE_HOST.22                ESTABLISHED
tcp4       0      0 127.0.0.1.5432         *.*                    LISTEN

一旦我调用stopTunnel(),甚至删除了DBTunnel对象本身……我离开这个连接,直到我一起退出python,我认为是垃圾收集器负责它:

tcp4       0      0 *.3333                 *.*                    LISTEN

很好地弄清楚为什么这个打开的套接字独立于DBConnect对象而悬空,以及如何从我的脚本中正确地关闭它.如果我尝试在完全退出python之前使用相同的本地端口将不同的连接绑定到不同的IP(time_wait不是问题),那么我将使用臭名昭着的绑定错误48地址.提前致谢 :)

最佳答案 看来SocketServer的shutdown方法没有正确关闭/关闭套接字.通过以下代码更改,我保留对SocketServer对象的访问权限并直接访问套接字以关闭它.请注意,socket.close()在我的情况下工作,但其他人可能对socket.shutdown()以及socket.close()感兴趣,如果其他资源正在访问该套接字.

[参考:socket.shutdown vs socket.close

def ThreadTunnel():
    self.t = ForwardServer(('127.0.0.1', 3333), SubHandler)
    self.t.serve_forever()
Thread(target=ThreadTunnel).start()

def stopTunnel(self):
    self.t.shutdown()
    self.trans.close()
    self.c.close()
    self.t.socket.close()
点赞