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对编码和解码都支持,而使用者不用关心。

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

  • 相关阅读:
    SQLMAP注入教程-11种常见SQLMAP使用方法详解
    VS2012/2013/2015/Visual Studio 2017 关闭单击文件进行预览的功能
    解决 IIS 反向代理ARR URLREWRITE 设置后,不能跨域跳转 return Redirect 问题
    Spring Data JPA one to one 共享主键关联
    JHipster 问题集中
    Spring Data JPA 定义超类
    Spring Data JPA查询关联数据
    maven命名
    maven仓库
    Jackson读取列表
  • 原文地址:https://www.cnblogs.com/zhangyunlin/p/6167525.html
Copyright © 2011-2022 走看看