第八章:数据压缩与归档-zlib:GNU zlib压缩-压缩网络数据

8.1.5 压缩网络数据
下一个代码清单中的服务器使用流压缩器来响应文件名请求,它将文件的一个压缩版本写至与客户通信的套接字中。

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 which 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(
        level=logging.DEBUG,
        format='%(name)s: %(message)s',
        )
    logger = logging.getLogger('Client')

    # Set up a servre,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(target=server.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 uncomsumed 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 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()或decompress(),但没有得到完整的压缩或未压缩输出块,此时便会进行缓存。客户连接到套接字,并请求一个文件。 然后循环,接受压缩数据块。由于一个块可能未包含足够多的信息来完全解压缩,所以之前接收的声誉数据将与新数据结合,并且传递到解压缩器。解压缩数据时,会把它追加到一个缓冲区,处理循环结束时将与文件内容进行比较。

警告:这个服务器村长明显的安全隐患。不要在开放的互联网或者安全问题可能产生严重影响的环境中运行这个程序。
运行结果:
第八章:数据压缩与归档-zlib:GNU zlib压缩-压缩网络数据
第八章:数据压缩与归档-zlib:GNU zlib压缩-压缩网络数据
第八章:数据压缩与归档-zlib:GNU zlib压缩-压缩网络数据