编写 HTTP 服务器
介绍
研究 HTTP 服务器的目的是因为在看 libevent-book 的时候,编译出的版本是 HTML 版本,不是 PDF 版本的,看起来比较麻烦,所以干脆想写个 HTTP 服务器,将程序和 libevent-book 文档放到服务器上去运行。之前写过一版 Go 语言的,不过最近正好在看 libevent,所以试试看做一版 C 语言的。
libevent 安装脚本
服务器上没有装 libevent, 所以找了个安装脚本。
wget https://github.com/libevent/libevent/releases/download/release-2.1.11-stable/libevent-2.1.11-stable.tar.gz
tar -zxvf libevent-2.1.11-stable.tar.gz
cd libevent-2.1.11-stable
./configure --prefix=/usr
make
make install
代码
// website.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> //for getopt, fork
#include <string.h> //for strcat
//for struct evkeyvalq
#include <sys/queue.h>
#include <event.h>
//for http
#include <evhttp.h>
#include <signal.h>
#include <fcntl.h>
#define MYHTTPD_SIGNATURE "myhttpd v 0.0.1"
#define BUFF_LEN 1024 * 1024
//处理模块
void httpd_handler(struct evhttp_request *req, void *arg)
{
// char buffer[BUFF_LEN] = "\0";
char *buffer = malloc(BUFF_LEN);
buffer[0] = '\0';
//输出的内容
struct evbuffer *buf;
buf = evbuffer_new();
//获取客户端请求的URI(使用evhttp_request_uri或直接req->uri)
const char *uri;
uri = evhttp_request_uri(req);
FILE *fp = fopen(uri+1, "r");
if (fp == NULL)
{
return;
}
while(fgets(buffer, BUFF_LEN, fp) != NULL) {
evbuffer_add_printf(buf, "%s\n", buffer);
}
if (fgets(buffer, BUFF_LEN, fp) != NULL)
{
int len = strlen(buffer);
buffer[len - 1] = '\0';
}
/* 输出到客户端 */
//HTTP header
evhttp_add_header(req->output_headers, "Server", MYHTTPD_SIGNATURE);
evhttp_add_header(req->output_headers, "Content-Type", "text/html; charset=UTF-8");
evhttp_add_header(req->output_headers, "Connection", "close");
evhttp_send_reply(req, HTTP_OK, "OK", buf);
evbuffer_free(buf);
}
void show_help()
{
char *help = "written by Min (http://54min.com)\n\n"
"-l <ip_addr> interface to listen on, default is 0.0.0.0\n"
"-p <num> port number to listen on, default is 1984\n"
"-d run as a deamon\n"
"-t <second> timeout for a http request, default is 120 seconds\n"
"-h print this help and exit\n"
"\n";
fprintf(stderr, help);
}
//当向进程发出SIGTERM/SIGHUP/SIGINT/SIGQUIT的时候,终止event的事件侦听循环
void signal_handler(int sig)
{
switch (sig)
{
case SIGTERM:
case SIGHUP:
case SIGQUIT:
case SIGINT:
event_loopbreak(); //终止侦听event_dispatch()的事件侦听循环,执行之后的代码
break;
}
}
int main(int argc, char *argv[])
{
//自定义信号处理函数
signal(SIGHUP, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGINT, signal_handler);
signal(SIGQUIT, signal_handler);
//默认参数
char *httpd_option_listen = "127.0.0.1";
int httpd_option_port = 8081;
int httpd_option_daemon = 0;
int httpd_option_timeout = 120; //in seconds
//获取参数
int c;
while ((c = getopt(argc, argv, "l:p:dt:h")) != -1)
{
switch (c)
{
case 'l':
httpd_option_listen = optarg;
break;
case 'p':
httpd_option_port = atoi(optarg);
break;
case 'd':
httpd_option_daemon = 1;
break;
case 't':
httpd_option_timeout = atoi(optarg);
break;
case 'h':
default:
show_help();
exit(EXIT_SUCCESS);
}
}
//判断是否设置了-d,以daemon运行
if (httpd_option_daemon)
{
pid_t pid;
pid = fork();
if (pid < 0)
{
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid > 0)
{
//生成子进程成功,退出父进程
exit(EXIT_SUCCESS);
}
}
/* 使用libevent创建HTTP Server */
//初始化event API
event_init();
//创建一个http server
struct evhttp *httpd;
httpd = evhttp_start(httpd_option_listen, httpd_option_port);
evhttp_set_timeout(httpd, httpd_option_timeout);
//指定generic callback
evhttp_set_gencb(httpd, httpd_handler, NULL);
//循环处理events
event_dispatch();
evhttp_free(httpd);
return 0;
}
编译命令: gcc -g -O0 website.c -o website -levent
。
主要是将网上的代码改了下,使得能够读取同目录下的 HTML 文件,当然,没有做什么错误处理。
参考
ubuntu 下安装 libevent 使用 libevent 编写高并发 HTTP server
Home Page