Python TCP socket 不关闭?
也许这里有人能帮我解决一个让我抓狂的问题。
简单来说,我正在做一个代理。每当它收到请求时,就会把所有内容转发到一个服务器,然后把响应发回去。所以有一个套接字一直在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 个回答
首先,我很抱歉现在没有时间去实际运行和测试你的代码。
不过我想到一个可能的问题,可能跟你使用的socket(套接字)是阻塞模式还是非阻塞模式有关。如果是这样的话,你可以看看Python文档里的“socket”模块的帮助,特别是socket.setblocking()这个部分。
我猜测,proxy.conn.recv()这个函数只有在接收到BUFFER_SIZE字节的数据时才会返回。因为这个原因,线程会被阻塞,直到接收到足够的数据,所以socket就不会被关闭。
正如我刚才说的,这只是我的一个猜测,所以如果这个方法没有解决问题,请不要给我差评……