TCP/UDP接口使用
一、TCP与UDP优缺点
1、TCP面向连接(如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接。
2、TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付。TCP通过校验和,重传控制,序号标识,滑动窗口、确认应答实现可靠传输。如丢包时的重发控制,还可以对次序乱掉的分包进行顺序控制。
3、UDP具有较好的实时性,工作效率比TCP高,适用于对高速传输和实时性有较高的通信或广播通信。
4、每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信。
5、TCP对系统资源要求较多,UDP对系统资源要求较少。
二、TCP客户端
TCP&UDP 接口位于 ESP8266_NONOS_SDK/include/espconn.h。
注意:充当TCP客户端,必须要知道服务端的IP地址。
Step 1) 包含头文件
#include "osapi.h"
#include "user_interface.h"
#include "espconn.h"
#include "mem.h"
Step 2) 定义一个TCP连接结构体
struct espconn tcp_client_conn;
Step 3) 定义TCP客户端初始化函数
/**
@brief 初始化TCP客户端
@param remote_port 远程端口
@return 无
*/
void ICACHE_FLASH_ATTR
user_tcpclient_init(uint32 remote_port)
{
// 1. 定义相关结构体
struct ip_info local_info; // 定义一个用于存放本地IP信息的结构体
struct ip_addr remote_ip; // 定义一个用于存放远程IP地址的结构体
// 2.配置TCP客户端相关参数
char remote_ipbuffer[32] = "192.168.100.1";
remote_ip.addr = ipaddr_addr(remote_ipbuffer); // 点分十进制写入IP结构体
tcp_client_conn.type = ESPCONN_TCP; // 设置类型为TCP协议
tcp_client_conn.state = ESPCONN_NONE; // 设置为无连接状态
tcp_client_conn.proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp));
wifi_get_ip_info(STATION_IF, &info); // 查询连接路由器后分配的IP地址
os_memcpy(tcp_client_conn.proto.tcp->local_ip, &local_info.ip, 4); // 设置本地IP地址
os_memcpy(tcp_client_conn.proto.tcp->remote_ip, &remote_ip, 4); // 设置远程IP地址
tcp_client_conn.proto.tcp->local_port = espconn_port(); // 获取可用端口作为本地端口
tcp_client_conn.proto.tcp->remote_port = remote_port; // 设置远程端口
// 3.注册连接成功和重连的回调函数
espconn_regist_connectcb(&tcp_client_conn, tcp_client_connect_cb);
espconn_regist_reconcb(&tcp_client_conn, tcp_client_recon_cb);
// 4.连接服务器
espconn_connect(&tcp_client_conn);
}
Step 4) 定义相关回调函数
/**
@brief 成功连接到服务器的回调函数
@param arg 指向传入参数的指针
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_client_connect_cb(void *arg)
{
struct espconn *pespconn = arg;
espconn_regist_recvcb(pespconn, tcp_client_recv_cb); // 注册接收回调函数
espconn_regist_sentcb(pespconn, tcp_client_send_cb); // 注册发送回调函数
espconn_regist_disconcb(pespconn, tcp_client_discon_cb); // 注册断连回调函数
espconn_sent(pespconn, "8226", strlen("8226")); // 发送消息
}
/**
@brief 连接失败的回调函数
@param arg 指向传入参数的指针
@param err 为错误代码
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_client_recon_cb(void *arg, sint8 err)
{
os_printf("tcpclient connect error, error number:%d\r\n", err);
espconn_connect((struct espconn *) arg); // 连接服务器
}
/**
@brief 成功接收到服务器返回数据的回调函数
@param arg 指向传入参数的指针
@param pdata 字符串数组
@param len 字符串数组长度
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_client_recv_cb(void *arg, char *pdata, unsigned short len)
{
os_printf("tcpclient_recvdata:\t%s\n", pdata);
}
/**
@brief 发送数据到服务器成功的回调函数
@param arg 指向传入参数的指针
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_client_send_cb(void *arg)
{
os_printf("tcpclient_send succeed!\n");
}
/**
@brief 断开服务器成功的回调函数
@param arg 指向传入参数的指针
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_client_discon_cb(void *arg)
{
os_printf("tcpclient_disconnect!\n");
}
三、TCP服务端
注意:充当服务端时候,要自身开启WIFI热点,等待设备接入,好比一个网关。
Step 1) 包含头文件
#include "osapi.h"
#include "user_interface.h"
#include "espconn.h"
#include "mem.h"
Step 2) 定义一个TCP连接结构体
struct espconn tcp_server_conn;
Step 3) 定义TCP服务端初始化函数
/**
@brief 初始化TCP服务端
@param local_port 本地端口
@return 无
*/
void ICACHE_FLASH_ATTR
user_tcpserver_init(uint32 local_port)
{
// 1.配置TCP服务端相关参数
tcp_server_conn.type = ESPCONN_TCP; // 设置类型为TCP协议
tcp_server_conn.state = ESPCONN_NONE; // 设置为无连接状态
tcp_server_conn.proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp));
tcp_server_conn.proto.tcp->local_port = local_port; // 设置本地端口
// 2.注册监听成功和重连的回调函数
espconn_regist_connectcb(&tcp_server_conn, tcp_server_listen_cb);
espconn_regist_reconcb(&tcp_server_conn, tcp_server_recon_cb);
// 3.开始监听
espconn_accept(&tcp_server_conn); // 创建 TCP server,建立监听
espconn_regist_time(&tcp_server_conn, 180, 0); // 设置超时断开时间 单位:秒,最大值:7200 秒
}
Step 4) 定义相关回调函数
/**
@brief 成功连接到客户端的回调函数
@param arg 指向传入参数的指针
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_server_listen_cb(void *arg)
{
struct espconn *pespconn = arg;
espconn_regist_recvcb(pespconn, tcp_server_recv_cb); // 注册接收回调函数
espconn_regist_sentcb(pespconn, tcp_server_send_cb); // 注册发送回调函数
espconn_regist_disconcb(pespconn, tcp_server_discon_cb); // 注册断连回调函数
}
/**
@brief 连接失败的回调函数
@param arg 指向传入参数的指针
@param err 为错误代码
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_server_recon_cb(void *arg, sint8 err)
{
os_printf("tcpserver connect error, error number:%d\r\n", err);
}
/**
@brief 成功接收到客户端发来数据的回调函数
@param arg 指向传入参数的指针
@param pdata 字符串数组
@param len 字符串数组长度
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_server_recv_cb(void *arg, char *pdata, unsigned short len)
{
os_printf("tcpserver_recvdata:\t%s\n", pdata);
espconn_sent((struct espconn *) arg, "this is 8266", strlen("this is 8266")); // 发送消息
}
/**
@brief 发送数据成功的回调函数
@param arg 指向传入参数的指针
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_server_send_cb(void *arg)
{
os_printf("tcpserver_send succeed!\n");
}
/**
@brief 断开连接成功的回调函数
@param arg 指向传入参数的指针
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
tcp_server_discon_cb(void *arg)
{
os_printf("tcpserver_disconnect!\n");
}
四、UDP客户端
注意:充当客户端时候,可用于广播消息。
Step 1) 包含头文件
#include "osapi.h"
#include "user_interface.h"
#include "espconn.h"
#include "mem.h"
Step 2) 定义一个UDP连接结构体
struct espconn udp_client_conn;
Step 3) 定义UDP客户端初始化函数
/**
@brief 初始化UDP客户端
@param remote_port 远程端口
@return 无
*/
void ICACHE_FLASH_ATTR
user_udpclient_init(uint32 remote_port)
{
// 1. 定义相关结构体和变量
struct ip_info ipconfig; // 定义一个用于存放IP信息的结构体
const char udp_remote_ip[4] = {0}; // 定义一个用于存放远端IP地址的字符串数组
// 2.设置广播模式
wifi_set_broadcast_if(STATION_MODE); // 仅在sta模式udp广播
// 3.配置UDP客户端相关参数
udp_client_conn.type = ESPCONN_UDP; // 设置类型为UDP协议
udp_client_conn.proto.udp = (esp_udp *) os_zalloc(sizeof(esp_udp));
udp_client_conn.proto.udp->local_port = espconn_port(); // 获取可用端口作为本地端口
udp_client_conn.proto.udp->remote_port = remote_port; // 设置远程端口
wifi_get_ip_info(STATION_IF, &ipconfig); // 查询连接路由器后分配的IP地址
udp_remote_ip[4] = { 192, 168, ip4_addr3(&ipconfig.ip), 255 }; // 目标IP地址(广播)
os_memcpy(udp_client_conn.proto.udp->remote_ip, udp_remote_ip, 4);
// 4.注册发送和接收的回调函数
espconn_regist_sentcb(&udp_client_conn, udp_client_send_cb);
espconn_regist_recvcb(&udp_client_conn, udp_client_recv_cb);
// 5.建立UDP传输
espconn_connect(&udp_client_conn);
// 6.发送消息
espconn_sent(&udp_client_conn, "8266", strlen("8266"));
}
Step 4) 定义相关回调函数
/**
@brief 成功接收到服务端返回数据的回调函数
@param arg 指向传入参数的指针
@param pdata 字符串数组
@param len 字符串数组长度
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
udp_client_recv_cb(void *arg, char *pdata, unsigned short len)
{
os_printf("udpclient_recvdata:\t%s\n", pdata);
}
/**
@brief 发送数据到服务端成功的回调函数
@param arg 指向传入参数的指针
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
udp_client_send_cb(void *arg)
{
os_printf("udpclient_send succeed!\n");
}
五、UDP服务端
注意:充当UDP服务端时候,要自身开启WIFI热点,等待设备接入,好比一个网关。
Step 1) 包含头文件
#include "osapi.h"
#include "user_interface.h"
#include "espconn.h"
#include "mem.h"
Step 2) 定义一个UDP连接结构体
struct espconn udp_server_conn;
Step 3) 定义UDP服务端初始化函数
/**
@brief 初始化UDP服务端
@param local_port 本地端口
@param remote_port 远程端口
@return 无
*/
void ICACHE_FLASH_ATTR
user_udpserver_init(uint32 local_port, uint32 remote_port)
{
// 1.配置UDP服务端相关参数
udp_server_conn.type = ESPCONN_UDP; // 设置类型为UDP协议
udp_server_conn.proto.udp = (esp_udp *) os_zalloc(sizeof(esp_udp));
udp_server_conn.proto.udp->local_port = local_port; // 设置本地端口
udp_server_conn.proto.udp->remote_port = remote_port; // 设置远程端口
// 2.注册发送和接收的回调函数
espconn_regist_sentcb(&udp_server_conn, udp_server_send_cb);
espconn_regist_recvcb(&udp_server_conn, udp_server_recv_cb);
// 3.建立UDP传输
espconn_connect(&udp_server_conn);
}
Step 4) 定义相关回调函数
/**
@brief 成功接收到客户端返回数据的回调函数
@param arg 指向传入参数的指针
@param pdata 字符串数组
@param len 字符串数组长度
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
udp_server_recv_cb(void *arg, char *pdata, unsigned short len)
{
os_printf("udpserver_recvdata:\t%s\n", pdata);
espconn_sent((struct espconn *) arg, "this is 8266", strlen("this is 8266")); // 发送消息
}
/**
@brief 发送数据到客户端成功的回调函数
@param arg 指向传入参数的指针
@return 无
*/
LOCAL void ICACHE_FLASH_ATTR
udp_server_send_cb(void *arg)
{
os_printf("udpserver_send succeed!\n");
}
接入私有云服务器
一、简介
1、一种方案是自己搭建设计这么一个中心服务器,只是费时费力;
2、另一种方案,就是直接使用大厂提供的物联网平台服务,使设计方案Pass化。
上网搜寻物联网平台方案,百度、阿里、腾讯早就推出了自己的物联网平台了.
二、代码分析
2.1 平台初始化
在 user_main.c 中
void ICACHE_FLASH_ATTR
user_init(void)
{
TcpCloudInit(); // 南京云服务初始化
}
2.2 检查是否被分配IP
在 user_smartconfig.h 中
static os_timer_t s_tcpCloudCheckIpTimer;
static const uint32 TCP_CLOUD_CHECK_IP_PERIOD = 1000;
/**
@brief TCP平台TCP客户端初始化
@param 无
@return 无
*/
void ICACHE_FLASH_ATTR
TcpCloudInit(void)
{
startTcpCloudCheckIpTimer();
}
/**
@brief 开始TCP云平台检查IP的定时器
@param 无
@return 无
*/
static void ICACHE_FLASH_ATTR
startTcpCloudCheckIpTimer(void)
{
os_timer_disarm(&s_tcpCloudCheckIpTimer);
os_timer_setfn(&s_tcpCloudCheckIpTimer, (os_timer_func_t *) tcpCloudCheckIpTimerCallback, NULL);
os_timer_arm(&s_tcpCloudCheckIpTimer, TCP_CLOUD_CHECK_IP_PERIOD, false);
}
/**
@brief 停止TCP云平台检查IP/DNS的定时器
@param 无
@return 无
*/
static void ICACHE_FLASH_ATTR
stoptcpCloudCheckIpTimer(void)
{
os_timer_disarm(&s_tcpCloudCheckIpTimer);
}
startTcpCloudCheckIpTimer();
开启定时器,周期检查是否被分配IP
/**
@brief TCP云平台检查IP定时器的回调函数
@param 无
@return 无
*/
static void ICACHE_FLASH_ATTR
tcpCloudCheckIpTimerCallback(void)
{
stoptcpCloudCheckIpTimer();
if(wifi_station_get_connect_status() == STATION_GOT_IP)
{
//TCP客户端初始化
tcpCloudClientInit();
//启动PING定时器
startSendPingTimer();
}
else
{
startTcpCloudCheckIpTimer();
}
}
2.3 TCP客户端初始化
在 wifi_station_get_connect_status() == STATION_GOT_IP
检查到获取IP后
/**
@brief TCP客户端初始化
@param 无
@return 无
*/
static void ICACHE_FLASH_ATTR
tcpCloudClientInit(void)
{
s_tcpCloudCheckIpEspconn.type = ESPCONN_TCP;
s_tcpCloudCheckIpEspconn.state = ESPCONN_NONE;
s_tcpCloudCheckIpEspconn.proto.tcp = (esp_tcp *) os_zalloc(sizeof(esp_tcp));
struct ip_info ipInfo;
wifi_get_ip_info(STATION_IF, &ipInfo);
char ip[16] = "115.29.202.58";
g_tcpCloudServerConfig.ip.addr = ipaddr_addr(ip);
g_tcpCloudServerConfig.port = 8000;
connectEspPlatformByIp();
}
/**
@brief 通过IP连接云平台
@param 无
@return 无
*/
static void ICACHE_FLASH_ATTR
connectEspPlatformByIp(void)
{
//断开原来的服务器
espconn_disconnect(&s_tcpCloudCheckIpEspconn);
os_memcpy(s_tcpCloudCheckIpEspconn.proto.tcp->remote_ip, (uint8 *)(&g_tcpCloudServerConfig.ip.addr), 4);
s_tcpCloudCheckIpEspconn.proto.tcp->remote_port = g_tcpCloudServerConfig.port;
s_tcpCloudCheckIpEspconn.proto.tcp->local_port = espconn_port();
espconn_regist_connectcb(&s_tcpCloudCheckIpEspconn, connectCallback);
espconn_regist_disconcb(&s_tcpCloudCheckIpEspconn, disconnectCallback);
espconn_regist_reconcb(&s_tcpCloudCheckIpEspconn, reconnectCallback);
espconn_connect(&s_tcpCloudCheckIpEspconn);
}
可选择通过IP和端口进行连接 connectEspPlatformByIp()
或选择通过DNS域名解析连接 onnectEspPlatformByDns()
2.4 注册连接/重连/断连函数
/**
@brief 连接成功的回调函数
@param arg -[in] 指向传递给这个回调函数来使用的参数
@return 无
*/
static void ICACHE_FLASH_ATTR
connectCallback(void *arg)
{
struct espconn *pEspconn = arg;
espconn_regist_recvcb(pEspconn, receiveDataCallback);
//启动PING定时器
startSendPingTimer();
}
/**
@brief 重连的回调函数
@param arg -[in] 指向传递给这个回调函数来使用的参数
@param error -[in] 错误码
@return 无
*/
static void ICACHE_FLASH_ATTR
reconnectCallback(void *arg, sint8 error)
{
struct espconn *pEspconn = arg;
startTcpCloudCheckReconnectTimer();
stopSendPingTimer();
}
/**
@brief 断连的回调函数
@param arg -[in] 指向传递给这个回调函数来使用的参数
@return 无
*/
static void ICACHE_FLASH_ATTR
disconnectCallback(void *arg)
{
struct espconn *pEspconn = arg;
stopSendPingTimer();
if(pEspconn == NULL)
{
return ;
}
pEspconn->proto.tcp->local_port = espconn_port();
tcpCloudCheckReconnectTimerCallback();
}
2.5 注册接收回调函数
/**
@brief 接收数据的回调函数
@param arg -[in] 指向传递给这个回调函数来使用的参数
@param pData -[in] 接收的数据
@param len -[in] 接收的数据长度
@return 无
*/
static void ICACHE_FLASH_ATTR
receiveDataCallback(void *arg, char *pData, unsigned short len)
{
static uint32 s_dfu_index = 0;
os_printf("\n ========================================================== \n ");
os_printf(" Nanjing tcpCloudCheck recvData: %s \n",pData);
//转16进制
uint8 buffer[256] = {0};
smartconfigHexStr2Bytes(pData, 6, buffer);
if((buffer[0] == 0X5A) && (buffer[1] == 0Xa5))
{
//字符转十六进制
mscpWifiToI2cHexStr2Bytes(&I2ctReceiveServerMsg_t, pData, strlen(pData));
}
else
{
os_printf("\n Err: Nanjing tcpCloudCheck recvData does not start with 0x5A 0XA5 ! ");
os_printf("\n ========================================================== \n ");
}
stopSendPingTimer();
startSendPingTimer();
}
2.6 接收云平台响应后开启周期PingreceiveDataCallback()
中调用 startSendPingTimer()
/**
@brief 开始发送PING的定时器
@param 无
@return 无
*/
static void ICACHE_FLASH_ATTR
startSendPingTimer(void)
{
os_timer_disarm(&s_tcpPingTimer);
os_timer_setfn(&s_tcpPingTimer, (os_timer_func_t *) sendPingTimerCallback, NULL);
os_timer_arm(&s_tcpPingTimer, TCP_PLATFORM_PING_PERIOD, true);
}
/**
@brief 停止发送PING的定时器
@param 无
@return 无
*/
static void ICACHE_FLASH_ATTR
stopSendPingTimer(void)
{
os_timer_disarm(&s_tcpPingTimer);
}
/**
@brief 发送PING定时器的回调函数
@param 无
@return 无
*/
static void ICACHE_FLASH_ATTR
sendPingTimerCallback(void)
{
os_printf("\n ========================================================== \n ");
os_printf("\n Nanjing tcpcli_cloud disconnect ! \n");
os_printf("\n ========================================================== \n ");
espconn_disconnect(&s_tcpCloudCheckIpEspconn);
}
- 由 ChiKong_Tam 写于 2020 年 7 月 24 日