Сокеты Беркли предоставляют возможность передавать датаграмы без гарантии доставки (UDP) либо поток байт с гарантией (TCP). Смысл потока в отличии от датаграм состоит в том что при передачи определенного колличества байт на передающей стороне функцией send нет гарантии что вы получите ровно столько же за один вызов recv на принимающей стороне. Например вы можете передать один раз 1000 байт а получить сначала 400 а потом 600 байт. Аналогично в потоке возможен и обратный процесс - склейки - вы можете 3жды отправить по 200 байт а получите сразу 600. Причиной такого поведения есть алгоритм Нейгла позволяющий за счет накапливания байт и последующей отправке значительно повысить эффективность передачи данных. Но что если передавать нужно именно пакеты целиком, то есть датаграмы целиком. Один из вариантов - использовать подход TLV (Tag-length-value), пример на Python приведен в статье.

Предлагаю вам вариант простого экстрактора пакетов из пакета основанного на примитивном TLV. К переданным данным мы добавляем один байт маркера (просто число) и размер пакета два байта. Это позволит нам знать когда в потоке накопилось достаточно байт для пакета целиком.

 

Вот класс:

 

class StreamGramsExtractor:
    """
    Allows receive datagrams via reliable stream using simple TLV (Type-length-value)
    """
    # STReam States
    STRS_WAIT_MARKER = 0
    STRS_WAIT_HI_SIZE = 1
    STRS_WAIT_LO_SIZE = 2
    STRS_WAIT_DATA = 3

    PACK_MARKER = 0x72

    def __init__(self, log=None):
        self._log = log
        self._rcv_size = 0
        self._rcv_state = self.STRS_WAIT_MARKER
        self._rcv_buf = bytearray([])

    def put_chunk(self, chunk):
        """
        Put stream chunk and receive list of packets
        :param chunk:
        :return:
        """
        i = 0
        packets = []
        while i < len(chunk):
            if self._rcv_state == self.STRS_WAIT_MARKER:
                if chunk[i] == self.PACK_MARKER:
                    self._rcv_state = self.STRS_WAIT_HI_SIZE
                else:
                    if self._log:
                        self._log.warning(
                            "Received init byte {} not equal marker {}".format(chunk[i], self.PACK_MARKER))
                    return []  # drop this chunk
                i += 1
            elif self._rcv_state == self.STRS_WAIT_HI_SIZE:
                self._rcv_state = self.STRS_WAIT_LO_SIZE
                self._rcv_size = chunk[i] << 8
                i += 1
            elif self._rcv_state == self.STRS_WAIT_LO_SIZE:
                self._rcv_state = self.STRS_WAIT_DATA
                self._rcv_size |= chunk[i]
                i += 1
            else:
                if len(self._rcv_buf) + len(chunk) - i >= self._rcv_size:
                    packets.append(self._rcv_buf + chunk[i:i + self._rcv_size])
                    self._rcv_buf = bytearray([])
                    i += self._rcv_size
                    self._rcv_state = self.STRS_WAIT_MARKER
                else:
                    self._rcv_buf += chunk[i:]
                    i = len(chunk)
        return packets

    def wrap_packet(self, packet):
        """
        Call this method before send to stream protocol,
        It will add metaheader for extract full packet from stream
        :param packet: packet
        :return: meta header + packet
        """
        l = len(packet)
        return bytearray([
            self.PACK_MARKER,
            (l >> 8) & 0xFF, l & 0xFF]) + packet

Как использовать:

 

  1. Создаем инстанс класса на передатчике и на приемнике.
  2. Перед вызовом функции send заворачиваем bytearray функцией wrap_packet она вернет bytearray который нужно передать в send.
  3. При приеме очередной пачки байт передаем их в put_chunk. Эта функция возвращает список полных принятых пакетов (или пустой список если ни одного пакета пока не удалось целиком собрать).

Итоги:

  • UDP сокеты могут быть использованы для передачи пакетов данных целиком но не обеспечивают гарантию их доставки, то есть пакет(датаграмма) приходит или правильная и целостная в таком же виде как была отправлена, либо теряется гдето и не приходит вообще.
  • Для обеспечения гарантии передачи пакетов по сети нужно использовать TCP. 
  • TCP гарантирует надежность доставки данных но предназначен для передачи потока байт а не целостных пакетов. Более простыми словами TCP дает гарантию что все отправленные байты будут рано или поздно доставлены но могут быть побиты на куски или склеены воедино. При этом конечно же гарантируется что куски будут приходить в правильном порядке.
  • Для передачи пакетов через поток можно просто добавлять к данным размер и таким образом разделять их между собой, пример чего и приведен в статье.