3

我想创建一个可以在多个端口(例如:TCP:1234、TCP:5678 等)上运行的 python 网络应用程序。

所以我可以说 n 个套接字,每个套接字都侦听一个客户端连接。我编写了一个简单的网络应用程序,它侦听一系列端口,但是当我运行该应用程序时,它卡在了第一个套接字进程的侦听阶段!

当运行以侦听 N 个端口并且每个端口都等待客户端连接到它时,如何制作我的单个 python 程序。所有套接字都在同时运行和监听。

Socket/Process #1: Listening on TCP Port 5000
Socket/Process #2: Listening on TCP Port 5001
Socket/Process #3: Listening on TCP Port 5002
...
Socket/Process #N: Listening on TCP Port 6000

欣赏任何想法。

#!/usr/bin/env  python

import socket

def getPortList():
    ports=[]
    nPort=int(raw_input("# how many ports you want? "))
    j = 0
    for i in range(0,nPort):
        ports.append(int(raw_input("Enter port number: ")))
        j+=1
    return ports

def myTCPSocket(port=5000):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
    s.bind(("", int(port)))
    print ("\nWaiting for connections!\n")
    s.listen(5)
    (clientsock, clientaddr) = s.accept()
    print(clientaddr)

    data = "start"

    while len(data):
                clientsock.send("\nWelcome to Echo Server\n")
                data = clientsock.recv(1024)
                print ("Data sent is: ", data)
                clientsock.send(data)
                if data == "exit\r\n":
                        clientsock.close()

plst = getPortList()

for item in plst:
    myTCPSocket(item)
4

1 回答 1

2

侦听多个套接字实际上与侦听单个套接字没有什么不同。

您已经需要以某种方式处理侦听器套接字和所有客户端连接套接字。您可以通过以下方式做到这一点:

  • select.select围绕(或pollkqueueepoll等)编写一个循环。
  • 使用标准库反应器asyncore
  • 使用第三方 reactor 或 proactor,例如 Twisted。
  • 使用特定于操作系统的功能(例如,通过 PyObjC 使用 Cocoa 运行循环和服务器)。
  • 为每个新连接创建一个线程。
  • 为每个新连接创建一个子进程。

几乎所有这些方案也适用于处理多个听众。最简单的做法是将两者合二为一(例如,select处理所有侦听器和所有客户端套接字的单个循环,或者每个侦听器和客户端套接字的单独线程)。

出于性能或调试原因,您可能希望改用两层混合方法(例如,每个侦听器一个线程,每个侦听select器的所有客户端套接字都有一个循环,或者每个侦听器一个进程,每个侦听器都有一个线程用于每个客户端套接字)。但是,如果您没有任何充分的理由这样做,请不要增加复杂性。

http://pastebin.com/QebZMKz3展示了一个简单的单一select实现。这是输出:

$ ./multiserve.py 22222 22223 &
(('127.0.0.1', 22222), ' listening')
(('127.0.0.1', 22223), ' listening')
$ echo 'abc' | nc localhost 22222
(('127.0.0.1', 22222), ' <- ', ('127.0.0.1', 64633))
(('127.0.0.1', 64633), ' <- ', 'abc\n')
(('127.0.0.1', 64633), ' EOF')

如果您认为您实际上永远不需要同时处理两个客户端……好吧,您可能错了,但是……您可以使用上述大多数技术,而且可能稍微简单一些。例如,您可以在侦听器上进行选择,然后在返回循环之前同步进行接受和客户端套接字通信。或者您可以为每个侦听器创建一个进程或线程,但在每个侦听器中同步处理接受和客户端套接字通信。等等。

http://pastebin.com/wLVLT49i显示了一个简单的示例,这似乎是您想要做的。由于它为每个套接字使用一个进程(通过os.fork),它确实允许不同端口上的同时连接;由于它不会在每个进程中异步执行任何操作,因此它不允许同时连接到同一个端口。(当然它是 POSIX 特定的,因为它使用fork.)

如果您首先想学习如何编写异步网络服务器,我建议您做两种不同的实现:select和线程。它们在概念上是基本的,并且相对容易编码。

首先,对于select,您必须了解事件循环的概念——事件是每个新的传入连接、现有连接上的每个传入网络数据包,甚至是每次您正在写入的管道畅通时。这里的棘手之处在于,与任何事件循环一样,您需要处理每个事件并在没有阻塞的情况下返回,并且不会花费太多的 CPU 时间。例如,对于回显服务器,您不能只在其他套接字上进行写入,因为其中一些可能很忙。因此,您必须将输出粘贴到每个套接字的写入缓冲区中,当您准备好时,他们将在未来通过事件循环运行时获得它。

同时,对于线程,每个连接的单独线程似乎使一切变得微不足道,但是当您需要将消息从一个线程回显到另一个线程时会发生什么?您要么需要某种形式的线程间通信,要么需要具有线程间同步的共享数据。因此,您可能Queue在每个套接字上都有一个 for writes,因此任何其他套接字的线程都可以将消息推送到队列中。

这些都不会像经过精心改造的反应器或前摄器那样好,但值得学习基础知识 - 特别是因为您将面临阻塞问题(来自select)和通信问题(来自线程) 使用任何解决方案,当您在更高级别工作时,它们会变得更加神秘和难以调试。

于 2012-10-29T22:07:41.127 回答