Ethernet - JSON Application#
准备#
网站服务器返回 json 数据和返回 text/html 没有多大不同,也是以 http 方式传输 http 报文。该报文有报头和正文两行,中间用空行隔开。该 json 可以文件方式存在服务器上,也可以临时计算产生,对于接收端来说,看起来相同。
硬件连接与软件设置#
Arduino UNO 连接 Ethernet 模块
电脑和 UNO Ethernet 模块都用以太网双绞线连到同一个交换机。
此时在电脑上用 ping 命令可以测试电脑和 UNO 的网络连通性。 ping 通的时候,Ethernet 上的3个小灯会有规律地闪烁,表明有数据在传输。
代码的功能是完成服务器的功使,使得 Arduino UNO 小板作为 Web (Json) 服务器 (http://10.0.0.1
),用电脑上的浏览器去访问该址,得到 json 格式的相应。
作为客户端的电脑配置 IP 地址为 10.0.0.100
,需要与 UNO 在同一个局域网。如果你的电脑只有一个有线网卡,实验期间你可能上不了因特网。
Ethernet Demo Code#
Ethernet - EthernetServer() - Arduino Reference
这节的代码来自于官方说明书,可作为模板使用,后面的代码均在此基础上修改。
下面是在原代码的基础上,根据实际场景不同,略有改编后的代码:
#include <SPI.h>
#include <Ethernet.h>
// network configuration. gateway and subnet are optional.
// the media access control (ethernet hardware) address for the shield:
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; // (1)
//the IP address for the shield:
byte ip[] = { 10, 0, 0, 1}; // (2)
// the router's gateway address:
// byte gateway[] = { 10, 0, 0, 1 };
// the subnet:
// byte subnet[] = { 255, 255, 0, 0 };
// telnet defaults to port 23
// EthernetServer server = EthernetServer(23);
void setup()
{
// initialize the ethernet device
//Ethernet.begin(mac, ip, gateway, subnet);
Ethernet.begin(mac, ip); // (3)
// start listening for clients
server.begin(); // (4)
}
void loop()
{
// if an incoming client connects, there will be bytes available to read:
EthernetClient client = server.available(); // (5)
if (client == true) {
// read bytes from the incoming client and write them back
// to any clients connected to the server:
server.write(client.read()); // (6)
}
}
Ethernet 硬件模块没有 MAC 地址,可指定一个,值随意,满足 MAC 地址的规范即可;
ip 地址改为了简短的 10.0.0.1, 网关和子网掩码用不到,注释掉了
以太网卡启动起来,并分配 IP 地址
启动 TCP/UDP Socket 服务
等待来自 client 的连接
进一步处理 ... 这一行的代码需要改写为自己想要的功能
JSON for VLAN#
和前一个代码相比,提供了返回 json 字符串的功能。
#include <SPI.h>
#include <Ethernet.h>
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0x00, 0xAA, 0xBB, 0xCC, 0xDA, 0x02 };
IPAddress ip(10,0,0,1); //<<< ENTER YOUR IP ADDRESS HERE!!!
// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80); // (1)
// int buttonPress = 1;
void setup()
{
// pinMode(2, INPUT);
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
}
void loop()
{
// buttonPress = digitalRead(2);
// listen for incoming clients
EthernetClient client = server.available();
if (client) {
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank) {
// send a standard http response header
client.println("HTTP/1.0 200 OK"); // (2)
// client.println("Content-Length: 215");
// client.println("Content-Type: text/html");
client.println("Content-Type: application/json"); // (3)
client.println("Connection: close"); // (4)
client.println(); // (5)
client.println("[{\"vlanid\": \"10\",\"vlanName\": \"one\"},");
client.println("{\"vlanid\": \"20\",\"vlanName\": \"two\"},");
client.println("{\"vlanid\": \"30\",\"vlanName\": \"three\"}]");
client.println();
client.stop(); // (6)
}
}
}
}
}
考虑到我们将用浏览器作为客端,选择了 80 端口,这样我们就可以在 url 中省略 80,方便一点。你也可以根据你的实际需要改为不同的端口
发送 http 响应报头。即正确收到了客户端的响应后,需要发送的响应。该报头连续多行,最后以一个空行结束。
设置响应报文的类型为 json 应用类型。在不作修改的默认情况下是 text/html
设置服务器在完成传输后,立即断开 TCP 连接。这可能不符合你的需求,只不过我希望发送往响应后立即断开,方便调试。实际这个设置并不起作用,因为断不断开,得靠服务器的进一步行为来确定。
以空行结束 HTTP 报头。
主动终止 TCP 连接。否则实际上 TCP 连接还是打开的,即光有头部的
connection: close
是不够的,此时浏览器就不知道数据接收完了没有,会一直刷新(转圈)。由服务器发送完数据后就断开连接,调试起来更省时间。从 (5) 到 (6) 之间是显示 vlan id 的业务所需要的返回的 json 字符串。
此内容你也可以通过下面的网址看到,只不过因为服务器不同,报头是不同的: https://share-1304436219.cos.ap-beijing.myqcloud.com/api/vlan.json
你可以用 Firefox 浏览器分别访问这两个网址,查看结果和报头 (Header),比较它们的异同。甚至你还可以通过 Wireshark 抓包来研究 Arduino Ethernet 到底在网络上发送了哪些数据,更深入的理解 HTTP 到底做了啥。
VLAN add Ports#
下面的代码的业务是将端口加入 vlan 后返回的结果。因为所有的 json 都包括返回 http 报头,然后是业务数据这两块。为了主程序逻辑更清楚,也方便代码复用,我们将这两块代码写成函数,要实现其它功能时,只需修改业务子函数即可。
#include <SPI.h>
#include <Ethernet.h>
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = { 0x00, 0xAA, 0xBB, 0xCC, 0xDA, 0x02 };
IPAddress ip(10, 0, 0, 1); //<<< ENTER YOUR IP ADDRESS HERE!!!
// Initialize the Ethernet server library
// with the IP address and port you want to use
// (port 80 is default for HTTP):
EthernetServer server(80);
void setup() {
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
}
void send_http_response_header(EthernetClient client){ // (1)
// send a standard http response header
client.println("HTTP/1.0 200 OK");
client.println("Content-Type: application/json");
client.println("Connection: close");
client.println();
}
void send_json(EthernetClient client){ // (2)
client.println("{");
client.println("\"action\": \"addPortsToVlan\",");
client.println("\"vlanId\": 10,");
client.println("\"ports\": [");
client.println(" \"GigabitEthernet0/1\",");
client.println(" \"GigabitEthernet0/2\",");
client.println(" \"GigabitEthernet0/4\"");
client.println(" ]");
client.println("}");
}
void loop() {
// buttonPress = digitalRead(2);
// listen for incoming clients
EthernetClient client = server.available();
if (client) {
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank) {
send_http_response_header(client); // (3)
send_json(client); // (4)
client.println();
client.stop();
}
}
}
}
}
此时,我们省去了和之前相重复的解释,将重点放在不同的代码上。
发送报头的子函数定义
发送报文正文(body)的子函数定义
调用发送报头的子函数
调用发送报文正文(body)的子函数
可以看出,此时的主程序逻辑简单得多。
需要进一步解决的问题#
根据不同的网址返回不同的 json
将多个(3个)不同的功能集成为一个主程序