高并发tinywebserver

目录

项目需求

minihttp.c

demo文件夹下的demo.html文件

文件的目录结构

注意事项

读取文件

stat函数

线程创建函数


项目需求

实现一个http服务器项目,服务器启动后监听80端口的tcp连接,当用户通过任意一款浏览器访问我们的http服务器,http服务器会查找用户访问的html页面是否存在,如果存在则通过http协议响应客户端请求,把页面返回给浏览器,浏览器显示html页面,页面不存在,则按照http协议的规定,通知浏览器此页面不存在

minihttp.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include<ctype.h>
#include <sys/stat.h>
#include <arpa/inet.h> 	// 网络地址转换的接口函数
#include <errno.h>
#include <stdlib.h>
#include <pthread.h>

#define IP "0.0.0.0"

#define SERVER_PORT 80
const int debug = 1 ;

void perror_exit(const char * des)
{
	fprintf(stderr, "%s error ,reason : %s\n",des,strerror(errno));
	perror(des);
	exit(1);
}

void do_http_response(int client_sock,const char* path );
void do_http_response1(int client_sock);
void not_found(int client_sock); // 404
void unimplemented(int client_sock); // 500
void bad_request(int client_sock); // 400
void* do_http_request(void* pclient_sock);
int get_line(int sock,char *buf,int size);
int headers(int client_sock,FILE *resource);
void cat(int client_sock,FILE *resource);
void inner_error(int client_sock);




//返回值 : -1 表示读取出错,  = 0 表示读到一个空行 ,>0 成功读取一行  
int get_line(int sock,char *buf,int size)
{
	int cnt =0,len =0 ;char ch ='\0';

// 当这一行的长度 > size的时候,分段读取 
	while(cnt < size - 1  && ch != '\n')
	{
		len = read(sock,&ch,1);  // 往后尝试性的读一个字符
		if(len == 1 )
		{
			if(ch == '\r') continue; // 回车符
			else if(ch == '\n')  break;


			// 这里处理一般的字符
			buf[cnt ++  ] = ch;
		}
		else if(len == -1 ) // 读取 出错
		{
			perror("read failed");
			cnt = -1;
			break; 
		}
		else //read 0 的情况,客户端关闭sock 连接
		{
			cnt = - 1; 
			fprintf(stderr,"client close \n");break;
		}

	}

	if(cnt >= 0 ) 
	buf[cnt] = '\0';
	return cnt ;

}

void not_found(int client_sock)
{
	const char * reply = "HTTP/1.0 404 NOT FOUND\r\n\
	Content-Type:text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html; charset=utf-8\"http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>NOT FOUND</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
<P> 文件不存在! \r\n\
<P>The server could not fulfill your request because the resource specified is unavailabled or nonexistent.\r\n\
</BODY>\r\n\
</HTML>";
	
	int len = write(client_sock,reply,strlen(reply));

	if(debug) fprintf(stdout,reply);

	if(len <= 0 )
	{
		fprintf(stderr,"send reply failed. reason : %s \n",strerror(errno));
	}
}


void unimplemented(int client_sock)
{
	const char * reply = "HTTP/1.0 501 Method Not Implemented\r\n\
	Content-Type:text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html; charset=utf-8\"http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>Method Not Implemented</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
<P> HTTP request Method Not Implemented \r\n\
</BODY>\r\n\
</HTML>";
	
	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout, reply);
	if(len <=  0)
	{
		fprintf(stderr, "send reply failed. reason :  %s\n", strerror(errno));
	}

}


void bad_request(int client_sock)
{
	const char * reply = "HTTP/1.0 400 BAD REQUEST\r\n\
	Content-Type:text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html; charset=utf-8\"http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>BAD REQUEST</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
<P>Your browser send a bad request ! \r\n\
</BODY>\r\n\
</HTML>";
	
	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);
	if(len <= 0 )
	{
		fprintf(stderr,"send reply failed. reason : %s \n",strerror(errno));
	}


}



void inner_error(int client_sock)
{
	const char *reply = "HTTP/1.0 500 Internal Sever Error\r\n\
Content-Type:text/html\r\n\
\r\n\
<HTML lang=\"zh-CN\">\r\n\
<meta content=\"text/html;charset=utf-8\"http-equiv=\"Content-Type\">\r\n\
<HEAD>\r\n\
<TITLE>Inner Error</TITLE>\r\n\
</HEAD>\r\n\
<BODY>\r\n\
	<P>服务器内部出错\r\n\
<BODY>\r\n\
</HTML>";
	
	int len = write(client_sock,reply,strlen(reply));
	if(debug) fprintf(stdout,reply);

	if(len <=0 )
	{
		fprintf(stderr,"send reply failed. readson : %s \n",strerror(errno));
	}


}




void * do_http_request(void *pclient_sock)
{
	int len=0;
	char buf[256];
	char method[64],url[256];
	char path[512];

	int client_sock = *(int*)pclient_sock;


	struct stat st ;


// 读取客户端发送的http请求 
	// 1 读取请求行
	len = get_line(client_sock,buf,sizeof(buf)); // 从client_sock 读到buf中

	if(len > 0 )  // 读到了请求行
	{
		int i=0 ,j = 0 ;
		while(!isspace(buf[j]) && i <  sizeof(method ) -1  ) 
		// 不是白空格,当前method 还放得下
		{
			method[i] = buf[j];i ++ ; j ++; 
		} 
		method[i] = '\0';
		if(debug)
		printf("request method : %s \n" ,method);
// strncasecmp()  比较两字符串 是否相等,不区分大小写,最后面是指定的长度
		if(strncasecmp(method,"GET",strlen(method)) == 0 ) // 只处理 get 请求 ,不区分大小写来比较
		{
			if(debug) printf("method = GET \n" );




			// 获取URL
			while(isspace(buf[j ++ ])) ; // 跳过多余的空格
			i = 0 ;

			while(!isspace(buf[j]) && i <sizeof(url) - 1 )
			{
				url[i] = buf[j] ;i ++ ; j ++ ; 
			}		
			url[i] = '\0';

			if(debug) printf("url : %s \n",url);




			// 继续读取 http 的头部
			do
			{
				len = get_line(client_sock,buf,sizeof(buf));
				if(debug) printf("read : %s \n",buf);

			}while(len > 0 );



			// 定位服务器本地的html 文件
			// 处理url 中的 ? 
			{
				char * pos = strchr(url,'?'); 
				if(pos) 
				{
						*pos = '\0'; 
	// 如果找到了,改为字符串结束符
				printf("real url : %s \n", url);
				}
			}
			sprintf(path,"./demo/%s",url);
			// sprintf(path,"./%s",url);

			if(debug) printf("path : %s \n",path);





			// 执行 http 响应 
//判断 文件是否存在 ,如果存在就响应200 OK, 同时发送相应HTML文件,如果不存在响应404 NOT FOUND.
			if(stat(path,&st) )  // 文件不存在或出错 返回 -1
			{
				not_found(client_sock);
				if(debug)printf(" ------------------------------------------------- ------ -- - -- - - -1323 \n");
				fprintf(stderr, "stat %s failed. reason :%s \n",path,strerror(errno));
			}
			else  // 文件存在
			{
				if(S_ISDIR(st.st_mode)) // 如果是目录
				{
					strcat(path,"/index.html");
				}


				do_http_response(client_sock,path);

			}






		}
		else 
// 非get请求,读取http 头部, 并响应客户端 501  Method Not Implemented
		{

			fprintf(stderr,"warning other request [%s] \n",method);
			do{
				len = get_line(client_sock,buf,sizeof(buf));
				if(debug) printf("read : %s \n",buf);
			}while(len > 0 );

			unimplemented(client_sock); // 501 在响应时再实现

		}



		
	}
	else // 请求格式有问题,出错处理
	{
		bad_request(client_sock);
	}

	// 2 高并发 
	close(client_sock);
	if(pclient_sock) free(pclient_sock); // 释放动态分配的内存
	return NULL;
}



void do_http_response1(int client_sock)
{
	// 固定的头部
	const char * main_header = "HTTP/1.0 200 OK\r\n\
	Server : ck Server\r\n\
	Content-Type:text/html\r\n\
	Connection:close\r\n";

	const char * welcomde_content = "\
	<html lang=\"zh-CN\"> \n\
<head>\n\
	<meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\n\
	<title> This is a test </title>\n\
</head>\n\
<body>\n\
<div align=center hight=\"500px\">\n\
<br/><br/><br/>\n\
<h2> hello ,Welcome to vip 骑牛! </h2> <br/><br/>\n\
<from action =\"commit\" method =\"post\">\n\
name: <input type=\"text\" name = \"name\"/>\n\
<br> year: <input type=\"password\"name=\"age\"/>\n\
<br/><br/><br/><input type=\"submit\"value=\"提交\"/>\n\
<input type=\"reset\" value=\"重置\"/>\n\
</from>\n\
</div>\n\
</body>\n\
</html>>\n";

	
	//1. 送main_header
	int len = write(client_sock,main_header,strlen(main_header));
	if(debug) fprintf(stdout, "... do_http_response ... \n" );
	if(debug) fprintf(stdout,"write [%d] : %s ",len,main_header);
	//2. 生成 Content-Lenth行 
	int wc_len = strlen(welcomde_content);
	char send_buf[64];

	len = snprintf(send_buf,64,"Content-Length : %d \r\n\r\n",wc_len);
	len = write(client_sock,send_buf,len);


	if(debug) fprintf(stdout,"write [%d] : %s \n",len,send_buf);


	// 3.发送html文件

	len = write(client_sock,welcomde_content,wc_len);

	if(debug) fprintf(stdout,"write [%d] : %s \n",len ,welcomde_content);

}



// 返回关于 响应文件信息的 http 头部
// 输入 : client_sock - 客户端socket 句柄 
// resource - 文件的句柄 
// 成功返回0,失败返回-1
int headers(int client_sock,FILE * resource)
{
	struct stat st;
	int fileid =0 ;
	char tmp[64];
	char buf[1024] = {0};
// strcat 不断在 最后面追加内容 
	strcpy(buf,"HTTP/1.0 200 OK\r\n");
	strcat(buf,"Server : ck Server\r\n");
	strcat(buf,"Content-Type:text/html\r\n");
	strcat(buf,"Connection:close\r\n");


	fileid = fileno(resource);

	if( fstat(fileid,&st)  ==  -1 )//服务器内部出错 
	{
		inner_error(client_sock); 
		return -1 ;
	}

	snprintf(tmp,64,"Content-Length: %ld\r\n\r\n",st.st_size);
	
	strcat(buf,tmp);


	if(debug) fprintf(stdout,"header : %s \n",buf);

	//send 往socket上送消息
	if( send(client_sock,buf,strlen(buf),0) < 0 )
	{
		fprintf(stderr,"send failed. data : %s , readson : %s \n",buf,strerror(errno));
		return -1;
	} 
	return 0;
}

// 实现将html文件的内容按行读取并发送给客户端

void cat(int client_sock,FILE *resource)
{
	char buf[1024];
	fgets(buf,sizeof(buf),resource);

	while(!feof(resource)) // 当resource没有到达文件的尾部
	{
		int len = write(client_sock,buf,strlen(buf));
		if(len < 0 ) // 发送 body 的过程中出现问题 
		{
			fprintf(stderr, "send body error. reason : %s\n",strerror(errno));

		}

		if(debug) fprintf(stdout,"%s\n",buf);

		fgets(buf,sizeof(buf),resource);

	}

}


void do_http_response(int client_sock,const char * path)
{
	int res= 0; 
	FILE  * resource  = NULL ;
	resource = fopen(path,"r");

	if(resource == NULL )
	{
		not_found(client_sock);return;
	}
	// 1.发送 http头部
	res = headers(client_sock,resource);

	// 2.发送 http body 
	if(!res)  // 前面成功才 发送
	cat(client_sock,resource);


	fclose(resource);

}

int main()
{
	int sock; // 代表信箱
	sock = socket(AF_INET,SOCK_STREAM,0); // 美女创建信箱

	if(sock == -1 ) // 对	socket 异常的处理  
	{
		perror_exit("create socket");
	}

	// 即时释放端口

	{
		int opt = 1;

setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));
	}



	struct sockaddr_in server_addr;
	//清空标签 ,写上地址和端口号
	bzero(&server_addr,sizeof(server_addr)); // 将这个结构体清0

	server_addr.sin_family = AF_INET; // 指定协议家族 IPV4


	inet_pton(AF_INET,IP,&server_addr.sin_addr.s_addr);
	//server_addr.sin_addr.s_addr = htonl(INADDR_ANY)	;
	// 监听本地所有ip地址	
// 绑定ip地址 这里绑定所有的ip地址,将机器字节顺序调整成网络字节顺序

	server_addr.sin_port = htons(SERVER_PORT); // 绑定端口号

	int res;
	// 实现标签贴到信箱上
	res = bind(sock, (struct sockaddr *)&server_addr,sizeof(server_addr));

	if(res ==  -1 )
	{
		perror_exit("bind");
	}



	//同一时间客户端向服务器发起链接的数量
	// 把信箱挂在传达室,这样就可以接收信件了
	res = listen(sock,128);

	if(res == - 1 )
	{
		perror_exit("listen");
	}



	// 万事俱备,只等来信
	printf("等待客户端的连接\n");


	int done =1 ;
	while(done)
	{
		struct sockaddr_in client; // 创建客户端
		int client_sock,len;
		char client_ip[64];
		char buf[256];

		socklen_t client_addr_len;
		client_addr_len = sizeof(client);

		client_sock = accept(sock,(struct sockaddr *)&client,&client_addr_len);

		// 打印客户端ip地址和	端口号
		printf("client ip: %s\t port  : %d\n",
			inet_ntop(AF_INET,&client.sin_addr.s_addr,client_ip,sizeof(client_ip)),
			ntohs(client.sin_port));

		// 处理 http 请求读取客户端发送的数据
		{
			// do_http_request(client_sock);
			// close(client_sock);
		}


		pthread_t id; 
		int * pclient_sock =NULL;
		pclient_sock =(int*)malloc(sizeof(int));
		*pclient_sock = client_sock;
		// 启动线程 处理http 请求
		pthread_create(&id,NULL,do_http_request,(void*)pclient_sock);




	}

	close(sock);

	return 0;
}

demo文件夹下的demo.html文件

<html lang=\"zh-CN\">
<head>
	<meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
	<title> This is a test </title>
</head>

<body>
<div align=center hight=\"500px\">
<br/><br/><br/>
<h2> hello ,Welcome to vip 骑牛! </h2> <br/><br/>
<from action ="commit" method ="post">
name: <input type="text" name = "name"/>
<br> year: <input type="password"name="age"/>
<br/><br/><br/><input type="submit"value="提交"/>
<input type="reset" value="重置"/>
</from>
</div>
</body>

</html>>

文件的目录结构

 

注意事项

编译时要指定库参数 -pthread

gcc minihttp.c -pthread -o http.exe

运行用root

读取文件

文件 = 块文件数据 +Inode 元信息

inode -"索引节点",存储文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等。每个inode都有一个号码,操作系统inode号码来识别不同的文件。 ls -i 查看inode号

stat函数

stat函数
作用:返回文件的状态信息
#include <sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
int  stat(const char *path,struct stat *buf);
int  fstat (int fd,struct stat *buf);
int  lstat(const char *path,struct stat *buf);

作用:返回文件的状态信息

path : 文件的路径

buf: 传入的保存文件状态的指针,用于保存文件的状态

返回值 : 成功返回0,失败返回-1,设置errno

线程创建函数

创建一个新线程,并行的执行任务

#include

int pthread_create(pthread_t * thread,const pthread_attr_t *attr,void *(*star_routine)(void*),void *arg);

返回值 : 成功 : 0,失败 : 错误号

参数 :

pthread_t:当前Linux 中可理解为:typedef unsigned long int pthread_t;

参数1 : 传出参数,保存系统为我们分配好的线程ID

参数2 :通常传NIULL,表示使用线程个默认属性,若想使用具体属性也可以修改该参数

参数3 : 函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束

参数4: 线程主函数执行期间所使用的参数

在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_rountine决定。start_rountine函数接收一个参数,是通过pthread_create的arg参数传递给它的,该参数的类型为void*,这个指针按什么类型解释由调用者自己定义。

start_routine的返回值类型也是void *,这个指针的含义同样由调用者自己定义。start_rountine返回时,这个线程就退出了,其他线程可以调用pthread_join得到start_routinue的返回值

pthread_create成功返回后,新创建的线程ID被填写到thread参数所指向的内存单元。

attr参数表示线程属性,传NULL给attr参数,表示线程属性缺省值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值