首页 > 系统相关 >史上最全最详细的适合新手的从零搭建一个Linux的HTTP服务器

史上最全最详细的适合新手的从零搭建一个Linux的HTTP服务器

时间:2024-06-17 21:32:18浏览次数:25  
标签:HTTP int 最全 sock char html file Linux buff

一.概念梳理

        http(hyper-text-transmission-protocol)超文本传输协议,顾名思义就是传输超文本(html)的协议,具体地来说,我们不需要知道html怎么写,我们只需要梳理服务器的数据接收和响应.具体业务具体分析,你们可以自行丰富内容,这里只做最简单的功能演示.http协议是应用层的协议,我们这里只需要知道http规定的数据包格式,对其进行封装和解析就行了.

二.HTTP数据包分析

         数据包分为请求报文和响应报文.请求报文是浏览器给服务器发送的报文,响应报文是服务器回给浏览器的报文.这很好理解.下面分别解析两种报文.

        HTTP请求报文:

        两种报文有相似之处,都是分行进行包装,每一行结尾都有回车+换行符,我们只需要理解当web浏览器发送过来的报文我们如何去解析,怎么去获取每一行的信息.

第一行: 请求方法---浏览器想要服务器做什么动作 列举几个常用的方法,不过本文只使用了GET

  1. GET        
    1. 向服务器请求指定的HTML,并返回实体主体。
  2. POST
    • 向服务器发送数据,并让服务器对数据进行处理并返回.
  3. DELETE
    • 请求服务器删除指定的页面。

URL:资源定位符,也就是你想要取得的html文件名 

第一行主要就是分析这两个关键点

其次请求头部会有若干个 头部字段 这些不重要 , 浏览器自己根据自己的情况发送,只需要知道最后到请求正文时,有两个连续的回车换行符(因为请求头部的最后一行也有回车换行符),这代表着头部的结束.也是界定头部结束的标志.我们解析到这就行了

一般最重要的就是请求方法还有URL

        HTTP请求报文:

HTTP响应报文:

        

         对于响应报文,注意状态码!!!有些对应的头部域名称是必须的!对此我踩了巨大的坑,耗费了我几天时间卡在这......读者们一定注意 在此我总结了所有必要的状态码和头部域名称,一定对应着编写响应报文,不然出错都不知道该从哪找错误!!!     

1. 200 OK

  • Content-Length: 必须包含,告知客户端响应主体的长度。
  • Content-Type: 必须包含,告知客户端响应主体的类型。
  • Last-Modified: 通常包含,表示资源的最后修改时间。

2. 301 Moved Permanently302 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

相关文章

  • Linux – menuconfig讲解
    menuconfig1.简介        menuconfig是一套图像化配置工具,由ncurses库提供软件支持。ncurses库提供了一系列的函数以便使用者调用它们去生成基于文本的用户界面。        menuconfig本身的软件只负责提供menuconfig工作的这一套逻辑,比如说通过上下左右调整......
  • ARM Linux 设备树详细介绍(2)共二篇
    承接上文,第一篇        3.Device&Tree引发的BSP和驱动变更        有了DeviceTree后,大量的板级信息都不再需要,譬如过去经常在arch/arm/plat-xxx和arch/arm/mach-xxx实施的如下事情:        1.注册platform_device,绑定resource,即内存、......
  • ssh-key-deploy:一个在Windows上创建ssh密钥并且自动部署到Linux服务器上的小工具
    ssh-key-deploy简介使用Python编写的一个在Windows上创建ssh密钥并且自动部署到Linux服务器上的小工具。功能特点创建具有自定义名称和可选密码的SSH密钥。列出本地存储的所有SSH密钥。将SSH密钥安全地上传到远程服务器。使用直观的命令行界面进行操作,支持菜单导航。友好......
  • 【Azure App Service】遇见az命令访问HTTPS App Service 时遇见SSL证书问题,暂时跳过证
    问题描述访问AppService的高级工具KUDU站点的URL,遇见无法访问,在通过azwebappdeploy发布时候,也遇见SSL错误(SSL:CERITIFICATE_VERIFY_FAILEDcertificateverifyfailed:unabletogetlocalissuercertificate)问题解答为AZ指令配置,跳过SSL认证,来实现AZWEBAPPDEPLOY......
  • OpenCloudOS 支持 Linux 原生版微信,开启生态新篇章
    如今,微信已成为办公领域、日常生活以及娱乐方面的刚性需求软件。作为一款通用开源操作系统,OpenCloudOS积极地与微信展开Linux平台的适配工作,全方位地满足广大用户的需求。近期,经过数月的开发与测试,OpenCloudOS社区与微信团队实现了OpenCloudOS与Linux原生版微信的适配支......
  • linux在文件夹中查找文件内容
    linux在文件夹中查找文件内容在Linux中,可以通过以下多个途径,在文件夹中查找文件内容:1、使用grep命令:grep-r"要查找的内容"/path/to/folder-r参数表示递归地在文件夹及其子文件夹中搜索。/path/to/folder是要搜索的文件夹路径。2、使用ack命令ack"要查找的内容......
  • linux清除history
    linux清除history要清除Linux系统中的历史记录(history),可以使用以下几种方法:方法1:通过修改.bash_history文件这是最简单直接的方法,但是只会影响当前用户的历史记录。执行以下命令即可清除:1>~/.bash_history2history-c 方法2:使用export命令同样只会影响当前......
  • linux远程访问及控制
    补充:终端:接收用户的指令TTY终端虚拟终端ssh:22端口号,加密。telnet:23端口号,不加密。解释器:shellSSH远程管理SSH(SecureShell)是一种安全通道协议,主要用来实现字符界面的远程登录、远程复制等功能。SSH协议对通信双方的数据传输进行了加密处理,其中包括用户登录时输入的......
  • 从Linux内核设计者的角度看 - 设备驱动的架构设计
    Linux中的设备驱动概念中的设备和驱动指的是啥?  直接说设备驱动其实是比较抽象的,举个例子就特别明了了,比如我们要控制1个led的亮灭,那么led就是设备,控制led运行的软件就是该设备的驱动。也就是说,这里的设备就是现实中的一个电子设备,设备驱动就是控制这个电子设备运行的软件程序......
  • Linux 提权-Capabilities
    本文通过Google翻译Capabilities–LinuxPrivilegeEscalation-Juggernaut-Sec这篇文章所产生,本人仅是对机器翻译中部分表达别扭的字词进行了校正及个别注释补充。导航0前言1什么是Capabilities?2枚举Capabilities2.1枚举Capabilities-手动方法2.1.......