【问题标题】:Python TCP socket doesn't close?Python TCP套接字不关闭?
【发布时间】:2011-08-08 21:06:36
【问题描述】:

也许这里有人会对这件让我发疯的事情做出回应。

为了简单起见,我正在制作一种代理。每当它接收到某些东西时,它会将所有内容转发到服务器,然后发回响应。所以有一个socket总是监听4557端口的客户端,对于每一个传入的连接,都会在一个随机端口上创建一个新的socket来连接到服务器的4556端口。

客户端 代理 服务器

此外,还有另一个套接字被实例化并侦听来自服务器的请求并转发到相应的客户端。

这是一个例子:

  • 客户端 A 连接到端口 4557 上的代理
  • 代理在端口 4556 上创建一个到服务器的套接字
  • 除此之外,它还会创建一个侦听端口 40100 的套接字
  • 客户端发送内容,转发到服务器
  • 客户端断开连接。关闭客户端连接和到服务器的套接字
  • 一段时间后,服务器在端口 40100 上向代理发送内容
  • 所有内容都转发给客户端 A(对应客户端 A 的端口 40100)
  • 等等..

到目前为止,在我的测试中,我使用一个简单的 python 脚本向代理发送一个唯一的 tcp 数据包,以及一个显示接收到的数据并回显的转储服务器。

所以问题是,当与代理的连接关闭时,与服务器的连接也应使用“sock.close()”关闭。然而,它似乎被完全忽略了。套接字保持为 ESTABLISHED。

关于现在的代码。

一些笔记。

  • DTN 和 Node 分别是服务器和客户端。
  • runCallback 在循环中被调用,直到线程终止。
  • finalCallback 在线程死亡时调用。
  • 远程主机(客户端)、代理端口(到服务器)和代理之间的关联保存在字典中:TCPProxyHostRegister (RemoteHost => Proxy)、TCPProxyPortRegister (Port => Proxy)、TCPPortToHost (Port => RemoteHost)。

第一个类是 TCPListenerThread。 它只是侦听特定端口并实例化代理(每个客户端=>服务器对和服务器=>客户端对一个)并将它们转发连接。

class TCPListenerThread(StoppableThread):
    def __init__(self, tcp_port):
        StoppableThread.__init__(self)

        self.tcp_port = tcp_port

        self.sock = socket.socket( socket.AF_INET, # Internet
                        socket.SOCK_STREAM ) # tcp
        self.sock.bind( (LOCAL_ADDRESS, self.tcp_port) )

        self.sock.listen(1)

    def runCallback(self):
        print "Listen on "+str(self.tcp_port)+".."
        conn, addr = self.sock.accept()

        if isFromDTN(addr):
            tcpProxy = getProxyFromPort(tcp_port)
            if not tcpProxy:
                tcpProxy = TCPProxy(host, True)
        else:
            host = addr[0]
            tcpProxy = getProxyFromHost(host)
            if not tcpProxy:
                tcpProxy = TCPProxy(host, False)

        tcpProxy.handle(conn)

    def finalCallback(self):
        self.sock.close()

现在是 TCP 代理: 它将远程主机(客户端)与连接到服务器的端口相关联。 如果它是来自新客户端的连接,它将为服务器创建一个新的侦听器(见上文)并创建一个准备将所有内容转发到服务器的套接字。

class TCPProxy():
    def __init__(self, remote, isFromDTN):
        #remote = port for Server or Remote host for Client
        self.isFromDTN = isFromDTN
        self.conn = None

        #add itself to proxy registries

        #If listening from a node
        if not isFromDTN:
            #Set node remote host
            self.remoteHost = remote
            TCPProxyHostRegister[self.remoteHost] = self

            #Set port to DTN interface + listener
            self.portToDTN = getNewTCPPort()
            TCPPortToHost[self.portToDTN] = self.remoteHost
            newTCPListenerThread(self.portToDTN)
        #Or from DTN
        else:
            self.portToDTN = remote
            TCPProxyPortRegister[self.portToDTN] = self

            self.remoteHost = getRemoteHostFromPortTCP(self.portToDTN)

    def handle(self, conn):
        print "New connection!"

        #shouldn't happen, but eh
        if self.conn != None:
            self.closeConnections()

        self.conn = conn

        #init socket with remote
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        #self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        if self.isFromDTN:
            self.sock.connect((self.remoteHost, 4556)) #TODO: handle dynamic port..
        else:
            self.sock.connect((DTN_Address, DTN_TCPPort))

        #handle connection in a thread
        self.handlerThread = newTCPHandlerThread(self)
        #handle reply in a therad
        self.replyThread = newTCPReplyThread(self)

    def closeConnections(self):
        try:
            if self.conn != None:
                print "Close connections!"
                self.sock.close()
                self.conn.close()
                self.conn = None
                self.handlerThread.kill()
                self.replyThread.kill()
        except Exception, err:
            print str(err)
            #pass

    def forward(self, data):
        print "TCP forwarding data: "+data
        self.sock.send(data)

    def forwardBack(self, data):
        print "TCP forwarding data back: "+data
        self.conn.send(data)

在这个代理类中,我实例化了两个类,TCPHandlerThread 和 TCPReplyThread。它们分别负责转发给Server,和转发回Client。

class TCPHandlerThread(StoppableThread):
    def __init__(self, proxy):
        StoppableThread.__init__(self)
        self.proxy = proxy

    def runCallback(self):
        test = False
        while 1:

            data = self.proxy.conn.recv(BUFFER_SIZE)    
            if test:
                self.proxy.sock.close()

            test = True
            if not data:
                break
            print "TCP received data:", data
            self.proxy.forward(data)
        self.kill()

    def finalCallback(self):
        self.proxy.closeConnections()



class TCPReplyThread(StoppableThread):
    def __init__(self, proxy):
        StoppableThread.__init__(self)
        self.proxy = proxy

    def runCallback(self):
        while 1:
            data = self.proxy.sock.recv(BUFFER_SIZE)
            if not data:            
                break
            print "TCP received back data: "+data
            self.proxy.forwardBack(data)
        self.kill()

    def finalCallback(self):
        self.proxy.closeConnections()

您会看到,每当一个连接关闭时,线程就会终止,而另一个连接(客户端/服务器到代理或代理到服务器/客户端)应该在 Proxy.closeConnections() 中关闭

我注意到当 closeConnections() 是“data = self.proxy.conn.recv(BUFFER_SIZE)”时,它运行良好,但是当它在后一条语句之后被调用时,它就出错了。

我wireshark TCP,代理没有发送任何“再见信号”。套接字状态不会进入 TIME_WAIT 或其他状态,它只是保持 ESTABLISHED。

另外,我在 Windows 和 Ubuntu 上对其进行了测试。

  • 在 Windows 上完全按照我的解释进行
  • 在 Ubuntu 上,它适用于通常(并非总是)2 个连接,并且第三次以完全相同的方式连接到同一客户端到代理时,它再次出现错误,完全按照说明进行。

这是我正在使用的三个文件,以便您查看整个代码。很抱歉,代理文件可能不太容易阅读。应该是一个快速的开发者。

http://hognerud.net/stackoverflow/

提前谢谢.. 这肯定是一件愚蠢的事情。当你看到它时请不要打我太重:(

【问题讨论】:

  • 您能否分享您在哪里跟踪打开的客户端套接字列表,以及它们如何与代理服务的代理套接字相关联?我可以花一些时间阅读代码,但这种基本信息会更容易,所以每个人都不会做同样的研究。
  • 你为什么要发布应该有效的代码而不是无效的代码来处理打开和关闭有问题的服务器套接字?
  • 感谢 cmets。最初的帖子已编辑。

标签: python sockets tcp


【解决方案1】:

首先很抱歉,我目前没有时间实际运行和测试您的代码。

但是我想到了这个想法,您的问题实际上可能与在套接字上使用阻塞模式与非阻塞模式有关。在这种情况下,您应该查看 python 文档中的“socket”模块帮助,尤其是 socket.setblocking()。

我的猜测是,proxy.conn.recv() 函数仅在套接字实际接收到 BUFFER_SIZE 字节时才返回。因此,线程被阻塞,直到接收到足够的数据,因此套接字不会关闭。

正如我首先所说,这目前只是一个猜测,所以如果它不能解决问题,请不要投票给我......

【讨论】:

  • 抱歉回复晚了。其实这解决了我的问题。谢谢!
  • np - 我很高兴能帮上忙。
  • 您的猜测不正确。 recv() 在存在 EOS、错误或至少一个字节已传输时返回。否则编写任何类型的网络代码几乎是不可能的。
猜你喜欢
  • 2011-01-26
  • 2015-12-07
  • 1970-01-01
  • 2015-09-04
  • 2012-02-10
  • 2013-04-29
  • 2011-08-07
  • 2015-10-16
  • 2015-03-29
相关资源
最近更新 更多