一、浏览器与服务器通信过程


        浏览器与web服务器在应用层通信使用的是HTTP协议80号端口(超文本传输协议)(https443端口),HTTP协议在传输层使用的是TCP协议,那么浏览器需要和web服务器三次握手建立连接后,才可以发送HTTP请求报文,服务器收到请求报文后,向浏览器回复HTTP应答报文。

 

        IP 地址是由数字组成的,例如 192.168.1.1 ,难以记忆。而域名如 baidu.com 更便于人们记忆和使用。用户在浏览器地址栏输入域名,就能访问对应的网站或服务器,无需记住复杂的 IP 地址。

1.1 域名解析与建立连接

例如:浏览器要访问 “www.baidu.com”。

        域名解析:通过 DNS(域名系统)将域名解析为对应的 IP 地址。

        连接请求:使用 “connect()” 函数,指定解析出的 IP 地址和 HTTP 协议默认端口 “80” (现在也常用 443 端口用于 HTTPS )来发起连接请求。

        建立连接:浏览器与 HTTP 服务器通过三次握手建立 TCP 连接。三次握手过程为:客户端发送 SYN 报文段;服务器收到后,回复 SYN + ACK 报文段;客户端再发送 ACK 报文段,至此连接建立成功。

1.2 数据交互 

        浏览器发送请求报文:连接建立后,浏览器向 HTTP 服务器发送 HTTP 请求报文,请求报文中包含请求方法(如 GET、POST 等 )、请求资源路径、协议版本等信息。

        服务器应答报文:服务器接收到请求报文后,进行处理并回复 HTTP 应答报文,应答报文中包含状态码(如 200 表示成功 )、响应头(包含内容类型、长度等信息 )和响应体(如网页内容 )。

1.3 连接管理

        短连接:数据交互完成后,通过 “close” 关闭连接。这种方式适用于一次简单的请求 - 响应交互场景,每次请求都要重新建立和关闭连接,开销相对较大。

        长连接:保持连接不关闭,后续浏览器可在该连接上继续向服务器发送请求,避免了重复建立连接的开销,适用于需要频繁交互的场景,如网页中包含多个资源需多次请求的情况。  

二、HTTP请求报头 

        HTTP请求报头的主要作用是为服务器提供关于客户端请求的详细信息,以便服务器能够正确地处理请求并返回合适的响应。这些报头信息对于确保HTTP通信的有效性和安全性至关重要。一个完整的HTTP请求由请求行、请求报头、空行和请求体(对于某些请求方法如POST)组成。 

2.1 请求行

        请求行包括三个部分:HTTP方法、请求的资源路径(URI)和HTTP协议版本。它们之间用空格分隔。

例如:GET /index.html HTTP/1.0\r\n

        GET:这是一个HTTP方法,表示请求获取资源。其他常用的HTTP方法还包括POST(提交数据)、PUT(更新资源)、DELETE(删除资源)等。

        /index.html:这是请求的资源路径,表示客户端希望从服务器获取/index.html这个文件。

        HTTP/1.0:这是HTTP协议的版本,表示客户端使用的是HTTP 1.0版本。HTTP协议已经发展到1.1和2版本,每个版本都有一些改进和新特性。

        \r\n:这是回车换行符,用于分隔HTTP请求的各个部分。在HTTP协议中,回车换行符(CRLF)是一个重要的分隔符。

2.2 请求报头

        请求报头由多个字段组成,每个字段都包含一个名称和一个值,名称和值之间用冒号(:)分隔,字段之间用换行符(CRLF,即\r\n)分隔。请求报头提供了客户端环境、请求体信息、缓存控制等数据。

        1.User-Agent:这个头部字段告诉服务器,发起请求的客户端应用程序是什么。在这个例子中,Wget/1.12表示这是一个使用Wget工具(一个命令行下载工具)发起的请求,版本是1.12。

        2.Host:这个头部字段指定了请求的目标主机。在这个例子中,192.168.141.128是目标服务器的IP地址。这个字段在服务器使用虚拟主机时特别重要,因为它帮助服务器确定请求应该路由到哪个网站。

        3.Connection:这个头部字段指定了连接的管理方式。在这个例子中,close表示请求完成后,服务器应该关闭连接。另一个常用的值是keep-alive,表示服务器应该保持连接打开,以便可以复用这个连接来发送更多的请求。

2.3 空行

        请求报头之后是一个空行,用于分隔请求报头和请求体(如果有的话)。这个空行不包含任何内容,仅由CRLF组成。(\r\n)。

2.4 请求体

        请求体包含客户端向服务器发送的数据。请求体通常用于POST、PUT等方法,GET请求通常不包含请求体。

        1.GET:

                用于请求指定资源的数据。

                不应包含请求体(请求体在GET请求中被忽略)。

                可被缓存、收藏为书签和保留历史记录。

        2.POST:

                用于向指定资源提交数据,请求服务器进行处理(例如提交表单或上传文件)。

                可以包含请求体,其中包含要提交给服务器的数据。

                不可被缓存,也不会保存到历史记录中。

        3.PUT:

                上传某个资源

                可以包含请求体,其中包含要更新的资源数据。

                有幂等性,多次执行相同操作结果相同。

        4.DELETE:

                删除指定的资源。

                可以包含请求体,尽管通常不包含。

                有幂等性。

        5.HEAD:

                类似于GET请求,但不返回响应体。

                用于获取资源的元数据,如响应头。

                不应包含请求体。

        6.OPTIONS:

                用于描述目标资源的通信选项。

                可以包含请求体。

        7.PATCH:

                用于对已知资源进行部分修改。

                可以包含请求体,其中包含要应用的部分修改。

                不像PUT那样要求幂等性。

        8.CONNECT:

                用于将连接改为管道方式的代理服务器。

                通常用于SSL加密服务器的连接(通过HTTP隧道技术)。

                可以包含请求体。

三、HTTP应答报头

3.1 http应答报文头部信息

1. 状态行

HTTP/1.0 200 OK\r\n

        HTTP/1.0:指定了HTTP协议的版本,这里是1.0版本。

        200:状态码,表示请求已成功处理。

        OK:状态码的描述,进一步说明了状态码的含义。在这里,它表明请求成功并且服务器返回了请求的资源。

        \r\n:回车换行符,用于分隔状态行和后续的响应报头。

2. 服务器名称

Server: MYWEB/1.0\r\n

        Server:这是一个通用报头字段,用于指定处理请求的服务器软件的信息。

        MYWEB/1.0:服务器软件的名称和版本。在这个例子中,服务器软件名为MYWEB,版本为1.0。

3. 数据长度

Content-Length: 8024\r\n

        Content-Length:这是一个实体报头字段,用于指定实体主体(即响应体)的长度,单位是字节。

        8024:响应体的长度为8024字节。

4. 内容类型

Content-Type: text/html;charset=utf-8\r\n

        Content-Type:这是一个实体报头字段,用于指定实体主体的媒体类型。

        text/html:指定响应体的媒体类型为HTML文档。

        charset=utf-8:指定字符编码为UTF-8,这意味着响应体中的文本数据使用UTF-8编码。

5. 分隔符

\r\n

        这个回车换行符(CRLF)用于分隔HTTP响应报头和响应体。它标志着响应报头的结束和响应体的开始。

总的来说,这个HTTP响应报文头部提供了关于响应的基本信息,包括协议版本、状态码、服务器软件信息、响应体长度和内容类型。这些信息对于客户端(如浏览器)正确解析和处理响应内容至关重要。

3.2 应答状态

        HTTP应答状态码是服务器对客户端请求的响应结果的数字代码。这些状态码帮助客户端理解请求是否成功,以及如果失败,失败的原因是什么。

HTTP状态码分为五类,每类状态码都表示不同的结果类型:

1. 信息性状态码(100-199)

这些状态码表示临时的响应,请求正在处理中,并且客户端可能需要执行一些操作才能继续。

        100 Continue:服务器已收到请求的初始部分,客户端可以继续发送请求的剩余部分。

        101 Switching Protocols:服务器同意客户端的请求,将交换协议,例如切换到WebSocket协议。

        102 Processing:服务器已收到并正在处理请求,但没有响应可用。

2. 成功状态码(200-299)

这些状态码表示请求已成功处理。

        200 OK:请求成功,服务器返回请求的资源。

        201 Created:请求成功,服务器创建了新的资源。

        202 Accepted:请求已接受,但尚未处理完成。

        204 No Content:服务器成功处理了请求,但没有返回任何内容。

        206 Partial Content:服务器成功处理了部分内容请求。

3. 重定向状态码(300-399)

这些状态码表示请求的资源已被移动到新的URL,客户端需要使用新的URL重新发起请求。

        300 Multiple Choices:请求的资源有多个响应可供选择。

        301 Moved Permanently:请求的资源已被永久移动到新的URL。

        302 Found:请求的资源临时移动到新的URL。

        304 Not Modified:自从上次请求后,资源没有被修改,客户端可以使用缓存的版本。

        307 Temporary Redirect:请求的资源临时移动到新的URL,但应使用原始请求方法发起新请求。

4. 客户端错误状态码(400-499)

这些状态码表示客户端请求有错误,服务器无法处理。

        400 Bad Request:服务器无法理解客户端的请求。

        401 Unauthorized:请求需要用户的身份验证。

        403 Forbidden:服务器理解请求,但拒绝执行。

        404 Not Found:服务器找不到请求的资源。

        405 Method Not Allowed:请求方法(如POST、GET等)不被允许。

5. 服务器错误状态码(500-599)

这些状态码表示服务器在处理请求时遇到错误,无法完成请求。

        500 Internal Server Error:服务器遇到意外情况,无法完成请求。

        503 Service Unavailable:服务器暂时过载或维护,无法处理请求

四、用C语言实现web服务器

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<fcntl.h>

#define PATH "/home/zgl/mycode/day22"

int socket_init();
char* get_filename(char buff[])
{
    char* ptr=NULL;
    char* s=strtok_r(buff," ",&ptr);
    if(s==NULL)
    {
        return NULL;
    }
    printf("请求方法:%s\n",s);
    s=strtok_r(NULL," ",&ptr);
    return s;
}
void* fun(void* arg)
{
    int* p=(int*) arg;
    int c=*p;
    free(p);

    while(1)
    {
        int buff[1024]={0};
        int n=recv(c,buff,1023,0);//浏览器发送过来的请求报文
        if(n<=0)
        {
            break;
        }

        printf("recv: %s\n",buff);
        char* filename = get_filename(buff);//获取请求报文中文件的名字
        if(filename == NULL)
        {
            send(c,"404",3,0);
            break;
        }
        char path[128] = {PATH};
        if(strcmp(filename,"/") == 0)
        {
            strcat(path,"/index.html");
        }
        else
        {
            strcat(path,filename);
        }
        printf("path:%s\n",path);//组装文件的绝对路径

        int fd=open(path,O_RDONLY);
        if(fd==-1)
        {
            send(c,"404",3,0);
            break;
        }

        int filesize = lseek(fd,0,SEEK_END);//获取文件大小
        lseek(fd,0,SEEK_SET);

        char head[128] = {"HTTP/1.1 200 OK\r\n"};//
        strcat(head,"Server: myhttp\r\n");
        sprintf(head+strlen(head),"Content-Length: %d\r\n",filesize);
        strcat(head,"\r\n");//组装http应答报文
        send(c,head,strlen(head),0);//发送http应答报文的报头给浏览器

        char data[1024];
        int num=0;
        while((num = read(fd,data,1024)) >0)
        {
            send(c,data,num,0);//循环发送文件的数据,直到发完
        }
        close(fd);
    }
    close(c);
}

int main()
{
    int sockfd=socket_init();
    if(sockfd==-1)
    {
        exit(1);
    }
    while(1)
    {
        struct sockaddr_in caddr;
        int len =sizeof(caddr);
        int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
        if(c<0)
        {
            continue;
        }

        printf("accept c=%d\n",c);
        int* p=(int*)malloc(sizeof(int));
        if(p==NULL)
        {
            close(c);
            continue;
        }
        *p=c;
        pthread_t id;
        pthread_create(&id,NULL,fun,p);
    }
}

int socket_init()
{
    int sockfd=socket(AF_INET,SOCK_STREAM,0);
    if(sockfd==-1)
    {
        return -1;
    }

    struct sockaddr_in saddr;
    memset(&saddr,0,sizeof(saddr));
    saddr.sin_family=AF_INET;
    saddr.sin_port=htons(80);//http端口
    saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
    int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
    if(res==-1)
    {
        printf("bind err\n");
        return -1;
    }

    res=listen(sockfd,10);
    if(res==-1)
    {
        return -1;
    }
    return sockfd;
}

运行结果:

Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐