如何使服务器接受来自多个端口的连接?

5

我该如何让一个简单的服务器(比如只接受连接并将接收到的内容打印到终端)可以接受来自多个端口或者一个端口范围的连接?

我需要为每个绑定调用使用多线程,还是有其他解决方案?

这个简单的服务器代码可能长这样。

def server():
import sys, os, socket

port = 11116
host = ''
backlog = 5 # Number of clients on wait.
buf_size = 1024

try:
    listening_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    listening_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 
    listening_socket.bind((host, port)) 
    listening_socket.listen(backlog)
except socket.error, (value, message):
    if listening_socket:
        listening_socket.close()
    print 'Could not open socket: ' + message
    sys.exit(1)

while True:
    accepted_socket, adress = listening_socket.accept()

    data = accepted_socket.recv(buf_size)
    if data:
        accepted_socket.send('Hello, and goodbye.')
    accepted_socket.close()

server()

编辑: 这是一个示例,说明如何完成。感谢大家。

import socket, select

def server():
import sys, os, socket

port_wan = 11111
port_mob = 11112
port_sat = 11113

sock_lst = []
host = ''
backlog = 5 # Number of clients on wait.
buf_size = 1024

try:
    for item in port_wan, port_mob, port_sat:
        sock_lst.append(socket.socket(socket.AF_INET, socket.SOCK_STREAM))
        sock_lst[-1].setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 
        sock_lst[-1].bind((host, item)) 
        sock_lst[-1].listen(backlog)
except socket.error, (value, message):
    if sock_lst[-1]:
        sock_lst[-1].close()
        sock_lst = sock_lst[:-1]
    print 'Could not open socket: ' + message
    sys.exit(1)

while True:
    read, write, error = select.select(sock_lst,[],[])

    for r in read:
        for item in sock_lst:
            if r == item:
                accepted_socket, adress = item.accept()

                print 'We have a connection with ', adress
                data = accepted_socket.recv(buf_size)
                if data:
                    print data
                    accepted_socket.send('Hello, and goodbye.')
                accepted_socket.close()

server()
3个回答

7
我不是Python专家,但你感兴趣的函数是“select”。它可以让你监视多个套接字,并在任何一个套接字有活动时退出。
这里有一个使用select的Python示例

3
我是一名Python程序员,你需要选择:http://docs.python.org/library/select.html。 - S.Lott

3
由于Python有很多开销,多线程应用程序是一个重要的争议点。此外,还存在整个阻塞操作-GIL问题。幸运的是,Python的座右铭“如果似乎是一个大问题,那么可能已经有人想出了解决方案(或者几个!)”在这里也成立。我最喜欢的解决方案往往是微线程模型,特别是 gevent
Gevent是一个事件驱动的单线程并发库,通过猴子补丁(mokey-patching)为您处理大多数问题。 gevent.monkey.patch_socket()是一个函数,它使用非阻塞变体替换正常的套接字调用,轮询和睡眠以允许根据需要切换到其他绿色线程。如果您需要更多控制,或者它不能满足您的需求,您可以轻松地使用select和gevent的协作式yield来管理切换。
以下是一个简单的例子。
import gevent
import socket
import gevent.monkey; gevent.monkey.patch_socket()

ALL_PORTS=[i for i in xrange(1024, 2048)]
MY_ADDRESS = "127.0.0.1"    

def init_server_sock(port):
    try: 
        s=socket.socket()
        s.setblocking(0)
        s.bind((MY_ADDRESS, port))
        s.listen(5)
        return s
    except Exception, e:
        print "Exception creating socket at port %i: %s" % (port, str(e))
        return False

def interact(port, sock):
    while 1:
        try:
            csock, addr = sock.accept()
        except:
            continue
        data = ""
        while not data:
            try:
                data=csock.recv(1024)
                print data
            except:
                gevent.sleep(0) #this is the cooperative yield
        csock.send("Port %i got your message!" % port)
        csock.close()
        gevent.sleep(0)


def main():
   socks = {p:init_server_sock(p) for p in ALL_PORTS}
   greenlets = []
   for k,v in socks.items():
       if not v:
           socks.pop(k)
       else:
           greenlets.append(gevent.spawn(interact, k, v))

   #now we've got our sockets, let's start accepting
   gevent.joinall(greenlets)

这是一个超级简单、完全未经测试的服务器,可以在1024-2048端口上提供纯文本消息“我们收到了您的信息!”。涉及select有点困难;你需要有一个管理greenlet来调用select,然后启动活动的greenlet;但实现起来并不是非常困难。希望这能帮到你!Greenlet的好处之一是,选择调用实际上是它们的hub模块的一部分,这将使您更轻松地创建一个更可扩展和复杂的服务器。它也非常高效;有一些基准测试结果在浮动。

2
如果你真的想变得懒一些(从一个程序员的角度来看,而不是评估的角度),你可以在阻塞读取上设置超时,然后只需循环遍历所有的套接字;如果超时,则没有可用的数据。功能上来说,这类似于 select 所做的事情,但它将控制权从操作系统中拿走,并将其放入应用程序中。
当然,这意味着随着睡眠时间变得更短,你的程序将接近100%的CPU利用率,因此你不会在生产应用程序中使用它。但对于玩具应用程序来说是可以的。
它可能会像这样:(未经测试)
def server():
    import sys, os, socket

    port = 11116
    host = ''
    backlog = 5 # Number of clients on wait.
    buf_size = 1024
    NUM_SOCKETS = 10
    START_PORT = 2000

    try:
            socket.setdefaulttimeout(0.5) # raise a socket.timeout error after a half second
            listening_sockets = []
            for i in range(NUM_SOCKETS):
                listening_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                listening_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) 
                listening_socket.bind((host, START_PORT + i)) 
                listening_socket.listen(backlog)
                listening_sockets.append(listening_socket)
    except socket.error, (value, message):
            if listening_socket:
                    listening_socket.close()
            print 'Could not open socket: ' + message
            sys.exit(1)

    while True:
            for sock in listening_sockets:
                try:
                    accepted_socket, adress = sock_socket.accept()

                    data = sock.recv(buf_size)
                    if data:
                            sock_socket.send('Hello, and goodbye.')
                    sock.close()
                except socket.timeout:
                    pass

    server()

1
为什么要实现自己的select函数?内置的select模块难道不比这个简单吗? - S.Lott
谢谢。我猜因为选择使用的 CPU 更少,所以我选择了那个解决方案。 - Orjanp
我模糊地记得它并不简单。但是现在我再仔细想想,当我做那件事情的时候,我很可能是在使用C语言而不是Python。 :( - Mark Rushakoff

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接