HTTP服务器框架代码分析(三)

HTTP服务器框架分析(一)
HTTP服务器框架分析(二)

1、头文件
#define SIZE 1024
typedef struct Request
{
	char first_line[SIZE];
	char *method;
	char *url;
	char *url_path;
	char *query_string;
	int content_length;
}Request;
2、Makefile文件和nohup(httpserver.sh文件)?
httpserver:httpserver.c
	gcc $^ -o $@ -lpthread
	
.PHONY:clear
clean:
	rm httpserver
while [ 1 ]
do
ps -ef | grep ./httpserver 0 9090 | grep -v grep
if [ $? -ne 0 ]
then
	echo "go,开始执行!!"
./httpserver 0 9090
else
	echo "runing!!"
fi
	sleep 2000
done
3、初始化服务器?
void HttpServerStart(const char *ip, short port)
{
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if(listen_sock < 0)
	{
		perror("socket");
		return;
	}
	
	int opt = 1;
	setsocketopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
	
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = inet_addr(ip);
	addr.sin_port = htons(port);
	int ret = bind(listen_sock, (sockaddr *)&addr, sizeof(addr));
	if(ret < 0)
	{
		printf("Binf Failed.\n"):
		perror("bind"):
		return;
	}
	
	ret = listern(listen_sock, 5);
	if(ret < 0)
	{
		perror("listen");
		return;
	}
	printf("Serverlint OK!!\n");
	while(1)
	{
		sockaddr_in peer;
		socklen_t len = sizeof(peer);
		int64_t new_sock = accept(listen_sock, (sockaddr *)&peer, &len);
		if(new_sock < 0)
		{
			perror("accept");
			return;
		}
		
		//使用多线程实现TCP服务器
		pthread_t tid;
		pthread_create(&tid, NULL, ThreadEntry, (void *)new_sock);
		pthred_detach(tid);
	}
}

void *ThreadEntry(void *arg)
{
	int64_t new_sock = (int64_t)grg;
	//此处使用HandlenRequest函数完成具体的处理请求过程
	//解耦合
	HandlenRequest(new_sock);
	return NULL;
}
4、逻辑层(自顶向下)?
void HandlenRequest(int new_sock)
{
	int err_code = 200;
	//反序列化,把字符串解析成request对象
	Request req;
	memset(&req, 0, sizeof(req));
	//从socket读取首行
	if(ReadLine(new_sock, req.first_line, sizeof(req.first_line))<0)
	{
		err_code = 404;
		goto END;
	}
	
	//解析首行
	if(ParseFirstLine(req.first_line, &req.url, &req.method))
	{
		err_code = 404;
		goto END;
	}
	
	//解析URL
	if(ParseQueryString(req.url, &req.url_path, &req.query_string))
	{
		err_code = 404;
		goto END;
	}
	
	//处理Header,只关心content-length
	if(ParseHeader(new_sock, &req.content_length))
	{
		err_code = 404;
		goto END;
	}
	
	//打印日志
	printf("method:%s\n", req.method);
	printf("url_path:&s\n", req.url_path);
	printf("query_string:%s\n", req.query_string);
	printf("content_length:%d\n", req.content_length);
	printf("\n");
	
	//静态请求、动态请求
	
	if(strcasecmp(req.method, "GET") == 0 && req.query_string == NULL)
	{
		//静态页面
		err_code = HandlerStaticFile(new_sock, &req);
	}
	
	else if(strcasecmp(req.method, "GET") == 0 && req.content_length)
	{
		err_code = HandlerCGI(new_sock, &req);
	}
	
	else if(strcasecmp(req.method, "POST") == 0)
	{
		err_code = HandlerCGI(new_sock, &req);
	}
	
	else
	{
		err_code = 404;
		goto END;
	}
	
	END:
	if(err_code != 200)
	{
		Handler404(new_sock);
	}
	close(new_sock);
	//如果服务器先断开,会出现大量time_wait
}
5、404页面?
void Handler404(int sock)
{
	//构造完整的HTTP响应
	const char *type_line = "Content-Type:test/html;charset=utf-8\n";
	const char *first_line = "HTTP/1.1 404 Not Found\n";
	const char *blank_line = "\n";
	const char *html = "
	<head><meta http-equiv=\"content-type\"content=\"text/html;charset=utf-8\"><\head>"
	"<h2>你的页面找不到了</h2>";
	
	send(sock, first_line, strlen(first_len), 0);
	send(sock, type_line, strlen(type_line), 0);
	send(sock, blank_line, strlen(blank_line), 0);
	send(sock, html, strlen(html), 0);
	
	return;
}
6、解析首行?

int ParseFirstLine(char first_line[], char **p_url, char **p_method)
{
	char *tok[10];
	int tok_size = Split(first_line, " ", tok, 10);
	if(tok_size != 3)
	{
		printf("Split failed!!\n");
		return -1;
	}
	*p_method = tok[0];
	*p_url = tok[1];
	return 0;
}
7、字符串切分函数?
int  Split(char input[], char *split_char, char *output[], int output_size)
{
	char *pch;
	int i = 0;
	char *tmp = NULL;
	pch = strtok_r(intput, split_char, &tmp);
	while(pch != NULL)
	{
		if(i >= output_size)
		{
			return i;
		}
		output[i++] = pch;
		pch = strtok_r(NULL, split_char, &tmp);
	}
	return i;
}

strtok_r函数是线程安全的,如果在多线程环境下使用strtok函数,线程不安全函数,容易造成程序崩溃。
但是如果加锁,意味着不同的线程间竞争锁,大部分时间在等待锁,导致不必要的等待,程序被拖慢。

8、怎么读取首行?
int ReadLine(int sock, char buf[], ssize_t size)
{
	//一次读一行,将数据读到buf缓冲区
	//从socket中读,一次读一个字符,读到换行(\n,\r,\r\n)就返回
	
	char c = '\0';
	ssize_t i = 0;      //当前读了多少个字符了
	while(i<size-1 && c!='\n')
	{
		//结束条件:超过缓冲区长度,读到换行
		//还要兼容\r\n
		ssize_t read_size = recv(sock, &c, 1, 0);
		if(read_size < 0)
			return -1;
		
		if(read_size == 0)
			return -1;     //为了读换行,结果读到了EOF,所以失败
		
		if(c == '\r')
		{
			//遇到\r,还要确定下一个是不是'\n'
			recv(sock, &c, 1, MSG_PEEK); 
			//从缓冲区读后,读完一个字符,不会从缓冲区删除这个字符
			if(c == '\n')
			{
				//此时就是\r\n作为换行的
				recv(sock, &c, 1, 0);
			}
			else
			{
				//确定了是\r,就将\r转化成\n
				c = '\n';
			}
		}
		//把\r,\r\n统一转化成\n
		buf[i++] = c;
	}
	buf[i] = '\0';
	return i;
}
9、CGI主逻辑?
int HandlerCGI(int new_sock, Request *req)
{
	int err_code = 200;
	
	int fd1[2];
	int fd2[2];
	int ret = pipe(fd1);
	if(ret < 0)
		return 404;
	ret = pipe(fd2);
	if(ret < 0)
	{
		close(fd1[0]);
		close(fd1[1]);
		return 404;
	}
	
	int father_read = fd1[0];
	int child_write = fd1[1];
	int father_write = fd2[0];
	int father_read = fd2[0];
	
	ret = fork();
	
	if(ret > 0 )
	{
		//父进程
		close(child_read);
		close(chile_write);
		err_code = HanderCGIFather(new_sock, father_read, father_write, ret, req)
	}
	
	else if
	{
		//子进程
		close(child_write);
		close(child_read);
		err_code = HandlerCGIChild(child_read, child_write, req);
	}
	
	else
	{
		perror("fork");
		err_code = 404;
		goto END;
	}
	END:
	close(fd1[0]);
	close(fd1[1]);
	close(fd2[0]);
	close(fd2[1]);
}
10、FatherCGI部分?
int HandlerCGIFather(int new_sock, int father_read, int father_read,
     int father_write, int child_pid, Request *req)
	 {
		 //把请求部分的内容写到管道,POST就是body部分
		 if(strcasecmp(req->method, "POST") == 0)
		 {
			 int i = 0;
			 char c = '\0';
			 
			 for(; i< req->content_length; ++i)
			 {
				 read(new_sock, &c, 1);
				 write(father_write, &c, 1);
			 }
		 }
		 
		 //构造HTTP响应
		 const char *first_line = "HTTP/1.1 200 OK\n";
		 send(new_sock, first_line, strlen(first_line), 0);
		 
		 const char *type_line = "Content-Type:text/html;charset=utf-8\n";
		 send(new_sock, type_line, strlen(type_line), 0);
		 
		 const chat *blank_line = "\n";
		 send(new_sock, blank_line, strlen(blank_line), 0);
		 
		 //循环从管道读取数据并写入到socket
		 char *c = '\0';
		 while(read(father_read, &c, 1) > 0)
		 {
			 send(new_sock, &c, 1, 0);
		 }
		 //read<=0,读到文件结束
		 //对于管道,写端全部关闭,再继续读,read返回0
		 //对于管道,读端全部关闭,再继续写,产生GIGPIPE信号
		 
		 //子进程回收
		 //wait是无差别等待,等待任意子进程,所以不能用
		 waitpid(child_pid, NULL, 0);
		 return 200;
	 }
11、ChildCGI部分?
 int HanaderCGIChild(int child_read, int child_write, Request *req)
	 {
		 char method_env[SIZE] = {0};
		 sprintf(method_env, "REQUEST_METHOD=%s", req->method);
		 putenv(method_env);
		 
		 if(strcasecmp(req->method, "GET") == 0)
		 {
			 char query_string_env[SIZE]  = {0};
			 sprintf(query_string_env, "QUERY_STRING=%s", req->query_string);
			 putenv(query_string_env);
		 }
		 
		 else
		 {
			 cahr content_length_env[SIZE] = {0};
			 sprintf(content_length_env, "CONTENT_LENGTH+%d", req->content_length);
			 putenv(content_length_env);
		 }
		 
		 //把标准输入、标准输出重定向到管道中
		 dup2(child_read, 0);
		 dup2(child_write, 1);
		 
		 //对子进程进行程序替换
		 char file_path[SIZE] = {0};
		 HandlerFilePath(req->url_path, file_path);
		 
		 execl(file_path, file_path, NULL);
		 exit(1);
		 
		 return 200;
	 }

如果需要项目源码,请在留言区备注邮箱。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

askunix_hjh

谢谢请我吃糖~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值