zoukankan      html  css  js  c++  java
  • 系统程序员成长计划管道过滤器(PipeAndFilter)模式

    Sunday, June 28th, 2009 | Author: admin | » Edit «

    转载时请注明出处和作者联系方式
    文章出处:http://www.limodev.cn/blog
    作者联系方式:李先静 <xianjimli at hotmail dot com>

    系统程序员成长计划-文本处理(三)

    管道过滤器(Pipe-And-Filter)模式

    按照《POSA(面向模式的软件架构)》里的说法,管道过滤器(Pipe-And-Filter)应该属于架构模式,因为它通常决定了一个系统的基 本架构。管道过滤器和生产流水线类似,在生产流水线上,原材料在流水线上经一道一道的工序,最后形成某种有用的产品。在管道过滤器中,数据经过一个一个的 过滤器,最后得到需要的数据。

    o 基本的管道过滤器:

    管道负责数据的传递,它把原始数据传递给第一个过滤器,把一个过滤器的输出传递给下一个过滤器,作为下一个过滤器的输入,重复这个过程直到处理结 束。要注意的是,管道只是对数据传输的抽象,它可能是管道,也可能是其它通信方式,甚至什么都没有(所有过滤器都在原始数据基础上进行处理)。

    过滤器负责数据的处理,过滤器可以有多个,每个过滤器对数据做特定的处理,它们之间没有依赖关系,一个过滤器不必知道其它过滤器的存在。这种松耦合 的设计,使得过滤器只需要实现单一的功能,从而降低了系统的复杂度,也使得过滤器之间依赖最小,从而以更加灵活的组合来实现新的功能。

    编译器就是基于管道过滤器模式设计的:

    输入:源程序
    预处理:负责宏展开和去掉注释等工作。
    编译:进行词法分析、语法分析、语义分析、代码优化和代码产生。
    汇编:负责把汇编代码转换成机器指令,生成目标文件。
    链接:负责把多个目标文件、静态库和共享库链接成可执行文件/共享库。
    输出:可执行文件/共享库。

    o 复合过滤器

    过滤器可以由多个其它过滤器组合起来的,比如上面的“编译”过程可以认为是一个复合过滤器:

    输入:预处理之后的源代码。
    词法分析:负责将源程序分解成一个一个的token,这些token是组成源程序的基本单元。
    语法分析:把词法分析得到的token解析成语法树。
    语义分析:对语法树进行类型检查等语义分析。
    代码优化:对语法树进行重组和修改,以优化代码的速度和大小。
    代码产生:根据语法树产生汇编代码。
    输出:汇编代码。

    o 支持多个输入的过滤器

    过滤器可以有多个输入。比如上面“链接”,它接收多个输入:

    “链接”过滤器能接收多个数据源,如目标文件、静态库和共享库。

    o 具有多个输出的过滤器

    过滤器可以有多个输出。如多媒体播放器的解码过程:

    输入:AVI文件,包括音频和视频数据。
    分离器:把音频和视频数据分离成两个流,音频数据传递给音频解码器,视频数据传递给视频解码器。
    音频解码器:把压缩的音频数据解码成原始的音频数据。
    视频解码器:把压缩的视频数据解码成原始的图像数据。
    输出:音频数据传递给声卡,图像数据传递给显示器。

    管道过滤器是最贴近程序员生活的模式,也是Unix-like系统的基本设计理念之一。作为Linux下的程序员,我们天天都使用这个模式。比如:

    o删除当前目录及子目录下的目标文件。
    find -name /*.o|xargs rm –f
    find是过滤器:它找出所有目标文件,它不需要关心查找文件的目的。
    rm是过滤器:它删除找到目标文件,rm不需要关心文件名是如何得来的。

    o 查看某个进程的栈的大小
    grep stack /proc/2976/maps|sed -e “s/-/ /”|awk ‘{print strtonum(”0x”$2)-strtonum(”0x”$1)}’

    /proc/2976/maps是进程2976内存映射表。内容可能如下:

    00110000-00111000 r-xp 00110000 00:00 0          [vdso]
    00111000-0011b000 r-xp 00000000 08:01 154857 /lib/libnss_files-2.8.so
    0011b000-0011c000 r--p 0000a000 08:01 154857 /lib/libnss_files-2.8.so
    0011c000-0011d000 rw-p 0000b000 08:01 154857 /lib/libnss_files-2.8.so
    00907000-00923000 r-xp 00000000 08:01 157280 /lib/ld-2.8.so
    00923000-00924000 r--p 0001c000 08:01 157280 /lib/ld-2.8.so
    00924000-00925000 rw-p 0001d000 08:01 157280 /lib/ld-2.8.so
    00927000-00a8a000 r-xp 00000000 08:01 157281 /lib/libc-2.8.so
    00a8a000-00a8c000 r--p 00163000 08:01 157281 /lib/libc-2.8.so
    00a8c000-00a8d000 rw-p 00165000 08:01 157281 /lib/libc-2.8.so
    00a8d000-00a90000 rw-p 00a8d000 00:00 0
    00abd000-00ac0000 r-xp 00000000 08:01 157284 /lib/libdl-2.8.so
    00ac0000-00ac1000 r--p 00002000 08:01 157284 /lib/libdl-2.8.so
    00ac1000-00ac2000 rw-p 00003000 08:01 157284 /lib/libdl-2.8.so
    0383f000-03855000 r-xp 00000000 08:01 157307 /lib/libtinfo.so.5.6
    03855000-03858000 rw-p 00015000 08:01 157307 /lib/libtinfo.so.5.6
    08047000-080fa000 r-xp 00000000 08:01 1180910 /bin/bash
    080fa000-080ff000 rw-p 000b3000 08:01 1180910 /bin/bash
    080ff000-08104000 rw-p 080ff000 00:00 0
    088bd000-088ff000 rw-p 088bd000 00:00 0 [heap]
    b7bfb000-b7bfd000 rw-p b7bfb000 00:00 0
    b7bfd000-b7c04000 r--s 00000000 08:01 237138 /usr/lib/gconv/gconv-modules.cache
    b7c04000-b7d1e000 r--p 047d3000 08:01 237437 /usr/lib/locale/locale-archive
    b7d1e000-b7d5e000 r--p 0236e000 08:01 237437 /usr/lib/locale/locale-archive
    b7d5e000-b7f5e000 r--p 00000000 08:01 237437 /usr/lib/locale/locale-archive
    b7f5e000-b7f60000 rw-p b7f5e000 00:00 0
    bfe5e000-bfe73000 rw-p bffeb000 00:00 0 [stack]

    grep是过滤器:它从文件/proc/2976/maps里找到下面这行数据。

    bfe5e000-bfe73000 rw-p bffeb000 00:00 0 [stack]

    sed是过滤器:它把‘-’替换成‘ ’,数据变成下面的内容。
    bfe5e000 bfe73000 rw-p bffeb000 00:00 0 [stack]

    awk是过滤器:它计算0xbfe73000和0x bfe5e000差值,并打印出来。

    下面我们来看看,管道过滤器在程序里的实现方式。这里我们以TinyMail为例,TinyMail是一款针对移动设备定制的邮件客户端软件。它使用camel-lite完成邮件内容解析和传输。Camel-lite对邮件内容的处理基本上基于管道过滤器模式的。

    CamelMimeFilter是过滤器接口,所有过滤器都要实现它要求的接口函数:

    struct _CamelMimeFilterClass {
    CamelObjectClass parent_class;

    void (*filter)(CamelMimeFilter *f,
    char *in, size_t len, size_t prespace,
    char **out, size_t *outlen, size_t *outprespace);
    void (*complete)(CamelMimeFilter *f,
    char *in, size_t len, size_t prespace,
    char **out, size_t *outlen, size_t *outprespace);
    void (*reset)(CamelMimeFilter *f);
    };

    CamelMimeFilterClass是从CamelObjectClass继承过来的。这里的接口定义和我们前面所讲的接口定义有些差别,但原理上都是一样,通过函数指针来抽象具体的功能。这里要求实现三个接口函数:

    filter:过滤器的处理函数。
    complete:过滤器的处理函数。与filter不同的是,调用complete之后不能调其它filter。
    reset:重置当前filter的状态。

    Camel实现了很多Filter,其中CamelMimeFilterBasic实现了邮件基本的编码和解析功能。它的filter函数实现如下:

    static void
    filter(CamelMimeFilter *mf, char *in, size_t len, size_t prespace, char **out, size_t *outlen, size_t *outprespace)
    {
    CamelMimeFilterBasic *f = (CamelMimeFilterBasic *)mf;
    size_t newlen;

    switch(f->type) {
    case CAMEL_MIME_FILTER_BASIC_BASE64_ENC:
    /* wont go to more than 2x size (overly conservative) */
    camel_mime_filter_set_size(mf, len*2+6, FALSE);
    newlen = g_base64_encode_step((const guchar *) in, len, TRUE, mf->outbuf, &f->state, &f->save);
    g_assert(newlen <= len*2+6);
    break;
    case CAMEL_MIME_FILTER_BASIC_QP_ENC:
    /* *4 is overly conservative, but will do */
    camel_mime_filter_set_size(mf, len*4+4, FALSE);
    newlen = camel_quoted_encode_step((unsigned char *) in, len, (unsigned char *) mf->outbuf, &f->state, (gint *) &f->sa
    ve);
    g_assert(newlen <= len*4+4);
    break;
    case CAMEL_MIME_FILTER_BASIC_UU_ENC:
    /* won't go to more than 2 * (x + 2) + 62 */
    camel_mime_filter_set_size (mf, (len + 2) * 2 + 62, FALSE);
    newlen = camel_uuencode_step ((unsigned char *) in, len, (unsigned char *) mf->outbuf, f->uubuf, &f->state, (guint32
    *) &f->save);
    g_assert (newlen <= (len + 2) * 2 + 62);
    break;
    case CAMEL_MIME_FILTER_BASIC_BASE64_DEC:
    /* output can't possibly exceed the input size */
    camel_mime_filter_set_size(mf, len+3, FALSE);
    newlen = g_base64_decode_step(in, len, (guchar *) mf->outbuf, &f->state, (guint *) &f->save);
    g_assert(newlen <= len+3);
    break;
    case CAMEL_MIME_FILTER_BASIC_QP_DEC:
    /* output can't possibly exceed the input size */
    camel_mime_filter_set_size(mf, len + 2, FALSE);
    newlen = camel_quoted_decode_step((unsigned char *) in, len, (unsigned char *) mf->outbuf, &f->state, (gint *) &f->sa
    ve);
    g_assert(newlen <= len + 2);
    break;
    case CAMEL_MIME_FILTER_BASIC_UU_DEC:
    if (!(f->state & CAMEL_UUDECODE_STATE_BEGIN)) {
    register char *inptr, *inend;
    size_t left;

    inptr = in;
    inend = inptr + len;

    while (inptr < inend) {
    left = inend - inptr;
    if (left < 6) {
    if (!strncmp (inptr, "begin ", left))
    camel_mime_filter_backup (mf, inptr, left);
    break;
    } else if (!strncmp (inptr, "begin ", 6)) {
    for (in = inptr; inptr < inend && *inptr != '/n'; inptr++);
    if (inptr < inend) {
    inptr++;
    f->state |= CAMEL_UUDECODE_STATE_BEGIN;
    /* we can start uudecoding... */
    in = inptr;
    len = inend - in;
    } else {
    camel_mime_filter_backup (mf, in, left);
    }
    break;
    }

    /* go to the next line */
    for ( ; inptr < inend && *inptr != '/n'; inptr++);

    if (inptr < inend)
    inptr++;
    }
    }

    if ((f->state & CAMEL_UUDECODE_STATE_BEGIN) && !(f->state & CAMEL_UUDECODE_STATE_END)) {
    /* "begin <mode> <filename>/n" has been found, so we can now start decoding */
    camel_mime_filter_set_size (mf, len + 3, FALSE);
    newlen = camel_uudecode_step ((unsigned char *) in, len, (unsigned char *) mf->outbuf, &f->state, (guint32 *) &f-
    >save);
    } else {
    newlen = 0;
    }
    break;
    default:
    g_warning ("unknown type %u in CamelMimeFilterBasic", f->type);
    goto donothing;
    }

    *out = mf->outbuf;
    *outlen = newlen;
    *outprespace = mf->outpre;

    return;
    donothing:
    *out = in;
    *outlen = len;
    *outprespace = prespace;
    }

    这个过滤器实现了下面三种编码方式的编码和解码:
    1. UU(Unix-to-Unix encoding):
    2. Base64
    3. QP(Quote-Printable)

    Camel还提供了其它一些过滤器,如:
    CamelMimeFilterGZip:压缩和解压
    CamelMimeFilterHTML:去掉HTML Tag。
    CamelMimeFilterCRLF:使用/r/n作为换行符。
    CamelMimeFilterToHTML:加上HTML Tag。
    CamelMimeFilterCharset:字符集转换。

    所有的过滤器由CamelStreamFilter来组合,CamelStreamFilter提供了下面两个函数:

    增加过滤器:
    int camel_stream_filter_add (CamelStreamFilter *stream, CamelMimeFilter *filter);

    移除过滤器:
    void camel_stream_filter_remove (CamelStreamFilter *stream, int id);

    CamelStreamFilter实现了CamelStream接口,这里应用了前面所讲的装饰模式,它不改变CamelStream的接口,但给CamelStream加上了数据转换功能。它的创建函数如下:

    CamelStreamFilter *camel_stream_filter_new_with_stream (CamelStream *stream);

    传入一个CamelStream对象,然后对这个对象进行装饰。在读写数据时,调用相应的Filter,下面是写函数的实现:

    do_read (CamelStream *stream, char *buffer, size_t n)
    {
    CamelStreamFilter *filter = (CamelStreamFilter *)stream;
    struct _CamelStreamFilterPrivate *p = _PRIVATE(filter);
    ssize_t size;
    struct _filter *f;

    p->last_was_read = TRUE;

    g_check(p->realbuffer);

    if (p->filteredlen<=0) {
    size_t presize = READ_PAD;

    size = camel_stream_read(filter->source, p->buffer, READ_SIZE);
    if (size <= 0) {
    /* this is somewhat untested */
    if (camel_stream_eos(filter->source)) {
    f = p->filters;
    p->filtered = p->buffer;
    p->filteredlen = 0;
    while (f) {
    camel_mime_filter_complete(f->filter, p->filtered, p->filteredlen,
    presize, &p->filtered, &p->filteredlen, &presize);
    g_check(p->realbuffer);
    f = f->next;
    }
    size = p->filteredlen;
    p->flushed = TRUE;
    }
    if (size <= 0)
    return size;
    } else {
    f = p->filters;
    p->filtered = p->buffer;
    p->filteredlen = size;
    d(printf ("/n/nOriginal content (%s): '", ((CamelObject *)filter->source)->klass->name));
    d(fwrite(p->filtered, sizeof(char), p->filteredlen, stdout));
    d(printf("'/n"));

    while (f) {
    camel_mime_filter_filter(f->filter, p->filtered, p->filteredlen, presize,
    &p->filtered, &p->filteredlen, &presize);
    g_check(p->realbuffer);

    d(printf ("Filtered content (%s): '", ((CamelObject *)f->filter)->klass->name));
    d(fwrite(p->filtered, sizeof(char), p->filteredlen, stdout));
    d(printf("'/n"));

    f = f->next;
    }
    }
    }

    size = MIN(n, p->filteredlen);
    memcpy(buffer, p->filtered, size);
    p->filteredlen -= size;
    p->filtered += size;

    g_check(p->realbuffer);

    return size;
    }

    这里先调用camel_stream_read读取数据,然后依次调用fitler对数据进行处理,最后把数据返回给调用者。do_write的过程类似,CamelStreamFilter对编码和解码都支持,而使用者不用关心。

    管道过滤器模式应用相当广泛,它不限于文本数据处理,任何以数据处理为中心的系统,都可以用管道过滤器模式作为基本架构。

  • 相关阅读:
    python入门的120个基础练习
    python日志打印模块
    自动化测试总结
    Http_requests
    安装electron-ssr出现的问题
    豆瓣油猴脚本
    ubuntu 16.04 無法進入tty1-6(未解決)
    如何用firefox chrome chromium看只支持IE浏览器的视频 通过wine 安装IE
    python reverse 和reversed
    python 编码问题
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167525.html
Copyright © 2011-2022 走看看