zoukankan      html  css  js  c++  java
  • Web之CGIC的介绍与使用20171229

    一、CGIC简介

    1、CGI简介

          CGI(Common Gateway Interface)是外部应用扩展应用程序与WWW服务器交互的一个标准接口。按照CGI标准编写的外部扩展应用程序可以处理客户端浏览器输入的数据,从而完成客户端与服务器的交互操作。而CGI规范就定义了Web服务器如何向扩展应用程序发送消息,在收到扩展应用程序的信息后又如何进行处理等内容。通 过CGI可以提供许多静态的HTML网页无法实现的功能,比如搜索引擎、基于Web的数据库访问等等。

    CGI的主要功能:

    (1)解析数据,校正有缺陷和不一致的浏览器
    (2)透明的接受由GET和POST传递的表单数据
    (3)接受上传完毕的文件和普通的表单数据
    (4)提供设置和恢复cookies的函数
    (5)以一致的方式处理表单字段的换行符
    (6)提供字符串,整数,浮点数,单选,多选 函数来恢复表单数据
    (7)对于数字字段提供边界检查
    (8)加载CGI环境变量到C字符串,这些字符串总是不为空
    (9)在调试环境下,能够回放CGI程序执行时的CGI状态,提供捕获CGI状态的方法,包括文件上传和cookies

    CGIC是一个支持CGI开发的C语言库,用于基于CGI标准规范的www应用程序的创建。CGIC在兼容CGI的服务器环境下是可移植的,并支持Linux,Unix 和Windows等多操作系统。

    CGIC的主站点http://www.boutell.com/cgic/

    2、BOA与CGI工作机制

      HTTP协议是WWW的基础,基于客户/服务器模型,服务器可以为分布在网络中的客户提供服务。HTTP是建立在TCP/IP协议之上的“无连接”协议,每次连接只处理一个请求。在BOA服务器上,运行产着一个守护进程对端口进行监听,等待来自客户的请求。当一个请求到来时,将创建一个子进程为用户的连接服务。根据请求的不同,服务器返回HTML文件或者通过CGI调用外部应用程序,返回处理结果。服务器通过CGI与外部程序和脚本之间进行交互,根据客户端在进行请求时所采取的方法,服务器会收集客户所提供的信息,并将该部分信息发送给指定的CGI扩展程序。CGI扩展程序进行信息处理并将结果返回服务器,然后服务器 对信息进行分析,并将结果发送回客户端。

      外部CGI程序与BOA服务器进行通信、传递有关参数和处理结果是通过环境变量、命令行参数和标准输入来进行的。服务器提供了客户端(浏览器)与CGI扩展程序之间的信息交换的通道。CGI的标准输入是服务器的标准输出,而CGI的标准输出是服务器的标准输入。客户的请求通过服务器的标准输出传送给CGI的标准输入,CGI对信息进行处理后,将结果发送到它的标准输入,然后由服务器将处理结果发送给客户端。

    3、URL简介

      客户端浏览器向服务器发送数据采用编码的形式进行,编码就是URL编码。编码的主要工作是表单域的名字和值的转义,具体的做法为:每一对域和值里的空格都会被替换为一个加号(+)字符,不是字母或数字的字符将被替换为它们的十六进制数字形式,格式为%HH。HH是字符的ASCII十六进制值。<BR>标签将被替换为“%0D%0A”。

      信息是按它们在表单里出现的顺序排列的。数据域的名字和数据域的值通过等号(=)字符连在一起。各对名/值再通过“&”字符连接在一起。经过这些编码处理之后,表单信号就整个成为一个连续的字符流,里面包含着将被送往服务器的全部信息。因为表单输入信息都是经过编码后传递给脚本程序的,所以CGI扩展程序在使用这些参数之前必须对它们进行解码。

    二、CGIC编译配置

    1、下载CGIC源码

    tar -zxvf cgic206.tar.gz

    2、修改Makefile文件

    CC=arm-linux-gcc

    AR=arm-linux-ar

    RANLIB=arm-linux-ranlib

    CFLAGS=-g -Wall -static

    cgictest.cgi: cgictest.o libcgic.a

    $(CC) $(CFLAGS) cgictest.o -o cgictest.cgi ${LIBS}

    capture: capture.o libcgic.a

    $(CC) $(CFLAGS) capture.o -o capture ${LIBS}

    3、编译

        make

        编译得到的文件

        libcgic.a:CGIC库

        capture:调试辅助程序

        cgictest.cgi:测试程序

    4、安装CGIC

        make install

        CGIC安装路径为

        libcgic.a 安装在/usr/local/lib

        cgic.h 安装在/usr/local/include

        CGIC库安装后就可以使用CGIC编程了

    5、CGIC文件的移植

        将capture和cgictest.cgi拷贝到开发板的/var/www/cgi-bin目录

    6、运行cgi程序

        在客户端浏览器运行http://192.168.6.210/cgi-bin/cgictest.cgi

        如果正常显示网页内容,则BOA与CGIC可以正常工作

    三、CGIC移植过程中错误的解决

    1、 html网页可以运行,CGI程序运行报错

    Boa服务器报错:cgi_header: unable to find LFLF

    客户端浏览器报错:502 Bad Gateway The CGI was not CGI/1.1 compliant.

    解决方法:静态编译cgi程序

        arm-linux-gcc -o hello.cgi hello.c -static

    四、CGIC编程

    1、CGIC通信方式

      当有数据从客户端浏览器传到Web服务器后,web服务器会根据传送的类型(基本有二类:GET/POST),将接收到的数据传入 QUERY_STRING或变量中, CGI程序可以通过标准输入,在程序中接收web服务器接收的数据。当要向浏览器发送信息时,只要向Web服务器发送特定的文件头信息,即可通过标准输出将信息发往Web服务器, Web服务器处理完由CGI程序发来的信息后就会将信息发送给浏览器。

    2、接收数据

      用GET方式接收到的数据保存在Web服务器的QUERY_STRING 变量里,而通过POST方式接收到的数据是保存在Web服务器变量里。两种数据接收方式的区别是:以GET方式接收的数据是有长度限制,而用POST方式接收的数据是没有长度限制的;以GET方式发送数据,可以通过URL的形式来发送,但POST方式发送的数据必须要通过Form才到发送。 

    3、CGIC变量

      这部分提供程序员可以利用的cgic库提供的全局变量的使用手册。为了cgic库的CGI调试功能的可移植性,应该使用这些变量代替stdin,stdout,getevn()。
    这些变量的大部分与CGI环境变量相同。最大的不同是cgic环境字符串绝不会是空指针。如果没有内容,用空字符串代替。

    (1)char *cgiServerSoftware

    指向服务器软件名,如果不知道,指向一个空字符串。

    (2)char *cgiServerName

    指向服务器名字,如果不知道,指向一个空字符串。

    (3)char *cgiGatewayInterface

    指向网关接口名字(通常是CGI/1.1),如果不知道,指向一个空字符串。

    (4)char *cgiServerProtocol

    指向使用的协议(通常是HTTP/1.0),如果不知道,指向一个空字符串。

    (5)char *cgiServerPort

    指向服务器监听HTTP连接的端口号(通常是80),如果不知道,指向一个空字符串。

    (6)char *cgiRequestMethod

    指向请求使用的方法(通常是GET 或 POST),如果不知道,指向一个空字符串(不该发生)。

    (7)char *cgiPathInfo

    大部分的网页服务器能够识别出请求URL中在CGI程序名前的附加路径信息,并且传递这些信息到CGI程序中。cgiPathInfo指向附加路径信息。

    (8)char *cgiPathTranslated

    大部分的网页服务器能够识别出请求URL中在CGI程序名前的附加路径信息,并且传递这些信息到CGI程序中。cgiPathTranslated指向附加路劲信息,这些信息被本地服务器转换成文件系统路径。

    (9)char *cgiScriptName

    指向被调用的脚本程序名字。

    (10)char *cgiQueryString

    包含用户使用GET方法或者一个<ISINDEX>标签提交的查询信息。没有必要直接去解析这个信息,除非使用的是<ISINDEX>标签。
    通常,cgic库会自动的解析,只需调用cgiForm家族的函数来获取相关表单输入字段的值就可以了。

    (11)char *cgiRemoteHost

    指向浏览器的完全解析主机名,如果不知道,指向一个空字符串。

    (12)char *cgiRemoteAddr

    指向浏览器的点分十进制ip地址,如果不知道,指向一个空字符串。

    (13)char *cgiAuthType

    如果有的话,指向请求使用的身份验证类型,如果不知道,指向一个空字符串。

    (14)char *cgiRemoteUser

    指向已经通过身份验证的用户名。如果没有经过身份验证,为空字符串。确切的信息取决于使用的身份验证类型。

    (15)char *cgiRemoteIdent

    指向用户自愿给出的使用用户鉴别协议的用户名;如果不知道,为空字符串。这个信息不是安全的。鉴别进程可能被用户安装在不安全的系统,例如windows。

    (16)char *cgiContentType

    指向用户提交的信息的MIME类型;如果没有消息被提交,为空字符串。如果这个字符串是application/x-www-form-urlencoded 或者multipart/form-data,cgic库会自动的检查提交的表单数据。如果这个字符串是其他的非空字符串,代表被提交的数据是其他类型。
    目前这很少见,因为大部分浏览器仅仅提交表单和上传文件,这些cgic可以直接解析。

    (17)char *cgiCookie

    指向网页浏览器提交的原生cookie数据(浏览器端持续存储)。程序员应该使用cgiCookies,cgiCookieString,和cgiCookieInteger来检测数据。

    (18)char *cgiAccept

    指向浏览器可接受的以空格分隔的MIME内容类型列表,或者是一个空字符串。不幸的是,现在大多数的浏览器不在表单提供这个变量。

    (19)char *cgiUserAgent

    指向使用的浏览器的名字,如果这些信息不可用,指向一个空字符串。

    (20)char *cgiReferrer

    指向用户前一个访问页面的URL。这经常是表单的URL,把用户带到你的程序。报告这个信息完全取决于浏览器,它可能选择不这样做,
    或者选择不诚实的做。然而,这个变量通常是准确的。经常使用的另外一个拼写cgiReferer作为一个宏提供。

    (21)int cgiContentLength

    接收的表单的或者查询数据的字节数。注意,如果提交的是一个表单或者查询信息,cigc库会直接从cgiIn或者cgiQueryStirng读取并解析这些信息。
    程序员不该这样做,并且这种情况下cgiIn会指向文件的末尾。

    (22)FILE *cgiOut

    指向CGI输出。cgiHeader函数,例如cgiHeaderContentType,要首先被调用来输出mime头。接着,HTML页面,GIF图像或者其他的网页文档被输出到cgiOut,
    使用标准C I/O函数,如fprintf和fwrite。cgiOut通常等价于stdout。然而,考虑到对未来cgic的版本在特殊环境下的兼容性,建议使用cgiOut。

    (23)FILE *cgiIn

     CGI输入。

    4、CIGC库主要函数

      用一般 ANSI C或C++编译器就可以编译CGIC程序 , 与C程序不同的是,用CGIC写的源码其主函数是cgiMain(), 而不是通常的main。 CGIC的函数库会自动把cgiMain连接到相应的main上。 

    CGIC库主要函数说明:

    1cgiFormResultType cgiFormString( char *name, char *result, int max) 

        cgiFormString试图检索发送给指定字段的字符串。文本会被拷贝到指定的result buffer中,最多但不超过max-1个字节。
        null字符标记字符串的结束。不管浏览器提交的换行格式是什么,cgiFormString总是把每一个换行符都编码成一个单一的换行符(ascii 十进制 10)。最终的字符串总是可能比调用cgiFormStringSpaceNeeded的稍微短一点,但是不可能更长。
        成功取出字符串,返回cgiFormSuccess;
        获取字符串但是为了适应buffer而被截断,返回cgiFormTruncated;
        取回的字符串是空字符串,返回cgiFormEmpty;
        没有相应的输入字段被提交,返回cgiFormNotFound,并且一个空字符串被拷贝到result buffer。

    2cgiFormResultType cgiFormStringNoNewlines( char *name, char *result, int max)

        这个函数几乎和cgiFormString()一样,除了出现在输入中的任何回车或者换行符将被去掉。建议使用这个函数在单行的文本输入字段,因为有一些浏览器会提交回车和换行符当他们不该这样的时候。

    3cgiFormResultType cgiFormStringSpaceNeeded( char *name, int *length) 

     cgiFormStringSpaceNeeded()被用来确定输入文本缓冲区需要多少长度来接收指定输入字段的内容。这对于不管任意输入长度都分配足够内存的程序员来说是有用的。由cgiFormString()调用获取的字符串的实际长度可能稍微更短一点,但是不会更长。
        如果成功,函数设置*length,单位字节,包含null结束符,返回cgiFormSuccess。
        如果指定的字段没有提交,该函数设置*length的值为1,返回cgiFormNotFound。1被用来确保空字符串的空间(null结束符),
        如果cgiFormString被调用,则忽略这个返回值。

    4cgiFormResultType cgiFormStringMultiple( char *name, char ***ptrToStringArray)

     cgiFormStringMultiple在表单中有好几个输入元素具有相同的名字的特例情况非常有用,不管因为什么,程序员不想使用下面提供的复选框,单选按钮和选项菜单等函数。这偶尔是需要的,如果程序员事先不知道在一个表单中什么值可能出现在一个多选列表或者复选框组。结果是一个字符串数组,以null标记结尾。这个数组由cgic库分配空间。
        重要:完成这个数组的工作后,必须调用cgiStringArrayFree()来释放数组。
        如果这个名字至少出现一次,返回cgiFormSuccess。
        如果没有出现,返回cgiFormNotFound。
        如果没有足够的空间分配数组保存返回值,返回cgiFormMemory。
        除了最后一种情况,ptrToStringArray指向一个以null指针结尾的合法数组。在内存不足的情况下,ptrToStringArray被设置成null指针。

    5cgiFormResultType cgiFormEntries( char ***ptrToStringArray)

     当程序员事先不知道所有相关的表单字段名称的时候cgiFormEntries是有用的。结果被设置成一个指向字符串的数组,最后一个值是null。这个数组由cgic库分配。
     重要:完成这个数组的工作后,必须调用cgiStringArrayFree()来释放数组。这个函数返回cgiFormSuccess,除非发生了内存不足的错误事件。

     在成功的情况下,ptrToStringArray设置成指向一个字符串的数组,null指针结尾。

     在内存不足的情况ptrtoStringArray设置成null指针,且返回cgiFormOutOfMemory。

    6void cgiStringArrayFree(char **stringArray)

     cgiStringArrayFree()用来释放由cgiFormStringMultiple(), cgiFormEntries(), or cgiFormCookies()函数创建的字符串数组。

    7cgiFormResultType cgiFormInteger( char *name, int *result, int defaultV) 

     cgiFormInterger()尝试获取发送给指定输入字段的整型值。*result设置为被提交的值。
     成功获取值,返回cgiFormSuccess;
     如果值是空字符串,返回cgiFormEmpty;
     如果提交的不是整型值,返回cgiFormBadType;
     如果没有该输入字段,返回cgiFormNotFound。
     在后三种情况,*result设置为指定的默认值。

    8cgiFormResultType cgiFormIntegerBounded( char *name, int *result, int min, int max, int defaultV) 

     cgiFormIntegerBounded()尝试获取发送给指定输入字段的整型值,并且限定结果在指定的区间内。*result被设置为提交的值。
        如果成功获取到值,返回cgiFormSuccess;
        如果值越界且被相应的调整,返回cgiFormConstrained;
        如果提交的是一个空字符串,返回cgiFormEmpty;
        如果提交的不是整型值,返回cgiFormBadType;
        如果没有该输入字段,返回cgiFormNotFound。
        在后三种情况,*result设置为指定的默认值。

    9cgiFormResultType cgiFormDouble( char *name, double *result, double defaultV)

     cgiFormDouble尝试获取发送给指定输入字段的浮点值,*result设置为被提交的值。
     成功获取值,返回cgiFormSuccess;
     如果值是空字符串,返回cgiFormEmpty;
     如果提交的不是数字,返回cgiFormBadType;
     如果没有该输入字段,返回cgiFormNotFound。
     在后三种情况,*result设置为指定的默认值。

    10cgiFormResultType cgiFormDoubleBounded( char *name, double *result, double min, double max, double defaultV) 

     cgiFormDoubleBounded()尝试获取发送给指定输入字段的浮点值,并且限定结果在指定的区间内。*result被设置为提交的值。
        如果成功获取到值,返回cgiFormSuccess;
        如果值越界且被相应的调整,返回cgiFormConstrained;
        如果提交的是一个空字符串,返回cgiFormEmpty;
        如果提交的不是数字,返回cgiFormBadType;
        如果没有该输入字段,返回cgiFormNotFound。
        在后三种情况,*result设置为指定的默认值。

    11cgiFormResultType cgiFormSelectSingle( char *name, char **choicesText, int choicesTotal, int *result, int defaultV) 

     cgiFormSelectSingle()获取与<SELECT>元素相关的选项号码,这个元素不允许多选。name用来标识<SELECT>元素的NAME属性,choicesText指向标识每个选项的字符串数组;choicesTotal表示选项的个数。如果有被选中的选项,*result设置为实际选中的选项
     在选项数组中的位置。如果没有选择被提交或者是一个非法选项,那么就设置为默认值。
        成功获取值,返回cgiFormSuccess。
        如果没有选项被提交,返回cgiFormNotFound。
        如果提交的选项在choicesText中没有匹配的选项,返回cgiFormNoSuchChoice。

    12cgiFormResultType cgiFormSelectMultiple( char *name, char **choicesText, int choicesTotal, int *result, int *invalid)

     cgiFormSelectMultiple()获取与<SELECT>元素相关的选项号码,这个元素允许多选。name用来标识<SELECT>元素的NAME属性,choicesText指向标识每个选项的字符串数组,choicesTotal表示选项的个数。result是个数和choicesText数组个数相同的整数数组。对于每一个选项,选中设为1,否则为0.
        如果至少有一个合法选项被成功获取,返回cgiFormSuccess;
        如果没有合法的选项被提交,返回cgiFormNotFound。
        *invalid设置为被提交的非法选项的个数,这个值应该为0,除非表单和选项数组不一致。

    13cgiFormResultType cgiFormSubmitClicked( char *name) 

     通常需要知道一个指定的提交按钮是否被点击,不同的名字属性区分不同的提交按钮。cgiFormSubmitClicked函数可替换为cgiFormCheckboxSingle函数,它适合用来测试一个特定的提交按钮是否被使用。

    14cgiFormResultType cgiFormCheckboxSingle( char *name)

     cgiFormCheckboxSingle用来确认一个指定名字的复选框是否被选中。
     如果这个按钮被选中,返回cgiFormSuccess;
     如果没有被选中,返回cgiFormNotFound。
     cgiFormCheckboxSingle用于具有唯一名字的单复选框。下面的函数用于解决具有相同名字的多复选框和单选按钮。

    15cgiFormResultType cgiFormCheckboxMultiple( char *name, char **valuesText, int valuesTotal, int *result, int *invalid) 

     cgiFormCheckboxMultiple()检测在一个具有相同名字的复选框组里面哪一个被选中。这跟单选按钮是不同的。valuesText指向标识每个复选框的VALUE属性的字符串数组。ValuesTotal 是复选框的总数。result是个数和valuessText数组个数相同的整数数组。对每一个选项,选中设为1,否则为0.
        如果至少有一个合法复选框被选中,返回cgiFormSuccess。
        如果没有合法的复选框被选中,返回cgiFormNotFound。
        *invalid设置为被提交的非法选项的个数,这个值应该为0,除非表单和valuesText数组不一致。

    16cgiFormResultType cgiFormRadio( char *name, char **valuesText, int valuesTotal, int *result, int defaultV)

     如果有做选择的话,cgiFormRadio用于检测,带有相同名字的radio boxes组中的哪一个被选中。valuesText指向标识每个radio box的VALUE属性的字符串数组,ValuesTotal 是radio boxes组中的radio box个数。
        如果有做选择的话,*result设为valuesText数组中被选中的位置。
        如果没有radio box被选中或者是一个非法的选项,那么设为默认值。
        如果组里面有一个radio box被选中,返回cgiFormSuccess。
        如果没有box被选中,返回cgiFormNotFound。
        如果被提交的radio box不匹配valuesText中的任何一个,返回cgiFormNoSuchChoice。

    17cgiFormResultType cgiFormFileName( char *name, char *fileName, int max) 

     cgiFormFileName尝试根据指定的带有file类型的表单输入字段,取出用户上传文件的名字。
        如果在服务端直接使用,永远不要相信这个文件名是合理和安全的。文本会被拷贝到fileName buffer中,最多但不超过max-1个字节,null结尾。
        成功取出字符串并且不为空,返回cgiFormSuccess;
        字符串被成功取回但为空,表示没有上传文件,返回cgiFormNoFileName。
        获取字符串但是为了适应buffer而被截断,返回cgiFormTruncated;
        如果没有相应的输入字段被提交,返回cgiFormNotFound,并且一个空字符串被拷贝到结果。

    18cgiFormResultType cgiFormFileSize( char *name, int *sizeP) 

     cgiFormFileSize尝试根据name参数指定的具有file类型的输入字段,获取浏览器上传文件的大小,单位字节。
        如果成功,大小存在*sizeP,并且函数返回cgiFormSuccess。
        如果这个表单字段不存在,函数返回cgiFormNotFound。
        如果表单字段存在但没有文件上传,返回cgiFormNotAFile。

    19cgiFormResultType cgiFormFileContentType( char *name, char *contentType, int max) 

     cgiFormFileContentType尝试根据name参数指定的具有file类型的输入字段,获取浏览器上传文件的内容类型。不保证这个
    内容类型是精确的。类型字符串拷贝到contentType buffer中,最多但不超过max-1个字节,null结尾。
     成功取出字符串并且不为空,返回cgiFormSuccess;
     字符串被成功取回但为空,表示没有上传文件,或者浏览器不认识内容类型,返回cgiFormNoContentType。
     获取字符串但是为了适应buffer而被截断,返回cgiFormTruncated;
     如果没有相应的输入字段被提交,返回cgiFormNotFound,并且一个空字符串被拷贝到结果。

    20cgiFormResultType cgiFormFileOpen( char *name, cgiFilePtr *cfpp)

     cgiFormFileOpen尝试根据指定的带有file类型的输入字段打开实际上传的文件。
     如果成功,这个函数设置*cfpp为一个有效地cgiFilePtr对象,返回cgiFormSuccess;
     如果失败,设置*cfpp为一个null指针,返回cgiFormNotFound,cgiFormNotAFile, cgiFormMemory or cgiFormIO中的合适值。

    21cgiFormResultType cgiFormFileRead( cgiFilePtr cfp, char *buffer, int bufferSize, int *gotP)

      cgiFormFileRead尝试从之前由cgiFormFileOpen产生的cgiFilePtr对象读取最多bufferSize字节数据。如果成功的读取到了数据,
      拷贝到buffer,实际读取字节数保存到*gotP。
      如果有任何数据被成功读取,返回cgiFormSuccess。
      到达文件尾,函数返回cgiFormEOF。
      如果发生I/O错误事件,返回cgiFormIO。
      如果cfg是空指针,函数返回cgiFormOpenFailed。

    22cgiFormResultType cgiFormFileClose( cgiFilePtr cfp)

      cgiFormFileClose关闭一个cgiFilePtr对象,释放内存和其他系统资源。
      这个函数返回cgiFormSuccess除非cfg是空指针。
      在cfg为null的情况下,返回cgiFormOpenFailed。

    23void cgiHeaderLocation(char *redirectUrl)

      如果程序员想要重定向用户到不同的URL,可以调用cgiHeaderLocation(),在这种情况,没有额外的输出。
      如果你想设置cookies,你必须在调用cgiHeaderLocation之前调用cgiHeaderCookieSetString()和 
      cgiHeaderCookieSetInteger()

    24void cgiHeaderStatus(int status, char *statusMessage)

      如果程序员想要输出一个HTTP错误状态码而不是一个文档,应该调用cgiHeaderStatus()。第一个参数是状态码,第二个参数是
      状态信息用于显示给用户。
      如果你想设置cookies,你必须在调用cgiHeaderStatus之前调用cgiHeaderCookieSetString()和 
      cgiHeaderCookieSetInteger()

    25void cgiHeaderContentType(char *mimeType)

      如果程序员希望输出一个新的文档作为用户请求的响应,需要调用cgiHeaderContentType()。这是普通情况。
      这单独的参数是响应的MIME文档类型。典型值是“text/html”对应于HTML文档,“text/plain”对应于普通的ASCII文本没有
      HTML标签,“image/gif” 对应于GIF图像,“audio/basic”对应于音频格式。
      如果你想设置cookies,你必须在调用cgiHeaderContentType()之前调用cgiHeaderCookieSetString()和 
      cgiHeaderCookieSetInteger()

    26void cgiHeaderCookieSetString(char *name, char *value, int secondsToLive, char *path, char *domain)

      如果程序员希望存储一些信息在用户的客户端,以便于后续访问相关网站时这些信息再次传回服务器,就应该调用
      cgiHeaderCookieSetString()。第一个参数是cookie的名字,为了在所有的浏览器都有好的结果,使用一个没有空格
      和特殊标点符号的短名字。再次强调,为了最好的结果,使用一个短字符串。建议cookies用来存储一个唯一的标识信息,
      这样在服务器端可以在数据库中查询更加详细的信息。试图在浏览器保存一些复杂的信息更可能失败。第三个参数是
      cookie在浏览器端的保存时间,单位秒。86400是一天,365*86400 大概是一年。第四个参数是这个网站的部分URL,在
      这里面cookie才是有意义的。如果想要每次访问整个网站,cookie都被发送回服务器,设置这个参数为/。最后的参数
      是网站的名字或者是整个域,对于它们来说,cookie应该被提交。如果你选择对于整个域,cookie都要被传回,这个参数
      必须以一个dot(.)开始,例如.boutell.com。cgic 变量cgiServerName对于第四和第五个参数是很方便的值。参考
      cgiHeaderCookieSetInteger, cgiCookieString, cgiCookieInteger and cgiCookies.

    27void cgiHeaderCookieSetInteger(char *name, int value, int secondsToLive, char *path, char *domain)

      cgiHeaderCookieSetInteger和cgiHeaderCookieSetString是一样的,除了值是一个整数而不是字符串。参考
      cgiHeaderCookieSetString获取更完整的信息。

    28cgiFormResultType cgiCookieString( char *name, char *result, int max)

      cgiCookieString尝试获取发送给指定cookie的值(浏览器端持续存储的),该文本被拷贝到result buffer。最多但不
      多于max-1个字节,null结尾。
      成功获取到字符串值,返回cgiFormSuccess。
      成功获取到值,但是为了适应buffer被截断,返回cgiFormTruncated;
      字符串被取出但为空,返回cgiFormEmpty。
      如果这样的cookie没有提交,返回cgiFromNotFound。在最后一种情况,一个空字符串拷贝到result。

    29cgiFormResultType cgiCookieInteger( char *name, int *result, int defaultV)

      cgiCookieInteger()尝试获取发送给指定cookie的整型值(浏览器端持续存储的),*result设为被提交的值。
      成功获取值,返回cgiFormSuccess;
      如果值是空字符串,返回cgiFormEmpty;
      如果提交的不是整型值,返回cgiFormBadType;
      如果没有该输入字段,返回cgiFormNotFound。
      在后三种情况,*result设置为指定的默认值。

    30cgiFormResultType cgiCookies( char *name, char ***ptrToStringArray) 

      当程序员事先不知道所有相关的cookies的时候,cgiCookies是有用的(浏览器端持续存在的字符串)。
      结果指向的值是一个字符串数组的指针,最后一个值是null指针。这个数组由cgic库分配。
      重要:当完成了该数组的工作后,你必须调用cgiStringArrayFree()释放该数组指针指向的内存。
      函数返回cgiFormSuccess,除非发生了内存不足的错误事件。
      如果成功,ptrToStringArray设为指向一个合法字符串数组的指针,最后一个是null。
      在out-of-memory的情况下,ptrToStringArray设为null pointer,且返回cgiFormOutOfMemory.

    31cgiFormResultType cgiHtmlEscape(char *s)

      cgiHtmlEscape输出一个null结尾的字符串到cgiOut,转换任何 ‘<, &, >‘ 为正确的符号(&lt,&gt,&amp),以便于他们不会妨碍HTML标记。
      返回cgiFormSuccess 或者如果发生了I/O错误事件,返回cgiFormIO。

    32cgiFormResultType cgiHtmlEscapeData(char *data, int len)

      cgiHtmlEscapeData和cgiHtmlEscape是一样的,除了数据不是null结尾的。这个版本的函数输出len长度字节数。

    33cgiFormResultType cgiValueEscape(char *s)

      cgiValueEscape()输出一个null结尾的字符串到cgiOut,转换 引号(") 字符到正确的符号以便于不妨碍HTML属性值的
      引用标记。当输出一个字符串作为一个输入标签的value属性的一部分的时候,或者一个link和表单标签的href属性的时候
      它是有用的,函数返回cgiFormSuccess,或者cgiFormIO,如果发生I/O错误事件。

    34cgiFormResultType cgiValueEscapeData(char *data, int len)

      cgiValueEscapeData和cgiValueEscape是一样的,除了数据不是null结尾的。这个版本的函数输出len长度字节数。

    35cgiEnvironmentResultType cgiWriteEnvironment(char *filename)

      cgiWriteEnvironment()被用来写所有的CGI 环境变量包括表单数据,到指定的输出文件。然后cgiReadEnvironment()
      用来从指定的输入文件还原环境变量用于调试。当然,只有你使用cgic 拷贝CGI环境变量并且使用cgiIn和cgiOut而不是
      stdin和stdout,这些才会如你所期望的那样运行。这些函数用来捕获服务器运行期间真实的CGI状态,然后在调试环境下创建他们。
      如果成功的话这两个函数都返回cgiEnvironmentSuccess;
      如果发生IO错误返回cgiEnvironmentIO;
      如果发生了内存不足的错误返回cgiEnvironmentMemory。

    36cgiEnvironmentResultType cgiReadEnvironment(char *filename)

      参考cgiWriteEnvironment。

    37int cgiMain()

      程序员必须实现这个函数,这个程序完成一个唯一的任务并且由cgic库中的真正的main()函数调用。cgiMain的返回值就是
    程序的返回值。在这个函数里面会调用很多cgiForm函数。参考”如何写一个cgic应用程序“查看细节。

    5、CGIC返回码

    1cgiFormSuccess

    表示函数成功执行了最少一个动作(或者在适用的情况下表示获取到至少一个值)

    2cgiFormTruncated

    表示截断了一个取出的字符串,以避免缓冲区越界。

    3cgiFormBadType

    表示用户提交的数字型值实际上不是合法值。

    4cgiFormEmpty

    表示一个取出的字段值没有数据。

    5cgiFormNotFound

    表示一个指定的字段没有被提交

    6cgiFormConstrained

    表示一个数字型值超过了指定的区间,被强制转换成上边界或者下边界。

    7cgiFormNoSuchChoice

    表示用户提交的一个单选字段(如单选按钮组)的值不是可接受的值,这通常表示在表单和程序之间有矛盾。

    8cgiFormEOF

    表示cgiFormFileRead函数调用的时候,cgiFilePtr对象已经指向上传文件数据的末尾。

    9cgiFormIO

    当cgiFormFileRead读取上传文件数据遇到一个I/O错误时返回这个错误。

    10cgiFormNotAFile

    试图使用一个文件相关的函数操作一个非上传文件表单字段时,返回这个错误作为响应。

    11cgiFormNoContentType

    试图获取上传文件的内容类型,浏览器没有指定内容类型时,返回这个错误作为响应。

    12cgiFormNoFileName

    试图获取上传文件的名字,浏览器没有指定名字时,返回这个错误作为响应

    13cgiFormOpenFailed

    试图从一个空的cgiFilePtr对象读数据,通常是程序员检测cgiFormFileOpen的调用失败。

    14cgiEnvironmentMemory

    表示试图从/向一个捕获文件读/写CGI环境变量时,发生了内存不足的错误。

    15cgiEnvironmentSuccess

    表示试图从/向一个捕获文件读/写CGI环境变量成功。

    16cgiEnvironmentIO

    表示试图从/向一个捕获文件读/写CGI环境变量时,发生了I/O的错误。

    17cgiEnvironmentWrongVersion

    表示试图读取由2.0以前的CGIC版本保存的用于调试的CGI环境变量。(版本不对)

    6、CGIC环境变量

    REQUEST_METHOD    请求类型,如“GET”或“POST”

    CONTENT_TYPE    被发送数据的类型

    CONTENT_LENGTH    客户端向标准输入设备发送的数据长度,单位为字节

    QUERY_STRING    查询参数,如“id=10010&sn=liigo”

    SCRIPT_NAMECGI    脚本程序名称

    PATH_INFOCGI    脚本程序附加路径

    PATH_TRANSLATEDPATH_INFO    对应的绝对路径

    REMOTE_ADDR    发送此次请求的主机IP

    REMOTE_HOST    发送此次请求的主机名

    REMOTE_USER    已被验证合法的用户名

    REMOTE_IDENTWEB    服务器的登录用户名

    AUTH_TYPE    验证类型

    GATEWAY_INTERFACE    服务器遵守的CGI版本,如:CGI/1.1

    SERVER_NAME    服务器主机名、域名或IP

    SERVER_PORT    服务器端口号

    SERVER_PROTOCOL    服务器协议,如:HTTP/1.1

    DOCUMENT_ROOT    文档根目录

    SERVER_SOFTWARE    服务器软件的描述文本

    HTTP_ACCEPT    客户端可以接收的MIME类型,以逗号分隔

    HTTP_USER_AGENT    发送此次请求的web浏览器

    HTTP_REFERER    调用此脚本程序的文档

    HTTP_COOKIE    获取COOKIE键值对,多项之间以分号分隔,如:key1=value1;key2=value2

    7、输出标头

    cgiHeaderContentType,在任何向浏览器输出之前被调用 ,否则将出错或浏览器不能识别。

    cgiHeaderContentType()函数被调用来标识输出文档的MIME类型,这个例子中是”text/html“。其他一些普通的MIME类是”image/gif“,”image/jpeg“ 和 ”audio/wav“。

    注意,cgiHeaderStatus()或者cgiHeaderLocation()被调用来输出一个错误代码或者重定向到一个不同的URL。在程序的单次执行中,只能有一个cgiHeader函数可以被调用。

    重要:一个cgiHeader函数,通常是cgiHeaderContentType(),必须在输出任何其他应答之前调用。否则,结果将是一个不合法的文档,浏览器的行为不可预测。你当然也可以输出你自己的 ContentType 和其他的标题信息到cgiOut如果你喜欢。cgiHeader函数只是提供了一种便利。

    8、处理输入文本

    void Name() { 
    char name[81]; 
    cgiFormStringNoNewlines("name", name, 81); 
    fprintf(cgiOut, "Name: %s<BR> ", name); 
    } //获取并显示由用户输入的 name

    cgitest中Name()这个函数的目的是取回并且显示用户输入的名字。因为程序员决定名字被允许的最大长度是80个字符,一个81字符缓冲区被声明(接收最后的null字符)。接着,cgiFormStringNoNewlines()函数被调用来取出名字并且确保回车不出现在这个名字中(忽略一些网页浏览器的错误行为)。

    第一个参数是表格中的输入字段的名称,第二个参数是数据存储的buffer,第三个参数是buffer的长度。cgic绝不会超出buffer大小,并且总是会提供一个null结尾的字符串作为响应。

    如果buffer太小,字符串被截断。如果这个不可接受的,可以用cgiFormStringSapceNeeded()函数来检测所需空间量。cgiFormStringNoNewlines()函数的返回值同样可以说明截断是否出现。参考详细的cgiFormStringNoNewlines()描述。

    9、处理单一复选框

    void Hungry() 

        if (cgiFormCheckboxSingle("hungry") == cgiFormSuccess) 

        { 

            fprintf(cgiOut, "I'm Hungry!<BR> ");  

        } 

        else 

        { 

            fprintf(cgiOut, "I'm Not Hungry!<BR> ");  

        } 

    }

    cgiFormCheckboxSingle接checkbox名字的属性值,如果存在就返回cgiFormSuccess,否则返回cgiFormNotFound。如果是多项checkboxes,就用cgiFormCheckboxMultiple和cgiFormStringMultiple函数。

    这个函数使用了cgiFormCheckboxSingle()函数,它检查是否一个单复选框被勾选。cgiFormCheckboxSingle()接受复选框的名字属性作为它唯一的参数,选中返回cgiFormSuccess,否则返回cgiFormNotFound。如果多个复选框带有相同的名字,考
    虑使用cgiFormCheckboxMultiple(),cgiFormStringMultiple()函数。

    10、处理数字输入

    void Temperature()


        double temperature; 
        cgiFormDoubleBounded("temperature", &temperature, 80.0, 120.0, 98.6); 
        fprintf(cgiOut, "My temperature is %f.<BR> ", temperature); 

    cgiFormDoubleBounded 返回的值被检查确信用户输入的资料在规定范围内, 而不是其他无效的数据。

    Temperature()函数,它取回一个温度的度数(一个浮点数),并且确保位于一个特定的边界内。cgiFormDoubleBounded()函数用来取出温度,第一个参数是表格中温度字段名,第二个参数指向接受结果的变量地址,接下来的两个参数是下限和上限,最后一个是默认值,当用户没有提交这个值时返回默认值。

    这个函数总是返回一个区间内的合理值,值如果超出下限或者上限,则被限制在边界值。然而,cgiFormDoubleBounded函数的返回值可以确定用户的输入是否在区间,是否为空,等等。参考cgiFormDoubleBounded()函数的详细描述。如果区间检查没有必要,考虑使用cgiFormDouble()代替。
    注意,对于整数输入,可用函数cgiFormInteger和cgiFormIntegerBounded ,他们的行为和上面的浮点数函数类似。

    11、处理单一选择输入

    char *colors[] = { 
    "Red", 
    "Green", 
    "Blue" 
    }; 
    void Color()


        int colorChoice; 
        cgiFormSelectSingle("colors", colors, 3, &colorChoice, 0); 
        fprintf(cgiOut, "I am: %s<BR> ", colors[colorChoice]); 

      HTML的<SELECT>标签用来提供几个选项。单选按钮和复选框用于选择的数量相对较小的时候。Color()。这个函数测试用户在表单中的<select>列表中选择了哪个颜色。先声明一个颜色数组,然后调用cigFormSelectSingle ()函数测试哪个选项被选中。第一个参数表示表单中字段名,第二个参数指向颜色数组,第三个参数表示数组元素个数,第四个参数指向一个下标变量,最后一个参数表示下标默认值,用于浏览器没有提交选择时。
      cigFormSelectSingle()总是标识一个合理的选项值。然而,如果程序员希望确认实际被提交的值是一个合法的值,等等,可以咨询cgiFormSelectSingle()的返回值。查看完整的cgiFormSelectSingle()函数的描述获取更多信息。
      注意单选按钮组合<SELECT>列表都可以被这个函数处理,如果你处理单选按钮组,你可能更喜欢调用cgiFormRadio(),这个函数是一样的。
      如果我在运行期间不知道可接受的选项呢?
    如果可接受选项直到运行时还不知道,可以简单的从磁盘加载选项。但是如果可接受的选项不固定(考虑一个国家名字列表新名字可能在任何时候加入表单并且直接改变代码或者分离列表是不方便的),简单的调用cgiFormStringNoNewlines()代替直接取出字符串。记住,如果你这样做,验证响应以确认它是安全的和合法的将会变成你的程序要解决的一个问题。
    cgiFormSelectSingle()函数的优点是不合法的响应不会返回。

    12、处理多项选择的输入

    char *votes[] = { "A", "B", "C", "D" }; 

    void NonExButtons() 

        int voteChoices[4]; 

        int i; int result; 

        int invalid; 

        char **responses; 

        fprintf(cgiOut, "Votes (method 1):<BR> "); 

        result = cgiFormCheckboxMultiple("vote", votes, 4, voteChoices, &invalid); 

        if (result == cgiFormNotFound) 

        { 

            fprintf(cgiOut, "I hate them all!<p> "); 

        } 

        else 

        { 

            fprintf(cgiOut, "My preferred candidates are: "); 

            fprintf(cgiOut, "<ul> "); 

            for (i=0; (i < 4); i++) 

            { 

                if (voteChoices[i]) 

                { 

                    fprintf(cgiOut, "<li>%s ", votes[i]); 

                } 

            } 

            fprintf(cgiOut, "</ul> "); 

        }

    }

    看看NonExButtons()函数前半部分:

     这个函数调用cgiFormCheckboxMultiple()函数来识别一个或多个被选中的带有相同名字的复选按钮。这个函数与cgiFormSelectMultiple()有相同的功能。也就是说,处理<SELECT>标签的MULTIPLE属性类似于处理带有相同名字的复选按钮选项组。
        cgiFormCheckboxMultiple()函数的第一个参数是复选框选项组字段的名称,第二个参数指向一个合法值得数组,这些会对应到复选框的VALUE属性(或者<SELECT>列表中的OPTION标签),第三个参数是合法值数组中值的个数。第四个参数指向一个具有与合法值数组相同个数的整型数组,对于每一个值,如果复选框被选中,就设置成true,否则false。最后一个参数指向一个整型值,这个被设置成提交的非法响应的个数。(不在合法响应值数组内)如果对这个值不感兴趣,那么最后一个参数是null指针。
        注意cgiFormCheckboxMultiple()的返回值可以用来检测所有选项都被选中。查看该函数的其他返回值完整描述请看。。。
        如果我在运行期间不知道可接受的选项呢?
    如果可接受选项直到运行时还不知道,可以简单的从磁盘加载选项。但是如果可接受的选项不固定(考虑一个冰淇淋口味列表,新名字可能在任何时候加入表单并且直接改变代码或者分离列表是不方便的),需要一个动态的处理方法。
        看看NonExButtons()函数的后半部分:
        这段代码摘录展示了一种可替代的方法来获取选项列表。函数cgiFormStringMultiple()使用一个特定的输入字段名来获取一个包含所有被提交选项的字符串数组,这种方法对<SELECT>标签的MULTIPLE属性和具有相同名字的复选按钮组同样适用。
        cgiFormStringMultiple()函数的第一个参数是输入字段名或者复选框选项组字段名。第二个参数是字符串数组的地址。看看这个函数的简单调用:cgiFormStringMultiple("vote", &responses);要怎么知道有多少个响应呢?
    调用之后,这个字符串数组的最后一个值时null。可以通过一个简单的循环计数来统计响应个数。

    重要:cgiFormStringMultiple()函数返回一个指向动态内存的指针。你不应该改变响应数组中的字符串或是数组本身,如果有修改的需求,应该拷贝字符串。当你完成了对响应数组的使用,你要调用cgiStringArrayFree()来释放内存。否则找出内存泄露。不要调用free()函数,如果你这样做,单个字符串不会被释放。

    13、处理表单GET

     表单通常分为两个部分:HTML表单格式和处理数据的脚本,处理程序由标签的ACTION属性指定,每个输入区都有一个NAME属性用来称呼表单元素,当表单数据被递交给ACTION中定义的处理程序时,NAME和其输入内容被以数字或字符的形式保存在环境变量中,脚本程序再通过读取环境变量的方式获得用户输入,根据编程语言的不同获取环境变量的方式也不同,C语言中可以通过stdlib库函数getenv来获得环境变量。

        表单从浏览器发给服务器有两种方法(METHOD属性):GET和POST。GET方法将数据打包放在环境变量QUERY_STRING中作为URL整体的一部分传递给服务器。POST做很多类型GET的事情,但是它分离地传递数据给脚本的,程序要通过标准输入或得数据,POST方式不会改变数据,也就是说同样的数据可以多次提交而不必重新输入。当数据量超过1024时只能使用POST来传递,由于GET将数据直接放到URL中,数据的传输也就变得很不安全。

        表单输入的数据通过URL编码后传输到服务器端,URL的编码规则如下:

        A、每个name/value以name=valu的形式配对出现,每对name/valu之间用&分隔

        B、若用户没有对某个name赋值,则以“name=”的形式出现

        C、任何特殊字符以百分号%用十六进制编码。

        D、输入区的空格将以“+”显示

        要想得到用户输入的数据就必须对传递进来的URL编码进行解码,解码有很多工具可用,比如CGIC库中就已经对解码进行了包装,常用的工具还有uncgi

    五、文件上传编程实例

    1、HTML网页制作

    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"

       "http://www.w3.org/TR/html4/strict.dtd">

    <html>

    <head>

        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

        <title>Test Upload</title>

        <meta name="author" content="scorpio">

        <!-- Date: 2016-07-30 -->

    </head>

    <body>

    <form action="cgi-bin/upload.cgi" method="post" enctype="multipart/form-data" target="_blank">

        <input type="file" name="file" value="" />

        <input type="submit" name="submit" value="OK">

    </form>

    </body>

    </html>

    2、cgi程序编程

    #include <stdio.h>   

    #include <string.h>   

    #include <unistd.h>   

    #include <fcntl.h>   

    #include <sys/stat.h>   

    #include "cgic.h" 

      

    #define BufferLen 1024   

      

    int cgiMain(void)

    {   

        cgiFilePtr file;   

        int targetFile;   

        mode_t mode;   

        char name[128];   

        char fileNameOnServer[64];   

        char contentType[1024];   

        char buffer[BufferLen];   

        char *tmpStr=NULL;   

        int size;   

        int got,t;   

        cgiHeaderContentType("text/html");   

        //取得html页面中file元素的值,应该是文件在客户机上的路径名   

        if (cgiFormFileName("file", name, sizeof(name)) !=cgiFormSuccess)

        {   

            fprintf(stderr,"could not retrieve filename/n");   

            goto FAIL;   

        }   

        cgiFormFileSize("file", &size);   

        //取得文件类型,不过本例中并未使用   

        cgiFormFileContentType("file", contentType, sizeof(contentType));   

        //目前文件存在于系统临时文件夹中,通常为/tmp,通过该命令打开临时文件。临时文件的名字与用户文件的名字不同,所以不能通过路径/tmp/userfilename的方式获得文件   

        if (cgiFormFileOpen("file", &file) != cgiFormSuccess) 

            {   

            fprintf(stderr,"could not open the file/n");   

            goto FAIL;   

        }   

        t=-1;   

        //从路径名解析出用户文件名   

        while(1)

            {   

            tmpStr=strstr(name+t+1,"//");   

            if(NULL==tmpStr)   

            tmpStr=strstr(name+t+1,"/");//if "//" is not path separator, try "/"   

            if(NULL!=tmpStr)   

                t=(int)(tmpStr-name);   

            else   

                break;   

        }   

        strcpy(fileNameOnServer,name+t+1);   

        mode=S_IRWXU|S_IRGRP|S_IROTH;   

        //在当前目录下建立新的文件,第一个参数实际上是路径名,此处的含义是在cgi程序所在的目录(当前目录))建立新文件   

        targetFile=open(fileNameOnServer,O_RDWR|O_CREAT|O_TRUNC|O_APPEND,mode);   

        if(targetFile<0)

            {   

            fprintf(stderr,"could not create the new file,%s/n",fileNameOnServer);   

            goto FAIL;   

        }   

        //从系统临时文件中读出文件内容,并放到刚创建的目标文件中   

        while (cgiFormFileRead(file, buffer, BufferLen, &got) ==cgiFormSuccess)

        {   

            if(got>0)   

            write(targetFile,buffer,got);   

        }   

        cgiFormFileClose(file);   

        close(targetFile);   

        goto END;   

        FAIL:   

        fprintf(stderr,"Failed to upload");   

        return 1;   

        END:   

        printf("File %s has been uploaded",fileNameOnServer);   

        return 0;   

    }

    六、实际项目应用

    1).在src目录下执行make命令,生成.cgi文件,

    Makefile内容:

    CFLAGS=-g -Wall -ldl

    CROSS=arm-hisiv100nptl-linux-

    #CROSS=arm-hismall-linux-

    CC=$(CROSS)gcc

    CPP=$(CROSS)g++ -O2

    STRIP=$(CROSS)strip

    AR=$(CROSS)ar

    RANLIB=$(CROSS)ranlib

    LIBS=-L./ -lcgic

    libcgic.a: cgic.o cgic.h

          rm -f libcgic.a

          $(AR) rc libcgic.a cgic.o

          $(RANLIB) libcgic.a  

    %.o: %.cpp

          $(CPP) $(CFLAGS) -c $<

    %.cgi: %.o cgic.o CgiUtils.o

          $(CPP) $(CFLAGS) -o $@ $^

    2).运行httpd -h ./webroot,首先会执行index.html,在这里面又会调用其他的html,在html中会调用各个cgi进程 即cgiMain(cgiMain进程也可以单独运行),同时

    在html中会有调用JS(httpd服务器 海思提供,httpd -h后不带参数则使用htttpd.conf(/etc/httpd/conf.d/*.conf),带参数则去参数目录下找index.html)

    3).浏览器发送请求到httpd服务器,httpd(html)会调用cgi,cgi执行完后又会输出到HTTPD。

    七、参考原文

    http://www.cnblogs.com/programmer-wfq/p/5582114.html

    http://blog.csdn.net/wuqiongrj/article/details/51993313

    CGIC英文文档地址:

    https://boutell.com/cgic/

    https://github.com/boutell/cgic

  • 相关阅读:
    如何加速JavaScript 代码
    以Kafka Connect作为实时数据集成平台的基础架构有什么优势?
    Java多线程开发系列之一:走进多线程
    java运行环境和运行机制
    C#先序遍历2叉树(非递归)
    Java 之 List<T> 接口的实现:ArrayList
    string.split() 解读---------->从java 和C#的角度剖析
    究竟什么是语法糖呢
    Eclipse 恢复删除的文件
    Notepad++自动刷新文本
  • 原文地址:https://www.cnblogs.com/yuweifeng/p/8146125.html
Copyright © 2011-2022 走看看