作者: Andy. 时间: 2016-11-12 22:45:07
写了一个简单的httpserver。
httpserver和平常的聊天服务器并不相同。http是短链接,在浏览器通过tcp协议连接到服务器后,服务器将浏览器要访问的资源返回给浏览器后就断开连接了。浏览器在解析完html页面后,还要继续访问后续的资源,就会又一个一个的发起连接,请求资源。这种情况使用epoll是很不适合的,因为每个连接很短,epoll的epoll_ctl是很消耗资源的。每个连接很短,所以很不适合。所以多线程并发更适合。
知道选择什么的处理方法就比较简单了。接着分析http的流程:
1、首先,浏览器通过tcp连接到服务器。
2、发送http的头部,request。
3、服务器接收到浏览器的请求后,处理好后加上response的头部再发送给客户端。
4、最后断开连接。
那么,根据上面的流程,首先我们要创建一个监听套接字,处于监听状态。其次进行监听。
其实应该写成守护进程的,但是偷懒了。守护进程参考:Linux守护进程,如果守护进程想要看到输出到终端的信息的话,刚刚那篇文章的底端也有说明。发送信号,处理信号,从管道中读取到终端来占用标准输出设备就行了。
看主函数:
int main(int argc, char * argv[]){ int port = atoi(argv[1]); int socket = create_listen_socket(port); if(socket == 0) exit(-1); accept_st(socket); }
其中create_listen_socket,为创建监听套接字,然后绑定端口进行监听没什么说的。如下:
int create_listen_socket(int port){ int st = socket(AF_INET, SOCK_STREAM, 0); if(st == -1){ perror("create st failed."); return 0; } int on = 1; if(setsockopt(st, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(int))){ perror("set st failed"); return 0; } struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); //addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_addr.s_addr = inet_addr("192.168.1.201"); if(bind(st, (struct sockaddr *)&addr, sizeof(addr)) == -1){ perror("bind failed"); return 0; } if(listen(st, 100) == -1){ perror("listen failed"); return 0; } return st; }
接收套接字。在接收到浏览器的请求后,创建一个线程来处理这个请求,将线程detach。其中套接字的标识符通过堆变量传入。如果是栈的话,有可能线程中还没有使用就被外边释放了。堆变量传入,那么释放在子线程中完成。代码:
void accept_st(int st){ pthread_t thread; int client_st = 0; struct sockaddr_in client_addr; int addr_length = sizeof(client_addr); while(1){ memset(&client_addr, 0, sizeof(client_addr)); client_st = accept(st, (struct sockaddr *)&client_addr, &addr_length); if(client_st == -1){ perror("accept error"); break; }else{ int * temp_st = malloc(sizeof(int)); *temp_st = client_st; pthread_create(&thread, NULL, process_request, temp_st); pthread_detach(thread); } } }
处理的时候就需要知道http协议的头部包含一些什么东西了。简单点儿的方法,在recv(socket, request, sizeof(request), 0);获取到浏览器发过来的数据的时候printf打印出来,多改变几次就能发现浏览器要访问的资源是怎样传过来的。第一行:GET / HTTP/1.1,其中GET空格后到HTTP前面是浏览器要访问的资源的路径。我们提取出去,将该资源读到内存中再加上头部和尾部发送给浏览器就完成了。
其中response也有一点儿规则呢。看这段:
那么只需要将content的长度和content的类型进行设置,再将数据发送给浏览器就可以了。这个就比较,只是很繁琐,但是流程非常的简单,代码如下:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/stat.h> #define HEAD "HTTP/1.0 200 OK\n\ Content-Type: %s\n\ Transfer-Encoding: chunked\n\ Connection: Keep-Alive\n\ Accept-Ranges:bytes\n\ Content-Length:%d\n\n" #define TAIL "\n\n" const char * get_file_type(const char * filename){ int len = strlen(filename); int i; char extension[32]; memset(extension, 0, sizeof(extension)); for (i = 0; i < len; i++) { if (filename[i] == '.') { strncpy(extension, &filename[i + 1], sizeof(extension)); break; } } if (strncmp(extension, "bmp", 3) == 0) return "image/bmp"; if (strncmp(extension, "gif", 3) == 0) return "image/gif"; if (strncmp(extension, "ico", 3) == 0) return "image/x-icon"; if (strncmp(extension, "jpg", 3) == 0) return "image/jpeg"; if (strncmp(extension, "avi", 3) == 0) return "video/avi"; if (strncmp(extension, "css", 3) == 0) return "text/css"; if (strncmp(extension, "dll", 3) == 0) return "application/x-msdownload"; if (strncmp(extension, "exe", 3) == 0) return "application/x-msdownload"; if (strncmp(extension, "dtd", 3) == 0) return "text/xml"; if (strncmp(extension, "mp3", 3) == 0) return "audio/mp3"; if (strncmp(extension, "mpg", 3) == 0) return "video/mpg"; if (strncmp(extension, "png", 3) == 0) return "image/png"; if (strncmp(extension, "ppt", 3) == 0) return "application/vnd.ms-powerpoint"; if (strncmp(extension, "xls", 3) == 0) return "application/vnd.ms-excel"; if (strncmp(extension, "doc", 3) == 0) return "application/msword"; if (strncmp(extension, "mp4", 3) == 0) return "video/mpeg4"; if (strncmp(extension, "ppt", 3) == 0) return "application/x-ppt"; if (strncmp(extension, "wma", 3) == 0) return "audio/x-ms-wma"; if (strncmp(extension, "wmv", 3) == 0) return "video/x-ms-wmv"; return "text/html"; } int get_file_content(char * filename, char ** content){ if(*filename == 0){ *content = malloc(3); *(*content) = '4'; *(*content + 1) = '0'; *(*content + 2) = '0'; return 3; } struct stat t; memset(&t, 0, sizeof(t)); FILE *fd = fopen(filename, "rb"); if (fd != NULL) { stat(filename, &t); *content = malloc(t.st_size); fread(*content, t.st_size, 1, fd); fclose(fd); return t.st_size; }else{ perror("open failed"); printf("%s", filename); *content = malloc(3); *(*content) = '4'; *(*content + 1) = '0'; *(*content + 2) = '0'; return 3; } } int get_content(char * filename, char ** allmsg){ char * content; char head[1024]; int content_length = get_file_content(filename, &content); sprintf(head, HEAD, get_file_type(filename), content_length); int head_length = strlen(head); int tail_length = strlen(TAIL); int all_length = head_length + content_length + tail_length; *allmsg = malloc(head_length + content_length + tail_length); memcpy(*allmsg, head, head_length); memcpy(*allmsg + head_length, content, content_length); memcpy(*allmsg + head_length + tail_length, TAIL, tail_length); free(content); return all_length; } void get_filename(char * http_head, char * file_name){ int i; int start = 0; for(i = 0; i < strlen(http_head); i++){ if(!start && *http_head != ' '){ http_head++; continue; } if(!start && *http_head == ' '){ start = 1; http_head += 2; continue; } if(*http_head == ' ' && start) break; *file_name = *http_head; http_head++; file_name++; } } void * process_request(void * st){ int socket = *(int *)st; char request[1024*10]; memset(request, 0, sizeof(request)); int length = recv(socket, request, sizeof(request), 0); if(length <= 0){ perror("recv failed"); close(socket); return NULL; } char * content; char filename[256]; memset(filename, 0, 256); get_filename(request, filename); length = get_content(filename, &content); send(socket, content, length, 0); //printf("%s\n", filename); free(content); close(socket); //printf("%s\n", request); }
这样子就完成了以个简单的httpserver,专门处理静态资源的。
代码放在了我的github:https://github.com/Mekabetre/SimpleHttpServer
最后测试: