Программа на Python с несколькими сокетами с разными номерами портов из одного скрипта? - PullRequest
1 голос
/ 11 ноября 2019

Я пытаюсь отправить данные через сокеты на один и тот же 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.py, test_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

Я не понимаю, почему 2-е соединение не пройдет, б / с, если я разделю test_client.pyв 2 отдельные программы следующим образом:

# 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.py, test_client1.py, test_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) Почему 1-й способ (с различными номерами портов в test_client.py) не работает, но работает, если я разделю его на два сценария?

2) Есть ли более элегантный / надежный способ достижения той же цели? Я должен упомянуть, что в моей окончательной программе мне абсолютно необходимо содержимое подтверждения и гибкость для использования различных номеров портов, а также IP-адресов.

3) Другое ограничение вышесказанного - во 2-м случае, когда оно работало, ядолжны запускать программы в определенном порядке test_server.py, test_client1.py, test_client2.py. В производственной версии я со временем сделаю заказ, будет меняться. Есть ли рекомендуемые изменения для элегантной обработки?

1 Ответ

0 голосов
/ 11 ноября 2019

Я понимаю, почему это сбивает с толку. К счастью, объяснения просты.

Вопрос 1 - блокировка вызовов и условия гонки

В вашем серверном скрипте, когда вы говорите ему принять (), ваш скрипт "ждет" (мы говорим этовызов функции "блокировка"). Остальная часть вашего серверного скрипта еще даже не была рассмотрена. Теперь вы запускаете свой клиентский скрипт, он подключается к первому порту, и это вызывает вызов в вашем серверном скрипте, чтобы разблокировать и продолжить. Теперь "гонка" включена, будет ли ваш серверный скрипт выполнять вызов listen () и затем accept () до того, как ваш клиент сделает следующий вызов connect () ?? Может быть! Но маловероятно, если вы не усыпите своего клиента. Тогда вы увидите, как это работает каждый раз. Но это не правильное решение.

Вопрос 2 - «элегантный» способ

Вы действительно хотите ожидать соединения на количестве портов X одновременно, а не последовательно. Простой способ сделать это - использовать многопроцессорный модуль python для запуска параллельных потоков выполнения сервера. Но это тяжелое решение.

На самом деле правильный путь - это использовать select (), который является официальным средством сказать: «Я всего лишь один процесс, но я хочу ждать ожидания активности на нескольких вещах (портах)». Итак, вы настроите несколько сокетов и затем выберите список вещей, которые вы хотите ждать. Он будет блокироваться, пока не будет активности на любом из них. Вы выполняете работу, чтобы обработать это, и затем возвращаетесь к select () снова, чтобы заблокировать и ждать большей активности (возможно, в следующий раз на другом порту, или, возможно, новое соединение на том же порту).

Существует важное предупреждение, которое может сделать выбор инструмента «продвинутого уровня». Когда вы обрабатываете запросы, будьте осторожны, вы не тратите слишком много времени на обработку, потому что все эти другие соединения ожидают. Существуют методы, позволяющие сделать это должным образом, например, управление всеми сокетами и файлами с помощью libevent.

Использование многопроцессорной обработки более просто, но оно не будет масштабироваться так же, как и хорошо спроектированное проектирование на основе событий. Вот обсуждение этого примера с Apache против Nginx. Другим примером мощных решений, основанных на архитектуре обработки событий, является NodeJS, который классно запускает все в цикле событий.

Все эти детали должны выделить, , так как вы сказали, что собираетесь перейти кследующая производственная реализация , о которой есть над чем подумать.

Лучшее решение: сфокусируйтесь на решении вашей реальной проблемы и позвольте кому-нибудь другому спроектировать сервер. Научитесь использовать gunicorn или wsgi (уже хорошие серверы) и разместите там обработку ваших запросов.

Вопрос 3 - правильный путь к клиенту

Естественно, вы захотите ускорить работу вашего клиента. слишком. Правильный способ сделать клиента, конечно, не ожидать, что все будет идеально. Вы пытаетесь подключиться к далекой машине. Сеть может быть недоступна, сервер может быть отключен и т. Д. Итак, выберите свою стратегию среди популярных вариантов для клиентов в целом:

  • сбой (вне сценария) с явной ошибкой дляПользователь, он попытается, если и когда захочет, повторно запустив ваш клиентский скрипт
  • , сообщит пользователю, что вы делаете паузу, и попытается через 10 секунд. Сделайте X попыток (например, 10 попыток), затем выйдите из сценария (клиента).
  • - клиент с несколькими вариантами, автоматически пробуйте другие серверы. Если ваш проблемный домен этого требует, вы можете планировать разработку грандиозного решения с планами на случай непредвиденных обстоятельств. Отличный клиент автоматически пробует другие серверы, если первый не отвечает (а отличный сервис предоставляет несколько доступных серверов).

Итак, общий ответ: поймайте это исключение и затем решите, что вы хотите сделать,Подождите и повторите попытку в цикле, залоге или "другом"?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...