授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力。希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石。。。
快速导航
单片机菜鸟的博客快速索引(快速找到你要的)
如果觉得有用,麻烦点赞收藏,您的支持是博主创作的动力。
接下来,博主开始介绍TCP Client的重要伙伴 —— Tcp Server。
现在,手机上网已经是人们每天必不可少的事情。比如刷微博,刷朋友圈,刷新闻等等; 那么这些朋友圈、微博、新闻内容都是从哪里来的呢?做个App开发的同学都应该知道,手机App属于client端,属于UI端,展示UI内容,而显示什么UI内容基本上都是发送一些http请求到后端服务(server),服务器根据具体的请求内容返回对应的响应内容。
所谓server,可以简单理解为提供服务,提供数据的一个地方。
先来理解一下概念图:
mobile phone作为client端,通过路由热点,向Server端的ESP8266请求数据,8266获取到请求后解析请求然后返回响应数据。
但是,请开发者注意:ESP32上建立一个server是比较简单的,不过是属于局域网内的server,因为真正意义上的server并不是这样的,大伙了解一个这样的概念就好。
在ESP32上建立TCP Server需要用到WiFiServer库,WiFiServer库也是属于WiFi库里面的一部分,主要是负责跟server有关的操作。
方法总体上可以分为三部分:
函数说明:
/**
* 函数功能:创建TCP server
* @param port server的端口
* @param max_clients 最大连接数
*/
WiFiServer(uint16_t port=80, uint8_t max_clients=4);
函数说明:
/**
* 函数功能:启动TCP server
*/
void begin();
/**
* 函数功能:启动TCP server
* @param port server端口号
*/
void begin(uint16_t port);
注意点:
函数说明:
/**
* 是否禁用 Nagle 算法。
* @param nodelay true表示禁用 Nagle 算法
*/
void setNoDelay(bool nodelay);
注意点:
函数说明:
/**
* 关闭TCP server
*/
void close();
函数说明:
/**
* 停止TCP server
*/
void stop();
注意点:
void WiFiServer::stop() {
close();
}
函数说明:
/**
* 获取有效的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();
}
函数说明:
/**
* 判断是否有client连接
* @return bool 如果有client连接就返回true
*/
bool hasClient();
注意点:
前面讲了这么多理论内容,接下来用几个例子来说明一下。
例子介绍:
32作为WiFiServer端,打开TCP调试助手,模拟TCP Client的请求。
例子源码:
/**
* Demo:
* 演示WiFiServer功能
* 打开TCP调试助手 模拟TCP client请求
* @author 单片机菜鸟
* @date 2020/01/08
*/
#include
//定义最多多少个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);
}
}
}
}
例子介绍:
32作为web server端,打开PC浏览器输入IP地址,请求web server。
例子源码:
/**
* Demo:
* 演示web Server功能
* 打开PC浏览器 输入IP地址。请求web server
* @author 单片机菜鸟
* @date 2020/01/08
*/
#include
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" +
"" +
"" +
"Analog input: " + String(analogRead(A0)) +
"" +
"\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]");
}
}
例子介绍:
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
//以下三个定义为调试定义
#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\r\n\r\nGPIO is now ";
s += (val) ? "high" : "low";
s += "\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
}
这一篇章,博主主要讲了TCP通信的server。大家需要区分tcp http。并且也要区分工作模式和client server不是一个概念,两者没有必然的联系。这篇算是入门http请求的重点内容,希望读者可以仔细研读,并结合源码去理解。