管道(PIPE)
进程通信(IPC)的几种方式及比较
int pipe(int fd[2]);


| Query Identifier(16) | QR | OPCodes | Flags | Reserved | RCodes |
| QDCount(16) | ANCount(16) | ||||
| NSCount(16) | ARCount(16) | ||||
| Question Section(32) | |||||
| Answer Section(32) | |||||
| Authority Section(32) | |||||
| Additional Records Section(32) | |||||
以下为各栏位的简要说明:
QID
DNS 查询封包编号,作为确认依据。
QR
查询封包为 0 ﹔回应为 1 。长度为 1 byte 。
OPCodes
封包类别(QUERY, IQUERY, STATUS, Reserved)。长度为 4 bytes。
Flags
共 4 bytes ,各表示:AA(Authoritative Answer)、TC(Truncation)、RD(Recursion Desired)、RA(Recursion Avalable)。
Reserved
保留未用。
RCodes
回应讯息,长 4 bytes ,除 0 及 6-15 保留未用外,1-5 分别为:Format Error、Server Failure、Name Error、Not Implemented、Refused。
Question Section、Answer Section、Authority Section、Additional Records Section
每一 Section 分为 NAME、TYPE、CLASS 三个子栏位,分别作为查询、应答、授权、额外记录等封包之资讯,及各自长度。
好了﹐关于 DNS 协定的讨论﹐暂时介绍到这里﹐相信上面说的这些也够您消化的了。
DNS 协定之 RFC 文件
RFC-822﹑RFC-883﹑RFC-920﹑RFC-973﹑RFC-974﹑RFC-1032﹑RFC-1033﹑RFC-1034﹑RFC-1035﹑RFC-1101﹑RFC-1296
1、什么动作会触发EV_READ, 什么动作触发EV_WRITE?
2、为什么connection_accept()的eventflag有EV_PERSIST,而connection_time的没有?
通过查资料,终于搞明白了!
在《UNIX网络编程第1卷(第2版)》第6章 “I/O 复用:select和poll函数”(129页),有解释:
===============
描述字符在什么条件下准备好?
1、下列的4个条件中的任何一个满足时,socket准备好读(即有EV_READ):
a)套接口接收缓冲区中的数据字节大于等于套接口接收缓冲区低潮限度的当前值(可以设定,默认为1)
b)连接的读这一半关闭(即收到了FIN的TCP连接)。(也就是读到结尾了)
c)套接口是一个接听套接口且已完成的连接数为非0。
d)有一个套接口错误待处理。
2、下列的3个条件中的任何一个满足时,socket准备好写(即有EV_WRITE):
a)套接口发送缓冲区中的数据字节大于等于套接口发送缓冲区低潮限度的当前值(可以设定,默认为2048),且:
aa)套接口已连接 或 bb)套接口不需要连接(如UDP)
b)连接的写这一半关闭。
c)有一个套接口错误待处理。
===============
根 据刚才的time server中的例程,当client有socket连上来,应该是EV_READ,因为满足1中的c“套接口是一个接听套接口且已完成的连接数为非 0”,当该连接被acept以后,就有EV_WRITE了,因为它满足2中的a“套接口发送缓冲区中的数据字节大于等于套接口发送缓冲区低潮限度的当前 值,且套接口已连接”。所以,event_set的时候,EV_READ是有人连上来,回调函数是connection_accept(),而 EV_WRITE是在accept以后。
那EV_PERSIST呢?
EV_PERSIST,是在event发生了以后,不从队列中拿开,就是下次再有这个消息的时候,继续调那个回调函数,知道程序主动调用了event_del后,才从队列中删除--就算有那个消息也不回调函数。
connection_accept函数,只要有连接上来,就要调用的,所以它需要设置为EV_PERSIST。
但 connection_time函数,用途是在client连上来以后,给client一个返回,然后就断开连接。那它必须在连接连上了以后,只做一次, 所以不需要设置为EV_PERSIST,而且必须在accept中设置--如果没有accept就算有EV_WRITE也不应该调用它啊。
最近两天在学习nginx,顺便也学习了下libevent。将相关的一些技术文章地址摘录如下:
http://night9.cn/category/nginx
http://amix.dk/blog/viewEntry/19414
http://www.igvita.com/2008/02/11/nginx-and-memcached-a-400-boost/
http://wiki.nginx.org/Nginx3rdPartyModules
http://code.google.com/p/php-redis/
http://blog.sina.com.cn/iyangjian
如何写nginx module
http://timyang.net/web/nginx-module/
http://code.google.com/p/redis/
另外,计划使用libevent改写扫描和发送报警引擎。
from http://blog.gslin.net 在課堂上學過 Unix Network Programming 後,我們知道在處理多 User 時會有幾種方法解決: 1. 一個新的 Connection 進來,用 fork() 產生一個 Process 處理。 2. 一個新的 Connection 進來,用 pthread_create() 產生一個 Thread 處理。 3. 一個新的 Connection 進來,丟入 Event-based Array,由 Main Process 以 Nonblocking 的方式處理所有的 I/O。 這三種方法當然也都有各自的缺點: 1. 用 fork() 的問題在於每一個 Connection 進來時的成本太高。 2. 用 Multi-thread 的問題在於 Thread-safe 與 Deadlock 問題難以解決,另外有 Memory-leak 的問題要處理。 3. 用 Event-based 的方式在於實做上不好寫,尤其是要注意到事件產生時必須 Nonblocking,於是會需要實做 Buffering 的問題,而 Multi-thread 所會遇到的 Memory-leak 問題在這邊會更嚴重。而在多 CPU 的系統上沒有辦法使用到所有的 CPU resource。 當然,針對前面兩項有各自的解法: 1. 以 Poll 的方式解決:當一個 Process 處理完一個 Connection 後,不直接死掉,而繼續回到 accept() 的狀態繼續處理,但這樣會遇到 Memory-leak 的問題,於是採用這種方式的人通常會再加上「處理過 N 個 Connection 後死掉,由 Parent Process 再 fork() 一隻新的」。最有名的例子是 Apache 1.3。 2. Thread-safe 的問題可以透過自己撰寫,或是尋找其他 Thread-safe Library 直接使用。Memory-leak 的問題可以試著透過 Garbage Collection Library 分析出來。Apache 2.0 的 Thread MPM 就是使用這個模式。 然而,目前高效率的 Server 都偏好採用 Event-based,一方面是沒有 Create Process/Thread 所造成的 Overhead,另外一方面是不需要透過 Shared Memory 或是 Mutex 在不同的 Process/Thread 之間交換資料。 然而,Event-based 在實做上的幾個複雜的地方在於: 1. select() 與 poll() 的效率過慢,造成每次要判斷「有哪些 Event 發生」這件事情的成本很高,這在 BSD 支援 kqueue()、Linux 支援 epoll()、Solaris 支援 /dev/poll 後就解決了,但這兩組 Function 都不是 Standard,於是在不同的平台上就必須再改一次。 2. 因為 Nonblocking,所以在 write() 或是 send() 時滿了需要自己 Buffering。 3. 因為 Nonblocking,所以不能使用 fgets() 或是其他類似的 function,於是需要自己刻一個 Nonblocking 的 fgets()。但是使用者所丟過來的資料又不能保證在一次 read() 或 recv() 就有一行,於是要自己做 Buffering。 實際上這三件事情在 libevent 都有 Library 處理掉了。 另外值得注意的一點在於 libevent 使用的是 3-clause BSD license 而非 GPL,所以在不想公開程式碼 (像是商業用途) 的情況下會比其他的 Library 適合。 接下來要談的是 libevent 要如何使用,不過為了方便起見,我們直接寫一個很簡單的 Time Server 來當作例子:當你連上去以後 Server 端直接提供時間,然後結束連線。 在這些例子裡面我以 FreeBSD 6.0 當作測試的平台,另外使用 libevent 1.1a 當作 Event-based Library,Compile 時請使用 gcc -I/usr/local/include -o timeserver timeserver.c -L/usr/local/lib -levent (如果 libevent 的 Header 與 Library 放在 /usr/include 與 /usr/lib 下的話可以省略這兩個參數)。 原始程式碼在文章的最後頭。 event_init() 表示初始化 libevent 所使用到的變數。 event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev) 把 s 這個 File Description 放入 ev (第一個參數與第二個參數),並且告知當事件 (第三個參數的 EV_READ) 發生時要呼叫 connection_accept() (第四個參數),呼叫時要把 ev 當作參數丟進去 (第五個參數)。 其中的 EV_PERSIST 表示當呼叫進去的時候不要把這個 event 拿掉 (繼續保留在 Event Queue 裡面),這點可以跟 connection_accept() 內在註冊 connection_time() 的程式碼做比較。 而 event_add(&ev, NULL) 就是把 ev 註冊到 event queue 裡面,第二個參數指定的是 Timeout 時間,設定成 NULL 表示忽略這項設定。 最後的 event_dispatch() 表示進入 event loop,當 Queue 裡面的任何一個 File Description 發生事件的時候就會進入 callback function 執行。 這隻程式非常粗糙,有很多地方沒有注意到 Blocking 的問題,這點我們就先不管了。當跑起來以後你就可以連到 port 7000,就會出現類似下面的結果:gslin@netnews [~] [3:14/W5] t 0 7000 gslin@netnews [~/work/C] [3:15/W3] t 0 7000 Trying 0.0.0.0... Connected to 0. Escape character is '^]'. Fri Nov 25 03:15:10 2005 Connection closed by foreign host. 最基本的使用就是這樣了,你可以 man event 看到完整的說明。 這是 timeserver.c: #include <netinet/in.h>#include <sys/socket.h>#include <sys/types.h>#include <event.h>#include <stdio.h>#include <time.h> void connection_time(int fd, short event, struct event *arg){ char buf[32]; struct tm t; time_t now; time(&now); localtime_r(&now, &t); asctime_r(&t, buf); write(fd, buf, strlen(buf)); shutdown(fd, SHUT_RDWR); free(arg);} void connection_accept(int fd, short event, void *arg){ /* for debugging */ fprintf(stderr, "%s(): fd = %d, event = %d.n", __func__, fd, event); /* Accept a new connection. */ struct sockaddr_in s_in; socklen_t len = sizeof(s_in); int ns = accept(fd, (struct sockaddr *) &s_in, &len); if (ns < 0) { perror("accept"); return; } /* Install time server. */ struct event *ev = malloc(sizeof(struct event)); event_set(ev, ns, EV_WRITE, (void *) connection_time, ev); event_add(ev, NULL);} int main(void){ /* Request socket. */ int s = socket(PF_INET, SOCK_STREAM, 0); if (s < 0) { perror("socket"); exit(1); } /* bind() */ struct sockaddr_in s_in; bzero(&s_in, sizeof(s_in)); s_in.sin_family = AF_INET; s_in.sin_port = htons(7000); s_in.sin_addr.s_addr = INADDR_ANY; if (bind(s, (struct sockaddr *) &s_in, sizeof(s_in)) < 0) { perror("bind"); exit(1); } /* listen() */ if (listen(s, 5) < 0) { perror("listen"); exit(1); } /* Initial libevent. */ event_init(); /* Create event. */ struct event ev; event_set(&ev, s, EV_READ | EV_PERSIST, connection_accept, &ev); /* Add event. */ event_add(&ev, NULL); event_dispatch(); return 0;} 這次要談的跟 Network Programming 沒有直接的關係。 在寫 Nonblocking Network Program 通常要處理 Buffering 的問題,但並不好寫,主要是因為 read() 或 recv() 不保證可以一次讀到一行的份量進來。 在 libevent 裡面提供相當不錯的 Buffer Library 可以用,完整的說明在 man event 的時候可以看到,最常用的應該就是以 evbuffer_add()、evbuffer_readline() 這兩個 Function,其他的知道存在就可以了,需要的時候再去看詳細的用法。 下面直接提供 libevent-buff.c 當作範例,編譯後看執行結果,再回頭來看 source code 應該就有感覺了: #include <sys/time.h>#include <event.h>#include <stdio.h> void printbuf(struct evbuffer *evbuf){ for (;;) { char *buf = evbuffer_readline(evbuf); printf("* buf = %p, the string = "e[1;33m%se[m"n", buf, buf); if (buf == NULL) break; free(buf); }} int main(void){ struct evbuffer *evbuf; evbuf = evbuffer_new(); if (evbuf == NULL) { fprintf(stderr, "%s(): evbuffer_new() failed.n", __func__); exit(1); } /* Add "gslin" into buffer. */ u_char *buf1 = "gslin"; printf("* Add "e[1;33m%se[m".n", buf1); evbuffer_add(evbuf, buf1, strlen(buf1)); printbuf(evbuf); u_char *buf2 = " is reading.nAnd he is at home.nLast."; printf("* Add "e[1;33m%se[m".n", buf2); evbuffer_add(evbuf, buf2, strlen(buf2)); printbuf(evbuf); evbuffer_free(evbuf);}
文章地址:
http://www.cublog.cn/u/17999/showart.php?id=159057
运行这个程序需要预先设置栈内存和文件描述符上限, 否则运行失败
ulimit -n 16384
ulimit -s 4096
文件名:server.c
编译: gcc server.c -Wall -O2 -pthread -o server
程序源码如下(请自行编辑宏定义SERVER_IP为自己的IP):
/*Linux 2.6 x86_64 only*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#include <pthread.h>
#define THREAD_MAX 4096
#define LISTEN_MAX 5000
#define SERVER_IP "192.168.1.103"
typedef struct {
char ip4[128];
int port;
int fd;
} LISTEN_INFO;
//服务器参数
static LISTEN_INFO s_listens[LISTEN_MAX];
//线程池参数
static unsigned int s_thread_para[THREAD_MAX][8];//线程参数
static pthread_t s_tid[THREAD_MAX];//线程ID
pthread_mutex_t s_mutex[THREAD_MAX];//线程锁
//私有函数
static int init_thread_pool(void);
static int init_listen4(char *ip4, int port, int max_link);
//线程函数
void * test_server4(unsigned int thread_para[]);
int main(int argc, char *argv[])//客户端驱动
{
//临时变量
int i, j, rc;
int sock_listen; //监听套接字
int sock_cli; //客户端连接
int listen_index;
int epfd;
int nfds;
struct epoll_event ev;
struct epoll_event events[LISTEN_MAX];
socklen_t addrlen; //地址信息长度
struct sockaddr_in addr4; //IPv4地址结构
//线程池初始化
rc = init_thread_pool();
if (0 != rc) exit(-1);
//初始化服务监听
for(i = 0; i < LISTEN_MAX; i++) {
sprintf(s_listens[i].ip4, "%s", SERVER_IP);
s_listens[i].port = 8000 + i;
//创建监听
rc = init_listen4(s_listens[i].ip4, s_listens[i].port, 64);
if (0 > rc) {
fprintf(stderr, "无法创建服务器监听于%s:%drn", s_listens[i].ip4, s_listens[i].port);
exit(-1);
}
s_listens[i].fd = rc;
}
//设置集合
epfd = epoll_create(8192);
for (i = 0; i < LISTEN_MAX; i++) {
//加入epoll事件集合
ev.events = EPOLLIN;
ev.data.u32 = i;//记录listen数组下标
if (epoll_ctl(epfd, EPOLL_CTL_ADD, s_listens[i].fd, &ev) < 0) {
fprintf(stderr, "向epoll集合添加套接字失败(fd =%d)rn", rc);
exit(-1);
}
}
//服务循环
for( ; ; ) {
//等待epoll事件
nfds = epoll_wait(epfd, events, LISTEN_MAX, -1);
//处理epoll事件
for(i = 0; i < nfds; i++) {
//接收客户端连接
listen_index = events[i].data.u32;
sock_listen = s_listens[listen_index].fd;
addrlen = sizeof(struct sockaddr_in);
bzero(&addr4, addrlen);
sock_cli = accept(sock_listen, (struct sockaddr *)&addr4, &addrlen);
if(0 > sock_cli) {
fprintf(stderr, "接收客户端连接失败n");
continue;
}
//查询空闲线程对
for(j = 0; j < THREAD_MAX; j++) {
if (0 == s_thread_para[j][0]) break;
}
if (j >= THREAD_MAX) {
fprintf(stderr, "线程池已满, 连接将被放弃rn");
shutdown(sock_cli, SHUT_RDWR);
close(sock_cli);
continue;
}
//复制有关参数
s_thread_para[j][0] = 1;//设置活动标志为"活动"
s_thread_para[j][1] = sock_cli;//客户端连接
s_thread_para[j][2] = listen_index;//服务索引
//线程解锁
pthread_mutex_unlock(s_mutex + j);
}//end of for(i;;)
}//end of for(;;)
exit(0);
}
static int init_thread_pool(void)
{
int i, rc;
//初始化线程池参数
for(i = 0; i < THREAD_MAX; i++) {
s_thread_para[i][0] = 0;//设置线程占用标志为"空闲"
s_thread_para[i][7] = i;//线程池索引
pthread_mutex_lock(s_mutex + i);//线程锁
}
//创建线程池
for(i = 0; i < THREAD_MAX; i++) {
rc = pthread_create(s_tid + i, 0, (void *)test_server4, (void *)(s_thread_para[i]));
if (0 != rc) {
fprintf(stderr, "线程创建失败n");
return(-1);
}
}
//成功返回
return(0);
}
static int init_listen4(char *ip4, int port, int max_link)
{
//临时变量
int sock_listen4;
struct sockaddr_in addr4;
unsigned int optval;
struct linger optval1;
//初始化数据结构
bzero(&addr4, sizeof(addr4));
inet_pton(AF_INET, ip4, &(addr4.sin_addr));
addr4.sin_family = AF_INET;
addr4.sin_port = htons(port);
//创建SOCKET
sock_listen4 = socket(AF_INET, SOCK_STREAM, 0);
if (0 > sock_listen4) return(-1);
//设置SO_REUSEADDR选项(服务器快速重起)
optval = 0×1;
setsockopt(sock_listen4, SOL_SOCKET, SO_REUSEADDR, &optval, 4);
//设置SO_LINGER选项(防范CLOSE_WAIT挂住所有套接字)
optval1.l_onoff = 1;
optval1.l_linger = 60;
setsockopt(sock_listen4, SOL_SOCKET, SO_LINGER, &optval1, sizeof(struct linger));
if (0 > bind(sock_listen4, (struct sockaddr *)&addr4, sizeof(addr4))) {
close(sock_listen4);
return(-1);
}
if (0 > listen(sock_listen4, max_link)) {
close(sock_listen4);
return(-1);
}
return(sock_listen4);
}
void * test_server4(unsigned int thread_para[])
{
//临时变量
int pool_index; //线程池索引
int sock_cli; //客户端连接
int listen_index; //监听索引
char buff[32768]; //传输缓冲区
char *p;
int i, j, len;
//线程脱离创建者
pthread_detach(pthread_self());
pool_index = thread_para[7];
wait_unlock:
pthread_mutex_lock(s_mutex + pool_index);//等待线程解锁
//线程变量内容复制
sock_cli = thread_para[1];//客户端连接
listen_index = thread_para[2];//监听索引
//接收请求
len = recv(sock_cli, buff, 32768, MSG_NOSIGNAL);
//构造响应
p = buff;
//HTTP头
p += sprintf(p, "HTTP/1.1 200 OKrn");
p += sprintf(p, "Content-Type: text/htmlrn");
p += sprintf(p, "Connection: closedrnrn");
//页面
p += sprintf(p, "<html>rn<head>rn");
p += sprintf(p, "<meta content="text/html; charset=UTF-8" http-equiv="Content-Type">rn");
p += sprintf(p, "</head>rn");
p += sprintf(p, "<body style="background-color: rgb(229, 229, 229);">rn");
p += sprintf(p, "<center>rn");
p += sprintf(p, "<H3>连接状态</H3>rn");
p += sprintf(p, "<p>服务器地址 %s:%d</p>rn", s_listens[listen_index].ip4, s_listens[listen_index].port);
j = 0;
for(i = 0; i < THREAD_MAX; i++) {
if (0 != s_thread_para[i][0]) j++;
}
p += sprintf(p, "<H3>线程池状态</H3>rn");
p += sprintf(p, "<p>线程池总数 %d 活动线程总数 %d</p>rn", THREAD_MAX, j);
p += sprintf(p, "</center></body></html>rn");
len = p – buff;
//发送响应
send(sock_cli, buff, len, MSG_NOSIGNAL);
//释放连接
shutdown(sock_cli, SHUT_RDWR);
close(sock_cli);
//线程任务结束
thread_para[0] = 0;//设置线程占用标志为"空闲"
goto wait_unlock;
pthread_exit(NULL);
}
1万个监测任务(相当于主机),自动运行100个线程,几秒钟就完成了。
不过并发写数据库日志时,MySQL数据库成为了瓶颈。
现在的架构是: 多进程+多线程的模式工作。
每次监测任务,派生一个子进程;每个子进程根据要监测的任务来自动计算,创建足够的线程来执行具体的监测任务。
在每次提交的时候写明提交的目的是一个很好的习惯,Subversion默认没有提供,但是可以通过钩子实现:
将下面的代码存为pre-commit.bat放到版本库的hooks目录下即可,当你不写日志提交的话就会报告错误。(如果你下载使用,需要修改SVN_BINDIR为你的Subversion安装的程序目录)
@echo off set SVN_BINDIR=d:Subversionbin setlocal set REPOS=%1 set TXN=%2 rem check that logmessage contains at least 10 characters %SVN_BINDIR%svnlook log "%REPOS%" -t "%TXN%" | findstr "………." > nul if %errorlevel% gtr 0 goto err exit 0 :err echo Empty log message not allowed. Commit aborted! 1>&2 exit 1http://www.x5dj.com/Blog/00114791/00490309.shtml
一、CGI概述
CGI(Common Gateway Interface: 公用网关接口)规定了Web服务器调用其他可执行程序(CGI程 序)的接口协议标准。Web服务器通过调用CGI程序实现和Web浏览器的交互,也就是CGI程序接受Web浏览器发送给Web服务器的信息,进行处理,将响应结果再回送给Web服务器及Web浏览器。CGI程序一般完成Web网页中表单(Form)数据的处理、数据库查询和实现与传统应用系统的集成等工作。CGI程序可以用任何程序设计语言编写,如Shell脚本语言、Perl、Fortran、Pascal、C语言等。但是用C语言编写的CGI程序具有执行速度快、安全性高(因为C语言程序是编译执行且不可被修改)等特点。
CGI接口标准包括标准输入、环境变量、标准输出三部分。
1.标准输入
CGI程序像其他可执行程序一样,可通过标准输入(stdin)从Web服务器得到输入信息,如Form中的数据,这就是所谓的向CGI程序传递数据的POST方法。这意味着在操作系统命令行状态可执行CGI程序,对CGI程序进行调试。POST方法是常用的方法,本文将以此方法为例,分析CGI程序设计的方法、过程和技巧。
2.环境变量
操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。Web服务器和CGI接口又另外设置了自己的一些环境变量,用来向CGI 程序传递一些重要的参数。CGI的GET方法还通过 环境变量QUERY-STRING向CGI程序传递Form中的数据。
3.标准输出
CGI程序通过标准输出(stdout)将输出信息传送给Web服务器。传送给Web服务器的信息可以用各种格式,通常是以纯文本或者HTML文本的形式,这样我们就可以在命令行状态调试CGI程序,并且得到它们的输出。
下面是一个简单的CGI程序,它将HTML中Form的信息直接输出到We b浏览器。
# include <stdio.h>
# include <stdib.h>
main()
{
int i , n ;
printf (″Content type: text/plainnn″);
n=0;
if(getenv(″CONTENT-LENGTH″))
n=atoi(getenv(CONTENT-LENGTH″));
for (i=0;i<n;i++)
putchar(getchar());
putchar (′n′);
fflush(stdout);
}
下面对此程序作一下简要的分析。
prinft (″Content type :text/plainnn″);
此行通过标准输出将字符串″Content type :text/plainnn″传送给Web服务器。它是一个MIME头信息,它告诉Web服务器随后的输出是以纯ASCII文本的形式。请注意在这个 头信息中有两个新行符,这是因为Web服务器需要在实际的文本信息开始之前先看见一个空行。
if (getenv(″CONTENT-LENGTH″))
n=atoi (getenv(″CONTENT-LENGTH″));
此行首先检查环境变量CONTENT-LENGTH是否存在。Web服务器在调用使用POST方法的CGI程序时设置此环境变量,它的文本值表示Web 服务器传送给CGI程序的输入中的字符数目,因此我们使用函数atoi() 将此环境变量的值转换成整数,并赋给变量n。请注意Web服务器并不以文件结束符来终止它的输出,所以如果不检查环境变量CONTENT- LENGTH,CGI程序就无法知道什么时候输入结束了。
for (i=0;i<n;i++)
putchar(getchar());
此行从0循环到(CONTENT-LENGTH-1)次将标准输入中读到的每一个字符直接拷贝到标准输出,也就是将所有的输入以ASCII的形式回送给Web服务器。
通过此例,我们可将CGI程序的一般工作过程总结为如下几点。
1.通过检查环境变量CONTENT-LENGTH,确定有多少输入;
2.循环使用getchar()或者其他文件读函数得到所有的输入;
3.以相应的方法处理输入;
4.通过″Contenttype:″头信息,将输出信息的格式告诉Web服务器;
5.通过使用printf()或者putchar()或者其他的文件写函数,将输出传送给Web服务器。
总之,CGI程序的主要任务就是从Web服务器得到输入信息,进行处理,然后将输出结果再送回给Web服务器。
二、环境变量
环境变量是文本串(名字/值对),可以被OS Shell或其他程序设置 ,也可以被其他程序访问。它们是Web服务器传递数据给CGI程序的简单手段,之所以称为环境变量是因为它们是全局变量,任何程序都可以存取它们。
下面是CGI程序设计中常常要用到的一些环境变量。
HTTP-REFERER:调用该CGI程序的网页的URL。
REMOTE-HOST:调用该CGI程序的Web浏览器的机器名和域名。
REQUEST-METHOD:指的是当Web服务器传递数据给CGI程序时所采用的方法,分为GET和POST两种方法。GET方法仅通过环境变量 (如QUERY-STRING)传递数据给CGI程序,而POST方法通过环境变量和标准输入传递数据给CGI程序,因此POST方法可较方便地传递较多 的数据给CGI程序。
SCRIPT-NAME:该CGI程序的名称。
QUERY-STRING:当使用POST方法时,Form中的数据最后放在QUERY-STRING中,传递给CGI程序。
CONTENT-TYPE:传递给CGI程序数据的MIME类型,通常为″applica tion/x-www-form-url encodede″,它是从HTML Form中以POST方法传递数据给CGI程序的数据编码类型,称为URL编码类型。
CONTENT-LENGTH:传递给CGI程序的数据字符数(字节数)。
在C语言程序中,要访向环境变量,可使用getenv()库函数。例如:
if (getenv (″CONTENT-LENGTH″))
n=atoi(getenv (″CONTENT-LENGTH″));
请注意程序中最好调用两次getenv():第一次检查是否存在该环境变量,第二次再使用该环境变量。这是因为函数getenv()在给定的环境变量 名不存在时,返回一个NULL(空)指针,如果你不首先检查而直接引用它,当该环境变量不存在时会引起CGI程序崩溃。
三、From输入的分析和解码
1.分析名字/值对
当用户提交一个HTML Form时,Web浏览器首先对Form中的数据以名字/值对的形式进行编码,并发送给Web服务器,然后由Web服务器传递给CGI程序。其格式如下:
name1=value1&name2=value2&name3=value3&name4=value4&…
其中名字是Form中定义的INPUT、SELECT或TEXTAREA等标置(Tag)名字,值是用户输入或选择的标置值。这种格式即为URL编 码,程序中需要对其进行分析和解码。要分析这种数据流,CGI程序必须首先将数据流分解成一组组的名字/值对。这可以通过在输入流中查找下面的两个字符来 完成。
每当找到字符=,标志着一个Form变量名字的结束;每当找到字符& ,标志着一个Form变量值的结束。请注意输入数据的最后一个变量的值不以&结束。
一旦名字/值对分解后,还必须将输入中的一些特殊字符转换成相应的ASCII字符。这些特殊字符是:
+:将+转换成空格符;
%xx:用其十六进制ASCII码值表示的特殊字符。根据值xx将其转换成相应的ASCII字符。
对Form变量名和变量值都要进行这种转换。下面是一个对Form数据进行分析并将结果回送给Web服务器的CGI程序。
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
int htoi(char *);
main()
{
int i,n;
char c;
printf (″Contenttype: text/plainnn″);
n=0;
if (getenv(″CONTENT-LENGTH″))
n=atoi(getenv(″CONTENT-LENGTH″));
for (i=0; i<n;i++){
int is-eq=0;
c=getchar();
switch (c){
case ′&′:
c=′n′;
break;
case ′+′:
c=′ ′;
break;
case ′%′:{
char s[3];
s[0]=getchar();
s[1]=getchar();
s[2]=0;
c=htoi(s);
i+=2;
}
break;
case ′=′:
c=′:′;
is-eq=1;
break;
};
putchar(c);
if (is-eq) putchar(′ ′);
}
putchar (′n′);
fflush(stdout);
}
/* convert hex string to int */
int htoi(char *s)
{
char *digits=″0123456789ABCDEF″;
if (islower (s[0])) s[0]=toupper(s[0]);
if (islower (s[1])) s[1]=toupper(s[1]);
return 16 * (strchr(digits, s[0]) -strchr (digits,′0′)
)
+(strchr(digits,s[1])-strchr(digits,′0′));
}
上面的程序首先输出一个MIME头信息给Web服务器,检查输入中的字符数,并循环检查每一个字符。当发现字符为&时,意味着一个名字/值对 的结束,程序输出一个空行;当发现字符为+时,将它转换成空格; 当发现字符为%时,意味着一个两字符的十六进制值的开始,调用htoi()函数将随后的两个字符转换为相应的ASCII字符;当发现字符为=时,意味着一 个名字/值对的名字部分的结束,并将它转换成字符:。最后将转换后的字符输出给Web服务器。
四、产生HTML输出
CGI程序产生的输出由两部分组成:MIME头信息和实际的信息。两部分之间以一个空行分开。我们已经看到怎样使用MIME头信息″Content type :text/plainnn″和printf()、put char()等函数调用来输 出纯ASCII文本给Web服务器。实际上,我们也可以使用MIME头信息″Content type :text/htmlnn″来输出HTML源代码给Web服务器。请注意任何MIME头信息后必须有一个空行。一旦发送这个MIME头信息给We b服务器后,Web浏览器将认为随后的文本输出为HTML源代码,在HTML源代码中可以使用任何HTML结构,如超链、图像、Form,及对其他CGI 程 序的调用。也就是说,我们可以在CGI程序中动态产生HTML源代码输出 ,下面是一个简单的例子。
#include <stdio.h>
#include <string.h>
main()
{
printf(″Contenttype:text/htmlnn″);
printf(″<html>n″);
printf(″<head><title>An HTML Page From a CGI</title></h ead>n″);
printf(″<body><br>n″);
printf(″<h2> This is an HTML page generated from with i n a CGI program.. .</h2>n″);
printf(″<hr><p>n″);
printf(″<a href="../output.html#two"><b> Go back to out put.html page <
/b></a>n″);
printf(″</body>n″);
printf(″</html>n″);
fflush(stdout);
}
上面的CGI程序简单地用printf()函数来产生HTML源代码。请注意在输出的字符串中如果有双引号,在其前面必须有一个后斜字符, 这是因为整个HTML代码串已经在双引号内,所以HTML代码串中的双引号符必须用一个后斜字符来转义。
五、结束语
本文详细分析了用C语言进行CGI程序设计的方法、过程和技巧。C语言的CGI程序虽然执行速度快、可靠性高,但是相对于Perl语言来说,C语言缺乏强有力的字符串处理能力,因此在实际应用中,应根据需 要和个人爱好来选择合适的CGI程序设计语言。
最新评论