博客文章

一个简单的httpserver

作者: 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也有一点儿规则呢。看这段:

blob.png

那么只需要将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

最后测试:

QQ截图20161112225454.png