目录
项目需求
实现一个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参数,表示线程属性缺省值