首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >Python程序有多个套接字,来自同一脚本的不同端口号?

Python程序有多个套接字,来自同一脚本的不同端口号?
EN

Stack Overflow用户
提问于 2019-11-11 10:59:21
回答 1查看 394关注 0票数 1

我正在尝试通过套接字将数据发送到相同的IP,但通过不同的端口。以下是我到目前为止开发的测试脚本:

服务器:

代码语言:javascript
复制
# test_server.py

import socket
import select

# module-level variables ##############################################################################################

HOST1 = '127.0.0.1'
PORT1 = 65432

HOST2 = '127.0.0.1'
PORT2 = 65433

#######################################################################################################################
def main():

    sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock1.bind((HOST1, PORT1))
    sock1.listen()
    conn1, addr1 = sock1.accept()

    sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock2.bind((HOST2, PORT2))
    sock2.listen()
    conn2, addr2 = sock2.accept()

    conns = [ conn1, conn2 ]

    while True:

        readyConns, _, _ = select.select(conns, [], [])

        for conn in readyConns:
            data = conn.recv(1024)

            if not data:
                print('no data received')
            else:
                print('received: ' + data.decode("utf-8"))
            # end if

            conn.sendall(bytes('acknowledgement from server', 'utf-8'))
        # end for
    # end while
# end main

#######################################################################################################################
if __name__ == '__main__':
    main()

客户端:

代码语言:javascript
复制
# test_client.py

import socket
import time

# module-level variables ##############################################################################################

HOST1 = '127.0.0.1'
PORT1 = 65432

HOST2 = '127.0.0.1'
PORT2 = 65433

#######################################################################################################################
def main():

    myCounter = 1

    sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock1.connect((HOST1, PORT1))

    sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock2.connect((HOST2, PORT2))

    while True:
        # sock1 ############################################

        # send the original message
        messageAsStr1 = 'message 1-' + myCounter
        sock1.sendall(bytes(messageAsStr1, 'utf-8'))

        # receive the acknowledgement
        ack1 = sock1.recv(1024)
        if ack1 is None:
            print('error receiving acknowledgement on port 1')
        else:
            print('received: ' + ack1.decode('utf-8'))
        # end if

        time.sleep(2)

        # sock2 ############################################

        # send the original message
        messageAsStr2 = 'message 2-' + myCounter
        sock2.sendall(bytes(messageAsStr2, 'utf-8'))

        # receive the acknowledgement
        ack2 = sock2.recv(1024)
        if ack2 is None:
            print('error receiving acknowledgement on port 2')
        else:
            print('received: ' + ack2.decode('utf-8'))
        # end if

        time.sleep(2)

        myCounter += 1

    # end while

# end main

#######################################################################################################################
if __name__ == '__main__':
    main()

如果我先启动test_server.py,然后启动test_client.pytest_server.py将成功启动,但在启动test_client.py时,我会得到:

代码语言:javascript
复制
$ python3 test_client.py 
Traceback (most recent call last):
  File "test_client.py", line 66, in <module>
    main()
  File "test_client.py", line 23, in main
    sock2.connect((HOST2, PORT2))
ConnectionRefusedError: [Errno 111] Connection refused

我不明白为什么第二个连接不能通过,b/c如果我将test_client.py分成两个独立的程序如下:

代码语言:javascript
复制
# test_client1.py

import socket
import time

# module-level variables ##############################################################################################

HOST1 = '127.0.0.1'
PORT1 = 65432

#######################################################################################################################
def main():

    myCounter = 1

    sock1 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock1.connect((HOST1, PORT1))

    while True:
        # sock1 ############################################

        # send the original message
        messageAsStr1 = 'message 1-' + str(myCounter)
        sock1.sendall(bytes(messageAsStr1, 'utf-8'))

        # receive the acknowledgement
        ack1 = sock1.recv(1024)
        if ack1 is None:
            print('error receiving acknowledgement on port 1')
        else:
            print('received: ' + ack1.decode('utf-8'))
        # end if

        myCounter += 1

        time.sleep(2)

    # end while

# end main

#######################################################################################################################
if __name__ == '__main__':
    main()

和:

代码语言:javascript
复制
# test_client2.py

import socket
import time

# module-level variables ##############################################################################################

HOST2 = '127.0.0.1'
PORT2 = 65433

#######################################################################################################################
def main():

    myCounter = 1

    sock2 = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock2.connect((HOST2, PORT2))

    while True:
        # sock2 ############################################

        # send the original message
        messageAsStr2 = 'message 2-' + str(myCounter)
        sock2.sendall(bytes(messageAsStr2, 'utf-8'))

        # receive the acknowledgement
        ack2 = sock2.recv(1024)
        if ack2 is None:
            print('error receiving acknowledgement on port 2')
        else:
            print('received: ' + ack2.decode('utf-8'))
        # end if

        myCounter += 1

        time.sleep(2)

    # end while

# end main

#######################################################################################################################
if __name__ == '__main__':
    main()

然后按test_server.pytest_client1.pytest_client2.py的顺序启动它们,我得到了预期的结果:

(第一个命令提示符):

代码语言:javascript
复制
$ python3 test_server.py 
received: message 1-1
received: message 2-1
received: message 1-2
received: message 2-2
received: message 1-3
received: message 2-3
received: message 1-4
received: message 2-4

(第二个命令提示符):

代码语言:javascript
复制
$ python3 test_client1.py 
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server

(第三个命令提示符):

代码语言:javascript
复制
python3 test_client2.py 
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server

现在我的问题是:

1)为什么第一种方式(在test_client.py中使用不同的端口号)不起作用,但如果我将它分成两个脚本,它就会起作用?

2)有没有更优雅/更健壮的方法来实现同样的目标?值得一提的是,在我的最终程序中,我绝对需要确认内容以及使用不同端口号和IP的灵活性。

3)上面的另一个限制是在第二种情况下,我必须按照test_server.pytest_client1.pytest_client2.py的特定顺序启动程序。在生产版本中,我最终要做的顺序会有所不同。有没有推荐的改变来优雅地处理这个问题?

EN

回答 1

Stack Overflow用户

发布于 2019-11-11 12:46:00

我明白为什么这会令人困惑。幸运的是,解释很简单。

问题1-阻塞调用和竞争条件

在你的服务器脚本中,当你告诉它接受()时,你的脚本会“等待”(我们说这个函数调用是“阻塞”的)。您的服务器脚本的其余部分甚至还没有被查看。现在你去运行你的客户端脚本,它在第一个端口上连接,这会导致你的服务器脚本中的调用解除阻塞并继续。现在“竞争”开始了,在客户端下一次调用connect()之前,您的服务器脚本是否会执行listen()调用,然后再接受()??也许吧!但不太可能,除非你让你的客户睡着了。然后你就会看到它每次都能工作。但这不是正确的解决方案。

问题2-“优雅”的方式

您真的希望同时在X个端口上等待连接,而不是串行连接。要做到这一点,简单的方法是使用python多处理模块启动并行服务器线程的执行。但是,这是一个沉重的解决方案。

真正正确的方法是使用select(),这是一种官方方式,表示“我只是一个进程,但我想侦听多个事物(端口)上的等待活动”。因此,您将设置多个套接字,然后让select列出您想要等待的东西。它将阻塞,直到其中任何一个都有活动为止。您完成了处理它的工作,然后再次循环到select()以阻塞并等待更多的活动(可能下一次是在不同的端口上,或者可能是在同一端口上的新连接)。

有一个重要的警告,这可能会使选择一个“高级”工具。在处理请求时,请注意不要花费太多时间进行处理,因为所有其他连接都在等待。有一些技术可以正确地做到这一点,例如使用libevent管理所有套接字和文件io。

使用多处理更简单,但它不会像架构良好的基于事件的设计那样具有伸缩性。Here's讨论了一个这样的例子,Apache与Nginx。基于事件处理架构的解决方案的另一个强大功能的示例是NodeJS,它以在事件循环中运行一切而闻名。

所有这些细节都是要强调的,因为您说过您将在下一个中逐步实现生产,所以有很多需要考虑的地方。

最佳解决方案:专注于解决您的实际问题,让其他人设计服务器。学习使用gunicorn或wsgi (已经很好的服务器了),并把你的请求处理放在那里。

问题3-客户端的正确方法

当然,你也会想让你的客户变得更好。当然,与客户打交道的正确方式是不要期望一切都是完美的。您正在尝试连接到一台远程计算机。网络可能停机,服务器可能离线,等等。因此,从客户机的流行选择中选择您的策略:

  • 失败(超出脚本)并向用户显示明显的错误,他们将通过重新运行您的客户端脚本
  • 通知用户您正在暂停并将在10秒后重试,从而在需要时重试。进行X次尝试(例如10次尝试),然后从(客户端)脚本中退出。
  • 是一个具有多个选项的客户端,可自动尝试其他服务器。如果您的问题领域需要这样做,您可以计划用应急计划构建一个宏大的解决方案。如果第一个服务器没有应答,一个好的客户端会自动尝试其他服务器(并且一个好的服务会提供多个可用的服务器)。

因此,一般的答案是:捕获异常,然后决定要做什么。在循环、保释或“其他”中等待并重试?

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/58795028

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档