这里是用一些简单的代码实现http客户端。
http是以tcp为基础的,所以跟之前的写的tcp/ip客户端服务器可以说是非常相似。
1.通过调用socket创建一个socket fd
int sockfd = -1;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
printf("socket\n打开文件描述符成功\n");
2.通过connect去与服务器连接
//第二步 通过connect连接服务器
cliaddr.sin_family = AF_INET; //设置地址族为ipv4
cliaddr.sin_port = htons(SERPORT);//设置端口号 大于5000的端口是其他服务器预留的,还要设置字节序
cliaddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址
ret = connect(sockfd,(const struct sockaddr*)&cliaddr,sizeof(cliaddr));
if (ret < 0)
{
perror("connect");
return -1;
}
printf("与服务器connect成功\n");
重点是这里的SERPORT
SERADDR
http默认端口号是80,所以宏定义的时候定义SERPORT
为80
SERADDR
是通过ping百度的的网址获得的一个ip地址,不同时间段ping获得的ip地址可能不一样,要自己修改一下(不过我这3天测试过来都能ping通)
3.关键点,HTTP的消息格式
char sendbuf[1024] = {0};//HTTP协议的语法格式
sprintf(sendbuf, "GET / HTTP/1.1\r\n"
"Host:www.baidu.com\r\n"
"User-Agent:whatever\r\n"
"Accept-Type:*/*\r\n"
"Connection:close\r\n"
"X-Power:XXX\r\n"
"\r\n");
格式一定不能出错,最后的\r\n一定不能省略,
GET是HTTP的获取方法,随后的’/’表示获取根目录下的默认页面,”HTTP/1.1”标明了协议及版本
Host是指明获取主机,消息格式里面的CRLF就是\r\n,所以不能省略
后面User-Agent和Accept-Type等等这些没有详细去了解。
之前测试的时候就是少了最后一行的\r\n导致一直请求无法通过,服务器识别不了。
4.发送信息
ret = send(sockfd,sendbuf,strlen(sendbuf),MSG_NOSIGNAL);
if (-1 == ret)
{
perror("send");
return -1;
}
5.接收信息
num = recv(sockfd, recvBuf, sizeof(recvBuf),0);
if(-1 == num)
{
perror("recv msg fail\n");
return -1;
}
//printf("the msg I recv is \n%s\n",recvBuf);
printf("接收信息接收成功...\n");
到此为止5步大概就能完成http客户端的构建,大体上跟tcp/ip服务器90%相似的,就是需要自己构建一个发送报文,HTTP除了GET还有POST等方法,想了解更多需要深入学习,现在都是https了,比http更加安全,s
代表security
。
下面是获取到的信息
HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0x840bf5c20006490e
Cache-Control: private
Content-Type: text/html;charset=utf-8
Date: Sun, 24 Jan 2021 12:44:31 GMT
Expires: Sun, 24 Jan 2021 12:44:08 GMT
P3p: CP=" OTI DSP COR IVA OUR IND COM "
P3p: CP=" OTI DSP COR IVA OUR IND COM "
Server: BWS/1.1
Set-Cookie: BAIDUID=BFC73282CA79EF917ECCB46576FD406E:FG=1; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BIDUPSID=BFC73282CA79EF917ECCB46576FD406E; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: PSTM=1611492271; expires=Thu, 31-Dec-37 23:55:55 GMT; max-age=2147483647; path=/; domain=.baidu.com
Set-Cookie: BAIDUID=BFC73282CA79EF91139A6391C6B1E4B4:FG=1; max-age=31536000; expires=Mon, 24-Jan-22 12:44:31 GMT; domain=.baidu.com; path=/; version=1; comment=bd
Set-Cookie: BDSVRTM=0; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=33425_33429_33258_33344_33284_33398_33334_33319; path=/; domain=.baidu.com
Traceid: 161149227106972684909514968851323177230
Vary: Accept-Encoding
Vary: Accept-Encoding
X-Ua-Compatible: IE=Edge,chrome=1
Connection: close
Transfer-Encoding: chunked
b46
<!DOCTYPE html><!--STATUS OK-->
<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"><meta content="always" name="re
之前通过GET获取到的信息里面,有一截有用的信息
Date: Sun, 24 Jan 2021 12:44:31 GMT
每一次访问都会收回一包信息,里面就包含了时间信息。
开发板每次关机都不会保存时间,每次开机都要自己设定时间,如果通过http来获取到时间信息然后通过调用api去设置时间,就可以实现每次开机自动更新时间。(不过其实可以下载ntpclient来自动同步时间的实现嵌入式linux自动同步网络时间—NTP )
但是既然有了这样的想法为何不去尝试一下呢?
//创建一个文件,用来存储信息
FILE *fp = NULL;
fp = fopen(PATHNAME,"w+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
printf("存储信息文件创建成功\n");
//存储信息
fwrite(recvBuf,sizeof(recvBuf),1,fp);
printf("信息存储成功\n");
//关闭文件
fclose(fp);
为什么要重新打开这个文件呢?
其实之前我是想着直接从保存好的文件里面抓取的,但是发现抓不到信息,都是空白的,所以我在想是不是要fwrite之后fclose关闭了才是真正的把信息保存下来了?
还是说fwrite是缓冲形式的,要fflush之后才行呢?
我尝试了前者,先fclose再重新打开。
//重新打开文件,抓取时间Date
fp = fopen(PATHNAME,"r");
if(NULL == fp)
{
perror("fopen");
return -1;
}
printf("重新打开文件,抓取时间Date\n");
char file_str[256];
int line = 0;
char target_buf[256] = {0};
while(fgets(file_str,sizeof(file_str),fp))
{
line++;
if(strstr(file_str,"Date:"))
{
printf("file_str %s\n",file_str);
strcpy(target_buf,file_str);
printf("target_buf %s\n",target_buf);
}
}
printf("strlen of target_buf is %ld\n",strlen(target_buf));
使用fgets
的方法是 从网上找的,具体网址忘记了
这里的设计思路是通过fgets
一行一行地找,每找到一行就通过使用strstr
去找“Date:”这个字符串,如果找到了的话,就把当前的这一段字符串截取下来,存放到target_buf
里面,下一步再进行解析。
代码没有截全,展示用
/***要解析的字符串Date: Sun, 24 Jan 2021 14:33:55 GMT **/
//找月份
char month[12][5] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"};
for(i = 0;i < 12; i++)
{
month_ptr = strstr(target_buf,month[i]);
if(NULL != month_ptr)
{
break;
}
}
i_month = i + 1 ;
在目标字符串里面通过一个for循环
,还有strstr
去遍历一次月份,找到之后就跳出for循环,这时候的月份数字是从0
开始的,所以还得+1
才是确切的几月
//星期
for(j = 0;j < 7; j++)
{
week_ptr = strstr(target_buf,week[j]);
if(NULL != week_ptr)
{
break;
}
}
k = 0;
while(k<3)
{
r_week[k] = *week_ptr;
week_ptr++;
k++;
}
printf("real week is %s\n",r_week);
思路跟找月份是一样的,这里为了保存星期的值,定义了一个指week_ptr
和数组r_week[5]
保存星期几的值
的思路是
strstr
找到之后会返回一个指针,指向被找目标的开头的地址,所以通过一个while循环去每次存放一个地址,然后地址+1移位。
不过其实这样做是有bug的,因为Thur是有4个位的,要移动4次,所以打印是不全面的,刚开始的时候我是想以月份的地址为基准,后面去移动指针获取时间,后面发现这样子不行,所以重新选择基准,找到了最后的GMT作为基准点
//找到"GMT",以GMT为基准计算年、日、时、分、秒
gmt_ptr = strstr(target_buf,"GMT");
printf("gmt is %s\n",gmt_ptr);
都是以这样的移动指针的方法去获取,然后保存
//日
k = 0;
temp = gmt_ptr;
while(k<2)
{
r_day[k] = *(temp - 21);
temp++;
k++;
}
day = atoi(r_day);
设置linux系统时间的格式为 date -s "xxxx-xx-xx “xx:xx:xx”
通过sprintf
里面的%02d
,保证了就算是个位时间也会在前面补0
通过strcat
去构造一个格式
最终通过system
这个api去执行命令。
sprintf(time,"%02d-%02d-%02d %02d:%02d:%02d",year,i_month,day,hour,min,sec);//月日时分秒>10
printf("time is %s\n",time);
char cmd[256] = "date -s";
printf("cmd is %s\n",cmd);
strcat(cmd," \"");
strcat(cmd,time);
strcat(cmd,"\"");
printf("cmd is %s\n",cmd);
system(cmd);
当然,前提条件是开发板能上网,关于开发板上网可以去看之前的相关帖子。
还有,计算hour的时候记得要+8,因为获取的是GMT时间,不是北京时间。
wpa_supplicant移植与使用
用C语言实现一个简单的HTTP客户端(HTTP Client)
Windows下C语言实现HTTP客户端
C语言在文件中查找字符串是否在某行,显示行号和该行内容
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERPORT 80 //服务器开放的端口号
#define SERADDR "183.232.231.174" //服务器开放的IP地址
#define PATHNAME "./msg.txt"
int main(void)
{
struct sockaddr_in cliaddr = {0};
int ret = -1;
//第一步,先socket打开文件描述符
int sockfd = -1;
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
printf("socket\n打开文件描述符成功\n");
//第二步 通过connect连接服务器
cliaddr.sin_family = AF_INET; //设置地址族为ipv4
cliaddr.sin_port = htons(SERPORT);//设置端口号 大于5000的端口是其他服务器预留的,还要设置字节序
cliaddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址
ret = connect(sockfd,(const struct sockaddr*)&cliaddr,sizeof(cliaddr));
if (ret < 0)
{
perror("connect");
return -1;
}
printf("与服务器connect成功\n");
char recvBuf[1024 * 2] = {0};
int num = -1;
char sendbuf[1024] = {0};//HTTP协议的语法格式
sprintf(sendbuf, "GET / HTTP/1.1\r\n"
"Host:www.baidu.com\r\n"
"User-Agent:whatever\r\n"
"Accept-Type:*/*\r\n"
"Connection:close\r\n"
"X-Power:XXX\r\n"
"\r\n");
//printf("sendbuf is : %s\nn",sendbuf);
ret = send(sockfd,sendbuf,strlen(sendbuf),MSG_NOSIGNAL);
if (-1 == ret)
{
perror("send");
return -1;
}
printf("正在接收信息...\n");
num = recv(sockfd, recvBuf, sizeof(recvBuf),0);
if(-1 == num)
{
perror("recv msg fail\n");
return -1;
}
//printf("the msg I recv is \n%s\n",recvBuf);
printf("接收信息接收成功...\n");
//创建一个文件,用来存储信息
FILE *fp = NULL;
fp = fopen(PATHNAME,"w+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
printf("存储信息文件创建成功\n");
//存储信息
fwrite(recvBuf,sizeof(recvBuf),1,fp);
printf("信息存储成功\n");
//关闭文件
fclose(fp);
//重新打开文件,抓取时间Date
fp = fopen(PATHNAME,"r");
if(NULL == fp)
{
perror("fopen");
return -1;
}
printf("重新打开文件,抓取时间Date\n");
char file_str[256];
int line = 0;
char target_buf[256] = {0};
while(fgets(file_str,sizeof(file_str),fp))
{
line++;
if(strstr(file_str,"Date:"))
{
printf("file_str %s\n",file_str);
strcpy(target_buf,file_str);
printf("target_buf %s\n",target_buf);
}
}
printf("strlen of target_buf is %ld\n",strlen(target_buf));
char month[12][5] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"};
char week[7][5] = {"Mon","Thu","Wed","Thur","Fri","Sat","Sun"};
char r_year[5] = {0};
char r_month[5] = {0};
char r_week[5] = {0};
char r_day[3] = {0};
char r_hour[3] = {0};
char r_min[3] = {0};
char r_sec[3] = {0};
char *temp;
int i,j,k;
int sec, min ,hour, year ,day,i_month;
char *month_ptr = NULL,*week_ptr = NULL,*gmt_ptr = NULL;
char time[32] = {0};
/***要解析的字符串Date: Sun, 24 Jan 2021 14:33:55 GMT **/
//找月份
for(i = 0;i < 12; i++)
{
month_ptr = strstr(target_buf,month[i]);
if(NULL != month_ptr)
{
break;
}
}
i_month = i + 1 ;
k = 0;
temp = month_ptr;
while(k<3)
{
r_month[k] = *temp;
temp++;
k++;
}
//printf("real month is %s\n",r_month);
//printf("real month is %d\n",i_month);
//星期
for(j = 0;j < 7; j++)
{
week_ptr = strstr(target_buf,week[j]);
if(NULL != week_ptr)
{
break;
}
}
k = 0;
while(k<3)
{
r_week[k] = *week_ptr;
week_ptr++;
k++;
}
//printf("real week is %s\n",r_week);
//找到"GMT",以GMT为基准计算年、日、时、分、秒
gmt_ptr = strstr(target_buf,"GMT");
printf("gmt is %s\n",gmt_ptr);
//日
k = 0;
temp = gmt_ptr;
while(k<2)
{
r_day[k] = *(temp - 21);
temp++;
k++;
}
day = atoi(r_day);
//printf("real day is %d\n",day);
//年
k = 0;
temp = gmt_ptr;
temp = temp - 14;
while(k<4)
{
r_year[k] = *temp;
temp++;
k++;
}
year = atoi(r_year);
//printf("real year is %d\n",year);
//时
k = 0;
temp = gmt_ptr;
temp = temp - 9;
while(k<2)
{
r_hour[k] = *temp;
temp++;
k++;
}
hour = atoi(r_hour) + 8; //东八区
if(hour >= 24)
{
hour = hour - 24;
}
//printf("real hour is %d\n",hour);
//分
k = 0;
temp = gmt_ptr;
temp = temp - 6;
while(k<2)
{
r_min[k] = *temp;
temp++;
k++;
}
min = atoi(r_min);
//printf("real min is %d\n",min);
//秒
k = 0;
temp = gmt_ptr;
temp = temp - 3;
while(k<2)
{
r_sec[k] = *temp;
temp++;
k++;
}
sec = atoi(r_sec);
//printf("real sec is %d\n",sec);
sprintf(time,"%02d-%02d-%02d %02d:%02d:%02d",year,i_month,day,hour,min,sec);//月日时分秒>10
printf("time is %s\n",time);
char cmd[256] = "date -s";
printf("cmd is %s\n",cmd);
strcat(cmd," \"");
strcat(cmd,time);
strcat(cmd,"\"");
printf("cmd is %s\n",cmd);
system(cmd);
return 0;
}
第一次写的代码在一个main函数里面,我觉得看上去是比较好理解的, 但是这样写的代码感觉比较冗余,而且不好维护等等,所以经过了一天的修改获得了第二版源码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERPORT 80 //服务器开放的端口号
#define SERADDR "183.232.231.174" //服务器开放的IP地址
#define PATHNAME "./msg.txt" //文件路径
//debug宏
#define DEBUG
//#undef DEBUG
#ifdef DEBUG
#define debug(format,...) printf("[debug]""File: "__FILE__", Line: %05d: "format"", __LINE__, ##__VA_ARGS__)
#else
#define debug(format,...)
#endif
//全局变量
int sockfd = -1;
int ret = -1;
typedef struct msg_S
{
char target_buf[256];
char time[32];
}msg_S;
//客户端函数
int client_init(void)
{
//第一步,先socket打开文件描述符
struct sockaddr_in cliaddr = {0};
sockfd = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sockfd)
{
perror("socket");
return -1;
}
debug("打开文件描述符成功\n");
//第二步 通过connect连接服务器
cliaddr.sin_family = AF_INET; //设置地址族为ipv4
cliaddr.sin_port = htons(SERPORT);//设置端口号
cliaddr.sin_addr.s_addr = inet_addr(SERADDR); //设置IP地址
ret = connect(sockfd,(const struct sockaddr*)&cliaddr,sizeof(cliaddr));
if (ret < 0)
{
perror("connect");
return -1;
}
debug("与服务器connect成功\n");
}
//抓取文件
int msg_parse(msg_S *pMsg)
{
//发送信息
msg_S *ptarget_msg = pMsg;
int num = -1;
char sendbuf[1024] = {0};//HTTP协议的语法格式
char recvBuf[1024 * 2] = {0};
sprintf(sendbuf, "GET / HTTP/1.1\r\n"
"Host:www.baidu.com\r\n"
"User-Agent:whatever\r\n"
"Accept-Type:*/*\r\n"
"Connection:close\r\n"
"X-Power:XXX\r\n"
"\r\n");
ret = send(sockfd,sendbuf,strlen(sendbuf),MSG_NOSIGNAL);
if (-1 == ret)
{
perror("send");
return -1;
}
debug("正在接收信息...\n");
//接收信息
num = recv(sockfd, recvBuf, sizeof(recvBuf),0);
if(-1 == num)
{
perror("recv msg fail\n");
return -1;
}
//debug("the msg I recv is \n%s\n",recvBuf);
debug("接收信息接收成功...\n");
//创建一个文件,用来存储信息
FILE *fp = NULL;
fp = fopen(PATHNAME,"w+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
debug("存储信息文件创建成功\n");
//存储信息
fwrite(recvBuf,sizeof(recvBuf),1,fp);
debug("信息存储成功\n");
//关闭文件
fclose(fp);
//抓取关键信息
//重新打开文件
debug("重新打开文件,抓取时间Date\n");
char file_str[256];
//FILE *fp = NULL;
fp = fopen(PATHNAME,"r");
if(NULL == fp)
{
perror("fopen");
return -1;
}
//抓取时间Date
while(fgets(file_str,sizeof(file_str),fp))
{
if(strstr(file_str,"Date:"))
{
debug("getmsg:\n%s\n",file_str);
strcpy(ptarget_msg->target_buf,file_str);
debug("target_buf:\n%s\n",ptarget_msg->target_buf);
}
}
}
//日、年、时、分、秒解析函数
/***要解析的字符串Date: Sun, 24 Jan 2021 14:33:55 GMT **/
/*len:日期值的长度,比如2021-01-23 12:23:45 日 时 分 秒 的长度都是2,年的长度是4
offset: 偏移量,例如,以GMT的G为基准,往前减3就是秒的起始地址,往前减去6就是分的起始地址
char src[]: 用来存放年月日时分秒的字符串,最后统一用atoi转换成整型
msg_S *pMsg:定义的一个结构体,从这里面获取target_msg,解析出GMT的首地址*/
int date_parse(const int len, const int offset , char src[], msg_S *pMsg)
{
int k = 0;
char *gmt_ptr = NULL;
msg_S *ptarget_msg = pMsg;
//找到"GMT",以GMT为基准计算年、日、时、分、秒
gmt_ptr = strstr((*ptarget_msg).target_buf,"GMT");
while(k < len)
{
src[k] = *(gmt_ptr - offset);
gmt_ptr++;
k++;
}
return atoi(src);
}
//月份解析函数
int month_parse(msg_S *pMsg)
{
msg_S *ptarget_msg = pMsg;
char *month_ptr = NULL;
int month;
char month_arr[12][5] = {"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sept","Oct","Nov","Dec"};
int i = 0;
//找月份 不能以GMT为基准 因为thur 或者sept都是4个字节的,跟其他的3字节不一样,这样会出错的。
for(i = 0;i < 12; i++)
{
month_ptr = strstr((*ptarget_msg).target_buf,month_arr[i]);
if(NULL != month_ptr)
{
break;
}
}
//debug("month i is %d\n",i);
//debug("month_ptr is %s\n",month_ptr);
month = i + 1 ;
return month;
}
//把时间整合成一个字符串
int catenate_time(msg_S *pst_time)
{
int sec, min ,hour, year ,day, month;
char r_year[5] = {0};
char r_month[5] = {0};
char r_day[3] = {0};
char r_hour[3] = {0};
char r_min[3] = {0};
char r_sec[3] = {0};
msg_S *p = pst_time;
/***要解析的字符串Date: Sun, 24 Jan 2021 14:33:55 GMT **/
month = month_parse(p); //月
day = date_parse(2, 21, r_day, p); //日
year = date_parse(4, 14, r_year, p); //年
min = date_parse(2, 6, r_min, p); //分
sec = date_parse(2, 3, r_sec, p); //秒
hour = date_parse(2, 9, r_hour, p); //时
hour = hour + 8; //时+8 北京时间
if(hour >= 24)
hour = hour - 24;
sprintf(p->time,"%02d-%02d-%02d %02d:%02d:%02d",year,month,day,hour,min,sec);//格式化月日时分秒
}
//整合出设置时间的命令,然后执行命令
void func_settime(msg_S * const pst_time)
{
msg_S *p = pst_time;
debug("time is %s\n",p->time);
char cmd[256] = "date -s";
strcat(cmd," \"");
strcat(cmd,p->time);
strcat(cmd,"\"");
debug("cmd is %s\n",cmd);
system(cmd);
}
int main(void)
{
msg_S st_time;
memset(&st_time, 0, sizeof(msg_S));
client_init(); //http 客户端
msg_parse(&st_time); //保存并获取信息
catenate_time(&st_time); //整合时间
func_settime(&st_time); //调用system命令去执行
return 0;
}
刚开始把v1代码修改的时候,虽然我封装了不少函数,但是弄了很多全局变量在外面,其实这样的做法是很不好的。
后面仔细推敲,根据每部分代码的作用,重新规划函数的封装,把大部分的全局变量都放在函数体里面,成为局部变量,但是还是有2个数组在外面,这时候我想起了之前看过的海思的源码,发现别人的代码很多时候传参都是通过结构体参数传参,所以我也学习模仿他们的写法:
1.定义一个结构体,里面包含我的数组
2.在main函数里面定义一个结构体变量,供给函数使用
3.当某些函数需要往结构体里面写入或者读出的时候,就调用这个结构体变量的地址,这样每次传参就只是传地址了。
4.每个调用这个地址的函数体里面,再自己定义一个结构体变量,然后把main函数里面的结构体变量的地址赋值给它,相当于进行了一个绑定,(有点类似C++的引用?)
当这个函数操作完之后自己挂掉就会释放这部分内存,不用像全局变量一样一直在占用静态存储空间。
最后,如果有写的不准确的地方欢迎指正。