Python TCP socket 不关闭?

3 投票
1 回答
7134 浏览
提问于 2025-04-16 16:03

也许这里有人能帮我解决一个让我抓狂的问题。

简单来说,我正在做一个代理。每当它收到请求时,就会把所有内容转发到一个服务器,然后把响应发回去。所以有一个套接字一直在4557端口监听客户端的连接,对于每一个进来的连接,都会在一个随机端口上创建一个新的套接字来连接服务器的4556端口。

客户端 <==> 代理 <==> 服务器

此外,还有另一个套接字,它会监听来自服务器的请求,并将其转发给相应的客户端。

以下是一个例子:

  • 客户端A连接到代理的4557端口
  • 代理创建一个套接字连接到服务器的4556端口
  • 同时,它还创建一个在40100端口上监听的套接字
  • 客户端发送数据,代理将其转发到服务器
  • 客户端断开连接。关闭客户端连接和到服务器的套接字
  • 过了一段时间,服务器在40100端口向代理发送数据
  • 所有数据都转发给客户端A(40100端口对应客户端A)
  • 依此类推..

到目前为止,我在测试中使用了一个简单的Python脚本,向代理发送一个独特的TCP数据包,同时有一个转储服务器显示接收到的数据并回显。

问题是,当与代理的连接关闭时,应该也要关闭与服务器的连接,使用“sock.close()”。但是这个操作似乎完全被忽略了。套接字仍然保持在ESTABLISHED状态。

关于代码的说明。

几点说明:

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

第一个类是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。它们分别负责转发到服务器和转发回客户端。

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上,通常(但不是总是)对于两个连接,它工作得很好,而第三次用同样的客户端以完全相同的方式连接到代理时,又出现了问题,正如我所描述的那样。

这里有我使用的三个文件,你可以看看完整的代码。抱歉代理文件可能不太容易阅读。本来应该是个快速开发。

http://hognerud.net/stackoverflow/

提前谢谢你们..这肯定是个愚蠢的问题。请不要太狠心,当你看到它时 :(

1 个回答

7

首先,我很抱歉现在没有时间去实际运行和测试你的代码。

不过我想到一个可能的问题,可能跟你使用的socket(套接字)是阻塞模式还是非阻塞模式有关。如果是这样的话,你可以看看Python文档里的“socket”模块的帮助,特别是socket.setblocking()这个部分。

我猜测,proxy.conn.recv()这个函数只有在接收到BUFFER_SIZE字节的数据时才会返回。因为这个原因,线程会被阻塞,直到接收到足够的数据,所以socket就不会被关闭。

正如我刚才说的,这只是我的一个猜测,所以如果这个方法没有解决问题,请不要给我差评……

撰写回答