运行 Python Web 服务器在 Windows 的同一端口上两次:没有 "Port already in use" 消息

Running a Python web server twice on the same port on Windows: no "Port already in use" message

我在 Windows 7. 当我启动 Bottle Web 服务器时:

run('0.0.0.0', port=80) 

然后再次 运行相同的Python脚本,它不会失败Port already in use 错误(这应该是正常行为),但反而成功地再次启动了 Python 脚本!

问题:如何以简单的方式停止这种行为?

这与 Multiple processes listening on the same port? 有关,但如何在 Python 上下文中防止这种情况发生?

这是一个 Windows 特定行为,需要在绑定网络套接字之前使用 SO_EXCLUSIVEADDRUSE 选项。

来自Using SO_REUSEADDR and SO_EXCLUSIVEADDRUSE article in the Windows Socket 2 documentation

Before the SO_EXCLUSIVEADDRUSE socket option was introduced, there was very little a network application developer could do to prevent a malicious program from binding to the port on which the network application had its own sockets bound. In order to address this security issue, Windows Sockets introduced the SO_EXCLUSIVEADDRUSE socket option, which became available on Windows NT 4.0 with Service Pack 4 (SP4) and later.

...

The SO_EXCLUSIVEADDRUSE option is set by calling the setsockopt function with the optname parameter set to SO_EXCLUSIVEADDRUSE and the optval parameter set to a boolean value of TRUE before the socket is bound.


为了使用 Bottle 模块执行此操作,您必须创建自定义后端以便在绑定之前访问套接字。这使我们有机会按照文档设置所需的套接字选项。

这在Bottle Deployment documentation中有简要描述:

If there is no adapter for your favorite server or if you need more control over the server setup, you may want to start the server manually.


这里是 Bottle Hello World example 的修改版本,演示了这一点:

import socket
from wsgiref.simple_server import WSGIServer
from bottle import route, run, template

@route('/hello/<name>')
def index(name):
  return template('<b>Hello {{name}}</b>!', name=name)

class CustomSocketServer(WSGIServer):
  def server_bind(self):
    # This tests if the socket option exists (i.e. only on Windows), then
    # sets it.
    if hasattr(socket, 'SO_EXCLUSIVEADDRUSE'):
      self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1)

    # Everything below this point is a concatenation of the server_bind
    # implementations pulled from each class in the class hierarchy.
    # wsgiref.WSGIServer -> http.HTTPServer -> socketserver.TCPServer

    elif self.allow_reuse_address:
      self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    self.socket.bind(self.server_address)
    self.server_address = self.socket.getsockname()
    host, port = self.server_address[:2]
    self.server_name = socket.getfqdn(host)
    self.server_port = port
    self.setup_environ()

print "Serving..."
run(host='localhost', port=8080, server_class=CustomSocketServer)   

请注意,复制的代码需要维护超级 classes 的预期行为。

server_bind() 的所有超级 class 实现都从调用它们的父 classes server_bind() 开始。这意味着调用它们中的任何一个都会导致立即绑定套接字,从而消除设置所需套接字选项的机会。


我在 Windows 10 上使用 Python 2.7.

测试了这个

一审:

PS C:\Users\chuckx\bottle-test> C:\Python27\python.exe test.py
Serving...

二审:

PS C:\Users\chuckx\bottle-test> C:\Python27\python.exe test.py
Traceback (most recent call last):
  File "test.py", line 32, in <module>
    server_class=CustomSocketServer)
  File "C:\Python27\lib\wsgiref\simple_server.py", line 151, in make_server
    server = server_class((host, port), handler_class)
  File "C:\Python27\lib\SocketServer.py", line 417, in __init__
    self.server_bind()
  File "test.py", line 19, in server_bind
    self.socket.bind(self.server_address)
  File "C:\Python27\lib\socket.py", line 228, in meth
    return getattr(self._sock,name)(*args)
socket.error: [Errno 10048] Only one usage of each socket address (protocol/network address/port) is normally permitted

另一种解决方案是使用@LuisMuñoz 的评论:check if the port is already opened 再次打开之前:

# Bottle web server code here
# ...

import socket
sock = socket.socket()
sock.settimeout(0.2)  # this prevents a 2 second lag when starting the server
if sock.connect_ex(('127.0.0.1', 80)) == 0:
    print "Sorry, port already in use."
    exit()

run(host='0.0.0.0', port=80)