一.概念梳理
http(hyper-text-transmission-protocol)超文本传输协议,顾名思义就是传输超文本(html)的协议,具体地来说,我们不需要知道html怎么写,我们只需要梳理服务器的数据接收和响应.具体业务具体分析,你们可以自行丰富内容,这里只做最简单的功能演示.http协议是应用层的协议,我们这里只需要知道http规定的数据包格式,对其进行封装和解析就行了.
二.HTTP数据包分析
数据包分为请求报文和响应报文.请求报文是浏览器给服务器发送的报文,响应报文是服务器回给浏览器的报文.这很好理解.下面分别解析两种报文.
HTTP请求报文:
两种报文有相似之处,都是分行进行包装,每一行结尾都有回车+换行符,我们只需要理解当web浏览器发送过来的报文我们如何去解析,怎么去获取每一行的信息.
第一行: 请求方法---浏览器想要服务器做什么动作 列举几个常用的方法,不过本文只使用了GET
- GET
- 向服务器请求指定的HTML,并返回实体主体。
- POST:
- 向服务器发送数据,并让服务器对数据进行处理并返回.
- DELETE:
- 请求服务器删除指定的页面。
URL:资源定位符,也就是你想要取得的html文件名
第一行主要就是分析这两个关键点
其次请求头部会有若干个 头部字段 这些不重要 , 浏览器自己根据自己的情况发送,只需要知道最后到请求正文时,有两个连续的回车换行符(因为请求头部的最后一行也有回车换行符),这代表着头部的结束.也是界定头部结束的标志.我们解析到这就行了
一般最重要的就是请求方法还有URL
HTTP请求报文:
HTTP响应报文:
对于响应报文,注意状态码!!!有些对应的头部域名称是必须的!对此我踩了巨大的坑,耗费了我几天时间卡在这......读者们一定注意 在此我总结了所有必要的状态码和头部域名称,一定对应着编写响应报文,不然出错都不知道该从哪找错误!!!
1. 200 OK
- Content-Length: 必须包含,告知客户端响应主体的长度。
- Content-Type: 必须包含,告知客户端响应主体的类型。
- Last-Modified: 通常包含,表示资源的最后修改时间。
2. 301 Moved Permanently
和 302 Found
- Location: 必须包含,告知客户端资源已移动的新地址。
3. 401 Unauthorized
- WWW-Authenticate: 必须包含,告知客户端需要进行的身份验证信息。
4. 403 Forbidden
- 通常不需要特定的头部字段,但可以包含Retry-After,告知客户端在多久后可以重试(尽管这不常见)。
5. 404 Not Found
- 不需要特定的头部字段,但可以包含Content-Type,告知客户端错误页面的类型。
6. 500 Internal Server Error
- 通常不需要特定的头部字段,但可以包含Retry-After,告知客户端在多久后可以重试。
三.源代码解析
这段代码主要是建立TCP连接 作为http服务器解析web来自浏览器的数据包,并且做出响应
不了解tcp具体怎么通信的可以看主页上有tcp通信的详细介绍.
这里的 do_http就是对数据包进行解析且响应
#include <stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <ctype.h>
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <pthread.h>
#define SERVER_PORT 80
#define debug 1
struct stat st;
void not_found(int sock);
int main(int argc,char* argv[]){
int server_sock= socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(SERVER_PORT);
bind(server_sock,(struct sockaddr*)&server_addr,sizeof(server_addr));
listen(server_sock,128);
printf(" 正在listen\n");
while(1){
struct sockaddr_in client_addr;
int client_sock;
char buff[64];
int client_length=sizeof(client_addr);
client_sock=accept(server_sock,(struct sockaddr*)&client_addr,&client_length);
printf("client ip:%s\t port:%d\n ",inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,buff,sizeof(buff)),ntohs(client_addr.sin_port));
do_http(client_sock);
close(client_sock);
}
return 0;
}
这一段是do_http 原理就是解析根据tcp协议传输过来的数据包根据get_line函数逐行进行读取,这跟http协议包的格式相对应,也是一行一行的. 然后根据数据包格式,根据空格的情况分析请求方式,URL,协议版本...且分别存储到数组中
这里我们只解析了请求方式为GET的情况,其他请求方式没做处理,只是做一个初步的了解,读者可以自行扩展...举个例子:
这是一个浏览器的网站输入框,192.168.157.134是博主的虚拟机ip地址,后面的welcome.html就是服务器里存储的html页面,如果敲击回车按钮,web浏览器会向虚拟机发出数据包,这里解析出来就是GET请求,web想要向服务器取得一个名为welcome.html的网站. 接下来的操作就是如何响应这个GET
void* do_http(int sock){
int len=0;
char buff[256];
char url[256];
char method[64];
char path[256];
len=get_line(sock,buff,sizeof(buff));
int i=0,j=0;
if(len>0){
while(!isspace(buff[j]) && i<sizeof(method)-1){
method[i]=buff[j];
i++;
j++;
}
method[i]='\0';
if(debug)printf("request method:%s\n",method);
}
if(strncasecmp(method,"GET",i)==0){
if(debug)printf("method: GET \n ");
while(isspace(buff[j++]));
i=0;
while(!isspace(buff[j]) && i<sizeof(url)-1){
url[i]=buff[j];
i++;
j++;
}
url[i]='\0';
if(debug)printf("url:%s\n",url);
do{
len=get_line(sock,buff,sizeof(buff));
if(debug)printf("read:%s\n",buff);
}while(len>0);
//处理url
{
char* p=strchr(url,'?');
if(p) *p='\0';
printf("actual url:%s\n",url);
}
sprintf(path,"./html_docs/%s",url);
if(debug)printf("path:%s\n",path);
if( stat(path,&st)==-1 ){
//文件不存在
fprintf(stderr,"The file: %s is not exist!\n",path);
not_found(sock);
}else{
//响应
do_responce(sock,path);
}
}else{
fprintf(stderr,"warning! other request:%s\n !",method);
do{
len=get_line(sock,buff,sizeof(buff));
if(debug)printf("read:%s\n",buff);
}while(len>0);
unimplement(sock);
}
return NULL;
}
这是读行函数,逐byte读根据"\r\n"判断是否换行.
int get_line(int sock,char* buff,int size){
int count=0;
char ch='\0';
int len =0;
while(count<size-1 && ch!='\n' ){
len=read(sock,&ch,1);
if(len==1){
if(ch=='\r'){
continue;
}else if(ch=='\n'){
//buff[count]='\0';
break;
}
buff[count]=ch;
count++;
}else if(len==-1) { //读取出错
perror("read error:");
count=-1;
break;
}else { //socket 断开连接
fprintf(stderr,"client error\n");
count=-1;
break;
}
}
if(count>0)buff[count]='\0';
return count;
}
这是not_found函数,比如你服务器里没有这个界面,就会回应一出错的html
这涉及到组装响应头部和主体
根据上图介绍的响应报文数据包 封装头部
这里的HTTP/1.0 404 NOT FOUND 对应版本和状态码
void not_found(int sock){
const char* head="HTTP/1.0 404 NOT FOUND\r\nContect-Type: text/html\r\n\r\n";
FILE * file=NULL;
file=fopen("./html_docs/error.html","r");
if(debug)printf("%s\n",head);
char buff[2048];
strcat(buff,head);
//先读一句
fgets(buff,sizeof(buff),file);
//是否到达文件末尾
while(!feof(file)){
write(sock,buff,strlen(buff));
fgets(buff,sizeof(buff),file);
}
}
这里是对应web浏览器的请求正常的响应 把html文件都放入 文件夹 html_docs中 ,根据请求报文中解析的URL文件夹中找对应的html文件,看是否有,有的话根据do_responce函数对头部和数据主体进行封装,发给web浏览器 这里注意我上面说的 200 ok这个状态码有些头部域是必须包含的
比如Content-Length: 这个字段,一定要计算出数据包的长度 才能正确解析
void do_responce(int sock,const char* path){
FILE * resource=NULL;
resource=fopen(path,"r");
if(resource==NULL){
printf("open file %s\n error!\n",path);
not_found(sock);
return;
}
printf("open file %s\n success!\n",path);
//发送头部
headers(sock,resource);
//发送body
bodys(sock,resource);
fclose(resource);
}
void headers(int sock,FILE* file){
struct stat st1;
char buff[1024]={0};
char temp[64];
const char* head="HTTP/1.0 200 OK\r\nServer:Private Server\r\nContent-Type: text/html\r\nConnection: Close\r\n";
strcpy(buff,head);
//获取文件描述符
int fd=fileno(file);
fstat(fd,&st1);
//文件内容大小
int size=st1.st_size;
sprintf(temp,"Content-Length:%d\r\n\r\n",size);
//往里加头部的最后一行
strcat(buff,temp);
//头部结束
write(sock,buff,strlen(buff));
if(debug)printf("%s\n",buff);
}
void bodys(int sock,FILE* file){
char buff[2048];
//先读一句
fgets(buff,sizeof(buff),file);
//是否到达文件末尾
while(!feof(file)){
int len=write(sock,buff,strlen(buff));
//继续读
fgets(buff,sizeof(buff),file);
}
}
这个函数是模仿web端给出了其他的请求,比如POST,我这里没对POST方法进行解析,直接给出一个报错的html文件给它
void unimplement(int sock){
const char* head="HTTP/1.0 501 Method Not Implement\r\nContect-Type: text/html\r\n\r\n";
FILE * file=NULL;
file=fopen("./html_docs/unimplement.html","r");
if(debug)printf("%s\n",head);
char buff[2048];
strcat(buff,head);
//先读一句
fgets(buff,sizeof(buff),file);
//是否到达文件末尾
while(!feof(file)){
write(sock,buff,strlen(buff));
fgets(buff,sizeof(buff),file);
}
}
这就是所有代码的解析,有些库函数不知道具体含义的,读者自行百度,只需要知道如何使用就好了.
四.运行结果
成功响应结果--web端
服务器端:
失败响应结果--web端(故意少打了一个l)
服务器端:
至此圆满结束.创作不易 希望读者多多点赞收藏支持 谢谢!有问题评论区回复.
标签:HTTP,int,最全,sock,char,html,file,Linux,buff From: https://blog.csdn.net/nananani/article/details/139690908