TCP client
client,又名客户端,也就是需要通过获取server提供的服务数据来展示自己。Tcp client,只是架构在tcp协议之上的客户端。上图中,ESP8266作为client端,通过路由,访问局域网内的Pc server或者广域网下的网络服务器信息,server收到请求后会处理请求并且把响应数据返回以供ESP8266使用。
总体上分为4种方法
- 第一类方法,连接操作;
- 第二类方法,发送请求操作;
- 第三类方法,响应操作;
- 第四类方法,普通设置;
1.建立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);
2.判断client是否还在连接
/**
* 判断tcp连接是否建立起来(ESTABLISHED)
* @return result of tcp connect
* 1 --- success
* 0 --- fail
*/
uint8_t connected();
3.停止tcp连接
/**
* 关闭tcp连接
*/
void stop();
4.获取tcp连接状态
/**
* 获取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();
2.发送数据操作
5.发送数据到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);
//注意:write(uint8_t)函数是发送数据的底层方法,也就是说print、println底层也是调用write;
write(const char *str) 函数底层是调用 write(const char *buffer, size_t size),通过strlen计算长度;
6.发送数据到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);
//读者需要特别关注 print(const __FlashStringHelper *) 这个函数,以后代码内存优化需用用到;
常见用法:
//实例代码 非完整代码 不可直接使用 理解即可
WiFiClient client;
client.print( F(“This is an flash string”)); //字符串“This is an flash string”存在于flash
7.发送数据到client连接的server
/**
发送数据,并且加上换行符 “
“
@param FlashStringHelper 需要发送的字符串,字符串存在flash中(PROGMEM)
@return size_t 成功写入发送缓冲区的字节数
*/
size_t println(const __FlashStringHelper *);
/**
- 发送数据,并且加上换行符 “
“
- @param String 需要发送的字符串,字符串存在内存中
- @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(const String &s);
/**
- 发送数据,并且加上换行符 “
“
- @param String 需要发送的字符数组,字符数组存在内存中
- @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(const char[]);
/**
- 发送数据,并且加上换行符 “
“
- @param String 需要发送的字符
- @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(char);
/**
- 发送数据,并且加上换行符 “
“
- @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);
/**
- 发送换行符 “
“ @return size_t 成功写入发送缓冲区的字节数
*/
size_t println(void);
//注意:println系列其实就是在print系列的基础上加上了换行符 “
”;
8.返回接收缓存区可读取字节数
/**
* 返回发送缓冲区剩余可写字节数
* @return int 发送缓冲区剩余可写字节数
*/
size_t availableForWrite();
//注意:一般来说,调用发送数据操作之后,并不会立刻发送出去,而是把数据放入发送缓冲区,通过机制不断读取发送缓冲区的数据不断发送出去;
可以通过此函数判断请求是否发送完毕;
9.读取接收缓冲区一个字节
/**
* 读取接收缓冲区一个字节
* @return int 一字节数据
*/
int read();
10.读取接收缓冲区size大小的字节数据
/**
* 读取接收缓冲区size大小的字节数据
* @param buf 数据存储到该buf
* @param size 读取大小
* @return int 成功读取的大小
*/
int read(uint8_t *buf, size_t size);
11.读取接收缓冲区size大小的字节数据
/**
* 读取接收缓冲区一个字节
* @return int 一字节数据
*/
int peek();
12.读取接收缓冲区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);
//注意:此函数读取完数据后,不会把该数据从缓冲区清掉,所以需要特别关注这一点
13.读取响应数据直到某个字符串为止
/**
* 读取响应数据直到某个字符串为止
* @param end 结束字符
* @return String 读取成功的字符串
*/
String readStringUntil(char end);
14.查找某个字符串
/**
* 判断是否存在某个目标字符串
* @param buffer 目标字符串
* @return bool 存在返回true
*/
bool find(char *buffer);
//注意:此函数会把数据从缓冲区清掉;
15.清除接收缓冲区
/**
* 清除缓冲区
*/
void flush(void);
//注意:新版本flush功能是等待缓冲区中的所有传出字符都已发送。所以做不了清除缓冲区的作用;
可以有以下代替:
while(client.read()>0);
方法要点:博主建议大家尽量用批量处理的方法,比如 readStringUntil、read(buf,size)、peekBytes(buf,length),性能方面会好很多; 博主通过查看源码,发现client的发送缓冲区的大小是256Bytes;
16.是否禁用 Nagle 算法。
/**
* 是否禁用 Nagle 算法。
* @param nodelay true表示禁用 Nagle 算法
*/
void setNoDelay(bool nodelay);
//注意:Nagle 算法的目的是通过合并一些小的发送消息,然后一次性发送所有的消息来减少通过网络发送的小数据包的tcp/ip流量。这种方法的缺点是延迟了单个消息的发送,直到一个足够大的包被组装。
实例操作
/** * Demo: * STA模式下,演示WiFiClient与TCP server之间的通信功能 * 本实验需要跟TCP调试助手一起使用。 * @author 单片机菜鸟 * @date 2019/1/25 */ #include <ESP8266WiFi.h>
//以下三个定义为调试定义
#define DebugBegin(baud_rate) Serial.begin(baud_rate)
#define DebugPrintln(message) Serial.println(message)
#define DebugPrint(message) Serial.print(message)#define AP_SSID “TP-LINK_5344” //这里改成你的wifi名字
#define AP_PSW “xxxxxxx”//这里改成你的wifi密码const uint16_t port = 8234;
const char * host = “192.168.1.102”; // ip or dns
WiFiClient client;//创建一个tcp client连接void setup() {
//设置串口波特率,以便打印信息
DebugBegin(115200);
//延时5s 为了演示效果
delay(5000);
// 我不想别人连接我,只想做个站点
WiFi.mode(WIFI_STA);
WiFi.begin(AP_SSID,AP_PSW);DebugPrint(“Wait for WiFi… “);
//等待wifi连接成功
while (WiFi.status() != WL_CONNECTED) {
Serial.print(“.”);
delay(500);
}DebugPrintln(“”);
DebugPrintln(“WiFi connected”);
DebugPrint(“IP address: “);
DebugPrintln(WiFi.localIP());delay(500);
}void loop() {
DebugPrint(“connecting to “);
DebugPrintln(host);if (!client.connect(host, port)) {
DebugPrintln(“connection failed”);
DebugPrintln(“wait 5 sec…”);
delay(5000);
return;
}// 发送数据到Tcp server
DebugPrintln(“Send this data to server”);
client.println(String(“Send this data to server”));//读取从server返回到响应数据
String line = client.readStringUntil(‘
‘);
DebugPrintln(line);DebugPrintln(“closing connection”);
client.stop();
DebugPrintln(“wait 5 sec…”);
delay(5000);
}
这里说一下我的具体操作。首先把程序烧到wifi模块里面。(注意自己配置好相关参数)
然后我们要获取自己的主机的ip地址
从这里我们可以看到,自己主机的IP地址在192.168.137.左右,所以地址位为192.168.127.1
接下来我们可以打开tcp调试助手
注意ip地址的配置
打开后直接烧录程序即可!(会自动连接,然后发送数据)
演示用http请求调用天气接口信息
/** * Demo: * 演示Http请求天气接口信息 * @author 单片机菜鸟 * @date 2019/09/04 */ #include <ESP8266WiFi.h> #include <ArduinoJson.h>
//以下三个定义为调试定义
#define DebugBegin(baud_rate) Serial.begin(baud_rate)
#define DebugPrintln(message) Serial.println(message)
#define DebugPrint(message) Serial.print(message)const char* ssid = “TP-LINK_5344”; // XXXXXX – 使用时请修改为当前你的 wifi ssid
const char* password = “6206908you11011010”; // XXXXXX – 使用时请修改为当前你的 wifi 密码
const char* host = “api.seniverse.com”;
const char* APIKEY = “wcmquevztdy1jpca”; //API KEY
const char* city = “guangzhou”;
const char* language = “zh-Hans”;//zh-Hans 简体中文 会显示乱码const unsigned long BAUD_RATE = 115200; // serial connection speed
const unsigned long HTTP_TIMEOUT = 5000; // max respone time from server
const size_t MAX_CONTENT_SIZE = 1000; // max size of the HTTP response// 我们要从此网页中提取的数据的类型
struct WeatherData {
char city[16];//城市名称
char weather[32];//天气介绍(多云…)
char temp[16];//温度
char udate[32];//更新时间
};WiFiClient client;
char response[MAX_CONTENT_SIZE];
char endOfHeaders[] = ““;
void setup() {
// put your setup code here, to run once:
WiFi.mode(WIFI_STA); //设置esp8266 工作模式
DebugBegin(BAUD_RATE);
DebugPrint(“Connecting to “);//写几句提示,哈哈
DebugPrintln(ssid);
WiFi.begin(ssid, password); //连接wifi
WiFi.setAutoConnect(true);
while (WiFi.status() != WL_CONNECTED) {
//这个函数是wifi连接状态,返回wifi链接状态
delay(500);
DebugPrint(“.”);
}
DebugPrintln(“”);
DebugPrintln(“WiFi connected”);
delay(500);
DebugPrintln(“IP address: “);
DebugPrintln(WiFi.localIP());//WiFi.localIP()返回8266获得的ip地址
client.setTimeout(HTTP_TIMEOUT);
}void loop() {
// put your main code here, to run repeatedly:
//判断tcp client是否处于连接状态,不是就建立连接
while (!client.connected()){
if (!client.connect(host, 80)){
DebugPrintln(“connection….”);
delay(500);
}
}
//发送http请求 并且跳过响应头 直接获取响应body
if (sendRequest(host, city, APIKEY) && skipResponseHeaders()) {
//清除缓冲
clrEsp8266ResponseBuffer();
//读取响应数据
readReponseContent(response, sizeof(response));
WeatherData weatherData;
if (parseUserData(response, &weatherData)) {
printUserData(&weatherData);
}
}
delay(5000);//每5s调用一次
}/**
- @发送http请求指令
/
bool sendRequest(const char host, const char* cityid, const char* apiKey) {
// We now create a URI for the request
//心知天气 发送http请求
String GetUrl = “/v3/weather/now.json?key=”;
GetUrl += apiKey;
GetUrl += “&location=”;
GetUrl += city;
GetUrl += “&language=”;
GetUrl += language;
// This will send the request to the server
client.print(String(“GET “) + GetUrl + “ HTTP/1.1
“ +
“Host: “ + host + ““ +
“Connection: close“);
DebugPrintln(“create a request:”);
DebugPrintln(String(“GET “) + GetUrl + “ HTTP/1.1
“ +
“Host: “ + host + “
“ +
“Connection: close
“);
delay(1000);
return true;
}/**
- @Desc 跳过 HTTP 头,使我们在响应正文的开头
*/
bool skipResponseHeaders() {
// HTTP headers end with an empty line
bool ok = client.find(endOfHeaders);
if (!ok) {
DebugPrintln(“No response or invalid response!”);
}
return ok;
}/**
- @Desc 从HTTP服务器响应中读取正文
/
void readReponseContent(char content, size_t maxSize) {
size_t length = client.peekBytes(content, maxSize);
delay(100);
DebugPrintln(“Get the data from Internet!”);
content[length] = 0;
DebugPrintln(content);
DebugPrintln(“Read data Over!”);
client.flush();//清除一下缓冲
}/**
- @Desc 解析数据 Json解析
- 数据格式如下:
- {
- “results”: [
{
"location": {
"id": "WX4FBXXFKE4F",
"name": "北京",
"country": "CN",
"path": "北京,北京,中国",
"timezone": "Asia/Shanghai",
"timezone_offset": "+08:00"
},
"now": {
"text": "多云",
"code": "4",
"temperature": "23"
},
"last_update": "2017-09-13T09:51:00+08:00"
}
- ]
}
/
bool parseUserData(char content, struct WeatherData weatherData) {
// – 根据我们需要解析的数据来计算JSON缓冲区最佳大小
// 如果你使用StaticJsonBuffer时才需要
// const size_t BUFFER_SIZE = 1024;
// 在堆栈上分配一个临时内存池
// StaticJsonBufferjsonBuffer;
// – 如果堆栈的内存池太大,使用 DynamicJsonBuffer jsonBuffer 代替
DynamicJsonBuffer jsonBuffer;JsonObject& root = jsonBuffer.parseObject(content);
if (!root.success()) {
DebugPrintln(“JSON parsing failed!”);
return false;
}//复制我们感兴趣的字符串
strcpy(weatherData->city, root[“results”][0][“location”][“name”]);
strcpy(weatherData->weather, root[“results”][0][“now”][“text”]);
strcpy(weatherData->temp, root[“results”][0][“now”][“temperature”]);
strcpy(weatherData->udate, root[“results”][0][“last_update”]);
// – 这不是强制复制,你可以使用指针,因为他们是指向“内容”缓冲区内,所以你需要确保
// 当你读取字符串时它仍在内存中
return true;
}// 打印从JSON中提取的数据
void printUserData(const struct WeatherData* weatherData) {
DebugPrintln(“Print parsed data :”);
DebugPrint(“City : “);
DebugPrint(weatherData->city);
DebugPrint(“, “);
DebugPrint(“Weather : “);
DebugPrint(weatherData->weather);
DebugPrint(“, “);
DebugPrint(“Temp : “);
DebugPrint(weatherData->temp);
DebugPrint(“ C”);
DebugPrint(“, “);
DebugPrint(“Last Updata : “);
DebugPrint(weatherData->udate);
DebugPrintln(“
“);
}// 关闭与HTTP服务器连接
void stopConnect() {
DebugPrintln(“Disconnect”);
client.stop();
}
void clrEsp8266ResponseBuffer(void){
memset(response, 0, MAX_CONTENT_SIZE); //清空
}
这里好像没有用,暂时先不解释,以后在研究
TCP Server
在ESP8266上建立TCP Server需要用到WiFiServer库,WiFiServer库也是属于ESP8266WiFi库里面的一部分,主要是负责跟server有关的操作。
总体分为3部分
- 管理server方法;
- WiFiClient接入方法;
- 响应WiFiClient的请求(这部分方法请看上面讲解);
测试很简单,先获取wifi模块的ip地址
然后直接用tcp调试助手连接
发送数据,wifi模块就可以接受到数据了。
演示webserver功能
/** * Demo: * 演示web Server功能 * 打开PC浏览器 输入IP地址。请求web server * @author 单片机菜鸟 * @date 2019/09/05 */ #include <ESP8266WiFi.h>
const char* ssid = “TP-LINK_5344”;//wifi账号 这里需要修改
const char* password = “xxxx”;//wifi密码 这里需要修改//创建 tcp server 端口号是80
WiFiServer server(80);void setup(){
Serial.begin(115200);
Serial.println();Serial.printf(“Connecting to %s “, ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED){
delay(500);
Serial.print(“.”);
}
Serial.println(“ connected”);
//启动TCP 连接
server.begin();
//打印TCP server IP地址
Serial.printf(“Web server started, open %s in a web browser
“, WiFi.localIP().toString().c_str());
}/**
- 模拟web server 返回http web响应内容
- 这里是手动拼接HTTP响应内容
- 后面楼主会继续讲解另外两个专用于http请求的库
*/
String prepareHtmlPage(){
String htmlPage =
String(“HTTP/1.1 200 OK
“) +
“Content-Type: text/html“ +
“Connection: close
“ + // the connection will be closed after completion of the response
“Refresh: 5
“ + // refresh the page automatically every 5 sec
“
“ +
““ +
““ +
“Analog input: “ + String(analogRead(A0)) +
““ +
“
“;
return htmlPage;
}void loop(){
WiFiClient client = server.available();
// wait for a client (web browser) to connect
if (client){
Serial.println(“
[Client connected]”);
while (client.connected()){
// 不断读取请求内容
if (client.available()){
String line = client.readStringUntil(‘
‘);
Serial.print(line);
// wait for end of client’s request, that is marked with an empty line
if (line.length() == 1 && line[0] == ‘
‘){
//返回响应内容
client.println(prepareHtmlPage());
break;
}
}
//由于我们设置了 Connection: close 当我们响应数据之后就会自动断开连接
}
delay(100); // give the web browser time to receive the data// close the connection: client.stop(); Serial.println("[Client disonnected]");
}
}
这里测试成功了,用法很简单,直接烧到自己的wifi模块里面。
然后在浏览器输入你wifi模块的IP地址就可以看到内容了。
演示webserver功能,根据响应做不同操作
/* * Demo: * 演示简单web Server功能 * web server会根据请求来做不同的操作 * http://server_ip/gpio/0 打印 /gpio0 * http://server_ip/gpio/1 打印 /gpio1 * server_ip就是ESP8266的Ip地址 * @author 单片机菜鸟 * @date 2019/09/05 */
#include <ESP8266WiFi.h>
//以下三个定义为调试定义
#define DebugBegin(baud_rate) Serial.begin(baud_rate)
#define DebugPrintln(message) Serial.println(message)
#define DebugPrint(message) Serial.print(message)const char* ssid = “TP-LINK_5344”;//wifi账号 这里需要修改
const char* password = “xxxx”;//wifi密码 这里需要修改// 创建tcp server
WiFiServer server(80);void setup() {
DebugBegin(115200);
delay(10);// Connect to WiFi network
DebugPrintln(“”);
DebugPrintln(String(“Connecting to “) + ssid);
//我只想做个安静的美男子 STA
WiFi.mode(WIFI_STA);
//我想连接路由wifi
WiFi.begin(ssid, password);while (WiFi.status() != WL_CONNECTED) {
delay(500);
DebugPrint(“.”);
}
DebugPrintln(“”);
DebugPrintln(“WiFi connected”);// 启动server
server.begin();
DebugPrintln(“Server started”);// 打印IP地址
DebugPrintln(WiFi.localIP().toString());
}void loop() {
// 等待有效的tcp连接
WiFiClient client = server.available();
if (!client) {
return;
}DebugPrintln(“new client”);
//等待client数据过来
while (!client.available()) {
delay(1);
}// 读取请求的第一行 会包括一个url,这里只处理url
String req = client.readStringUntil(‘
‘);
DebugPrintln(req);
//清掉缓冲区数据 据说这个方法没什么用 可以换种实现方式
client.flush();// 开始匹配
int val;
if (req.indexOf(“/gpio/0”) != -1) {
DebugPrintln(“/gpio0”);
val = 0;
} else if (req.indexOf(“/gpio/1”) != -1) {
DebugPrintln(“/gpio1”);
val = 1;
} else {
DebugPrintln(“invalid request”);
//关闭这个client请求
client.stop();
return;
}
//清掉缓冲区数据
client.flush();// 准备响应数据
GPIO is now "; s += (val) ? "high" : "low"; s += " ";
String s = “HTTP/1.1 200 OK
Content-Type: text/html// 发送响应数据给client
client.print(s);
delay(1);
DebugPrintln(“Client disonnected”);
// The client will actually be disconnected
// when the function returns and ‘client’ object is detroyed
}
这里演示一下效果(效果就是这样)
教程到此结束,本比较参考博哥博客 ,如果对你有帮助,给我几块硬币可好?