一个简易的http服务器。
Posted VictorTiper
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个简易的http服务器。相关的知识,希望对你有一定的参考价值。
从代码逻辑我们可以很清晰的看出简单的http服务器锁遵循的框架,以及后台调用的过程
并在此,应用了,多进程,进程间通信的管道,以及网络通信socket的知识
/*************************************************************************
> File Name: httpd.c
> Author:Victor Qi
> Mail:Victor_QiGetFuture@163.com
> Created Time: Fri 25 Mar 2016 12:30:13 AM CST
************************************************************************/
#include<stdio.h>//熟知的标准库
#include<sys/socket.h>//socket.h
#include<sys/types.h>//蕴含着linux系统下你看到的各种非人类却人性化的变量,而这种变量的出现就是原始的变量重命名后得来的
#include<netinet/in.h>//里面蕴含着网络协议结构体,通常是网络编程模型第一步就所需要的初始化
#include<arpa/inet.h>
#include<ctype.h>//判断字符是否满足条件,例如是否为数字,大小写字母什么的
#include<pthread.h>//线程库头文件
#include<sys/wait.h>//针对进程,让进程陷入等待阻塞状态,并且返回进程pid
#include<stdlib.h>//标准库头文件
#include<sys/stat.h>//包含文件结构信息内容
#include<unistd.h>//标准posix文件,包含大量系统调用函数
#include<string.h>//标准字符串头文件
#include<strings.h>//string.h的扩展
#define ISspace(x) isspace((int)(x))
#define SEVER_SIRING "sever:jdbhttpd/0.1.0\\r\\n"
//函数列表
void accept_request(int);//监听http请求,实现服务器处理请求流程
void bad_request(int);//返回个给客户端错误请求,http状态码400
void cat(int,FILE*);//读取服务器某个文件写到socket套接字描述符
void cannot_execute(int);//处理发生在执行cgi程序时出现的错误。
void error_die(const char *);//将错误信息写到perror并退出。
void execute_cgi(int ,const char * ,const char * , const char *);//运行cgi程序的处理,是主要函数
int get_line(int ,char *,int );//读取套接字的遗憾数据,将会车换行等情况作为统一换行符结束
void headers(int ,const char *);//吧http相应的头部写到套接字中进行传输
void not_found(int);//主要处理服务端找不到请求文件的情况
void sever_file(int ,const char *);//调用cat把服务器文件返回个浏览器。
int startup(u_short *);//初始化httpd服务,包括建立套接字,绑定端口,进行监听。
void unimplemented(int);//返回给浏览器表明收到的http请求的method不被支持
//对于轻量级服务器的调用顺序为 main -> startup -> accept_request -> execute_cgi.
//在这里开始实现main()函数
int main(int argc ,char ** argv)
int sever_sock =-1;
u_short port =0;
int client_sock =-1;
struct sockaddr_in client_name;
int client_name_len =sizeof(client_name);
pthread_t newpthread;//定义一个线程变量
sever_sock = startup(&port);//开启httpd服务,并且监听对应端口
print("httpd runing on port %d \\n",port);
for(;;)
//套接字接收到客户端连接请求
client_sock=accept(sever_sock,(struct sockaddr *)&client_name,&client_name_len);//其中(struct sockaddr *) 为通用套接字类型
if(client_sock==-1)
error_die("accept");
if(pthread_create(&newpthread,NULL,accept_request,client_sock)!=0)
perror("pthread_create failed");
close(sever_sock);
return 0;
//包裹函数。
void error_die(const char * error)
perror(error);
exit(1);
int startup(u_short *port)
int httpd=0;
struct sockaddr_in name;
if(httpd== -1)
error_die("socket error");
memset(&name,0,sizeof(name));
name.sin_family =AF_INET;
name.sin_port=htons(*port);
name.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(httpd,(struct sockaddr*)&name,sizeof(name))<0)
error_die("bind error");
if(*port==0)
int namelen=sizeof(name);
if(getsockname(httpd,(struct sockaddr *)&name,&namelen))//动态随机分配端口号
error_die("getsockname error");
*port=ntons(name.sin_port);
if(listen(httpd,5)<0)
error_die("listen");
return httpd;
void accept_request(int client)
char buf[1024];
int numchar;
char method[255];
char url[255];
cahr path[512];
size_t i,j;
int cgi=0;
char *query_string=NULL;
//判断请求文件头
numchar=get_line(client,buf,sizeof(buf));
i=0;j=0;
while(!ISspace(buf[j])&&(i<sizeof(method)-1))
method[i]=buf[j];
++i;
++j;
method[i]='\\0';
//判断是什么请求
if(strcasecmp(method,"GET")&&strcasecmp(method,"POST"))
unimplemented(client);
return ;
if(strcasecmp(method,"POST")==0)
cgi=1;//开启状态位cgi
i=0;
while(ISspace(buf[j])&&(j<sizeof(buf)))
j++;
while(!ISspace(buf[j])&&(i<sizeof(url)-1)&&j<sizeof(buf))
//存下URL值
url[i]=buf[j];
i++;
j++;
url[i]='\\0';//字符串最末尾值
//处理get请求
if(strcasecmp(method,"GET")==0)
query_string=url;
//之所以这么判断是因为get方法的特点是?后面是参数
while((*query_string!='?')&&(*query_string!='\\0'))
query_string++;
sprintf(path,"htdocs%s",url);//将html文件放在htdocs文件夹中
if(path[strlen(path)-1]=='/')
strcat(path,"index.html");
if(stat(path,&st)==-1)
//丢弃headers头信息
while((numchar>0)&&strcmp("\\n",buf))
numchar=get_line(client,buf,sizeof(buf));
not_found(client);//对客户端进行回应
else
if((st.st_mode&S_IFMT)==S_IFDIR)
strcat(path,"index.html");
if((st.st_mode&S_IFUSR)||(st.s_mode&S_IXGRP)||(st.st_mode&S_IXOTH))
cgi=1;
//不是cgi,直接把服务器文件返回,否则执行cgi函数
if(!cgi)
sever_file(client,path);
else
execute_cgi(client,path,method,query_string);
close(client);//http特点: 无连接
void bad_request(int client)
char buf[1024];
//此处回应客户端错误的http请求
sprintf(buf,"HTTP/1.1 400 BAD REQUEST\\r\\n");
send(client,buf,sizeof(buf),0);
sprintf(buf,"Content-type:text/html\\r\\n");
send(client,buf,sizeof(buf),0);
sprintf(buf,"\\r\\n");
send(client,buf,sizeof(buf),0);
sprintf(buf,"<p>your brower send a bad request,");
send(client,buf,sizeof(buf),0);
sprintf(buf,"such as a POST without a Content-length.\\t\\n");
send(client,buf,sizeof(buf),0);
void cat(int client,FILE*resourse)
char buf[1024];
//读取文件信息并写入socket描述字中发送到浏览器端
fgets(buf,sizeof(buf),strlen(buf),0);
while(!feof(resourse))
//返回文件数据并循环发送文件
send(client,buf,sizeof(buf),0);
fgets(buf,sizeof(buf),resourse);
void cannot_execute(int client)
//返回服务器错误
sprintf(buf,"HTTP/1.1 500 Internal Sever Error\\r\\n");
send(client,buf,strlen(buf),0);
sprintf(buf,"Content-type:text/html\\r\\n");
send(client,buf,strlen(buf),0);
sprintf(buf,"\\r\\n");
send(client,buf,strlen(buf),0);
sprintf(buf,"<p>Error prohibited CGI execution.\\r\\n");
send(client,buf,strlen(buf),0);
void execute_cgi(int client,const char *path ,const char * method ,const char *query_string)
char buf[1024];
int cgi_output[2];//pipe 管道标识符,用于进程间通信
int cgi_input[2];
pid_t pid;
int status;
int i;
char c;
int numchar =1;
int content_length=-1;
buf[0]='A';
buf[1]='\\0'
//读取文件头并丢弃 header
if(strcasecmp(method,"GET")==0)//对于strcasecmp函数是比较两个字符串是否相等,不区分大小写
while((numchar>0)&&strcmp("\\n"buf))
numchar=get_line(client,buf,sizeof(buf));//读文件头
else
//获取信息长度
numchar=get_line(client,buf,sizeof(buf));
buf[15]='\\0';
if(strcasecmp(buf,"content_length:")==0)
content_length=atoi(&(buf[16]));获取信息长度
numchar=get_line(client,buf,sizeof(buf));
if(content_length==-1)
bad_request(client);
return;//请求错误
sprintf(buf,"HTTP/1.1 200 OK\\r\\n");
send(client,buf,strlen(buf),0);
if(pipe(cgi_output)<0)
cannot_execute(client);
return;
if(pipe(cgi_input)<0)
cannot_execute(client);
return;
if((pid=fork())<0)
cannot_execute(client);
return ;
if(pid==0)
char meth_env[255];
char querry_env[255];
char length_env[255];
dup2(cgi_input[0],0);//读端
dup2(cgi_output[1],1);//写端
close(cgi_input[1]);
close(cgi_output[0]);
//设置request_method的环境变量
sprintf(meth_env,"REQUEST_METHOD=%s",method);
putenv(meth_env);
if(strcasecmp(method="GET")==0)
sprintf(querry_env,"QUERRY_STRING=%s",query_string);
putenv(querry_env);
else
sprintf(length_env,"Content-type=%d",content_length);
putenv(length_env);
execl(path,path,NULL);//用execl加载cgi程序
exit(0);
else
close(cgi_output[1]);
close(cgi_input[0]);
//接受信息
if(strcasecmp(method,"POST")==0)
for(i=0;i<content_length;i++)
recv(client,&c,1,0);
write(cgi_input[1],&c,1);
while(read(cgi_output[0],&c,1)>0)
send(client,&c,1,0);
close(cgi_output[0]);
close(cgi_input[1]);//关闭管道
waitpid(pid,&status,0);//等待子进程,防止僵死进程
int get_line(int sock,cahr*buf,int size)
int i=0;
char c='\\0';
int n;
//首先标准化buf数组
while((i<size-1)&&(c!='\\n'))
n=recv(sock,&c,1,0);
if(n>0)
if(c=='\\r')
n=recv(sock,&c,1,MSG_PEEK);
if((n>0)&&(c=='\\n'))
recv(sock,&c,1,0);
else
c='\\n';
buf[i]=c;
i++;
else
c='\\n';
buf[i]='\\0';
return i;
void headers(int client ,const char * filename)
char buf[1024];
(void)filename;//检测并显示文件类型
strcpy(buf,"HTTP/1.1 200OK\\r\\n");
send(client,buf,strlen(buf),0);
strcpy(buf,SEVER_SIRING);
send(client,buf,strlen(buf),0);
sprintf(buf,"Content-type:text/html\\r\\n");
send(client,buf,strlen(buf),0);
strcpy(buf,"\\r\\n");
send(client,buf,strlen(buf),0);
void not_found(int client)
//返回http 404协议
char buf[1024];
//先发送http协议头
sprintf(buf, "HTTP/1.0 404 NOT FOUND\\r\\n");
send(client, buf, strlen(buf), 0);
//再发送serverName
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
//再发送Content-Type
sprintf(buf, "Content-Type: text/html\\r\\n");
send(client, buf, strlen(buf), 0);
//发送换行符
sprintf(buf, "\\r\\n");
send(client, buf, strlen(buf), 0);
//发送html主体内容
sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>The server could not fulfill\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "your request because the resource specified\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "is unavailable or nonexistent.\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\\r\\n");
send(client, buf, strlen(buf), 0);
void sever_file(int client,const char * filename)
//返回文件数据
FILE *resource = NULL;
int numchars = 1;
char buf[1024];
buf[0] = 'A';
buf[1] = '\\0';
while ((numchars > 0) && strcmp("\\n", buf)) /* read & discard headers */
numchars = get_line(client, buf, sizeof (buf));
resource = fopen(filename, "r");
if (resource == NULL)
not_found(client);
else
//先返回头部信息
headers(client, filename);
cat(client, resource);
fclose(resource);
void unimplemented(int client)
char buf[1024];
sprintf(buf, "HTTP/1.0 501 Method Not Implemented\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, SERVER_STRING);
send(client, buf, strlen(buf), 0);
sprintf(buf, "Content-Type: text/html\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</TITLE></HEAD>\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "<BODY><P>HTTP request method not supported.\\r\\n");
send(client, buf, strlen(buf), 0);
sprintf(buf, "</BODY></HTML>\\r\\n");
send(client, buf, strlen(buf), 0);
void PrintSocketAddress(const struct sockaddr *address, FILE *stream)
// Test for address and stream
if (address == NULL || stream == NULL)
return;
void *numericAddress; // Pointer to binary address
// Buffer to contain result (IPv6 sufficient to hold IPv4)
char addrBuffer[INET6_ADDRSTRLEN];
in_port_t port; // Port to print
// Set pointer to address based on address family
switch (address->sa_family)
case AF_INET:
numericAddress = &((struct sockaddr_in *) address)->sin_addr;
port = ntohs(((struct sockaddr_in *) address)->sin_port);
break;
case AF_INET6:
numericAddress = &((struct sockaddr_in6 *) address)->sin6_addr;
port = ntohs(((struct sockaddr_in6 *) address)->sin6_port);
break;
default:
fputs("[unknown type]", stream); // Unhandled type
return;
// Convert binary to printable address
if (inet_ntop(address->sa_family, numericAddress, addrBuffer,
sizeof (addrBuffer)) == NULL)
fputs("[invalid address]", stream); // Unable to convert
else
fprintf(stream, "来自%s", addrBuffer);
if (port != 0) // Zero not valid in any socket addr
fprintf(stream, "-%u\\n", port);
//主函数入口,编译时
//gcc -W -Wall -lsocket -lpthread -o httpd httpd.c 在linux 可以去掉-lsocket
以上是关于一个简易的http服务器。的主要内容,如果未能解决你的问题,请参考以下文章