05-ESP8226的TCP客户端学习

前言

TCP和客户端对于我来说就是完全陌生的知识领域,所以这部分也是百度了解来的

05-ESP8226的TCP客户端库学习

有标题先拆分两个内容TCP和客户端,下图是ESP8266作为客户端的示意图,其中PC作为服务器,ESP8266作为客户端获取服务器的数据

image

什么是TCP?

对于一个不懂的小白确实需要初步了解一下TCP的三个过程。引用博客

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接、可靠的、基于字节流的传输层通信协议。

TCP 协议的执行过程分为三个阶段

  1. 连接创建(Connection Establishment)
  2. 数据传送(Data Transfer)
  3. 连接终止(Connection Termination)

其中「连接创建」与「连接终止」分别是耳熟能详的 TCP 协议三次握手(TCP Three-way Handshake)与四次挥手(TCP Four-way Handshake),也是理解本文 TCP 服务器与客户端通信过程的两个核心阶段。

下图是「连接创建」的三次握手示意图

75c85605d8343adb4a191d05951c5097.png

下图是「连接终止」的四次挥手示意图

555b8cf6f7f07b6e1cc67e9da95a2266.png

什么是客户端和服务器端?

简单点说:客户端(Client)是发送请求(request),服务器端(Service)是响应请求(response),返回相应的资源数据

服务器的特征:被动角色,等待来自客户端的连接请求,处理请求并回传结果。

客户端的特征:主动角色,发送连接请求,等待服务器的响应。

端口:是指用于区分不同服务的逻辑编号,端口号的范围从0到65535,SIEMENS设备的开放式以太网通信通常使用编号为2000~5000范围内端口。

客户端侧在配置TCP连接时,必须设置服务器IP地址及端口号,自身使用的端口号如果没有明确指定,则由设备自动分配。

服务器侧在配置TCP连接时,必须设置服务器使用的端口号,客户端IP地址及端口号为可选项。

下面来看库文件**WiFiClient.h**,同样做了分类:

连接操作:

//客户端连接
  virtual int connect(IPAddress ip, uint16_t port) override;
  virtual int connect(const char *host, uint16_t port) override;
  virtual int connect(const String& host, uint16_t port);

//停止TCP连接
  bool stop(unsigned int maxWaitMs);

//TCP连接状态
virtual uint8_t status();
//连接状态宏定义,与上面TCP部分对应
enum tcp_state {
  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
};
//客户端是否在连接
virtual uint8_t connected() override;

//写
  virtual size_t write(uint8_t) override;
  virtual size_t write(const uint8_t *buf, size_t size) override;
  virtual size_t write_P(PGM_P buf, size_t size);

//发送数据
	println();
	print();

//读取响应数据中的一个字符,清掉  
  virtual int read() override;
//读取固定大小的响应数据,清掉  
  virtual int read(uint8_t* buf, size_t size) override;
//读取响应数据中的一个字符,不清掉  
virtual int peek() override;
//读取固定大小的响应数据,不清掉   
virtual size_t peekBytes(uint8_t *buffer, size_t length);
//清除缓冲区
bool flush(unsigned int maxWaitMs);

官方例程1:

/*
    This sketch sends a string to a TCP server, and prints a one-line response.
    You must run a TCP server in your local network.
    For example, on Linux you can use this command: nc -v -l 3000
*/

#include <ESP8266WiFi.h>
#include <ESP8266WiFiMulti.h>

#ifndef STASSID
#define STASSID "tangbo"
#define STAPSK  "88888888"
#endif

const char* ssid     = STASSID;
const char* password = STAPSK;
//客户端访问IP地址
const char* host = "192.168.137.1";
//端口号
const uint16_t port = 3000;

ESP8266WiFiMulti WiFiMulti;

void setup() {
  Serial.begin(115200);

  // We start by connecting to a WiFi network
  WiFi.mode(WIFI_STA);

  //最好添加这步骤
  WiFi.disconnect();
  
  //连接到WiFi
  WiFiMulti.addAP(ssid, password);

  Serial.println();
  Serial.println();
  Serial.print("Wait for WiFi... ");

  while (WiFiMulti.run() != WL_CONNECTED) {
    Serial.print(".");
    delay(500);
  }

  Serial.println("");
  Serial.println("WiFi connected");
  Serial.println("IP address: ");
  Serial.println(WiFi.localIP());

  delay(500);
}


void loop()
{
  Serial.print("connecting to ");
  Serial.print(host);
  Serial.print(':');
  Serial.println(port);

  // Use WiFiClient class to create TCP connections
  WiFiClient client;

  if (!client.connect(host, port)) {
    Serial.println("connection failed");
    Serial.println("wait 5 sec...");
    delay(5000);
    return;
  }

  // This will send the request to the server
  client.println("hello from ESP8266");

  //read back one line from server
  Serial.println("receiving from remote server");
  String line = client.readStringUntil('\r');
  Serial.println(line);
  delay(10);

  Serial.println("closing connection");
  client.stop();

  Serial.println("wait 5 sec...");
  delay(5000);
}

菜鸟哥例程2:

/**
 * 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[] = "\r\n\r\n";
 
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\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n\r\n");
  DebugPrintln("create a request:");
  DebugPrintln(String("GET ") + GetUrl + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "Connection: close\r\n");
  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.readBytes(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;
//   在堆栈上分配一个临时内存池
//    StaticJsonBuffer<BUFFER_SIZE> jsonBuffer;
//    -- 如果堆栈的内存池太大,使用 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(", \t");
  DebugPrint("Weather : ");
  DebugPrint(weatherData->weather);
  DebugPrint(",\t");
  DebugPrint("Temp : ");
  DebugPrint(weatherData->temp);
  DebugPrint(" C");
  DebugPrint(",\t");
  DebugPrint("Last Updata : ");
  DebugPrint(weatherData->udate);
  DebugPrintln("\r\n");
}
   
// 关闭与HTTP服务器连接
void stopConnect() {
  DebugPrintln("Disconnect");
  client.stop();
}
  
void clrEsp8266ResponseBuffer(void){
    memset(response, 0, MAX_CONTENT_SIZE);      //清空
}

r);
DebugPrint(“,\t”);
DebugPrint(“Temp : “);
DebugPrint(weatherData->temp);
DebugPrint(” C”);
DebugPrint(“,\t”);
DebugPrint(“Last Updata : “);
DebugPrint(weatherData->udate);
DebugPrintln(”\r\n”);
}

// 关闭与HTTP服务器连接
void stopConnect() {
DebugPrintln(“Disconnect”);
client.stop();
}

void clrEsp8266ResponseBuffer(void){
memset(response, 0, MAX_CONTENT_SIZE); //清空
}



版权声明:本文为TB980181原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。