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

标签 python sockets

我正在尝试通过套接字将数据发送到同一 IP,但通过不同的端口。这些是我迄今为止开发的测试脚本:

服务器:

# 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()

客户:

# 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 我得到:

$ 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 分成两个单独的程序,如下所示:

# 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()

和:

# 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的顺序启动它们,我得到了预期的结果:

(第一个命令提示符):

$ 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

(第二个命令提示符):

$ python3 test_client1.py 
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server
received: acknowledgement from server

(第三个命令提示符):

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 的灵 active 。

3)上述的另一个限制是在第二种情况下,它起作用,我必须按特定顺序启动程序test_server.pytest_client1.pytest_client2.py。在生产版本中,我最终的订单会有所不同。是否有建议的更改来优雅地处理这个问题?

最佳答案

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

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

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

问题 2 - “优雅”方式

您确实希望同时等待 X 个端口上的连接,而不是连续等待。执行此操作的简单方法是使用 python 多处理模块来启动并行服务器执行线程。但是,这是一个沉重的解决方案。

正确的方法确实是使用 select(),这是表达“我只是一个进程,但我想监听多个事物(端口)上的等待事件”的官方方式。因此,您将设置多个套接字,然后选择您想要等待的事件列表。它将阻塞,直到其中任何一个出现事件为止。您执行处理该操作的工作,然后再次循环回 select() 以阻止并等待更多事件(也许下次在不同的端口上,或者可能是在同一端口上的新连接)。

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

使用多处理更简单,但它的扩展性不如架构良好的基于​​事件的设计。 Here's有关此示例的讨论,Apache 与 Nginx。基于事件处理架构的解决方案的强大功能的另一个例子是 NodeJS,它以在事件循环中运行所有内容而闻名。

所有这些细节都是为了强调,因为您说过接下来要进入生产实现,所以有很多事情需要考虑。

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

问题 3 - 与客户联系的正确方式

当然,您也想让您的客户端变得更加漂亮。做客户的正确方法当然是不要期望一切都是完美的。您正在尝试连接到远程计算机。网络可能会出现故障,服务器可能会离线等等。因此,请从一般客户的流行选择中选择您的策略:

  • 失败(在脚本之外),并给用户带来明显的错误,他们将在需要时通过重新运行您的客户端脚本来重试
  • 通知用户您正在暂停,并将在 10 秒后重试。进行 X 次尝试(例如 10 次尝试),然后从(客户端)脚本失败。
  • 作为一个有多种选择的客户端,自动尝试其他服务器。如果您的问题领域有必要,您可以计划构建一个带有应急计划的宏伟解决方案。如果第一个服务器没有应答,优秀的客户端会自动尝试其他服务器(并且优秀的服务会提供多个可用服务器)。

所以,一般答案:捕获该异常,然后决定你想要做什么。等待并循环重试、保释或“其他”?

关于Python程序具有来自同一脚本的多个具有不同端口号的套接字?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58795028/

相关文章:

python - 如何使用Reportlab编写选项卡?

java - C 服务器与 Java 客户端套接字连接。在客户端上阅读消息时,我看到实际消息之前有空格

multithreading - zmq : can multiple threads PUSH in a simple PUSH-PULL pattern

sockets - Twisted上的IPv6链路本地多播

c - 如何通过 sock_stream 一次发送一个字符的字符串

python - PyGObject 和 Glade 将窗口发送到前面

python - 在列表中找到最小的唯一元素

python - 使用 PyMongo 使用自己的值更新所有 MongoDB 字段

Java tcp套接字转换为udp

python - 澄清作者的意思(学习Python第五版)