如何在Arduino上运行一个Web服务器

404 阅读3分钟

在本教程中,我将向你展示如何在带有WiFi的Arduino设备上启动一个Web服务器,比如我的Arduino MKR WiFi 1010

我们将连接到一个现有的WiFi网络,我们将能够通过HTTP从我们的浏览器与Arduino互动。

这对各种应用来说是非常有趣的。从简单的检查传感器的数据,到根据执行的HTTP请求执行行动。

我们将从这个在使用Arduino连接到WiFi网络的教程中定义的程序开始。

#include <SPI.h>
#include <WiFiNINA.h>

void setup() {
  char ssid[] = SECRET_SSID;
  char pass[] = SECRET_PASS;

  Serial.begin(9600);
  while (!Serial);

  int status = WL_IDLE_STATUS;
  while (status != WL_CONNECTED) {
    Serial.print("Connecting to ");
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);
    delay(5000);
  }

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

void loop() {

}

在setup()之前,添加这一行。

来初始化80端口的TCP服务器,并在setup() 的最后调用

来启动该服务器。

现在这是一个TCP服务器,而不是一个HTTP服务器。但由于HTTP(一种TCP/IP应用协议)是建立在TCP(传输层)之上的,所以我们可以很容易地建立起自己的HTTP服务器。

首先,我们需要监听客户端的连接。我们在loop() 中这样做。

void loop() {
  WiFiClient client = server.available();
  if (client) {

  }
}

server 的available()方法监听传入的客户端。

if (client) {} 检查中,我们有一个HTTP客户端连接。我们需要做的是。

  • 调用client.connected() ,检查数据是否被连接,并且有数据可以读取
  • 调用client.available() ,以获得可供读取的字节数(这可确保有数据可读)。
  • 调用client.read() ,从传入的数据(客户端发送的HTTP请求)中读取一个字节。
  • 调用client.println()client.print() 向客户端发送数据,建立一个适当的 HTTP 响应。
  • 调用client.stop() 来结束连接

我们开始向串行接口打印客户端发送的每个字符,最后我们关闭连接。

void loop() {
  WiFiClient client = server.available();
  if (client) {
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);
      }
    }

    client.stop();
  }
}

尝试在Arduino上上传这个程序。把你的浏览器指向IP地址。你会看到类似这样的东西打印到串行接口。这是由浏览器发送的内容。

GET / HTTP/1.1
Host: 192.168.1.40
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.2 Safari/605.1.15
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Connection: keep-alive

注意结尾的空行。这是HTTP请求的结束。

我们需要拦截这个空行。

HTTP请求中的每一行都是由CR回车符(\r)和LF换行符(\n)结束的。

因此,请求的结束可以通过2组这些序列来确定:\r\n\r\n

这个简单的算法会起作用,我们只需记住当前字符之前的2个字符,并检查我们是否确定了序列\n\r\n (该序列的最后3个字符足以确定最后一行)。

void loop() {
  WiFiClient client = server.available();
  if (client) {

    char prevprev;
    char prev;

    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);

        if (prevprev == '\n' && prev == '\r' && c == '\n') {
          //we can send the response!
        }

        prevprev = prev;
        prev = c;
      }
    }

    client.stop();
  }
}

所以现在我们可以在if 里面发送响应,我们可以使用client.println() ,我们添加一个简单的响应,像这样。

HTTP/1.1 200 OK
Content-Type: text/html
Connection: close

<!DOCTYPE HTML>
<html>
test
</html>

以这种方式。

client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.println("test");
client.println("</html>");
break;

break; 语句结束了while (client.connected()) {} 块。

这里是完整的程序。

#include <SPI.h>
#include <WiFiNINA.h>

WiFiServer server(80);

void setup() {
  char ssid[] = SECRET_SSID;
  char pass[] = SECRET_PASS;

  Serial.begin(9600);
  while (!Serial);

  int status = WL_IDLE_STATUS;
  while (status != WL_CONNECTED) {
    Serial.print("Connecting to ");
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);
    delay(5000);
  }

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

  server.begin();
}

void loop() {
  WiFiClient client = server.available();
  if (client) {

    char prevprev;
    char prev;
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);

        if (prevprev == '\n' && prev == '\r' && c == '\n') {
          client.println("HTTP/1.1 200 OK");
          client.println("Content-Type: text/html");
          client.println("Connection: close");
          client.println();
          client.println("<!DOCTYPE HTML>");
          client.println("<html>");
          client.println("test");
          client.println("</html>");
          break;
        }

        prevprev = prev;
        prev = c;
      }
    }

    client.stop();
  }
}

试试吧,你应该看到test 在浏览器中显示出来。

这种方法是有效的,直到你需要弄清楚客户问我们什么。

在这种情况下,你想阅读每一行,所以这种替代方法效果更好。

void loop() {
  WiFiClient client = server.available();
  if (client) {
    String line = "";
    while (client.connected()) {
      if (client.available()) {
        char c = client.read();
        Serial.write(c);

        if (c != '\n' && c != '\r') {
          line += c;
        }

        if (c == '\n') {
          if (line.length() == 0) {
            client.println("HTTP/1.1 200 OK");
            client.println("Content-Type: text/html");
            client.println("Connection: close");
            client.println();
            client.println("<!DOCTYPE HTML>");
            client.println("<html>");
            client.println("test");
            client.println("</html>");
            break;
          } else {
            line = "";
          }
        }
      }
    }

    client.stop();
  }
}

在最后一个else ,我们可以检查该行,因为该行被终止了,并根据我们的需要采取相应的行动。