继续上一篇的关于天气程序的几个问题
1 原来是单个汉字单个汉字单蹦显示。想显示多个数字很麻烦。进行了修改
2 重复请求天气,我想请求两个地方的天气。这个地方给我很大的困扰,研究了好久一直没找到具体原因,直到今天,看了相关http请求的相关内容,才明白了原理。
OK。我们首先贴一下 代码的方法介绍
首先,要获取请求,在wiff建立的情况下,就是wiffclient进行请求。记住了,这个东西就是个http请求的玩意。基本上差不多的东东。无论是get or post。查看相关的信息就可以进行处理。这点毋庸置疑。包括给相关的服务端发送json数据等,都是通过这个来操作。
那我们首先就是要介绍wiffclient的相关方法。这个方法真的很重要
3.1.1 connect - 启动tcp连接
函数说明:
/**
* 建立一个tcp连接
* @param ip IPAddress of tcpserver
* @param port port of tcpserver
* @return result of tcp connect
* 1 --- success
* 0 --- fail
*/
int connect(IPAddress ip, uint16_t port);
/**
* 建立一个tcp连接
* @param host host of tcpserver (192.xx.xx.xx)
* @param port port of tcpserver
* @return result of tcp connect
* 1 --- success
* 0 --- fail
*/
int connect(const char *host, uint16_t port)
/**
* 建立一个tcp连接
* @param host host of tcpserver (192.xx.xx.xx)
* @param port port of tcpserver
* @return result of tcp connect
* 1 --- success
* 0 --- fail
*/
int connect(const String host, uint16_t port);
1234567891011121314151617181920212223242526272829
3.1.2 connected - 判断client是否还在连接
函数说明:
/**
* 判断tcp连接是否建立起来(ESTABLISHED)
* @return result of tcp connect
* 1 --- success
* 0 --- fail
*/
uint8_t connected();
1234567
3.1.3 stop - 停止tcp连接
函数说明:
/**
* 关闭tcp连接
*/
void stop();
1234
3.1.4 status - 连接状态
函数说明:
/**
* 获取tcp连接状态
* @return result of tcp connect
* CLOSED = 0,
* LISTEN = 1,
* SYN_SENT = 2,
* SYN_RCVD = 3,
* ESTABLISHED = 4,
* FIN_WAIT_1 = 5,
* FIN_WAIT_2 = 6,
* CLOSE_WAIT = 7,
* CLOSING = 8,
* LAST_ACK = 9,
* TIME_WAIT = 10
*/
uint8_t status();
12345678910111213141516
3.2 发送数据操作
发送操作的源码可以查阅 Print.cpp
3.2.1 write - 发送数据到client连接的server
函数说明:
/**
* 发送数据
* @param str 需要单个字节
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t write(uint8_t);
/**
* 发送数据
* @param str 需要发送字符串或者字符数组
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t write(const char *str);
/**
* 发送数据
* @param buffer 需要发送字符串或者字符数组
* @param size 数据字节数
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t write(const char *buffer, size_t size)
/**
* 发送数据
* @param stream 数据流,比如文件流
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t write(Stream& stream);
1234567891011121314151617181920212223242526272829
注意点:
write(uint8_t)函数是发送数据的底层方法,也就是说print、println底层也是调用write;
write(const char *str) 函数底层是调用 write(const char *buffer, size_t size),通过strlen计算长度;
size_t write(const char *str) {
if(str == NULL)
return 0;
return write((const uint8_t *) str, strlen(str));
}
12345
3.2.2 print - 发送数据到client连接的server
函数说明:
/**
* 发送数据
* @param FlashStringHelper 需要发送的字符串,字符串存在flash中(PROGMEM)
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t print(const __FlashStringHelper *);
/**
* 发送数据
* @param String 需要发送的字符串,字符串存在内存中
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t print(const String &);
/**
* 发送数据
* @param String 需要发送的字符数组,字符数组存在内存中
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t print(const char[]);
/**
* 发送数据
* @param String 需要发送的字符
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t print(char);
/**
* 发送数据
* @param String 需要发送的数据,多是数字,转成对应的进制,一般都是传输数字型数据
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t print(unsigned char, int = DEC);
size_t print(int, int = DEC);
size_t print(unsigned int, int = DEC);
size_t print(long, int = DEC);
size_t print(unsigned long, int = DEC);
size_t print(double, int = 2);
12345678910111213141516171819202122232425262728293031323334353637383940
注意点:
读者需要特别关注 print(const __FlashStringHelper *) 这个函数,以后代码内存优化需用用到;
常见用法:
//实例代码 非完整代码 不可直接使用 理解即可
WiFiClient client;
client.print( F("This is an flash string")); //字符串“This is an flash string”存在于flash
123
3.2.3 println - 发送数据到client连接的server
函数说明:
/**
* 发送数据,并且加上换行符 "\r\n"
* @param FlashStringHelper 需要发送的字符串,字符串存在flash中(PROGMEM)
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(const __FlashStringHelper *);
/**
* 发送数据,并且加上换行符 "\r\n"
* @param String 需要发送的字符串,字符串存在内存中
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(const String &s);
/**
* 发送数据,并且加上换行符 "\r\n"
* @param String 需要发送的字符数组,字符数组存在内存中
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(const char[]);
/**
* 发送数据,并且加上换行符 "\r\n"
* @param String 需要发送的字符
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(char);
/**
* 发送数据,并且加上换行符 "\r\n"
* @param String 需要发送的数据,多是数字,转成对应的进制,一般都是传输数字型数据
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(unsigned char, int = DEC);
size_t println(int, int = DEC);
size_t println(unsigned int, int = DEC);
size_t println(long, int = DEC);
size_t println(unsigned long, int = DEC);
size_t println(double, int = 2);
/**
* 发送换行符 "\r\n"
* @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(void);
123456789101112131415161718192021222324252627282930313233343536373839404142434445
注意点:
println系列其实就是在print系列的基础上加上了换行符 “\r\n”;
3.3 响应操作
3.3.1 available() - 返回接收缓存区可读取字节数
函数说明:
/**
* 返回接收缓存区可读取字节数
* @return int 接收缓冲区可读取字节数
*/
int available();
12345
注意点:
通过此方法,我们可以判断发送出去的请求是否有响应信息;
3.3.2 availableForWrite() - 返回发送缓冲区剩余可写字节数
函数说明:
/**
* 返回发送缓冲区剩余可写字节数
* @return int 发送缓冲区剩余可写字节数
*/
size_t availableForWrite();
12345
注意点:
一般来说,调用发送数据操作之后,并不会立刻发送出去,而是把数据放入发送缓冲区,通过机制不断读取发送缓冲区的数据不断发送出去;
可以通过此函数判断请求是否发送完毕;
3.3.3 read() - 读取接收缓冲区一个字节
函数说明:
/**
* 读取接收缓冲区一个字节
* @return int 一字节数据
*/
int read();
12345
注意点:
此函数读取完数据后,会把该数据从缓冲区清掉;
3.3.4 read(buf,size) - 读取接收缓冲区size大小的字节数据
函数说明:
/**
* 读取接收缓冲区size大小的字节数据
* @param buf 数据存储到该buf
* @param size 读取大小
* @return int 成功读取的大小
*/
int read(uint8_t *buf, size_t size);
1234567
注意点:
此函数读取完数据后,会把该数据从缓冲区清掉;
3.3.5 peek() - 读取接收缓冲区一个字节
函数说明:
/**
* 读取接收缓冲区一个字节
* @return int 一字节数据
*/
int peek();
12345
注意点:
此函数读取完数据后,不会把该数据从缓冲区清掉,所以需要特别关注这一点;
3.3.6 peekBytes(buf,size) - 读取接收缓冲区size大小的字节数据
函数说明:
/**
* 读取接收缓冲区length大小的字节数据
* @param buffer 数据存储到该 buffer
* @param length 读取大小
* @return size_t 成功读取的大小
*/
size_t peekBytes(uint8_t *buffer, size_t length);
size_t peekBytes(char *buffer, size_t length);
12345678
注意点:
此函数读取完数据后,不会把该数据从缓冲区清掉,所以需要特别关注这一点;
3.3.7 readStringUntil - 读取响应数据直到某个字符串为止
函数说明:
/**
* 读取响应数据直到某个字符串为止
* @param end 结束字符
* @return String 读取成功的字符串
*/
String readStringUntil(char end);
123456
3.3.8 find - 查找某个字符串
函数说明:
/**
* 判断是否存在某个目标字符串
* @param buffer 目标字符串
* @return bool 存在返回true
*/
bool find(char *buffer);
123456
注意点:
此函数会把数据从缓冲区清掉;
3.3.9 flush - 清除接收缓冲区
函数说明:
/**
* 清除缓冲区
*/
void flush(void);
1234
注意点:
新版本flush功能是等待缓冲区中的所有传出字符都已发送。所以做不了清除缓冲区的作用;
可以有以下代替:
while(client.read()>0);
1
方法要点
博主建议大家尽量用批量处理的方法,比如 readStringUntil、read(buf,size)、peekBytes(buf,length),性能方面会好很多;
博主通过查看源码,发现client的发送缓冲区的大小是256Bytes;
3.4 普通设置
3.4.1 setNoDelay - 是否禁用 Nagle 算法。
函数说明:
/**
* 是否禁用 Nagle 算法。
* @param nodelay true表示禁用 Nagle 算法
*/
void setNoDelay(bool nodelay);
12345
底层源码:
void setNoDelay(bool nodelay)
{
if(!_pcb) {
return;
}
if(nodelay) {
tcp_nagle_disable(_pcb);
} else {
tcp_nagle_enable(_pcb);
}
}
1234567891011
注意点:
Nagle 算法的目的是通过合并一些小的发送消息,然后一次性发送所有的消息来减少通过网络发送的小数据包的tcp/ip流量。这种方法的缺点是延迟了单个消息的发送,直到一个足够大的包被组装。
该方法出处:https://blog.csdn.net/dpjcn1990/article/details/92830087
上面的方法看完之后,讲讲他大体的流程
1 建立连接 2 get请求 3 获取数据 就这三步。如果你获取的是json数据,那就需要用的jsonobject这个库,如果是普通文本。那就需要一个char一个cha的单独读出来组文字。
这里有一个就是建立连接的问题。这个问题一直困扰着我,也就是说我想获取两个地方的天气的时候,是无法获取到的,哪怕你发送了两个get请求,原文中代码是这样写的:
// This will send the request to the server
client.print(String("GET ") + GetUrl + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
为什么获取不到呢,因为你在get之后,将Connection进行了close 。看最后一行。。。一开始我百思不得其解,为什么连接会自动关闭,直到看到了一篇文章,我才恍然大悟,原来在这里。相关链接 https://www.cnblogs.com/traxex/p/5803023.html
大体贴一下内容:
Connection头具体值
- 请求:
- close(告诉WEB服务器或者代理服务器,在完成本次请求的响应后,断开连接,不要等待本次连接的后续请求了)。
- keepalive(告诉WEB服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待本次连接的后续请求)。
- 响应:
- close(连接已经关闭)。
- keep-alive(连接保持着,在等待本次连接的后续请求)。
- Keep-Alive: timeout。这个值能够让一些浏览器主动关闭连接,这样服务器就不必要去关闭连接了。(见4)
这就是保持该链接是长链接还是短连接的唯一的根源。你要短连接,那就请求之后close.你如果想要长链接,那就alive
更改后代码如下
DebugPrintln(client.print(String("GET ") + GetUrl + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: alive\r\n\r\n"));
通过测试证明,该链接没有被关闭。而是保持畅通
那我们该如何关闭该链接呢。这就是我们client提供的一个方法,client.stop()方法。来讲该链接关闭。所以我们可以再保持连接的前提下,多次请求多个城市的天气,而不必每次请求都打开一个链接。唯一的诀窍就在于Connection。
OK,这个问题困扰我很久,今天终于解决掉了
第二个问题就是多汉字输出的问题。
你可以通过多维数组保存字模。你也放到一个一维数组中,用循环去处理。
汉字的输出,一定要记得,16*16尺寸的汉字,他的字模就是32个。这就是for循环的基础。
这里有个问题,就是位置问题。你一个汉字输出完,第二个汉字的位置,第三个汉字的位置,要做好处理,否则就会打到同一个位置了。
贴一下代码
/输出中文
void printChinese(uint x,uint y,unsigned char *Hz_code,uint arraySize)
{
DebugPrintln(String("arraySize") + arraySize);
short int temp_code=0;
int i,j,k;
for(i=0;i<arraySize/2;i++)
for(j=0;j<2;j++)
for(k=0;k<8;k++)
if(((Hz_code[i*2+j]>>(7-k))&0x1)!=NULL)
{
if(i<16)
{
tft.drawPixel(x+8*j+k,y+i, ST7735_WHITE);
}
else
{
x=20; //汉字的位置信息,x是横轴,y是纵轴。
tft.drawPixel(x+8*j+k,y+i-16, ST7735_WHITE);
}
}
}
经过上述两个步骤,我们的天气终于顺利完成了。贴一下图。