Программирование на сокете Python: как убедиться, что все сообщение получено? - PullRequest
2 голосов
/ 29 июня 2019

Я использую Python 3.x и модуль сокета.Сервер работает по адресу ipv4 и использует tcp.Я прочитал несколько уроков о том, как отправлять и получать данные.Чтобы сервер или клиент могли убедиться, что все сообщение было отправлено, вы можете просто проверить, равен ли объем отправленных данных размеру сообщения:

def mysend(self, msg):
    totalsent = 0
    while totalsent < MSGLEN:
        sent = self.sock.send(msg[totalsent:])
        if sent == 0:
            raise RuntimeError("socket connection broken")
        totalsent = totalsent + sent

Источник: https://docs.python.org/3/howto/sockets.html#socket-howto

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

Мои вопросы:

  1. Как я могу убедиться, что получаю первую часть сообщения, указывающую размер сообщения (при условии, что мое сообщение содержит 1000 символовМне нужно четыре символа, чтобы указать размер)?
  2. Почему я не могу просто добавить указанный символ, такой как «<» в начале сообщения и «>» в ​​конце, чтобы я знал, где оно начинаетсяи заканчивается?

Редактировать:

Когда я использую sock.recv(1024) и мои сообщения имеют размер от 500 до 1000 символов, разве это не гарантирует, что я получу их все?

Ответы [ 2 ]

1 голос
/ 29 июня 2019

Для отправки вам действительно нужен этот цикл, только если вы перевели сокет в неблокирующий режим.Если сокет находится в режиме блокировки (по умолчанию), sock.send() не вернется, пока не отправит сообщение целиком или не получит ошибку.

Однако для получения нет аналога, поскольку TCP невключить границы сообщения в протокол.sock.recv() возвращается, как только появятся какие-либо данные.

  1. Вызывайте sock.recv() в цикле, пока не получите все, что вам нужно.Подобно тому, как ваша подпрограмма отправки отправляет более короткие подстроки на каждой итерации, вы можете уменьшить размер аргумента recv() на величину, которую вы уже прочитали.Так что это может выглядеть так:
def myrecv(self, size):
    buffer = ''
    while size > 0:
        msg = self.sock.recv(size)
        buffer += msg
        size -= len(msg)
    return buffer

Если вы поместите 4-байтовую длину перед каждым сообщением, вы можете сделать что-то вроде:

msgsize = int(myrecv(4))
message = myrecv(msgsize)

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

Нет, recv(1024) может вернуться, как только получит какие-либо данные, которых может быть меньшечем размер сообщения, которое было отправлено.Если он гарантированно вернет 1024 символа, он зависнет, если отправитель отправит только 500 символов, поскольку он ожидает оставшиеся 524 символа.

1 голос
/ 29 июня 2019

Прежде всего, для отправки всех байтов вам не нужен цикл, потому что сокеты Python предоставляют простой метод: socket.sendall().

Теперь к вашим вопросам:

  1. Да, даже для получения всего 4 байтов у вас должен быть цикл приема, который вызывает recv() в сокете до тех пор, пока не будут прочитаны 4 байта.

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

  3. При вызове recv(n) гарантированно будет возвращено только не более n байтов, а не точно n байтов.

Вот три различных recvall() метода для сравнения:

def recvall(sock, size):
    received_chunks = []
    buf_size = 4096
    remaining = size
    while remaining > 0:
        received = sock.recv(min(remaining, buf_size))
        if not received:
            raise Exception('unexpected EOF')
        received_chunks.append(received)
        remaining -= len(received)
    return b''.join(received_chunks)

и намного короче

def recvall2(sock, size):
    return sock.recv(size, socket.MSG_WAITALL)

и, наконец, еще одна версия, которая немного короче первой, но в ней отсутствуют некоторые функции:

def recvall3(sock, size):
    result = b''
    remaining = size
    while remaining > 0:
        data = sock.recv(remaining)
        result += data
        remaining -= len(data)
    return result

Второй вариант хорош и короток, но он опирается на опцию сокета socket.MSG_WAITALL, которая, как мне кажется, гарантированно существует на каждой платформе. Первый и третий должны работать везде. Я не сравнивал и сравнивал их.

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