原理:由HTTP客户端发起一个请求,建立一个到服务器指定端口(默认端口:80)的TCP连接。HTTP在此端口监听客户端发送过来的请求。一旦收到请求,服务器向客户端回复一个状态行,比如:“HTTP/1.0 200 OK”,和响应消息。

HTTP协议:

超文本传输协议,是互联网上使用的最广泛的协议,HTTP是一个客户端和服务器端请求和应答的标准(TCP)。

HTTP请求:

HTTP请求由以下部分组成

请求行:请求方法,请求URL,协议版本

请求头:包含若干属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。

空行:

请求体:传递请求参数

小型http服务器

 

 HTTP响应:

HTTP响应由以下部分组成

状态行:协议版本,状态码,状态码描述

响应头部:头部字段名,字段值

空行:

响应正文:响应的参数

小型http服务器

 基本思想:

  1. HTTP协议是基于TCP的通信协议(连接可靠性),实现两台主机不同进程之间的通信(客户端和服务器)。
  2. 服务器接收客户端请求后,分析请求方法(GET(从指定的资源请求数据,参数在URL中)、POST(向指定的资源提交    要被处理的数据,参数在请求正文中)等)。
  3. 拿到请求的URL
  4. 判断资源是否存在,以网页的形式返回给客户端或返回错误。

http_server.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>

#define MAX 1024
#define MAIN_PAGE "index.html"
#define PAGE_404 "wwwziyuan/404.html"

static void usage(const char *proc)
{
	printf("Usage:%s prot\n",proc);
}
//监听套接字
int startup(int port)
{
	//创建socket
	int sock=socket(AF_INET,SOCK_STREAM,0);
	if(sock<0)
	{
		perror("socket");
		exit(2);
	}
	
	int opt=1;
	setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	struct sockaddr_in local;
	local.sin_family=AF_INET;
	local.sin_addr.s_addr=htonl(INADDR_ANY);
	local.sin_port=htons(port);

	//绑定
	if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
	{
		perror("bind");
		exit(3);
	}
	//监听
	if(listen(sock,5)<0)
	{
		perror("listen");
		exit(4);
	}

	return sock;
}
//对文本按行获取
int getLine(int sock,char line[],int len)
{
	char c='a';
	int i=0;
	while(c!='\n'&&i<len-1)
	{
		ssize_t s=recv(sock,&c,1,0);
		if(s>0){
			if(c=='\r'){
				recv(sock,&c,1,MSG_PEEK);
				if(c=='\n'){
					recv(sock,&c,1,0);
				}
				else{
					c='\n';
				}
			}
			line[i++]=c;
		}
		else
		{
			break;
		}
	}
	line[i]='\0';
	return i;
}

//错误响应
void echo_404(int sock)
{
	char line[MAX];
	struct stat st;			   
	stat(PAGE_404,&st);

	sprintf(line,"HTTP/1.0 404 Not Found\r\n");
	send(sock,line,strlen(line),0);
	sprintf(line, "Content-Type:text/html;application/x-csi;application/x-jpg\r\n");
	send(sock,line,strlen(line),0);
	sprintf(line, "Content-Length:%d\r\n",st.st_size);
	send(sock,line,strlen(line),0);
	sprintf(line,"\r\n");
	send(sock,line,strlen(line),0);

	int fd=open(PAGE_404,O_RDONLY);
	sendfile(sock, fd, NULL,st.st_size);
	close(fd);
}


//响应回报文
void echoError(int sock,int status_code)
{
	switch(status_code) 
	{
		case 404:
			echo_404(sock);
			break;
		case 503: 
			break; 
		default: 
			break; 
	}
}

void clearHeader(int sock)
{
	char line[MAX];
	do{
		getLine(sock,line,sizeof(line));
	}while(strcmp(line,"\n"));
}

//GET,PATH EXIST,NO CGI QUERY_STRING IS NULL
int echo_resource(int sock,char *path,int size)
{
	char line[MAX];
	clearHeader(sock);
	int fd = open(path,O_RDONLY);
	if(fd<0)
	{
		return 500;
	}
	
	//区分后缀
	sprintf(line,"HTTP/1.0 200 OK\r\n");
	send(sock,line,strlen(line),0);
	char *p=path+strlen(path)-1;
	while(*p!='.'){
		p--;
	}
	if(strcmp(p,".css")==0)
	{
		sprintf(line,"Content-Type:text/css\r\n");
	}
	else if(strcmp(p,".js")==0)
	{
		sprintf(line,"Content-Type:application/x-javascript\r\n");
	}
	else
	{
		sprintf(line,"Content-Type:text/html;application/x-csi;application/x-jpg\r\n");
	}
	send(sock,line,strlen(line),0);

	sprintf(line,"Content-Length:%d\r\n",size);
	send(sock,line,strlen(line),0);
	sprintf(line,"\r\n");
	send(sock,line,strlen(line),0);

	sendfile(sock,fd,NULL,size);

	close(fd);
	return 200;
}

int exe_cgi(int sock,char *method,char *path,char *query_string)
{
	char line[MAX];
	int content_length=-1;

	char method_env[MAX/16];
	char query_string_env[MAX];
	char content_length_env[MAX];

	if(strcasecmp(method,"GET")==0)
	{
		clearHeader(sock);
	}else{
		do{
			getLine(sock,line,sizeof(line));
			if(strncmp(line,"Content-Length:",16)==0)
			{
				content_length=atoi(line+16);
			}
		}while(strcmp(line,"\n"));
		if(content_length==-1)
		{
			return 400;
		}
	}
	//创建管道,实现进程间通信
	int input[2];
	int output[2];

	pipe(input);
	pipe(output);

	pid_t id=fork();
	if(id<0)
	{
		return 500;
	}else if(id==0){
		//子进程
		close(input[1]);
		close(output[0]);
		
		dup2(input[0],0);
		dup2(output[1],1);

		sprintf(method_env, "METHOD=%s", method);
		putenv(method_env);
		//GET:传递query_string     POST:传递content_len
		if(strcasecmp(method, "GET") == 0)
		{
			sprintf(query_string_env, "QUERY_STRING=%s",query_string);
			putenv(query_string_env);
		}
		else
		{
			sprintf(content_length_env, "CONTENT_LENGTH=%d",content_length);
			putenv(content_length_env);}

		execl(path,path,NULL);
		exit(1);
	}else{
		//父进程
		close(input[0]);
		close(output[1]);
		
		int i=0;
		char c;
		if(strcasecmp(method,"POST")==0)
		{
			for(;i<content_length;i++)
			{
				recv(sock,&c,1,0);
				write(input[1],&c,1);
			}
		}

		sprintf(line,"HTTP/1.0 200 OK\r\n");
		send(sock,line,strlen(line),0);
		sprintf(line,"Content-Type:text/html\r\n");
		send(sock,line,strlen(line),0);
		sprintf(line,"\r\n");
		send(sock,line,strlen(line),0);

		while(read(output[0],&c,1)>0)
		{
			send(sock,&c,1,0);
		}

		waitpid(id,NULL,0);

		close(input[1]);
		close(output[0]);

	}
	return 200;
}

//处理链接请求
void *handlerRequest(void *arg)
{
	printf("get a new client\n");
	int sock=(int)arg;
	//缓冲区
	char line[MAX];
	char method[MAX/16];
	char url[MAX];
	char path[MAX];
	int i=0;
	int j=0;
	int status_code=200;
	int cgi=0;
	char *query_string = NULL;

	//按行获取
	getLine(sock,line,MAX);
	
	while(i<sizeof(method)-1 && j<sizeof(line) && !isspace(line[j]))
	{
		method[i]=line[j];
		i++,j++;
	}
	method[i] = '\0';
	//GET POST  strcasecmp忽略大小写
	if(strcasecmp(method,"GET")==0)
	{

	}else if(strcasecmp(method,"POST")==0){
		cgi=1;
	}else{
		//清理报头
		clearHeader(sock);
		status_code=400;
		goto end;
	}

	//method url http_verdion
	i=0;
	while(j<sizeof(line) && isspace(line[j]))
	{
		j++;
	}
	while(i<sizeof(url)-1 && j<sizeof(line) && !isspace(line[j]))
	{
		url[i]=line[j];
		i++,j++;
	}
	url[i]='\0';

	printf("method: %s,url: %s\n",method,url);
	
	if(strcasecmp(method,"GET")==0)
	{
		query_string=url;
		while(*query_string)
		{
			if(*query_string=='?')
			{
				*query_string='\0';
				query_string++;
				cgi=1;
				break;
			}
			query_string++;
		}
	}
	//method,url[path,query_string(GET)]
	
	sprintf(path,"wwwziyuan%s",url);

	if(path[strlen(path)-1]=='/')
	{
		strcat(path,MAIN_PAGE);
	}

	printf("method: %s,url: %s,query_string:%s\n",method,path,query_string);

	struct stat st;
	if(stat(path,&st)<0)
	{
		clearHeader(sock);
		status_code=404;
		goto end;
	}else{
		if(S_ISDIR(st.st_mode))
		{
			strcat(path,"/");
			strcat(path,MAIN_PAGE);
		}else if((st.st_mode & S_IXUSR)||\
				(st.st_mode & S_IXGRP)||\
				(st.st_mode & S_IXOTH)){
			cgi=1;
		}else{
			//do nothing
		}

		//method(GET,POST),path(EXIST),cgi(0|1),query_string(GET)
		if(cgi)
		{
			status_code=exe_cgi(sock,method,path,query_string);
		}
		else
		{
			status_code=echo_resource(sock,path,st.st_size);
		}
	}
	

end:
	if(status_code!=200)
	{
		echoError(sock,status_code);
	}

	close(sock);
}

// ./httpd  8080
int main(int argc,char *argv[])
{

	if(argc!=2)
	{
		usage(argv[0]);
		return 1;
	}
	int listen_sock=startup(atoi(argv[1]));

	for( ; ;)
	{
		struct sockaddr_in client;
		socklen_t len=sizeof(client);
		int sock=accept(listen_sock,(struct sockaddr*)&client,&len);
		if(sock<0)
		{
			perror("accept");
			continue;
		}

		//创建线程
		pthread_t tid;
		pthread_create(&tid,NULL,handlerRequest,(void *)sock);
		//分离线程,防止线程卡在这里
		pthread_detach(tid);

	}
	return 0;
}

Makefile

cc=gcc
src=http_server.c
bin=httpd

$(bin):$(src)
    $(cc) -o [email protected] $^ -lpthread
.PHONY:clean
clean:
       rm -f $(bin)

 

相关文章:

  • 2021-08-23
  • 2021-11-13
  • 2021-09-11
  • 2021-11-13
  • 2021-11-04
  • 2021-11-15
猜你喜欢
  • 2021-11-23
  • 2021-06-10
  • 2021-11-13
  • 2021-12-16
  • 2021-11-04
  • 2021-11-13
相关资源
相似解决方案