转载请注明出处
http://blog.csdn.net/pony_maggie/article/details/49838107
作者:小马
一 介绍
Tinyhttpd是一个非常轻量级的http sever。代码不超过一千行。麻雀虽小,五脏俱全。反正我看完之后觉得很是畅快,收获很大。细心研究一下会对linux网络编程,http协议等概念有新的认识。
源码下载地址:
http://sourceforge.net/projects/tinyhttpd/
二 关于CGI
CGI要单独说一下,这是整个源码的核心,也是比较难理解的地方。
我们通过浏览器访问一个网站,会发送http请求给http 服务器,如果请求的是一个静态的页面或图片,服务器会直接返回结果给浏览器。但如果要完成一个动态的请求,比如需要查询数据库这样的操作,服务器会运行一个单独的程序来执行,这个程序处理完成后会把结果转化为服务器(或者浏览器)可以识别的格式输出。
这样的程序就是CGI程序,因为它一般都是以脚本的形式存在的,所以也叫CGI脚本。
CGI现在很少使用了,目前主流的webserver技术大多基于JSP。CGI的主要问题一是效率比较低,毕竟它是作为一个独立的进程被调用的。另一个是CGI的实现是依赖于服务器所用的操作系统,移植性差。
三 源码分析
如果你对http的协议相关内容不了解,开始阅读之前,建议先自行学习一下。本文只关注代码,不会对协议做过多的介绍。
第一部分客户端
客户端程序比较简单,创建一个socket对象连接服务器,然后读写一个字符:
int main(int argc, char *argv[]) { intsockfd; intlen; struct sockaddr_in address; intresult; charch = 'A'; sockfd = socket(AF_INET, SOCK_STREAM, 0); address.sin_family = AF_INET; address.sin_addr.s_addr =inet_addr("127.0.0.1"); address.sin_port = htons(49590); len= sizeof(address); result = connect(sockfd, (struct sockaddr*)&address, len); if(result == -1) { perror("oops: client1"); exit(1); } write(sockfd, &ch, 1); read(sockfd,&ch, 1); printf("char from server = %c\n",ch); close(sockfd); exit(0); }
第二部分服务器
Main函数,
int main(void) { intserver_sock = -1; u_short port = 0; intclient_sock = -1; struct sockaddr_in client_name; intclient_name_len = sizeof(client_name); pthread_t newthread; server_sock = startup(&port); printf("httpd running on port %d\n",port); printf("httpd server_sock: %d\n",server_sock); while (1) { /*套接字收到客户端连接请求*/ client_sock = accept(server_sock, (struct sockaddr*)&client_name, (socklen_t*)&client_name_len); printf("httpd client_sock: %d\n", client_sock); if(client_sock == -1) error_die("accept"); /*派生新线程用accept_request 函数处理新请求*/ /*accept_request(client_sock); */ if(pthread_create(&newthread , NULL, (void *)accept_request, client_sock) !=0) perror("pthread_create"); } close(server_sock); return(0); }
int startup(u_short *port) { inthttpd = 0; struct sockaddr_in name; httpd = socket(PF_INET, SOCK_STREAM, 0); if(httpd == -1) error_die("socket"); memset(&name, 0, sizeof(name)); name.sin_family = AF_INET; name.sin_port = htons(*port); name.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) error_die("bind"); if(*port == 0) /* if dynamicallyallocating a port */ { intnamelen = sizeof(name); if(getsockname(httpd, (struct sockaddr *)&name, (socklen_t *)&namelen) ==-1) error_die("getsockname"); *port = ntohs(name.sin_port); } if(listen(httpd, 5) < 0) error_die("listen"); return(httpd); }有一个细节稍微注意一下,如果传来的端口是0,程序会随机分配一个监听的端口。并通过指向port变量的地址返回该值。
接下来进入循环,服务器通过调用accept等待客户端的连接,Accept会以阻塞的方式运行,直接有客户端连接才会返回。连接成功后,服务器启动一个新的线程来处理客户端的请求(accept_request),处理完成后,重新等待新的客户端请求。
while (1) { client_sock = accept(server_sock, (struct sockaddr*)&client_name, (socklen_t*)&client_name_len); printf("httpd client_sock: %d\n", client_sock); if(client_sock == -1) error_die("accept"); /*accept_request(client_sock); */ if(pthread_create(&newthread , NULL, (void *)accept_request, client_sock) !=0) perror("pthread_create"); }
numchars = get_line(client, buf,sizeof(buf)); i =0; j = 0; while (!ISspace(buf[j]) && (i <sizeof(method) - 1)) { method[i] = buf[j]; i++; j++; } method[i] = '\0';
GET /index.html HTTP/1.1。
上面这段代码就是解析请求行,把方法字段保存在method变量中。关于http协议的方法有不明白的可以自已查下。
继续看,
if (strcasecmp(method, "GET")&& strcasecmp(method, "POST")) { unimplemented(client); return NULL; }只能识别get或post
i =0; while (ISspace(buf[j]) && (j <sizeof(buf))) j++; while (!ISspace(buf[j]) && (i <sizeof(url) - 1) && (j < sizeof(buf))) { url[i] = buf[j]; i++; j++; } url[i] = '\0'; //保存url
上面这一段代码解析并保存请求的URL(如有问号,也包括问号及之后的内容,后面会讲到)。
继续看,
if(strcasecmp(method, "GET") == 0) { query_string = url; while ((*query_string != '?') && (*query_string != '\0')) query_string++; if(*query_string == '?') { ///*开启 cgi */ cgi = 1; *query_string = '\0'; query_string++; } }如果是get方法,请求参数和对应的值附加在URL后面, 利用一个问号(“?”)代表URL的结尾与请求参数的开始,传递参数长度受限制。例如,/index.jsp?10023
其中10023就是要传递的参数。这段代码把参数保存在query_string中。
下面这段代码保存有效的url地址并加上请求地址的主页索引。默认的根目录是在htdocs下。
sprintf(path, "htdocs%s", url);//如果有问号,取url问号前面的部分 if(path[strlen(path) - 1] == '/') strcat(path, "index.html");
接下来的代码,访问请求的文件,如果文件不存在直接返回,如果存在就调用CGI程序来处理。
if(stat(path, &st) == -1) { while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ numchars = get_line(client, buf, sizeof(buf)); not_found(client); } else { if((st.st_mode & S_IFMT) == S_IFDIR)//目录类型 strcat(path, "/index.html"); if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH) ) cgi = 1; printf("accept_request cgi:%d\n", cgi); if(!cgi) serve_file(client, path); else execute_cgi(client, path, method, query_string);//query_string } close(client); return NULL; }
如果需要调用cgi(cgi标志位置1)在调用cgi之前有一段是对用户权限的判断,对应的含义如下:
S_IXUSR:用户可以执行 S_IXGRP:组可以执行 S_IXOTH:其它人可以执行
先来看看serve_file函数,它返回一个静态文本,比较简单。
buf[0] = 'A'; buf[1] = '\0'; while ((numchars > 0) &&strcmp("\n", buf)) /* read& discard headers */ numchars = get_line(client, buf, sizeof(buf));
void headers(int client, const char*filename) { charbuf[1024]; (void)filename; /* could use filename to determine file type*/ strcpy(buf, "HTTP/1.0 200 OK\r\n");//status line send(client, buf, strlen(buf), 0); strcpy(buf, SERVER_STRING);//server header send(client, buf, strlen(buf), 0); sprintf(buf, "Content-Type:text/html\r\n"); send(client, buf, strlen(buf), 0); strcpy(buf, "\r\n"); send(client, buf, strlen(buf), 0); }
void cat(int client, FILE *resource) { charbuf[1024] = {0}; fgets(buf, sizeof(buf), resource); while (!feof(resource)) { send(client, buf, strlen(buf), 0); fgets(buf, sizeof(buf), resource); } }
未完待续。
下一篇地址:
http://blog.csdn.net/pony_maggie/article/details/49838191