【源码下载】http://sourceforge.net/projects/tinyhttpd/files/tinyhttpd%20source/tinyhttpd%200.1.0/tinyhttpd-0.1.0.tar.gz/download
【编译环境】ubuntu14.04+gcc
【运 行】工程利用了多线程,需要链接多线程的库libpthread.so;直接编译httpd.c源文件 $ gcc -o 程序名 源文件.c -lpthread 然后在浏览器输入: localhost:程序动态分配的端口号/index.html就可运行了。
请求头:
1 【GET】【http://localhost:59776/index.html】 2 Host: localhost:59776 3 User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:39.0) Gecko/20100101 Firefox/39.0 4 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 5 Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3 6 Accept-Encoding: gzip, deflate 7 Connection: keep-alive
响应头:
1 Content-Type: text/html 2 Server: jdbhttpd/0.1.0
【注 意】源代码中提示将多线程注释掉,注释掉后直接编译不能运行。不注释直接编译虽然会又warnning但可运行。
服务器端原理:
服务器端调用socket()建立socket套接字,调用bind()函数绑定IP和端口,调用listen()函数监听端口,等待浏览器发送URL,调用accept()函数接受数据,关闭套接字。
accept()函数中创建多线程,主线程继续等待,子线程处理http请求,解析浏览器发送的URL字符串,结束后新线程退出。
main():
1 int main(void) 2 { 3 int server_sock = -1; 4 u_short port = 0; 5 int client_sock = -1; 6 struct sockaddr_in client_name; 7 int client_name_len = sizeof(client_name); 8 pthread_t newthread; 9 10 server_sock = startup(&port); //传递port的地址到startup()函数 11 printf("httpd running on port %d\n", port); 12 13 while (1) 14 { 15 client_sock = accept(server_sock, 16 (struct sockaddr *)&client_name, 17 &client_name_len); 18 if (client_sock == -1) 19 error_die("accept"); 20 /* accept_request(client_sock); */ 21 if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0) 22 perror("pthread_create"); 23 } 24 25 close(server_sock); 26 27 return(0); 28 }
主函数调用startup()函数创建套接字。其中有一点是sockaddr和sockaddr_in的不同,总结是先创建sockaddr_in结构体,将该结构体中的各个变量赋值完后,在bind()或者accept()函数中将其转化为sockaddr结构体,因为两者所占内存空间一样可相互转化,另外两者所在头文件不一样,sockaddr在socket.h中,sockaddr_in在in.h中。
startup():
1 int startup(u_short *port) 2 { 3 int httpd = 0; 4 struct sockaddr_in name; //声明结构体 5 6 httpd = socket(PF_INET, SOCK_STREAM, 0); //创建套接字 7 if (httpd == -1) 8 error_die("socket"); 9 memset(&name, 0, sizeof(name)); //初始化 10 name.sin_family = AF_INET; //协议簇赋值 11 name.sin_port = htons(*port); 12 name.sin_addr.s_addr = htonl(INADDR_ANY); 13 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0) //绑定IP和端口 14 error_die("bind"); 15 if (*port == 0) /* if dynamically allocating a port */ 16 { 17 int namelen = sizeof(name); 18 if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1) 19 error_die("getsockname"); 20 *port = ntohs(name.sin_port);//动态分配端口 21 } 22 if (listen(httpd, 5) < 0) //监听套接字 23 error_die("listen"); 24 return(httpd); //返回套接字 25 }
startup()函数返回套接字后创建多线程,多线程函数起始为accept_request()函数。
accept_request():
1 void accept_request(int client) 2 { 3 char buf[1024]; 4 int numchars; 5 char method[255]; 6 char url[255]; 7 char path[512]; 8 size_t i, j; 9 struct stat st; 10 int cgi = 0; /* becomes true if server decides this is a CGI 11 * program */ 12 char *query_string = NULL; 13 14 numchars = get_line(client, buf, sizeof(buf)); 15 i = 0; j = 0; 16 while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) 17 { 18 method[i] = buf[j]; 19 i++; j++; 20 } 21 method[i] = ‘\0‘; 22 //判断请求方式 23 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) 24 { 25 unimplemented(client); 26 return; 27 } 28 29 if (strcasecmp(method, "POST") == 0) 30 cgi = 1; 31 32 i = 0; 33 while (ISspace(buf[j]) && (j < sizeof(buf))) 34 j++; 35 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) 36 { 37 url[i] = buf[j]; 38 i++; j++; 39 } 40 url[i] = ‘\0‘; 41 //GET方式 42 if (strcasecmp(method, "GET") == 0) 43 { 44 query_string = url; 45 while ((*query_string != ‘?‘) && (*query_string != ‘\0‘)) 46 query_string++; 47 if (*query_string == ‘?‘) 48 { 49 cgi = 1; 50 *query_string = ‘\0‘; 51 query_string++; 52 } 53 } 54 sprintf(path, "htdocs%s", url);//默认路径为工程下的htddocs文件夹下index.html 55 if (path[strlen(path) - 1] == ‘/‘) 56 strcat(path, "index.html"); 57 if (stat(path, &st) == -1) { 58 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 59 numchars = get_line(client, buf, sizeof(buf)); 60 not_found(client); 61 } 62 else 63 { 64 if ((st.st_mode & S_IFMT) == S_IFDIR) 65 strcat(path, "/index.html"); 66 if ((st.st_mode & S_IXUSR) || 67 (st.st_mode & S_IXGRP) || 68 (st.st_mode & S_IXOTH) ) 69 cgi = 1; 70 if (!cgi) 71 serve_file(client, path); //处理静态页面 72 else 73 execute_cgi(client, path, method, query_string); 74 } 75 76 close(client); 77 }
处理静态文件函数serv_file():
发送一个文件到客户端,调用headers()发送响应头。调用cat()函数将一个文件的内容输出到socket。
1 void serve_file(int client, const char *filename) 2 { 3 FILE *resource = NULL; 4 int numchars = 1; 5 char buf[1024]; 6 7 buf[0] = ‘A‘; buf[1] = ‘\0‘; 8 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 9 //读取一行,将以\n或\r\n结束的字符串,转化为以\n\0字符结束 10 numchars = get_line(client, buf, sizeof(buf)); 11 12 resource = fopen(filename, "r"); 13 if (resource == NULL) 14 not_found(client); 15 else 16 { 17 //响应头 18 headers(client, filename); 19 cat(client, resource); 20 } 21 fclose(resource); 22 } 23 void headers(int client, const char *filename) 24 { 25 char buf[1024]; 26 (void)filename; /* could use filename to determine file type */ 27 28 strcpy(buf, "HTTP/1.0 200 OK\r\n"); 29 send(client, buf, strlen(buf), 0); 30 strcpy(buf, SERVER_STRING); 31 send(client, buf, strlen(buf), 0); 32 sprintf(buf, "Content-Type: text/html\r\n"); 33 send(client, buf, strlen(buf), 0); 34 strcpy(buf, "\r\n"); 35 send(client, buf, strlen(buf), 0); 36 } 37 void cat(int client, FILE *resource) 38 { 39 char buf[1024]; 40 41 fgets(buf, sizeof(buf), resource); 42 while (!feof(resource)) 43 { 44 send(client, buf, strlen(buf), 0); 45 fgets(buf, sizeof(buf), resource); 46 } 47 }