为什么套接字连接()到它自己的临时端口?

如果我使用自动分配的临时端口(5000-65534)范围内的端口连接到本地主机,我可以可靠地获得一个Winsock套接字connect()自身。 特别是,Windows似乎有一个系统范围的滚动端口号,它是下一个端口,它将尝试分配为客户端套接字的本地端口号。 如果我创build套接字,直到分配的数字低于我的目标端口号,然后重复创build套接字并尝试连接到该端口号,我通常可以让套接字连接到它自己。

我首先发现它是在一个应用程序中重复尝试连接到本地主机上的某个端口,并且当服务没有监听时,它很less成功地build立连接并接收到最初发送的消息(恰好是Redis PING命令)。

一个例子,在Python中(没有任何监听目标端口的情况下运行):

 import socket TARGET_PORT = 49400 def mksocket(): return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) while True: sock = mksocket() sock.bind(('127.0.0.1', 0)) host, port = sock.getsockname() if port > TARGET_PORT - 10 and port < TARGET_PORT: break print port while port < TARGET_PORT: sock = mksocket() err = None try: sock.connect(('127.0.0.1', TARGET_PORT)) except socket.error, e: err = e host, port = sock.getsockname() if err: print 'Unable to connect to port %d, used local port %d: %s' % (TARGET_PORT, port, err) else: print 'Connected to port %d, used local port %d' (TARGET_PORT, port) 

在我的Mac机器上,最终终止于Unable to connect to port 49400, used local port 49400 。 在我的Windows 7计算机上,连接成功build立并打印Connected to port 49400, used local port 49400 。 生成的套接字接收发送给它的任何数据。

这是Winsock中的错误吗? 这是我的代码中的错误?

编辑:这是一个TcpView的屏幕截图,显示违规的连接:

python.exe 8108 TCP(我的HOSTNAME)49400本地主机49400 ESTABLISHED

这似乎是RFC 793第3.4节中描述的“同时启动”。 请参见图8.请注意,在任何阶段,任何一方都不处于LISTEN状态。 在你的情况下,两端是相同的:这将导致它完全按RFC中所述工作。

这是你的代码中的一个逻辑错误。

首先,只有较新版本的Windows使用5000-65534作为临时端口。 旧版本改为使用1025-5000。

您正在创建多个明确地绑定到随机临时端口的套接字,直到您绑定了一个比您的目标端口少10个端口的套接字。 但是,如果这些套接字中的任何一个碰巧实际绑定到实际的目标端口,则忽略该套接字并保持循环。 所以你可能会或者可能最终得到一个绑定到目标端口的套接字,你可能会也可能不会得到一个实际上比目标端口小的最终port值。

之后,如果port恰好小于您的目标端口(这是不能保证的),那么您在创建更多的套接字时,会在调用connect()隐式绑定到不同的随机可用临时端口(它在内部执行一个隐式bind()如果bind()尚未被调用),它们中的任何一个都不会与您明确绑定的相同的临时端口,因为这些端口已经被使用并且不能再次使用。

在任何时候你都没有任何给定的套接字从一个短暂的端口连接到同一个临时端口。 除非另一个应用程序碰巧将自己绑定到目标端口, 并且正在主动侦听该端口,否则connect()无法成功连接到您创建的任何套接字上的目标端口,因为它们都不是处于聆听状态。 并且getsockname()在未绑定的套接字上无效,并且如果connect()失败,则不保证连接套接字被绑定。 所以你认为发生的症状实际上是不可能的,因为你已经显示的代码。 你的记录只是做出错误的假设,从而记录错误的东西,给你一个虚假的状态。

试试更像这样的东西,你会看到真正的端口是什么:

 import socket TARGET_PORT = 49400 def mksocket(): return socket.socket(socket.AF_INET, socket.SOCK_STREAM, socket.IPPROTO_TCP) while True: sock = mksocket() sock.bind(('127.0.0.1', 0)) host, port = sock.getsockname() print 'Bound to local port %d' % (port) if port > TARGET_PORT - 10 and port < TARGET_PORT: break if port >= TARGET_PORT: print 'Bound port %d exceeded target port %d' % (port, TARGET_PORT) else: while port < TARGET_PORT: sock = mksocket() # connect() would do this internal anyway, so this is just to ensure a port is available for logging even if connect() fails sock.bind(('127.0.0.1', 0)) err = None try: sock.connect(('127.0.0.1', TARGET_PORT)) except socket.error, e: err = e host, port = sock.getsockname() if err: print 'Unable to connect to port %d using local port %d' % (TARGET_PORT, port) else: print 'Connected to port %d using local port %d' % (TARGET_PORT, port)