CGI 是Web 服务器运行时外部程序的规范,按CGI 编写的程序可以扩展服务器功能。
CGI(Common Gateway Interface) 是WWW技术中最重要的技术之一,有着不可替代的重要地位。CGI是外部应用程序(CGI程序)与WEB服务器之间的接口标准,是在CGI程序和Web服务器之间传递信息的过程。
CGI规范允许Web服务器执行外部程序,并将它们的输出发送给Web浏览器,CGI将Web的一组简单的静态超媒体文档变成一个完整的新的交互式媒体。
CGI的处理步骤:
- 通过Internet把用户请求送到web服务器。
- web服务器接收用户请求并交给CGI程序处理。
- CGI程序把处理结果传送给web服务器。
- web服务器把结果送回到用户。
示例代码如下:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include "processpool.h"
// 用于处理客户cgi请求的类,他可以作为processpool类的模板参数
class cgi_conn {
public:
cgi_conn(){};
~cgi_conn(){};
// 初始化客户连接,清空读缓冲区
void init(int epollfd, int sockfd, const sockaddr_in& client_addr) {
m_epollfd = epollfd;
m_sockfd = sockfd;
m_address = client_addr;
memset(m_buf, '\0', BUFFER_SIZE);
m_read_idx = 0;
}
void process() {
int idx = 0;
int ret = -1;
// 循环读取和分析客户数据
while (true) {
idx = m_read_idx;
ret = recv(m_sockfd, m_buf + idx, BUFFER_SIZE - 1 - idx, 0);
// 如果读操作发生错误,则关闭客户连接,如果是暂时无数据可读,则退出循环
if (ret < 0) {
if (errno != EAGAIN) {
removefd(m_epollfd, m_sockfd);
}
break;
} else if (ret == 0) {
// 如果对方关闭连接,服务器也关闭连接
removefd(m_epollfd, m_sockfd);
break;
} else {
m_read_idx += ret;
printf("user content is: %s\n", m_buf);
for (; idx < m_read_idx; ++idx) {
if ((idx >= 1) && (m_buf[idx - 1] == '\r') && (m_buf[idx] == '\n')) {
break;
}
}
// 如果没有遇到字符\r\n 则需要读取更多客户数据
if (idx == m_read_idx) {
continue;
}
m_buf[idx - 1] = '\0';
char* filename = m_buf;
// 判断客户要运行的cgi程序是否存在
if (access(filename, F_OK) == -1) {
removefd(m_epollfd, m_sockfd);
break;
}
// 创建子进程来执行cgi程序
ret = fork();
if (ret == -1) {
removefd(m_epollfd, m_sockfd);
break;
} else if (ret > 0) { // 父进程
// 父进程只需要关闭连接
removefd(m_epollfd, m_sockfd);
break;
} else {
// 子进程需要将标准输出定向到 m_sockfd,并执行cgi程序
close(STDOUT_FILENO);
dup(m_sockfd); // 把m_sockfd重定向到标准输出
execl(m_buf, m_buf, 0);
exit(0);
}
} // 读数据end
} // while end
} // process end
private:
static const int BUFFER_SIZE = 1024;
static int m_epollfd;
int m_sockfd;
sockaddr_in m_address;
char m_buf[BUFFER_SIZE];
int m_read_idx; // 读的光标位置
};
int cgi_conn::m_epollfd = -1;
int main(int argc, char* argv[]) {
if (argc <= 2) {
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
assert(listenfd >= 0);
int ret = 0;
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
ret = listen(listenfd, 5);
assert(ret != -1);
processpool<cgi_conn>* pool = processpool<cgi_conn>::create(listenfd); // 创建唯一进程池实例
if (pool) {
pool->run();
delete pool;
}
close(listenfd); // 正如前文提到的,main函数创建了文件描述符listenfd,那么就由它亲自关闭之
return 0;
}
本程序需要配合上一节的线程池头文件processpool.h。
运行结果如下:
服务器
客户端:
这里只是简单模拟,由于服务器是将信息作为文件名去访问服务器文件,而客户端随意发送信息,所以判断信息不存在就直接退出了。
reference:linux高性能服务器编程——游双