基于HTTP GET方式获取网络时间的实现


上一节,我们介绍了基于NTP服务器获取网络时间的例子,但在有些情况下,比如我最近在使用RNDIS协议通过4G模块上网,这个协议不支持UDP协议,所以就用不了NTP服务器。或者有时候我们需要有更多的网络时间获取方式,以保证系统100%能获取到网络时间。本节就来介绍一下更通用的获取网络时间的方式:HTTP GET。

文章目录

1 HTTP GET原理1.1 网络中的工作流程1.2 HTTP GET请求组成部分1.3 测试

2 代码实现2.1 实现步骤2.2 完整代码

3 结果4 总结

1 HTTP GET原理

本节的原理实际上就是类似浏览器访问网站一样获取网站的数据,只需要找到一个能显示时间的网站就行了。下面就来了解一下使用socket请求HTTP GET网络数据的原理。

1.1 网络中的工作流程

用户发起请求: 用户通过浏览器或应用向服务器发送一个HTTP GET请求。服务器处理请求: 服务器接收到请求后,解析URL和头部信息,根据请求的资源进行处理。发送响应: 服务器将请求的数据(如HTML页面、图片等)打包在HTTP响应中返回给客户端。客户端处理响应: 客户端(通常是浏览器)接收响应并根据需要渲染或处理数据。

1.2 HTTP GET请求组成部分

请求行: 包括方法(GET)、请求的URI和HTTP版本。请求头: 包含请求的元数据,如用户代理信息、接受的内容类型等。空行: 请求头后面跟一个空行,表示请求头的结束。请求体: GET请求通常没有请求体,因为请求的数据包含在URI中。

下面是一些常见的HTTP请求头字段,这些字段在HTTP GET请求中经常使用:

1. Host

描述:Host 请求头指定了被请求资源的互联网主机和端口号,它通常由URI提供。由于一个服务器可能寄存多个域名,Host 请求头用来指定请求的是哪个域名。示例:如果请求 http://www.example.com/index.html,那么 Host 请求头将是:

Host: www.example.com

2. User-Agent

描述:User-Agent 请求头包含了一个特征字符串,用于让服务器识别客户端使用的操作系统,浏览器和浏览器版本等信息。这可以帮助服务器提供与设备兼容的响应。示例:一个典型的 User-Agent 请求头可能看起来像这样:

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36

3. Accept

描述:Accept 请求头用于告诉服务器,客户端能够处理哪些媒体类型。服务器可以根据这个头部信息决定返回什么类型的内容。示例:表明客户端可以处理HTML和XML,以及它们的特定版本:

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

4. Accept-Language

描述:Accept-Language 请求头用于告诉服务器,客户端希望优先接收哪种自然语言(如英语、中文等)。服务器可以据此返回相应语言的内容,实现本地化。示例:

Accept-Language: en-US,en;q=0.5

5. Accept-Encoding

描述:Accept-Encoding 请求头用于告诉服务器,客户端支持哪些压缩格式。服务器可以选择一个适合的压缩方法,以减小响应数据的体积,提高传输效率。示例:

Accept-Encoding: gzip, deflate, br

6. Connection

描述:Connection 请求头用于控制客户端和服务器之间的连接管理,常用的值有 keep-alive 和 close。keep-alive 告诉服务器保持连接打开,以便未来的请求可以使用同一连接,close 则相反。示例:

Connection: keep-alive

示例: 一个HTTP GET请求的结构

GET /example?page=1 HTTP/1.1

Host: www.example.com

User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate, br

Connection: keep-alive

当然,并非所有请求头都是必须的。某些头部信息如 User-Agent、Accept、Accept-Language 等是可选的,这些头部提供了关于客户端偏好和能力的信息,可以帮助服务器更优化地响应请求,但不包含这些头也不会阻止请求的基本功能。只有在特定场景下,例如多域托管、特定内容协商或需管理连接时,才需要特定的请求头。

1.3 测试

我们直接在电脑上用TCP客户端测试一下这样是否可行,首先我们获取服务器的IP: 然后我们连接这个IP,HTTP端口号一般为80,并发送请求报文: 可以看到,我们请求访问网页后,对方返回了HTTP头和网页内容,即我们成功获取了网页中的时间。

2 代码实现

这里在Linux环境下为例对HTTP网络时间进行获取,使用标准的POSIX/BSD套接字编程,这样如果想在单片机中LwIP实现的话,也可以直接使用。

2.1 实现步骤

1、HTTP时间服务端地址 这里以苏宁的时间服务器为例:https://quan.suning.com/getSysTime.doHTTP的端口一般都是80:

#define SERVER_PORT 80

#define SERVER_HOST "quan.suning.com"

2、创建套接字、解析域名 实际上HTTP也是基于TCP协议实现的,整个过程无非就是建立一个TCP连接,所以首先我们就是创建一个套接字。接着像苏宁这种大网站,一般会根据不同的地区分配不同的IP服务器以分担服务器负担,所以IP在不同地区解析出来都不一样,这里我们做一下域名DNS解析,用gethostbyname函数将域名解析为IP。

int sockfd;

struct hostent *server;

// 创建socket

sockfd = socket(AF_INET, SOCK_STREAM, 0);

// 获取服务器的地址

server = gethostbyname(SERVER_HOST);

3、建立连接 填充一下服务端结构体,建立连接。

// 填充服务器地址结构体

memset(&serveraddr, 0, sizeof(serveraddr));

serveraddr.sin_family = AF_INET;

memcpy(&serveraddr.sin_addr.s_addr, server->h_addr, server->h_length);

serveraddr.sin_port = htons(SERVER_PORT);

// 连接到服务器

connect(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr));

4、请求网页数据并读取

#define REQUEST "GET /getSysTime.do HTTP/1.1\r\nHost: quan.suning.com\r\n" \

"Connection: close\r\n\r\n"

char response[4096];

// 发送GET请求

write(sockfd, REQUEST, strlen(REQUEST);

read(sockfd, response, sizeof(response) - 1) ;

简单分析一下这个请求头:

Host: quan.suning.com

这个头是必需的,它指定了请求发送到的服务器的域名。在HTTP/1.1版本中,每个请求都必须包含Host头,因为一个服务器上可能托管多个域,服务器通过这个头部信息来确定要访问的具体域。 Connection: close

这个头部控制着连接的管理,close 的值意味着一旦请求完成后,客户端和服务器之间的连接将关闭,不会用于后续的请求。

2.2 完整代码

#include

#include

#include

#include

#include

#include

#include

#define SERVER_PORT 80

#define SERVER_HOST "quan.suning.com"

#define REQUEST "GET /getSysTime.do HTTP/1.1\r\nHost: quan.suning.com\r\nConnection: close\r\n\r\n"

int main() {

int sockfd;

struct sockaddr_in serveraddr;

struct hostent *server;

char response[4096];

// 创建socket

sockfd = socket(AF_INET, SOCK_STREAM, 0);

if (sockfd < 0) {

perror("ERROR opening socket");

exit(1);

}

// 获取服务器的地址

server = gethostbyname(SERVER_HOST);

if (server == NULL) {

fprintf(stderr, "ERROR, no such host\n");

exit(0);

}

// 填充服务器地址结构体

memset(&serveraddr, 0, sizeof(serveraddr));

serveraddr.sin_family = AF_INET;

memcpy(&serveraddr.sin_addr.s_addr, server->h_addr, server->h_length);

serveraddr.sin_port = htons(SERVER_PORT);

// 连接到服务器

if (connect(sockfd, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0) {

perror("ERROR connecting");

exit(1);

}

// 发送GET请求

if (write(sockfd, REQUEST, strlen(REQUEST)) < 0) {

perror("ERROR writing to socket");

exit(1);

}

// 读取响应

memset(response, 0, sizeof(response));

if (read(sockfd, response, sizeof(response) - 1) < 0) {

perror("ERROR reading from socket");

exit(1);

}

// 打印响应

printf("%s\n", response);

// 关闭socket

close(sockfd);

return 0;

}

注意,苏宁的网站有流量控制,有时候访问会出现系统忙,一般再请求一次即可,这个自己在代码中判断。

3 结果

编译后运行: 可以看到,结果和用TCP客户端上位机获取的一样,我们只需要做一些文本的操作就能获取到当前的时间了。

4 总结

本篇博客介绍了如何在Linux下使用C语言和Socket API发起HTTP GET请求。这个示例程序可以扩展到其他类型的HTTP请求和不同的API服务。如果不想用苏宁的服务器,可以随便请求一个网站和不存在的网页,如果网站用的是nginx的话,访问不存在的网页也会返回一个nginx时间。

靳东的“靳”怎么读 靳读音 靳念什么
潺潺是什麼意思?