简介HTTP请求
HTTP是应用层的协议,是我们上网最常使用到的协议,这篇博文来讲讲我们可以如何使用HTTP来获取到网页上的一些数据
首先我们需要知道HTTP的请求有哪些
| 方法 | 作用 | 常用程度 | 有无Body | 幂等性 |
|---|---|---|---|---|
| GET | 获取资源(查) | ⭐⭐⭐⭐⭐ | ❌ | ✅ |
| POST | 创建资源/提交数据(增) | ⭐⭐⭐⭐⭐ | ✅ | ❌ |
| PUT | 完整替换资源(全改) | ⭐⭐⭐ | ✅ | ✅ |
| PATCH | 部分更新资源(改) | ⭐⭐⭐ | ✅ | ❌ |
| DELETE | 删除资源(删) | ⭐⭐⭐ | ❌ | ✅ |
| HEAD | 只获取响应头 | ⭐⭐ | ❌ | ✅ |
| OPTIONS | 查询服务器支持的方法 | ⭐⭐ | ❌ | ✅ |
| CONNECT | 建立隧道连接(代理) | ⭐ | ❌ | ❌ |
| TRACE | 回显请求内容(调试) | ⭐ | ❌ | ✅ |
从上面我们可以看到,如果我们只是从网络上面获取数据的话,知道GET请求和POST请求就行了,因为网上大部分的API都是使用的GET和POST请求的,其他请求在获取信息上基本看不见
这篇文字将会着重在GET和POST请求的构成和使用上
GET请求
首先我们来看GET请求
GET请求的具体格式
GET /path/to/resource?key1=value1&key2=value2 HTTP/1.1必须
Host: www.example.com必须
User-Agent: MyClient/1.0
Accept: text/html,application/json
Accept-Language: zh-CN,zh;q=0.9
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cache-Control: no-cache
Cookie: session_id=abc123
If-Modified-Since: Wed, 12 Aug 2026 07:28:22 GMT
别看这里有这么多,其实必须的只有两行,其他的都是可选项(如果平台API没用硬性的GET请求要求的话)
GET /path/to/resource?key1=value1&key2=value2 HTTP/1.1
Host: www.example.com
这就是最小的GET请求了
我们来一行一行拆解
第一行
GET /path/to/resource?key1=value1&key2=value2 HTTP/1.1
我们可以拆成3个部分来看
GET /path/to/resource?key1=value1&key2=value2 HTTP/1.1
请求类型 URL?查询参数 HTTP的版本号
(1)请求类型:
我们这里使用的是GET请求嘛,所有请求类型是GET,这很合理
(2)URL:
URL指的是这个服务在网页下的路径,一般API都会给出参考的,如下图
这个是高德地图开放平台中天气查询的GET请求相关参数
URL是.com之后的部分,如下演示一般
https://restapi.amap.com/v3/assistant/coordinate/convert
https:// restapi.amap.com /v3/assistant/coordinate/convert
协议 域名 URL
这样我们就知道了域名和URL分别是什么了
(3)参数
参数都是紧跟在URL后面,用?隔开,参数一般就需要程序员根据API提供的来写,如下图
(4)HTTP版本号
这里版本号是1.1,说明这是1.1版本的HTTP
怎么确认这个HTTP的版本号填多少呢?
首先可以在官网的API下面找,要是想偷懒的话也可以问问AI,基本都是1.1版本的,这个比较常用
第二行
Host: www.example.com
Host: www.example.com
关键字 访问的域名(这个我们刚刚分析了,在URL那里)
第三----N行
User-Agent: MyClient/1.0
Accept: text/html,application/json
Accept-Language: zh-CN,zh;q=0.9
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cache-Control: no-cache
Cookie: session_id=abc123
If-Modified-Since: Wed, 12 Aug 2026 07:28:22 GMT
Referer: https://www.google.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
这些都请求头,不是必须的
这些请求头都在干嘛呢?
User-Agent: MyClient/1.0告诉服务器你是谁
Accept: text/html,application/json 告诉服务器你想要哪种数据包格式
Accept-Language: zh-CN,zh;q=0.9 告诉服务器你想要哪种语言
Accept-Encoding: gzip, deflate 告诉服务器你能接受压缩过的数据
Connection: keep-alive 告诉服务器发送完毕后还要不要保持连接(一般用close,省宽带)
Cache-Control: no-cache是否需要缓存(这里是无需缓存,直接发送即可)
Cookie: session_id=abc123 相当于告诉服务器你是回头客,有cookie的
举个例子第一次连接,没有cookie,服务器会发送一个cookie给你,之后连接就知道你是连接过的了
If-Modified-Since: Wed, 12 Aug 2026 07:28:22 GMT 上次访问是否有修改,没有修改就不用发送
Referer: https://www.google.com 告诉服务器你是从哪里来的(从谷歌网页来的)
Authorization: Bearer eyJhbGciOiJIUzI1NiIs... 相当于是用户认证,一般一些付费的API都会有,只有填上了才能使用
使用举例
我们来举个例子来发送GET请求,实战一下
就拿从高德地图提供的天气服务来举例
基础 API 文档-开发指南-Web服务 API | 高德地图API
从这里可以看到我们必须的所有参数
1.域名 restapi.amap.com
2.URL /v3/weather/weatherInfo
3.parameters:API中说必须的是key和city2个参数,到时候我们就需要填上这2个
获取到这3个,我们就能够开始构建GET请求了
通过GET请求获取信息的流程
知道网页的ip-->通过tcp与网页建立连接-->发送GET请求-->等待接收数据包-->解包
(1)获取网页ip
我们之前已经知道了网页的域名了,那么我们如何通过域名得到ip地址呢?这就需要我们使用DNS了
struct hostent *myhost = gethostbyname("restapi.amap.com"); char *ipstr = *(myhost->h_addr_list); printf("网址官方名:%s\n", myhost->h_name); //存放官方名称 printf("网址别名:%s\n", *(myhost->h_aliases)); //存放别名 printf("网址ip类型:%d\n", myhost->h_addrtype); //存放地址类型IPV4 or IPV6 printf("网址长度:%d\n", myhost->h_length); //存放长度这里向大家介绍一个结构体
struct hostent
{
char *h_name; //主机的官方域名
char **h_aliases; //主机的所有别名列表
int h_addrtype; //主机的地址类型 AF_INET(ipv4) AF_INET6(ipv6)
int h_length; //地址的长度 IPV4=4 IPV6=16
char **h_addr_list; //IP地址的二进制表示(网络字节序,或者叫大端序)
}
这里我们就需要使用这个结构体来实现DNS将域名解析为ip地址
struct hostent *myhost = gethostbyname("restapi.amap.com");函数介绍
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
返回值:
成功:上面提到的struct hostent 结构体的指针
失败:NULL 可以使用perror打印失败原因
参数:
name:需要解析的域名
通过这个,我们就能获取到网址ip的二进制网络字节序了
我们要使用这个ip还需要将ip转化为字符串
之前我们学到过struct sockaddr结构体,其中有一个存放ip地址的参数类型为struct in_addr
在这里存放的就是需要连接网址ip的二进制网络字节序了,那么后续我们就能够直接将数值存放在需要连接的地址里面了,当然,我们也可以将这个二进制的网络字节序转换为字符串来查看这个域名的ip地址是多少
//将二进制IP转换为字符串 struct in_addr addr; addr.s_addr = *(unsigned long *)ipstr; printf("网址IP: %s\n", inet_ntoa(addr));(2)与ip建立连接
既然已经获取到了服务器的ip了,那么我们就可以使用tcp通信来与其建立连接
//创建套接字 int tcpsocket = socket(AF_INET, SOCK_STREAM, 0); if(tcpsocket == -1) { perror("套接字创建失败\n"); return -1; } //创建需要连接的ip和端口 struct sockaddr_in bindaddr; bindaddr.sin_family = AF_INET; bindaddr.sin_port = htons(80); bindaddr.sin_addr = addr; //开始连接服务器 int ret = connect(tcpsocket, (struct sockaddr *)&bindaddr, sizeof(bindaddr)); if(ret == -1) { perror("服务器连接失败"); return -1; }(3)建立连接之后,我们就可以开始构建GET请求了
//发送GET请求 char request[256] = {0}; sprintf(request, "GET /v3/weather/weatherInfo?key=(填自己的key) HTTP/1.1\r\n" "Host: restapi.amap.com\r\n" "\r\n" );还记得最小的GET请求有哪些内容吗?第一行 + 第二行
GET URL?参数 HTTP/1.1\r\n (注意,每行结束记得加上\r\n)
Host: 域名\r\n
\r\n 注意,GET请求结束是以\r\n作为标志位,不要漏掉了
这样我们就构建了一个GET请求
可以发送GET请求了
ret = send(tcpsocket, request, strlen(request), 0);这里发送的长度要用strlen,不要用sizeof,不然会发送无效数据,导致GET请求出现格式错误
(4)接收数据包
使用tcp的接收函数recv即可
//获得原始数据 char recv_buf[1024] = {0}; ret = recv(tcpsocket, recv_buf, sizeof(recv_buf), 0); if(ret > 0) { //printf打印出来 printf("接收到的数据:%s\n", recv_buf); } else printf("没接收到\n");这样我们就获得了网页发送来的数据包了
完整代码如下
#include <stdio.h> #include <sys/socket.h> #include <unistd.h> #include <sys/stat.h> #include <string.h> #include <sys/types.h> #include <stdlib.h> #include <fcntl.h> #include <pthread.h> #include <netdb.h> //linux端序转换头文件 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> /* struct hostent *gethostbyname(const char *name) 功能: 返回值: 结构体指针:struct hostent * 参数: name:需要的解析的网址 struct hostent { char *h_name; // 主机官方域名(规范名) char **h_aliases; // 主机别名列表(指向指针的指针) int h_addrtype; // 地址类型(AF_INET 或 AF_INET6) int h_length; // 地址长度(IPv4=4, IPv6=16) char **h_addr_list; // IP地址列表(网络字节序) }; */ int main(int arg, char **argv) { if(arg < 2) { printf("没有传递网址\n"); return -1; } struct hostent *myhost = gethostbyname(argv[1]); char *ipstr = *(myhost->h_addr_list); printf("网址官方名:%s\n", myhost->h_name); //存放官方名称 printf("网址别名:%s\n", *(myhost->h_aliases)); //存放别名 printf("网址ip类型:%d\n", myhost->h_addrtype); //存放地址类型IPV4 or IPV6 printf("网址长度:%d\n", myhost->h_length); //存放长度 // 修正:将二进制IP转换为字符串 struct in_addr addr; addr.s_addr = *(unsigned long *)ipstr; printf("网址IP: %s\n", inet_ntoa(addr)); //连接ip int tcpsocket = socket(AF_INET, SOCK_STREAM, 0); if(tcpsocket == -1) { perror("套接字创建失败\n"); return -1; } struct sockaddr_in bindaddr; bindaddr.sin_family = AF_INET; bindaddr.sin_port = htons(80); bindaddr.sin_addr = addr; int ret = connect(tcpsocket, (struct sockaddr *)&bindaddr, sizeof(bindaddr)); if(ret == -1) { perror("服务器连接失败"); return -1; } //发送GET请求 char request[256] = {0}; sprintf(request, "GET /v3/weather/weatherInfo?key=(填自己的)&city=440100 HTTP/1.1\r\n" "Host: restapi.amap.com\r\n" "\r\n" ); ret = send(tcpsocket, request, strlen(request), 0); //获得原始数据 char recv_buf[1024] = {0}; ret = recv(tcpsocket, recv_buf, sizeof(recv_buf), 0); if(ret > 0) { //printf打印出来 printf("接收到的数据:%s\n", recv_buf); } else printf("没接收到\n"); }运行结果
可以看到,我们获取到了正确的json数据包。
那要如何解析这个json数据包呢?可以参考后面的博客,后面会加更一篇json数据解析的博文
POST请求
学习完了GET请求,我们再来看看POST请求是怎么做的
POST请求的格式
POST/path/to/resource HTTP/1.1 《--请求行
Host: www.example.com 《--请求头(是Host到Connection这一部分都是)
User-Agent: MyClient/1.0 可选
Accept: application/json 可选
Content-Type: application/json
Content-Length:52
Connection: close 可选《--这个空行一定要有,是用来区分请求头和请求体的
{"username": "zhangsan", "password": "123456"}《--请求体
POST请求这里的所有行大部分都是必须的了,就算是不必须的也推荐写上,能减少很多错误
第一行
POST /path/to/resource HTTP/1.1
POST /path/to/resource HTTP/1.1
请求类型 URL HTTP的版本
其实与GET请求很相似,只不过GET请求把参数给放到了URL的后面,而POST没有
第二行
Host: www.example.com
Host www.example.com
主机 主机域名
这个跟GET的一模一样
第三行--倒数第二行
User-Agent: MyClient/1.0 自己的信息
Accept: application/json 希望对方发来的数据包格式
Content-Type: application/json 自己发送给对方的数据包格式
Content-Length:52 自己发送的数据包长度
Connection: close 数据发送完后是否还需要连接
最后一行
{"username": "zhangsan", "password": "123456"} 发送的数据包
这里相当于是GET里面跟着URL?后面的参数,这里的格式要跟Content-Type设置的保持一致
举例实战
这一次我们使用POST请求发送到接口盒子,实现获取笑话的功能
随机笑话[20万] - 接口盒子 - 免费API-免费接口-站长工具-www.apihz.cn
虽然这里没用提供POST的示例,但是我们也可以参考GET里面提供的数据来实现
1.域名 cn.apihz.cn
2.URL /api/zici/xiaohua.php
3.参数 id=88888888 key=用自己的(可以注册一个账号获取)
提醒一下:这个API不支持用户使用JSON数据发送参数,我们要使用
"Content-Type: application/x-www-form-urlencoded\r\n"(表示发送的请求体为表单形式)
流程与GET请求基本一致,只不过GET请求换成了POST请求
来讲一下POST请求的构成
char *domain = "cn.apihz.cn"; //域名 char *url = "/api/zici/xiaohua.php";//URL char body[512] = {0}; int body_length = 0; //创建请求体 sprintf(body, "id=10019813&key=64064eba22179058f4bad8d1dbdf9812"); //获取请求体长度 body_length = strlen(body); //发送GET请求 char request[2048] = {0}; sprintf(request, "POST %s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: Test/1.0\r\n" "Accept: application/json\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %d\r\n" "Connection: close\r\n" "\r\n" "%s", url, domain, body_length, body );这里可以看到,这里的域名、URL、请求体长度以及请求体都使用了参数来填入,这样的好处是能够更加灵活的更改POST的请求,只需要修改参数就能够换为另一个网址的请求,非常的方便
完整代码
#include <stdio.h> #include <sys/socket.h> #include <unistd.h> #include <sys/stat.h> #include <string.h> #include <sys/types.h> #include <stdlib.h> #include <fcntl.h> #include <pthread.h> #include <netdb.h> //linux端序转换头文件 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(void) { char *domain = "cn.apihz.cn"; //域名 char *url = "/api/zici/xiaohua.php";//URL char body[1024] = {0}; //请求体 int body_length = 0; //请求体长度 struct hostent *myhost = gethostbyname(domain); char *ipstr = *(myhost->h_addr_list); printf("网址官方名:%s\n", myhost->h_name); //存放官方名称 printf("网址别名:%s\n", *(myhost->h_aliases)); //存放别名 printf("网址ip类型:%d\n", myhost->h_addrtype); //存放地址类型IPV4 or IPV6 printf("网址长度:%d\n", myhost->h_length); //存放长度 // 修正:将二进制IP转换为字符串 struct in_addr addr; addr.s_addr = *(unsigned long *)ipstr; printf("网址IP: %s\n", inet_ntoa(addr)); //连接ip int tcpsocket = socket(AF_INET, SOCK_STREAM, 0); if(tcpsocket == -1) { perror("套接字创建失败\n"); return -1; } struct sockaddr_in bindaddr; bindaddr.sin_family = AF_INET; bindaddr.sin_port = htons(80); bindaddr.sin_addr = addr; int ret = connect(tcpsocket, (struct sockaddr *)&bindaddr, sizeof(bindaddr)); if(ret == -1) { perror("服务器连接失败"); return -1; } //创建请求体 sprintf(body, "id=10019813&key=64064eba22179058f4bad8d1dbdf9812"); //获取请求体长度 body_length = strlen(body); //发送GET请求 char request[2048] = {0}; sprintf(request, "POST %s HTTP/1.1\r\n" "Host: %s\r\n" "User-Agent: Test/1.0\r\n" "Accept: application/json\r\n" "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %d\r\n" "Connection: close\r\n" "\r\n" "%s", url, domain, body_length, body ); ret = send(tcpsocket, request, strlen(request), 0); //获得原始数据 char recv_buf[1024] = {0}; ret = recv(tcpsocket, recv_buf, sizeof(recv_buf), 0); if(ret > 0) { //printf打印出来 printf("接收到的数据:%s\n", recv_buf); } else printf("没接收到\n"); }运行结果