Python:绑定套接字:“地址已在使用中”


81

我对TCP / IP网络上的客户端套接字有疑问。假设我用

try:

    comSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

except socket.error, msg:

    sys.stderr.write("[ERROR] %s\n" % msg[1])
    sys.exit(1)

try:
    comSocket.bind(('', 5555))

    comSocket.connect()

except socket.error, msg:

    sys.stderr.write("[ERROR] %s\n" % msg[1])

    sys.exit(2)

创建的套接字将绑定到端口5555。问题是结束连接后

comSocket.shutdown(1)
comSocket.close()

使用wireshark,我看到套接字从两侧都被FIN,ACK和ACK关闭,我无法再次使用该端口。我收到以下错误:

[ERROR] Address already in use

我想知道如何立即清除端口,以便下次仍可以使用该端口。

comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

setsockopt似乎无法解决问题,谢谢!


1
为什么客户端需要特定的端口?
AJ。

2
因为我必须将其放入生产服务器中,并且在该服务器中,所有传出连接都被阻止。我需要为套接字指定一个特定的端口,以便它们可以在防火墙上设置允许连接通过的规则。
土黄

3
您的网络管理员应该了解出站流量可以由目标端口控制。
AJ。

7
这有足够的信息。问题有99%的可能性是由TIME_WAIT套接字状态引起的,下面的答案为:)
lunixbochs 2011年

1
您的操作系统是什么?您通常可以使用netstat查看套接字的状态(查找端口号以标识套接字)
lunixbochs 2011年

Answers:


122

SO_REUSEADDR绑定套接字之前,请尝试使用套接字选项。

comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

编辑: 我看到你仍然有这个问题。在某些情况下SO_REUSEADDR不起作用。如果尝试绑定套接字并重新连接到相同的目标位置(已SO_REUSEADDR启用),则该端口TIME_WAIT仍然有效。但是,它将允许您连接到其他host:port。

我想到了几种解决方案。您可以继续重试,直到可以重新建立连接为止。或者,如果客户端启动套接字(而不是服务器)的关闭,那么它应该可以神奇地工作。


1
仍然无法重用它。我必须等待才能重新使用同一端口的时间是1分30秒:(
Tu Hoang

5
setsockopt以前打过电话bind吗?是使用创建的第一个套接字SO_REUSEADDR,还是失败的套接字?等待套接字必须已SO_REUSEADDR设置为可以正常工作
lunixbochs 2011年

是的,我确实包含了comSocket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1),但仍然无法正常工作。
土黄

1
tcp 0 0 98c5-9-134-71-1:freeciv mobile-166-132-02:2345 TIME_WAIT
Tu Hoang

1
@jcoffland我同意他不应该使用bind(),但是SO_REUSEADDR用于所有套接字,不仅包括TCP服务器套接字,而且包括TCP客户端套接字和UDP套接字。不要在此处发布错误信息。
罗恩侯爵,

26

这是我测试过的完整代码,绝对不会给我“地址已在使用中”错误。您可以将其保存在文件中,然后从要提供服务的HTML文件的基本目录中运行该文件。此外,您可以在启动服务器之前以编程方式更改目录

import socket
import SimpleHTTPServer
import SocketServer
# import os # uncomment if you want to change directories within the program

PORT = 8000

# Absolutely essential!  This ensures that socket resuse is setup BEFORE
# it is bound.  Will avoid the TIME_WAIT issue

class MyTCPServer(SocketServer.TCPServer):
    def server_bind(self):
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler

httpd = MyTCPServer(("", PORT), Handler)

# os.chdir("/My/Webpages/Live/here.html")

httpd.serve_forever()

# httpd.shutdown() # If you want to programmatically shut off the server

即使在httpd.shutdown()之后,您仍然想调用httpd.server_close()来完全释放所有资源。
Androbin '18年

另外,考虑将atexit模块用于任何意外情况。
Androbin '18年


13

根据这个链接

实际上,SO_REUSEADDR标志可能导致更大的后果:SO_REUSADDR允许您使用停留在TIME_WAIT中的端口,但是您仍然不能使用该端口建立与其最后连接的位置的连接。什么?假设我选择本地端口1010,并连接到foobar.com端口300,然后在本地关闭,将该端口保留在TIME_WAIT中。我可以立即重用本地端口1010以连接到foobar.com端口300以外的任何地方。

但是,通过确保远程端启动关闭(关闭事件),可以完全避免TIME_WAIT状态。因此,服务器可以通过让客户端先关闭来避免问题。必须设计应用程序协议,以便客户端知道何时关闭。服务器可以安全地响应客户端的EOF进行关闭,但是,在客户端期望EOF的情况下,服务器也需要设置超时时间,以防客户端恶意离开网络。在许多情况下,只需在服务器关闭之前等待几秒钟就足够了。

我还建议您进一步了解网络和网络编程。现在,您至少应该了解tcp协议的工作方式。该协议非常琐碎且很小,因此可以在将来节省大量时间。

netstat命令,您可以轻松查看哪些程序((program_name,pid)元组)绑定到哪些端口以及套接字当前状态是什么:TIME_WAIT,CLOSING,FIN_WAIT等。

可以在/server/212093/how-to-reduce-number-of-sockets-in-time-wait中找到有关linux网络配置的很好解释。


另外,您应该谨慎使用代码。如果您的代码仍在开发中,并且发生了某些异常,则连接可能无法正确关闭,尤其是从服务器端。
Rustem K 2014年

8
您应该是公平的,引用从Tom's Network Guide复制并粘贴的句子。请更正您的答案。
HelloWorld

8

绑定之前,需要设置allow_reuse_address。代替SimpleHTTPServer运行以下代码片段:

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
httpd = SocketServer.TCPServer(("", PORT), Handler, bind_and_activate=False)
httpd.allow_reuse_address = True
httpd.server_bind()
httpd.server_activate()
httpd.serve_forever()

这样可以防止服务器在我们有机会设置标志之前进行绑定。


子类化TCPServer并覆盖属性似乎容易得多,例如,请参见我的答案
Andrei

3

正如Felipe Cruze提到的那样,必须在绑定之前设置SO_REUSEADDR。我在另一个站点上找到了解决方案-在另一个站点上的解决方案,复制如下

问题是必须在将地址绑定到套接字之前设置SO_REUSEADDR套接字选项。可以通过子类化ThreadingTCPServer并重写如下的server_bind方法来完成:

导入SocketServer,套接字

类MyThreadingTCPServer(SocketServer.ThreadingTCPServer):
    def server_bind():
        self.socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
        self.socket.bind(self.server_address)


3

我发现了此异常的另一个原因。从Spyder IDE运行应用程序时(在我的情况下是Raspbian上的Spyder3),并且程序被^ C终止或出现异常,套接字仍处于活动状态:

sudo netstat -ap | grep 31416
tcp  0  0 0.0.0.0:31416  0.0.0.0:*    LISTEN      13210/python3

再次运行程序,发现“地址已在使用中”。IDE似乎将新的“运行”作为一个独立的进程来启动,该过程查找先前的“运行”所使用的套接字。

socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

没有帮助。

杀死进程13210有所帮助。从命令行启动python脚本,例如

python3 <app-name>.py

当SO_REUSEADDR设置为true时,总是可以正常工作。新的Thonny IDE或Idle3 IDE不存在此问题。



1

对我而言,以下是更好的解决方案。由于主动关闭连接是由服务器完成的,因此setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)无效,并且TIME_WAIT避免在同一端口上建立新连接,但出现错误:

[Errno 10048]: Address already in use. Only one usage of each socket address (protocol/IP address/port) is normally permitted 

我最终使用该解决方案让操作系统选择端口本身,然后如果先例仍在TIME_WAIT中,则使用另一个端口。

我更换:

self._socket.bind((guest, port))

与:

self._socket.bind((guest, 0))

正如在tcp地址的python套接字文档中指出的那样:

如果提供,则source_address必须为2元组(主机,端口),以便套接字在连接之前绑定为其源地址。如果主机或端口分别为''或0,则将使用操作系统默认行为。


1
您也可以忽略绑定,但是OP指出他必须使用端口5555,而答案不是。
罗恩侯爵,

1

当然,在开发环境中,另一种解决方案是杀死使用它的进程

def serve():
    server = HTTPServer(('', PORT_NUMBER), BaseHTTPRequestHandler)
    print 'Started httpserver on port ' , PORT_NUMBER
    server.serve_forever()
try:
    serve()
except Exception, e:
    print "probably port is used. killing processes using given port %d, %s"%(PORT_NUMBER,e)
    os.system("xterm -e 'sudo fuser -kuv %d/tcp'" % PORT_NUMBER)
    serve()
    raise e

终止进程不会以任何方式影响TIME_WAIT。
罗恩侯爵,

0

我知道您已经接受了答案,但是我相信问题与在客户端套接字上调用bind()有关。这可能还可以,但是bind()和shutdown()似乎不能很好地配合使用。另外,SO_REUSEADDR通常与侦听套接字一起使用。即在服务器端。

您应该通过和ip / port进行连接()。像这样:

comSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
comSocket.connect(('', 5555))

不要调用bind(),不要设置SO_REUSEADDR。


2
bind()shutdown()有完全无关,与对方和建议,“他们似乎没有发挥得很好”是毫无根据的。你已经错过了,他需要使用本地端口5555的一部分
洛恩侯爵

0

我认为最好的方法是通过在终端上键入来终止该端口上的进程fuser -k [PORT NUMBER]/tcp,例如fuser -k 5001/tcp


1
除非该进程已被终止并且它只是将端口保持打开状态以供操作系统清理。
克里斯·默克

1
终止进程不会以任何方式影响TIME_WAIT。
罗恩侯爵,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.