Linux网络编程-HTTP协议与报文结构

一. 万维网WWW

1.1 万维网

万维网WWW(WorldWideWeb)并非某种特殊的计算机网络。万维网是一个大规模的联机式的信息储藏所 ,英文简称为Web。万维网用链接的方法能非常方便地从因特网上的个站点访问另一个站点(也就是所谓的"链接到另一个站点"),从而主动地按需获取丰富的信息。

1.2 万维网的通信方法

万维网的客户端和服务端是如何通信的,主要有以下三个方面:

1、URL:万维网服务器如何区分万维网数据

2、HTTP协议:万维网客户端和万维网服务端用来进行通信的协议

3、html 超文本标记语言 :万维网客户端请求数据的格式

1.2.1 URL 统一资源定位符

1、统一资源定位符URL是用来表示从因特网上得到的资源位置和访问这些资源的方法。

2、URL的一般格式如下:

<协议>://<主机>:<端口>/<路径>

**3、注意:**端口号可以省略 HTTP:80 HTTPS:443

?后面是参数,为键值对:关键字 = 值

4、eg:

https://www.baidu.com/index.php?tn=68018901_58_oem_dg

1.2.2 域名系统DNS

1、域名到IP地址的解析过程的要点如下:当某一个应用进程需要把主机名解析为IP地址 时,该应用进程就调用解析程序(resolver) ,并成为DNS的一个客户,把待解析的域名放在DNS请求报文中,以UDP用户数据报方式发给本地域名服务器(使用UDP是为了减少开销)本地域名服务器在查找域名后,把对应的IP地址放在回答报文中返回。应用进程获得目的主机的IP地址后即可进行通信。

2、**DNS:**域名解析服务(www.baidu.com--->IP),完成域名到IP地址的转换

3、命名方法:层次树状的命名结构

二. HTTP协议

2.1 HTTP的作用

1、HTTP协议定义了浏览器(即万维网客户进程)怎样向万维网服务器请求万维网文档,以及服务器怎样把文档传送给浏览器 。从层次的角度看,HTTP是面向事务的(transaction-oriented)应用层协议,它是万维网上能够可靠地交换文件(包括文本、声音、图像等各种多媒体文件)的重要基础。

2、HTTP超文本传输协议应用层基于传输层的TCP协议

2.2 HTTP的工作流程

1、HTTP工作流程:

2.3 HTTP的报文结构

1、HTTP有两类报文:

**请求报文:**从客户向服务器发送请求报文

**响应报文:**从服务器到客户的回答

2、由于HTTP是面向文本的(text-oriented) ,因此在报文中的每一个字段都是一些ASCII码

,因而各个字段的长度都是不确定的。

2.3.1 请求报文与响应报文

可以看到,HTTP请求报文和响应报文都是由三个部分组成。可以看出,这两种报文格式的区别就是开始行不同。

(1) 开始行 :用于区分是请求报文还是响应报文。在请求报文中的开始行叫做请求行(Request-Line) ,而在响应报文中的开始行叫做状态行(Status-Line)在开始行的三个字段之间都以空格分隔开,最后的"CR"和"LF"分别代表"回车"和"换行"。

(2) 首部行 :用来说明浏览器、服务器或报文主体的一些信息。首部可以有好几行,但也可以不使用。在每一个首部行中都有首部字段名和它的值每一行在结束的地方都要有"回车"和"换行"整个首部行结束时,还有一空行将首部行和后面的实体主体分开注意一定是\r\n,顺序不可以反

(3) 实体主体(entity body):在请求报文中一般都不用这个字段,而在响应报文中也可能没有这个字段。

以下是一个请求报文的例子:

这里注意Connection这个字段:

Connection: keep-alive\r\n : -----长连接: 服务端不会立马断开连接,会保持连接一定时间

Connection: close\r\n : -----**短连接:**服务端立马断开连接

html 复制代码
请求报文:
GET / HTTP/1.1\r\n        / "/"代表请求的是目标站点的根目录
Host: news.sohu.com\r\n
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101
Firefox/113.0\r\n
Accept:
text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,
*/*;q=0.8\r\n
Accept-Language: en-US,en;q=0.5\r\n
Connection: keep-alive\r\n
\r\n

响应报文的例子:

html 复制代码
响应报文:
HTTP/1.1 200 OK\r\n
Date: Wed, 17 Jun 2026 07:24:29 GMT\r\n
Content-Type: text/html;charset=utf-8\r\n
Server: openresty
Vary: Accept-Encoding
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers
Trace-Id: 99dc9d84e1064593b9ce423e481768c0.97150.17816810696338415
Data-Source:
X-Content-Type-Options: nosniff
X-XSS-Protection: 0
S-REQ-ID: 8848949218722638101
S-REQ-TYPE: 0
X-Cache-Lookup: Cache Miss
Content-Encoding: gzip
Cache-Control: no-cache
Transfer-Encoding: chunked
X-NWS-LOG-UUID: 8848949218722638101
Connection: keep-alive\r\n // -----长连接
X-Cache-Lookup: Cache Miss\r\n
\r\n
<!DOCTYPE html><html lang=zh-CN><head><script>

2.3.2 请求报文的方法

方法名称 核心作用 支持请求体 是否幂等 是否安全 典型使用场景
GET 从服务器读取 / 获取资源,仅执行只读操作,不修改服务端数据 不建议(浏览器规范禁止) 访问网页、查询数据、打开图片 / 静态资源、接口查询列表 / 详情
POST 向服务器提交数据,用于新增 / 修改资源,会产生服务端数据变更 支持(核心特性) 用户登录 / 注册、发表评论、提交表单、文件上传、接口新增数据
HEAD 与 GET 逻辑一致,但服务器仅返回响应头,不返回实体主体内容 爬虫预检测链接有效性、下载前获取文件大小、校验资源缓存状态、检测 404/403 状态
PUT 全量覆盖更新目标资源,资源不存在则新建,存在则完整覆盖 支持 修改用户完整信息、上传覆盖文件、RESTful 接口全量更新资源
DELETE 删除服务器上指定 URL 对应的目标资源 可选(不建议) 删除文章 / 文件、注销用户、RESTful 接口删除资源
OPTIONS 预检请求,查询服务器对目标 URL 支持的请求方法、跨域权限配置 前端跨域时浏览器自动发送的预检请求、开发调试接口支持的方法
PATCH 局部增量更新资源,仅传输需要修改的字段,区别于 PUT 全量覆盖 支持 仅修改用户昵称 / 年龄、更新商品库存、无需传完整对象的局部修改场景
CONNECT 建立 TCP 双向隧道,用于 HTTPS 代理、VPN 穿透 - - 浏览器通过代理服务器访问 HTTPS 网站、VPN 隧道建立
TRACE 调试专用,回显完整请求报文,用于链路调试 可选 - 内网调试网络代理链路(生产环境默认禁用,存在 XSS 安全风险)

2.3.3 响应报文常用状态码

状态码(Status-Code)都是三位数字的,分为5大类共33种,例如:

1、1xx表示通知信息的,如请求收到了或正在进行处理。

2、2xx表示成功,如接受或知道了。

3、3xx表示重定向,如要完成请求还必须采取进一步的行动。

4、4xx 表示客户的差错,如请求中有错误的语法或不能完成。

5、5xx表示服务器的差错,如服务器失效无法完成请求。

下面三种状态行在响应报文中是经常见到的。

html 复制代码
HTTP/1.1 202 Accepted      接受
HTTP/1.1 400 Bad Request   错误的请求
Http/1.1 404 Not Found     找不到

三. 应用示例

以下代码是向sohu服务器发送请求报文并获取响应报文的代码

cpp 复制代码
#define SER_PORT 80
#define SER_IP "113.142.50.195"//sohu服务器的ip地址

//初始化套接字
int init_tcp_cli()
{
    int sockfd, ret;
    struct sockaddr_in seraddr;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0){
        perror("socket()");
        return -1;
    }
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SER_PORT);
    inet_pton(AF_INET, SER_IP, &seraddr.sin_addr);

    ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if(ret < 0){
        perror("connect()");
        return -1;
    }
    return sockfd;
}
//发送请求报文
int send_http_request(int sockfd)
{
    char agent[1024] = "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0\r\n";
    ssize_t size;
    char *preq = "GET / HTTP/1.1\r\n"
                 "Host: news.sohu.com\r\n"
                 "User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0\r\n"
                 "Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n"
                 "Accept-Language: en-US,en;q=0.5\r\n"
                 //"Connection: keep-alive\r\n\r\n";
                 "Connection: close\r\n\r\n";
    size = send(sockfd, preq, strlen(preq), 0);
    if(size < 0){
        perror("send()");
        return -1;
    }
    return 0;
}
//接收响应报文
int recv_http_response(int sockfd)
{
    char buff[512] = {0};
    ssize_t size;
    while(1){
        size = recv(sockfd, buff, sizeof(buff), 0);
        if(size < 0){
            perror("recv()");
            return -1;
        }
        else if(size == 0){
            printf("server shutdown!\n");
            return -1;
        }
        size = write(STDOUT_FILENO, buff, size);
        if(size < 0){
            perror("write()");
            return -1;
        }
    }
    return 0;
}

//主函数
int main()
{
    int sockfd, ret; 
    sockfd = init_tcp_cli();//初始化套接字
    if(sockfd == -1){
        exit(1);
    }
    ret = send_http_request(sockfd);//发送请求报文
    if(ret == -1){
        exit(1);
    }
    printf("send succeed\n");
    ret = recv_http_response(sockfd);//接收响应报文
    if(ret == -1){
        exit(1);
    }
    close(sockfd);
    return 0;
}

结果: