ESP32 开发之旅⑨ TCP Server

授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石。。。

快速导航
单片机菜鸟的博客快速索引(快速找到你要的)

如果觉得有用,麻烦点赞收藏,您的支持是博主创作的动力。

1. 前言

    接下来,博主开始介绍TCP Client的重要伙伴 —— Tcp Server。

    现在,手机上网已经是人们每天必不可少的事情。比如刷微博,刷朋友圈,刷新闻等等; 那么这些朋友圈、微博、新闻内容都是从哪里来的呢?做个App开发的同学都应该知道,手机App属于client端,属于UI端,展示UI内容,而显示什么UI内容基本上都是发送一些http请求到后端服务(server),服务器根据具体的请求内容返回对应的响应内容。

    所谓server,可以简单理解为提供服务,提供数据的一个地方。

    先来理解一下概念图:

image

    mobile phone作为client端,通过路由热点,向Server端的ESP8266请求数据,8266获取到请求后解析请求然后返回响应数据。

扫描二维码关注公众号,回复: 8631577 查看本文章

    但是,请开发者注意:ESP32上建立一个server是比较简单的,不过是属于局域网内的server,因为真正意义上的server并不是这样的,大伙了解一个这样的概念就好

2. WiFiServer库

    在ESP32上建立TCP Server需要用到WiFiServer库,WiFiServer库也是属于WiFi库里面的一部分,主要是负责跟server有关的操作。

    方法总体上可以分为三部分:

  • 管理server方法;
  • WiFiClient接入方法;
  • 响应WiFiClient的请求(这部分方法请看上面讲解);

2.1 管理server

2.1.1 WiFiServer server(port) —— 创建TCP server

函数说明:

/**
 * 函数功能:创建TCP server
 * @param port server的端口
 * @param max_clients 最大连接数
 */
WiFiServer(uint16_t port=80, uint8_t max_clients=4);

2.1.2 begin() —— 启动TCP server

函数说明:

/**
 * 函数功能:启动TCP server
 */
void begin();
/**
 * 函数功能:启动TCP server
 * @param port server端口号
 */
void begin(uint16_t port);

注意点:

  • begin()和 WiFiServer(port)一起使用;

2.1.3 setNoDelay() —— 关闭延时发送功能

函数说明:

/**
 * 是否禁用 Nagle 算法。
 * @param nodelay true表示禁用 Nagle 算法
 */
void setNoDelay(bool nodelay);

注意点:

  • Nagle 算法的目的是通过合并一些小的发送消息,然后一次性发送所有的消息来减少通过网络发送的小数据包的tcp/ip流量。这种方法的缺点是延迟了单个消息的发送,直到一个足够大的包被组装。

2.1.4 close() —— 关闭TCP server

函数说明:

/**
 * 关闭TCP server
 */
void close();

2.1.5 stop() —— 停止TCP server

函数说明:

/**
 * 停止TCP server
 */
void stop();

注意点:

  • stop()和 close()是同样的功能,所以调用哪一个都没有问题;
void WiFiServer::stop() {
    close();
}

2.2 WiFiClient接入

2.2.1 available —— 获取有效的wificlient连接

函数说明:

/**
 * 获取有效的wificlient连接
 * @return 如果存在有效的wificlient连接,就返回WiFilient对象,如果没有那就返回一个无效的wificlient(connected等于false,开发者可以通过判断connected()
 */
WiFiClient available();

函数源码:

WiFiClient WiFiServer::available(){
  if(!_listening)
    return WiFiClient();
  int client_sock;
  if (_accepted_sockfd >= 0) {
    client_sock = _accepted_sockfd;
    _accepted_sockfd = -1;
  }
  else {
  struct sockaddr_in _client;
  int cs = sizeof(struct sockaddr_in);
    client_sock = lwip_accept_r(sockfd, (struct sockaddr *)&_client, (socklen_t*)&cs);
  }
  if(client_sock >= 0){
    int val = 1;
    if(setsockopt(client_sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&val, sizeof(int)) == ESP_OK) {
      val = _noDelay;
      if(setsockopt(client_sock, IPPROTO_TCP, TCP_NODELAY, (char*)&val, sizeof(int)) == ESP_OK)
        return WiFiClient(client_sock);
    }
  }
  return WiFiClient();
}

2.2.2 hasClient —— 判断是否有client连接

函数说明:

/**
 * 判断是否有client连接
 * @return bool 如果有client连接就返回true
 */
bool hasClient();

注意点:

  • 开发者可以通过判断这个函数来判断是否有client连接,然后调用available() 方法来获取连接,这样拿到wificlient之后就可以调用wificlient的方法;

3. 实例操作

    前面讲了这么多理论内容,接下来用几个例子来说明一下。

7.1 演示WiFiServer功能

例子介绍
    32作为WiFiServer端,打开TCP调试助手,模拟TCP Client的请求。
例子源码:

/**
 * Demo:
 *    演示WiFiServer功能
 *    打开TCP调试助手 模拟TCP client请求
 * @author 单片机菜鸟
 * @date 2020/01/08
 */
#include <WiFi.h>
 
//定义最多多少个client可以连接本server(一般不要超过4个)
#define MAX_SRV_CLIENTS 1
//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
 
const char* ssid = "xxxxxx";
const char* password = "xxxxxx";
 
//创建server 端口号是23
WiFiServer server(23);
//管理clients
WiFiClient serverClients[MAX_SRV_CLIENTS];
 
void setup() {
  DebugBegin(115200);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  DebugPrint("\nConnecting to "); 
  DebugPrintln(ssid);
  uint8_t i = 0;
  while (WiFi.status() != WL_CONNECTED && i++ < 20) {
    delay(500);
  }
  if (i == 21) {
    DebugPrint("Could not connect to"); 
    DebugPrintln(ssid);
    while (1) {
      delay(500);
    }
  }
  //启动server
  server.begin();
  //关闭小包合并包功能,不会延时发送数据
  server.setNoDelay(true);
 
  DebugPrint("Ready! Use 'telnet ");
  DebugPrint(WiFi.localIP());
  DebugPrintln(" 23' to connect");
}
 
void loop() {
  uint8_t i;
  //检测是否有新的client请求进来
  if (server.hasClient()) {
    for (i = 0; i < MAX_SRV_CLIENTS; i++) {
      //释放旧无效或者断开的client
      if (!serverClients[i] || !serverClients[i].connected()) {
        if (serverClients[i]) {
          serverClients[i].stop();
        }
        //分配最新的client
        serverClients[i] = server.available();
        DebugPrint("New client: "); 
        DebugPrint(i);
        break;
      }
    }
    //当达到最大连接数 无法释放无效的client,需要拒绝连接
    if (i == MAX_SRV_CLIENTS) {
      WiFiClient serverClient = server.available();
      serverClient.stop();
      DebugPrintln("Connection rejected ");
    }
  }
  //检测client发过来的数据
  for (i = 0; i < MAX_SRV_CLIENTS; i++) {
    if (serverClients[i] && serverClients[i].connected()) {
      if (serverClients[i].available()) {
        //get data from the telnet client and push it to the UART
        while (serverClients[i].available()) {
          //发送到串口调试器
          Serial.write(serverClients[i].read());
        }
      }
    }
  }
 
  if (Serial.available()) {
    //把串口调试器发过来的数据 发送给client
    size_t len = Serial.available();
    uint8_t sbuf[len];
    Serial.readBytes(sbuf, len);
    //push UART data to all connected telnet clients
    for (i = 0; i < MAX_SRV_CLIENTS; i++) {
      if (serverClients[i] && serverClients[i].connected()) {
        serverClients[i].write(sbuf, len);
        delay(1);
      }
    }
  }
}

测试结果:
image

7.2 演示web Server功能

例子介绍:
    32作为web server端,打开PC浏览器输入IP地址,请求web server。
例子源码:

/**
 * Demo:
 *    演示web Server功能
 *    打开PC浏览器 输入IP地址。请求web server
 * @author 单片机菜鸟
 * @date 2020/01/08
 */
#include <WiFi.h>
 
const char* ssid = "xxxxx";//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\n", WiFi.localIP().toString().c_str());
}
 
/**
 * 模拟web server 返回http web响应内容
 * 这里是手动拼接HTTP响应内容
 * 后面楼主会继续讲解另外两个专用于http请求的库
 */
String prepareHtmlPage(){
  String htmlPage =
     String("HTTP/1.1 200 OK\r\n") +
            "Content-Type: text/html\r\n" +
            "Connection: close\r\n" +  // the connection will be closed after completion of the response
            "Refresh: 5\r\n" +  // refresh the page automatically every 5 sec
            "\r\n" +
            "<!DOCTYPE HTML>" +
            "<html>" +
            "Analog input:  " + String(analogRead(A0)) +
            "</html>" +
            "\r\n";
  return htmlPage;
}
 
 
void loop(){
  WiFiClient client = server.available();
  // wait for a client (web browser) to connect
  if (client){
    Serial.println("\n[Client connected]");
    while (client.connected()){
      // 不断读取请求内容
      if (client.available()){
        String line = client.readStringUntil('\r');
        Serial.print(line);
        // wait for end of client's request, that is marked with an empty line
        if (line.length() == 1 && line[0] == '\n'){
          //返回响应内容
          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]");
  }
}

测试结果:
image

7.3 演示简单web Server功能,webserver会根据请求来做不同的操作

例子介绍:
    32作为WiFiServer端,演示简单web Server功能,webserver会根据请求来做不同的操作。
例子源码:

/*
* Demo:
*    演示简单web Server功能
*    web server会根据请求来做不同的操作
*    http://server_ip/gpio/0 打印 /gpio0
*    http://server_ip/gpio/1 打印 /gpio1
*    server_ip就是ESP8266的Ip地址
* @author 单片机菜鸟
* @date 2020/01/08
*/
 
#include <WiFi.h>
 
//以下三个定义为调试定义
#define DebugBegin(baud_rate)    Serial.begin(baud_rate)
#define DebugPrintln(message)    Serial.println(message)
#define DebugPrint(message)    Serial.print(message)
 
const char* ssid = "xxxxxx";//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('\r');
  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();
 
  // 准备响应数据
  String s = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n<!DOCTYPE HTML>\r\n<html>\r\nGPIO is now ";
  s += (val) ? "high" : "low";
  s += "</html>\n";
 
  // 发送响应数据给client
  client.print(s);
  delay(1);
  DebugPrintln("Client disonnected");
 
  // The client will actually be disconnected
  // when the function returns and 'client' object is detroyed
}

测试结果:
image

4. 总结

    这一篇章,博主主要讲了TCP通信的server。大家需要区分tcp http。并且也要区分工作模式和client server不是一个概念,两者没有必然的联系。这篇算是入门http请求的重点内容,希望读者可以仔细研读,并结合源码去理解。

发布了110 篇原创文章 · 获赞 488 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/dpjcn1990/article/details/103899629