原理:由HTTP客户端发起一个请求,建立一个到服务器指定端口(默认端口:80)的TCP连接。HTTP在此端口监听客户端发送过来的请求。一旦收到请求,服务器向客户端回复一个状态行,比如:“HTTP/1.0 200 OK”,和响应消息。
HTTP协议:
超文本传输协议,是互联网上使用的最广泛的协议,HTTP是一个客户端和服务器端请求和应答的标准(TCP)。
HTTP请求:
HTTP请求由以下部分组成
请求行:请求方法,请求URL,协议版本
请求头:包含若干属性,格式为“属性名:属性值”,服务端据此获取客户端的信息。
空行:
请求体:传递请求参数
HTTP响应:
HTTP响应由以下部分组成
状态行:协议版本,状态码,状态码描述
响应头部:头部字段名,字段值
空行:
响应正文:响应的参数
基本思想:
- HTTP协议是基于TCP的通信协议(连接可靠性),实现两台主机不同进程之间的通信(客户端和服务器)。
- 服务器接收客户端请求后,分析请求方法(GET(从指定的资源请求数据,参数在URL中)、POST(向指定的资源提交 要被处理的数据,参数在请求正文中)等)。
- 拿到请求的URL
- 判断资源是否存在,以网页的形式返回给客户端或返回错误。
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)