Отправка сообщений всем клиентам

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

Сценарий: два клиента отправляют сообщения. Сначала отправляет Halo и Seeya. После первый отправляет эти сообщения, второй отправляет Hello и Bye (этот клиент просто будет спать 6 секунд, чтобы сохранить этот порядок). На все сообщения сервер отвечает (original msg), client (number)!, и ответное сообщение передается клиентам оба.

В идеале результат для обоих клиентов будет выглядеть так:

    Halo, client 1!
    Seeya, client 1!

    Hello, client 2!
    Bye, client 2 !

Я не смог пронумеровать каждого клиента, но вот мой код работает странно.

сервер

    import socket             

    clients = []

    # send msgs to every client
    def broadcast(message):          
        for client in clients : 
            client.send(message)

    # connection part
    s = socket.socket()         
    s.bind(('127.0.0.1', 7070)) 
    s.listen(2)    

    while True:
        c, addr = s.accept()
        if c:
            clients.append(c)
        msg = c.recv(1024).decode()
        msg += ', client!'
        broadcast(msg.encode())

client1

    ### here goes connection part ###

    s.send(("Halo").encode())
    print(f"server = {(s.recv(1024)).decode()}")

    # I've tried adding socket closing/connection part here

    s.send(("Seeya").encode())
    print((s.recv(1024)).decode())

    time.sleep(3)
    s.close()

client2 - первый подключился, но ждет 6 секунд порядка сообщения

    ### here goes connection part ###

    time.sleep(6)               # waits for message order
    s.send(("Hello").encode())
    print(f"server = {(s.recv(1024)).decode()}")

    # I've tried adding socket closing/connection part here

    s.send(("Bye").encode())
    print((s.recv(1024)).decode())
    time.sleep(3)
    s.close()

В результате я получаю ...

    # On client 1 side 
    Halo, client!                  # no Seeya, Hello, Bye
                                   # connection isn't closed

    # On client 2 side
    Hello, client!                 # no Seeya, Bye
    Halo, client!                  # connection is closed

Много недостающего кода. Предоставьте минимальный воспроизводимый пример, чтобы воспроизвести проблему.

Mark Tolonen 27.11.2018 18:23
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
2
1
1 694
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

У вас здесь несколько проблем. Первая и основная проблема заключается в том, что основной цикл вашего сервера испорчен. Каждый раз при прохождении цикла ваш сервер хочет установить соединение accept. Итак, первый подключившийся клиент принимается, и сразу же приходит его первое сообщение. Но другой клиент еще не принят и поэтому не получит это первое сообщение. Затем принимается второе клиентское соединение, и первое сообщение это затем отправляется обоим клиентам, но затем цикл повторяется снова, и больше сообщений с сервера не будет отправляться до тех пор, пока не будет установлено соединение третий клиент. И Т. Д.

Таким образом, вам нужно разделить прием соединений и получение сообщений. Это можно сделать несколькими способами. Самый простой способ - использовать функцию select для ожидания сразу нескольких сокетов. То есть, если у вас есть список сокетов, включая прослушивающий сокет и ранее принятые, вы должны сделать что-то вроде этого:

# List starts with only listening socket
list_of_sockets = [lsock]
...
while True:
    # Wait until some socket becomes "readable"
    rfds, _wfds, _xfds = select.select(list_of_socks, [], [])
    for sock in rfds:
        if sock is lsock:
            # Listening socket ready. Accept new connection
            c, addr = lsock.accept()
            print(f"Accepted {c}")
            list_of_socks.append(c)
        else:
            msg = sock.recv(1024)
            if msg:
                # Received data from connected socket. Send to all
                print(f"Got {msg.decode()} from {sock}")
                broadcast(msg)
            else:
                # Got end of file (this client closed). Remove client from list
                print(f"Closed {sock}")
                list_of_socks.remove(sock)

Другая проблема с вашим кодом, которая будет нет, будет решена серверным кодом выше: вы не можете предполагать, что каждое отправленное вами сообщение будет получено как отдельный блок. То есть, если сервер отправляет «Halo», а затем он отправляет «Hello» до того, как вы (клиент) выполнили recv, то, по всей вероятности, все данные будут возвращены одним махом; это "HaloHello".

Поэтому, как правило, вам нужно поместить какой-то разделитель в данные (например, новую строку [\n] - но тогда вам нужно будет проанализировать полученные данные) или, что еще лучше, поместить поле фиксированной длины перед каждым message, задающий длину последующей части переменной длины, чтобы вы могли получать и обрабатывать только одно сообщение за раз. (В Python это обычно связано с использованием функций struct и pack модуля unpack.) В результате ваш текущий клиентский код, вероятно, не будет должным образом упорядочивать сообщения, как вы хотите.

Кроме того, - хотя вероятность возникновения проблем с ним меньше - то же самое и с send: не следует предполагать, что send(N) отправляет именно байты N. Он может отправлять 1, 2, 3 или N-1 байта. Вы можете использовать sendall, чтобы гарантировать отправку всех байтов.

Другие вопросы по теме