如何解决twisted.internet.error.CannotListenError: 无法在任何:8081上监听: [Errno 98] 地址已被使用

0 投票
2 回答
12485 浏览
提问于 2025-04-18 08:34

我有一个使用 Twisted 框架的 Python 服务器,它运行在 8080 端口上。我为这个服务器写了不同的 API,现在我想让所有这些 API 都在同一个端口上运行。但是,当我尝试在同一个端口(比如 8081)上同时运行所有 API 时,就会出现一个错误:twisted.internet.error.CannotListenError: Couldn't listen on any:8081: [Errno 98] Address already in use。

因为我对 Twisted 还不太熟悉,所以不太了解相关的内容,而且关于 Twisted 的文档也不太完善。希望有人能指导我解决这个错误。

以下是代码片段:

from twisted.internet import epollreactor

epollreactor.install()

from zope.interface import implements
from twisted.internet import reactor,interfaces
from functools import partial
from pymongo import Connection
import json
from bson.objectid import ObjectId
import server_1
import replacePlus_Space

global data, data_new, datadB, coll_auth, coll_person


class Login_user(server_1.HTTPEchoFactory):

    def __init__(self):
        server_1.HTTPEchoFactory.__init__(self,"testsite")
        server_1.HTTPEchoFactory.initResources(self)
        self.initResources()

    def initResources(self):
        print "in Login"
        self.responses["/login_user"] = partial(self.user_login)


    # To connect to DB and Check error cases and insert into mongoDB..!!!
    def user_login(self, client):
        # some functinality..!!!!

d = Login_user()

reactor.listenTCP(8081,d)

reactor.run()         

第二个代码片段是:

from twisted.internet import epollreactor

epollreactor.install()

from zope.interface import implements
from twisted.internet import reactor,interfaces
from functools import partial
from pymongo import Connection
import json
from bson.objectid import ObjectId
import server_1
import replacePlus_Space

class AddFriendDB(server_1.HTTPEchoFactory):

    def __init__(self):
        server_1.HTTPEchoFactory.__init__(self,"testsite")
        server_1.HTTPEchoFactory.initResources(self)
        self.initResources()

    def initResources(self):
        print "in add friend"
        self.responses["/addFriend_DB"] = partial(self.addFriendDB)


    # To connect to DB and Check error cases and insert into mongoDB..!!!
    def addFriendDB(self, client):
        #some functionality here..!!!

d = AddFriendDB()

reactor.listenTCP(8081,d)

reactor.run()

2 个回答

3

只有一个程序可以在某个端口上进行监听。如果你启动了一个程序(称为进程1)来监听8081端口,然后再启动另一个程序也想用这个端口,就会出现错误。

这个错误是来自TCP协议的,不是Python或Twisted的问题。

解决这个问题的方法是为每个程序选择不同的端口,或者创建一个可以分叉的服务器。

10

翻译问题

你提问中的很多细节不太清楚,所以我把你的问题翻译成:

我有一个用 twisted 写的 独立程序,它运行在 8080 端口上。我还写了一个不同的 独立程序,它在这个服务器上运行。我想让 这两个程序都在同一个端口上运行。当我尝试让两个程序都使用同一个端口,比如用 8081 端口时,我遇到了这个错误:twisted.internet.error.CannotListenError: Couldn't listen on any:8081: [Errno 98] Address already in use。因为我对 twisted 还不太了解,所以不太清楚该怎么解决这个问题,网上也没有好的文档。请有人指导我解决这个错误。

Twisted 有很好的文档

你提到:

[...] twisted 没有好的文档

这个说法完全不对。

Twisted 确实有很好的文档,和大多数框架相比,它的文档非常优秀。这里列举几个高质量的文档来源:

  1. Dave Peticola(krondo)的 Twisted 入门 - 这是一个很棒且深入的 twisted 介绍,首先解释了 Twisted 所基于的技术。如果你真的想理解 twisted,这个(较长的)介绍是个不错的起点。
  2. Twisted 官方网站上高质量且详细的文档 twistedmatrix.com
  3. 直接查看 源代码。Twisted 的源代码注释很好,理解起来也相对简单,如果上面的文档没有教会你需要的内容,可以从代码中找答案。

什么导致“地址已被使用”

正如 Bart Friederichs 之前回答的

同一个端口只能被一个进程监听。如果你启动进程 1 来监听 8081 端口,然后再启动另一个进程在同一个端口上,就会出现这个错误。

这是来自 TCP 栈的错误,而不是来自 Python 或 twisted。

这是所有操作系统(至少是能运行 Python 的基于进程的操作系统)中 TCP/IP 栈的基本真理。

这个错误是操作系统在提醒你,当数据到达一个 IP 端口时,操作系统/IP 栈设计的初衷是只将数据转发给一个进程,这个进程在该端口上实现了应用层协议。当第二个程序尝试重新注册一个已经被其他程序注册的端口时,就会发生这个错误。

一般的解决方法

当遇到这样的端口重用问题时,你需要问自己:

  1. 这两个程序是否在运行相同的应用层协议?
  2. 如果是相同的协议,这个协议是否有路由功能,以便在任何给定的事务中识别出正确的子程序/例程?
  3. 这两个程序在路由中是否处于不同的位置?

如果它们不是相同的协议(例如,一个是 HTTP,另一个是 FTP),或者它们是相同的协议但没有路由功能(例如,两个 NTP 实例),那么就没有简单的方法将它们混合在一起,因为你试图打破 IP 的规则(以及应用协议实现的方式)。唯一的解决方案是将一个(或两个)协议封装到一个具有应用层路由的通用协议中(例如,将 FTP 封装在 HTTP 中,并使用 URI 进行路由)。

如果它们是相同的协议,并且该协议提供每个事务的路由功能(例如,HTTP 事务中的 URI),并且它们不在相同的路由位置,那么就有更简单的解决方案,具体包括:

  1. 将两个应用合并为一个。

    如果它们是相同的可路由协议,但在不同的位置(例如,HTTP 协议的第一个程序的 URI 是 /login,第二个程序的 URI 是 /addfriend),那么将两个程序的后路由逻辑提取出来并合并成一个程序来实现这两个功能应该是很简单的。

  2. 用重定向器前置这两个程序(这个解决方案在 HTTP 中比较简单,因为有现成的工具可用)。

    如果你有 HTTP 协议的程序,它们路由到不同的位置,但由于某种原因很难将逻辑合并在一起(例如,一个程序用 Java 写,另一个用 Python 写),那么你可以考虑用重定向器来前置这两个程序,比如 nginx

    在这种情况下,你可以在两个 不同 的未使用端口上运行这两个应用(例如,12001,12002),然后在你想要服务的端口上运行重定向器,使用配置文件将请求重定向到这两个程序的唯一路由位置(通过类似 SF 的配置:如何用 nginx 重定向请求到不同的域/URL)。

代码示例

以下三个程序展示了如何将两个程序合并为一个,以便从同一个端口访问这两组逻辑。

两个独立程序:

如果你运行以下代码,将会在 localhost:8081 启动一个网络服务器。如果你在浏览器中输入 http://127.0.0.1:8081/blah,将会显示 blah 页面。

#!/usr/bin/python

from twisted.internet import defer, protocol, reactor # the reactor

from twisted.web.server import Site # make the webserver go
from twisted.web.resource import Resource

class BlahPage(Resource):
    idLeaf = True
    def render_GET(self, request):
        return "<html><body>Blah Page!</body></html>"

class ShutdownPage(Resource):
    def render_GET(self, request):
        reactor.stop()

webroot = Resource()
webroot.putChild("blah", BlahPage())
webroot.putChild("shutdown", ShutdownPage())

def main():
    # Register the webserver (TCP server) into twisted
    webfactory = Site(webroot)
    reactor.listenTCP(8081, webfactory)

    print ("Starting server")
    reactor.run()


if __name__ == '__main__':
  main()

这段代码将在 localhost:8082 启动一个网络服务器。如果你在浏览器中输入 http://127.0.0.1:8082/foo,将会显示 foo 页面。

#!/usr/bin/python

from twisted.internet import defer, protocol, reactor # the reactor

from twisted.web.server import Site # make the webserver go
from twisted.web.resource import Resource

class FooPage(Resource):
    idLeaf = True
    def render_GET(self, request):
        return "<html><body>Foo Page!</body></html>"

class ShutdownPage(Resource):
    def render_GET(self, request):
        reactor.stop()

webroot = Resource()
webroot.putChild("foo", FooPage())
webroot.putChild("shutdown", ShutdownPage())

def main():
    # Register the webserver (TCP server) into twisted
    webfactory = Site(webroot)
    reactor.listenTCP(8082, webfactory)

    print ("Starting server")
    reactor.run()


if __name__ == '__main__':
  main()

合并逻辑

这段代码是将之前两个程序合并后的结果,可以看到只需要复制少量代码,就能将上面的两个程序结合成一个,允许访问 http://127.0.0.1:8080/blahhttp://127.0.0.1:8080/foo

#!/usr/bin/python

from twisted.internet import defer, protocol, reactor # the reactor

from twisted.web.server import Site # make the webserver go
from twisted.web.resource import Resource

class BlahPage(Resource):
    idLeaf = True
    def render_GET(self, request):
        return "<html><body>Blah Page!</body></html>"

class FooPage(Resource):
    idLeaf = True
    def render_GET(self, request):
        return "<html><body>Foo Page!</body></html>"

class ShutdownPage(Resource):
    def render_GET(self, request):
        reactor.stop()

webroot = Resource()
webroot.putChild("foo", FooPage())
webroot.putChild("blah", BlahPage())
webroot.putChild("shutdown", ShutdownPage())

def main():
    # Register the webserver (TCP server) into twisted
    webfactory = Site(webroot)
    reactor.listenTCP(8080, webfactory)

    print ("Starting server")
    reactor.run()


if __name__ == '__main__':
  main()

撰写回答