如何在Flask中选择随机端口号?

27 投票
5 回答
36499 浏览
提问于 2025-04-16 12:20

使用Flask时,我怎么才能知道当前连接的端口号?我想在一个随机端口上启动服务器,使用端口0,但我也需要知道我现在用的是哪个端口。

编辑

我想我找到了一个解决我问题的方法,虽然这不是对问题的直接回答。我可以从49152这个端口开始,逐个尝试使用不同的端口,通过app.run(port=PORT)来启动服务器。如果遇到Address already in use的错误,我就可以换下一个端口继续尝试。

5 个回答

3

我同意之前的回答,

你很难直接获取Flask使用的服务器套接字,因为它隐藏在标准库的内部(Flask使用Werkzeug,而Werkzeug的开发服务器是基于标准库的BaseHTTPServer)。

不过,我刚发现Werkzeug提供了一个选项,可以在运行werkzeug.serving.BaseWSGIServer时传递一个文件描述符作为套接字(这就是Flask的run()函数最终所做的)。通过这个功能,可以选择一个随机的可用端口,然后告诉Werkzeug使用这个端口。

这是我应用程序的启动代码:

import logging
import pprint
import sys
from flask import Flask

applog = logging.getLogger(__name__)
applog.setLevel(logging.INFO)
app = Flask(__name__)

if __name__ == '__main__':

    app.config.from_object('myapp.default_settings')
    try:
        app.config.from_envvar('MYAPP_SETTINGS')
    except RuntimeError:
        applog.warning("MYAPP_SETTINGS environment variable not set or invalid. Using default settings.")

    # SERVER_NAME should be in the form of localhost:5000, 127.0.0.1:0, etc...
    # So, split on : and take the second element as the port
    # If the port is 0, we need to pick a random port and then tell the server to use that socket
    if app.config['SERVER_NAME'] and int(app.config['SERVER_NAME'].split(':')[1]) == 0:
        import socket, os

        # Chose a random available port by binding to port 0
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.bind((app.config['SERVER_NAME'].split(':')[0], 0))
        sock.listen()

        # Tells the underlying WERKZEUG server to use the socket we just created
        os.environ['WERKZEUG_SERVER_FD'] = str(sock.fileno())

        # Update the configuration so it matches with the port we just chose
        # (sock.getsockname will return the actual port being used, not 0)
        app.config['SERVER_NAME'] = '%s:%d' % (sock.getsockname())

    # Optionally write the current port to a file on the filesystem
    if app.config['CREATE_PORT_FILE']:
        with open(app.config['PORT_FILE'], 'w') as port_file:
            port_file.write(app.config['SERVER_NAME'].split(':')[1])

    applog.info("Running app on %s" % (app.config['SERVER_NAME']))
    applog.info("Active Settings:%s" % pprint.pformat(app.config, compact=True))
    app.run()

myapp.default_settings的内容:

SERVER_NAME = '127.0.0.1:0'
PORT_FILE = 'current_port'
CREATE_PORT_FILE = True

这里重要的是设置os.environ['WERKZEUG_SERVER_FD']。Werkzeug在run_simple函数中会查看这个环境变量,如果它被定义了,就会把它作为fd参数传递给make_server函数。最终,这个参数会被用作通信的套接字。

虽然我不能保证这种方法的稳定性(我不知道WERKZEUG_SERVER_FD环境变量的支持程度如何),但我更喜欢这种方法而不是之前建议的解决方案,因为:

  1. 我不需要遍历一系列端口来捕获异常,我只需直接从操作系统获取第一个可用的端口。
  2. 在我绑定一个随机端口和运行应用程序之间,我选择的随机端口不会被占用,因为我绑定的端口就是我应用程序最终使用的端口。

上面的代码还会记录正在使用的端口,并可选地将当前使用的端口写入由配置选项指定的文件中。

3

正如@VinaySajip所指出的,Flask使用的是标准的服务器套接字,但它从来没有把这个实例赋值给任何变量,只是直接构造它并在这一行中调用了serve_forever()

不过,与其像@ThiefMaster所说的那样尝试从Flask的应用中提取套接字,我们可以拦截bind_socket()的调用,直接读取地址,而不需要担心同时创建多个套接字。下面是我的解决方案:

from flask import Flask, request
import socketserver

app = Flask("")

original_socket_bind = SocketServer.TCPServer.server_bind
def socket_bind_wrapper(self):
    ret = original_socket_bind(self)
    print("Socket running at {}:{}".format(*self.socket.getsockname()))
    # Recover original implementation
    socketserver.TCPServer.server_bind = original_socket_bind
    return ret

@app.route("/")
def hello():
    return 'Hello, world! running on {}'.format(request.host)

socketserver.TCPServer.server_bind = socket_bind_wrapper   #Hook the wrapper
app.run(port=0, debug=True)
46

你不能轻易地访问Flask使用的服务器套接字,因为它隐藏在标准库的内部(Flask使用Werkzeug,而Werkzeug的开发服务器是基于标准库的BaseHTTPServer)。

不过,你可以自己创建一个临时端口,然后关闭创建它的套接字,接着自己使用那个端口。例如:

# hello.py
from flask import Flask, request
import socket

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello, world! running on %s' % request.host

if __name__ == '__main__':
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost', 0))
    port = sock.getsockname()[1]
    sock.close()
    app.run(port=port)

这段代码会给你一个可以使用的端口号。运行的例子是:

$ python hello.py 
* Running on http://127.0.0.1:34447/

然后在浏览器中访问http://localhost:34447/,我看到

你好,世界!正在localhost:34447上运行

在我的浏览器中。

当然,如果在你关闭套接字和Flask用那个端口打开套接字之间,有其他东西占用了那个端口,你就会遇到“地址已被使用”的错误,但你可以在你的环境中试试这个方法。

撰写回答