Рубрики
Без рубрики

zlib – Сжатие zlib GNU

Автор оригинала: Doug Hellmann.

Цель:

Низкоуровневый доступ к библиотеке сжатия GNU zlib

Модуль zlib предоставляет интерфейс нижнего уровня для многих функций в библиотеке сжатия zlib из проекта GNU.

Работа с данными в памяти

Самый простой способ работы с zlib требует хранения всех данных для сжатия или распаковки в памяти.

zlib_memory.py

import zlib
import binascii

original_data  b'This is the original text.'
print('Original     :', len(original_data), original_data)

compressed  zlib.compress(original_data)
print('Compressed   :', len(compressed),
      binascii.hexlify(compressed))

decompressed  zlib.decompress(compressed)
print('Decompressed :', len(decompressed), decompressed)

Функции compress () и decopress () принимают аргумент байтовой последовательности и возвращают байтовую последовательность.

$ python3 zlib_memory.py

Original     : 26 b'This is the original text.'
Compressed   : 32 b'789c0bc9c82c5600a2928c5485fca2ccf4ccbcc41c85
92d48a123d007f2f097e'
Decompressed : 26 b'This is the original text.'

Предыдущий пример демонстрирует, что сжатая версия небольших объемов данных может быть больше, чем несжатая версия. Хотя фактические результаты зависят от входных данных, интересно наблюдать накладные расходы на сжатие для небольших наборов данных.

zlib_lengths.py

import zlib

original_data  b'This is the original text.'

template  '{:>15}  {:>15}'
print(template.format('len(data)', 'len(compressed)'))
print(template.format('-' * 15, '-' * 15))

for i in range(5):
    data  original_data * i
    compressed  zlib.compress(data)
    highlight  '*' if len(data) < len(compressed) else ''
    print(template.format(len(data), len(compressed)), highlight)

* в выводе выделяет строки, в которых сжатые данные занимают больше памяти, чем несжатая версия.

$ python3 zlib_lengths.py

      len(data)  len(compressed)
---------------  ---------------
              0                8 *
             26               32 *
             52               35
             78               35
            104               36

zlib поддерживает несколько различных уровней сжатия, обеспечивая баланс между вычислительными затратами и объемом уменьшения пространства. Уровень сжатия по умолчанию, zlib.Z_DEFAULT_COMPRESSION равен -1 и соответствует жестко запрограммированному значению, которое обеспечивает компромисс между производительностью и результатом сжатия. В настоящее время это соответствует уровню 6 .

zlib_compresslevel.py

import zlib

input_data  b'Some repeated text.\n' * 1024
template  '{:>5}  {:>5}'

print(template.format('Level', 'Size'))
print(template.format('-----', '----'))

for i in range(0, 10):
    data  zlib.compress(input_data, i)
    print(template.format(i, len(data)))

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

$ python3 zlib_compresslevel.py

Level   Size
-----   ----
    0  20491
    1    172
    2    172
    3    172
    4     98
    5     98
    6     98
    7     98
    8     98
    9     98

Инкрементное сжатие и декомпрессия

Подход в памяти имеет недостатки, которые делают его непрактичным для реальных случаев использования, в первую очередь из-за того, что системе требуется достаточно памяти для одновременного размещения в памяти как несжатой, так и сжатой версий. Альтернативой является использование объектов Compress и Decompress для постепенного управления данными, чтобы весь набор данных не помещался в память.

zlib_incremental.py

import zlib
import binascii

compressor  zlib.compressobj(1)

with open('lorem.txt', 'rb') as input:
    while True:
        block  input.read(64)
        if not block:
            break
        compressed  compressor.compress(block)
        if compressed:
            print('Compressed: {}'.format(
                binascii.hexlify(compressed)))
        else:
            print('buffering...')
    remaining  compressor.flush()
    print('Flushed: {}'.format(binascii.hexlify(remaining)))

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

$ python3 zlib_incremental.py

Compressed: b'7801'
buffering...
buffering...
buffering...
buffering...
buffering...
Flushed: b'55904b6ac4400c44f73e451da0f129b20c2110c85e696b8c40dde
dd167ce1f7915025a087daa9ef4be8c07e4f21c38962e834b800647435fd3b90
747b2810eb9c4bbcc13ac123bded6e4bef1c91ee40d3c6580e3ff52aad2e8cb2
eb6062dad74a89ca904cbb0f2545e0db4b1f2e01955b8c511cb2ac08967d228a
f1447c8ec72e40c4c714116e60cdef171bb6c0feaa255dff1c507c2c4439ec96
05b7e0ba9fc54bae39355cb89fd6ebe5841d673c7b7bc68a46f575a312eebd22
0d4b32441bdc1b36ebf0aedef3d57ea4b26dd986dd39af57dfb05d32279de'

Потоки смешанного контента

Класс Decompress , возвращаемый функцией decopressobj () , также можно использовать в ситуациях, когда сжатые и несжатые данные смешиваются вместе.

zlib_mixed.py

import zlib

lorem  open('lorem.txt', 'rb').read()
compressed  zlib.compress(lorem)
combined  compressed + lorem

decompressor  zlib.decompressobj()
decompressed  decompressor.decompress(combined)

decompressed_matches  decompressed  lorem
print('Decompressed matches lorem:', decompressed_matches)

unused_matches  decompressor.unused_data  lorem
print('Unused data matches lorem :', unused_matches)

После распаковки всех данных атрибут unused_data содержит все неиспользуемые данные.

$ python3 zlib_mixed.py

Decompressed matches lorem: True
Unused data matches lorem : True

Контрольные суммы

Помимо функций сжатия и распаковки, zlib включает две функции для вычисления контрольных сумм данных: adler32 () и crc32 () . Ни одна из контрольных сумм не является криптографически безопасной, и они предназначены только для использования для проверки целостности данных.

zlib_checksums.py

import zlib

data  open('lorem.txt', 'rb').read()

cksum  zlib.adler32(data)
print('Adler32: {:12d}'.format(cksum))
print('       : {:12d}'.format(zlib.adler32(data, cksum)))

cksum  zlib.crc32(data)
print('CRC-32 : {:12d}'.format(cksum))
print('       : {:12d}'.format(zlib.crc32(data, cksum)))

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

$ python3 zlib_checksums.py

Adler32:   3542251998
       :    669447099
CRC-32 :   3038370516
       :   2870078631

Сжатие сетевых данных

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

zlib_server.py

import zlib
import logging
import socketserver
import binascii

BLOCK_SIZE  64


class ZlibRequestHandler(socketserver.BaseRequestHandler):

    logger  logging.getLogger('Server')

    def handle(self):
        compressor  zlib.compressobj(1)

        # Find out what file the client wants
        filename  self.request.recv(1024).decode('utf-8')
        self.logger.debug('client asked for: %r', filename)

        # Send chunks of the file as they are compressed
        with open(filename, 'rb') as input:
            while True:
                block  input.read(BLOCK_SIZE)
                if not block:
                    break
                self.logger.debug('RAW %r', block)
                compressed  compressor.compress(block)
                if compressed:
                    self.logger.debug(
                        'SENDING %r',
                        binascii.hexlify(compressed))
                    self.request.send(compressed)
                else:
                    self.logger.debug('BUFFERING')

        # Send any data being buffered by the compressor
        remaining  compressor.flush()
        while remaining:
            to_send  remaining[:BLOCK_SIZE]
            remaining  remaining[BLOCK_SIZE:]
            self.logger.debug('FLUSHING %r',
                              binascii.hexlify(to_send))
            self.request.send(to_send)
        return


if __name__  '__main__':
    import socket
    import threading
    from io import BytesIO

    logging.basicConfig(
        levellogging.DEBUG,
        format'%(name)s: %(message)s',
    )
    logger  logging.getLogger('Client')

    # Set up a server, running in a separate thread
    address  ('localhost', 0)  # let the kernel assign a port
    server  socketserver.TCPServer(address, ZlibRequestHandler)
    ip, port  server.server_address  # what port was assigned?

    t  threading.Thread(targetserver.serve_forever)
    t.setDaemon(True)
    t.start()

    # Connect to the server as a client
    logger.info('Contacting server on %s:%s', ip, port)
    s  socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((ip, port))

    # Ask for a file
    requested_file  'lorem.txt'
    logger.debug('sending filename: %r', requested_file)
    len_sent  s.send(requested_file.encode('utf-8'))

    # Receive a response
    buffer  BytesIO()
    decompressor  zlib.decompressobj()
    while True:
        response  s.recv(BLOCK_SIZE)
        if not response:
            break
        logger.debug('READ %r', binascii.hexlify(response))

        # Include any unconsumed data when
        # feeding the decompressor.
        to_decompress  decompressor.unconsumed_tail + response
        while to_decompress:
            decompressed  decompressor.decompress(to_decompress)
            if decompressed:
                logger.debug('DECOMPRESSED %r', decompressed)
                buffer.write(decompressed)
                # Look for unconsumed data due to buffer overflow
                to_decompress  decompressor.unconsumed_tail
            else:
                logger.debug('BUFFERING')
                to_decompress  None

    # deal with data reamining inside the decompressor buffer
    remainder  decompressor.flush()
    if remainder:
        logger.debug('FLUSHED %r', remainder)
        buffer.write(remainder)

    full_response  buffer.getvalue()
    lorem  open('lorem.txt', 'rb').read()
    logger.debug('response matches file contents: %s',
                 full_response  lorem)

    # Clean up
    s.close()
    server.socket.close()

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

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

Предупреждение

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

$ python3 zlib_server.py

Client: Contacting server on 127.0.0.1:53658
Client: sending filename: 'lorem.txt'
Server: client asked for: 'lorem.txt'
Server: RAW b'Lorem ipsum dolor sit amet, consectetuer adipiscin
g elit. Donec\n'
Server: SENDING b'7801'
Server: RAW b'egestas, enim et consectetuer ullamcorper, lectus
ligula rutrum '
Server: BUFFERING
Server: RAW b'leo, a\nelementum elit tortor eu quam. Duis tincid
unt nisi ut ant'
Server: BUFFERING
Server: RAW b'e. Nulla\nfacilisi. Sed tristique eros eu libero.
Pellentesque ve'
Server: BUFFERING
Server: RAW b'l arcu. Vivamus\npurus orci, iaculis ac, suscipit
sit amet, pulvi'
Client: READ b'7801'
Client: BUFFERING
Server: BUFFERING
Server: RAW b'nar eu,\nlacus.\n'
Server: BUFFERING
Server: FLUSHING b'55904b6ac4400c44f73e451da0f129b20c2110c85e696
b8c40ddedd167ce1f7915025a087daa9ef4be8c07e4f21c38962e834b8006474
35fd3b90747b2810eb9'
Server: FLUSHING b'c4bbcc13ac123bded6e4bef1c91ee40d3c6580e3ff52a
ad2e8cb2eb6062dad74a89ca904cbb0f2545e0db4b1f2e01955b8c511cb2ac08
967d228af1447c8ec72'
Client: READ b'55904b6ac4400c44f73e451da0f129b20c2110c85e696b8c4
0ddedd167ce1f7915025a087daa9ef4be8c07e4f21c38962e834b800647435fd
3b90747b2810eb9'
Server: FLUSHING b'e40c4c714116e60cdef171bb6c0feaa255dff1c507c2c
4439ec9605b7e0ba9fc54bae39355cb89fd6ebe5841d673c7b7bc68a46f575a3
12eebd220d4b32441bd'
Client: DECOMPRESSED b'Lorem ipsum dolor sit amet, consectetuer
adi'
Client: READ b'c4bbcc13ac123bded6e4bef1c91ee40d3c6580e3ff52aad2e
8cb2eb6062dad74a89ca904cbb0f2545e0db4b1f2e01955b8c511cb2ac08967d
228af1447c8ec72'
Client: DECOMPRESSED b'piscing elit. Donec\negestas, enim et con
sectetuer ullamcorper, lectus ligula rutrum leo, a\nelementum el
it tortor eu quam. Duis tinci'
Client: READ b'e40c4c714116e60cdef171bb6c0feaa255dff1c507c2c4439
ec9605b7e0ba9fc54bae39355cb89fd6ebe5841d673c7b7bc68a46f575a312ee
bd220d4b32441bd'
Client: DECOMPRESSED b'dunt nisi ut ante. Nulla\nfacilisi. Sed t
ristique eros eu libero. Pellentesque vel arcu. Vivamus\npurus o
rci, iaculis ac'
Server: FLUSHING b'c1b36ebf0aedef3d57ea4b26dd986dd39af57dfb05d32
279de'
Client: READ b'c1b36ebf0aedef3d57ea4b26dd986dd39af57dfb05d32279d
e'
Client: DECOMPRESSED b', suscipit sit amet, pulvinar eu,\nlacus.
\n'
Client: response matches file contents: True

Смотрите также