【玩转ESP32】9、ESP32 作为TCP客户端连接服务器(非阻塞接收)

系列文章:

【玩转ESP32】1、开发环境搭建
【玩转ESP32】2、开发参考资料
【玩转ESP32】3、点亮LED,Blink,blink,blink
【玩转ESP32】4、ESP32驱动DHT11
【玩转ESP32】5、i2c-tools访问i2c设备
【玩转ESP32】6、驱动i2c设备—0.96 OLED
【玩转ESP32】7、ESP32连接wifi
【玩转ESP32】8、ESP32 Guru MeditationError报错分析

1、基本流程

wifi连接到sta,新建socket,连接到tcp server。

2、API函数

esp32的tcpip协议栈使用的是LWIP的,而LWIP的接口是符合posix标准的,因此和在其他平台的用法一致。

1、创建socket
int socket(int domain,int type,int protocol)

domain:为地址族,也就是 IP 地址类型,常用的有 AF_INET 和 AF_INET6;
type:数据传输方式/套接字类型,常用的有 SOCK_STREAM(流格式套接字/面向连接的套接字) 和 SOCK_DGRAM;
protocol:为协议类型,常用的有 IPPROTO_TCPIPPTOTO_UDP,分别表示 TCP 传输协议和 UDP 传输协议;
返回值为套接字。

2、连接
int connect(int s,const struct sockaddr *name,socklen_t namelen)

s:套接字;
sockaddr :套接字s想要连接的主机地址和端口号;
namelen:name缓冲区的长度。

3、发送
ssize_t send(int s,const void *dataptr,size_t size,int flags)

s:发送端套接字描述符;
dataptr:要发送数据的缓冲区;
size:要发送的数据的字节数;
flags:一般置0。

4、接收
ssize_t recv(int s,void *mem,size_t len,int flags)

s:接收端套接字描述符;
mem:接收数据缓冲区;
size:要接收的最大长度;
flags:一般置0。

5、关闭连接
int shutdown(int s,int how)

s:套接字描述符;
how:标志,用于描述禁止哪些操作。

6、关闭socket
close(int s)

s:套接字描述符。

7、控制套接口的模式
int ioctlsocket(int s,long cmd,void *argp)

s:套接字描述符;
cmd:对套接口s的操作命令;
argp:指向cmd命令所带参数的指针。
cmdFIONBIO时标识非阻塞,对应的argp1时是非阻塞,为0时是阻塞。

8、设置套接口的选项
int setsockopt(int s,int level,int optname,const void *opval,socklen_t optlen)

s:标识一个套接口的描述字;
level:选项定义的层次;支持SOL_SOCKETIPPROTO_TCPIPPROTO_IPIPPROTO_IPV6
optname:需设置的选项;
optval:指针,指向存放选项待设置的新值的缓冲区;
optlen:optval缓冲区长度;
对应的optnameSO_RCVTIMEO时标识接收超时,optval为超时时间,optlen为长度。

3、核心代码

static void tcp_client(void) 
{
    char rx_buffer[128];
    char host_ip[] = HOST_IP_ADDR;
    int addr_family = 0;
    int ip_protocol = 0;

    struct timeval timeout={
        .tv_sec = 0,
        .tv_usec = 20,
    }; 
    u_long non_blocking=1;
    int sendcnt=0;
    while (1) {
        struct sockaddr_in dest_addr;
        dest_addr.sin_addr.s_addr = inet_addr(host_ip);
        dest_addr.sin_family = AF_INET;
        dest_addr.sin_port = htons(PORT);
        addr_family = AF_INET;
        ip_protocol = IPPROTO_IP;

        int sock =  socket(addr_family, SOCK_STREAM, ip_protocol);
        //创建socket
        if (sock < 0) {
            ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Socket created, connecting to %s:%d", host_ip, PORT);

        int err = connect(sock, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr_in6));
        //建立连接
        if (err != 0) {
            ESP_LOGE(TAG, "Socket unable to connect: errno %d", errno);
            break;
        }
        ESP_LOGI(TAG, "Successfully connected");

        ioctlsocket(sock,FIONBIO,&non_blocking);
        //设置为非阻塞
        setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout));
        //超时接收时间
        while (1) {
            int err = send(sock, payload, strlen(payload), 0);
            //发送
            if (err < 0) {
                ESP_LOGE(TAG, "Error occurred during sending: errno %d", errno);
                break;
            }
            sendcnt++;
            if((sendcnt%5)==0)
            {
                non_blocking=0;
                ioctlsocket(sock,FIONBIO,&non_blocking);
            }
            else
            {
                non_blocking=1;
                ioctlsocket(sock,FIONBIO,&non_blocking);
            }

            int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);   
            //接收
        #if 1
            if (len >= 0) {
                rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);
            }
        #else
           // Error occurred during receiving
            if (len < 0) {
                ESP_LOGE(TAG, "recv failed: errno %d", errno);
                break;
            }
            // Data received
            else {
                rx_buffer[len] = 0; 
                ESP_LOGI(TAG, "Received %d bytes from %s:", len, host_ip);
                ESP_LOGI(TAG, "%s", rx_buffer);
            }
        #endif
            vTaskDelay(2000 / portTICK_PERIOD_MS);
        }

        if (sock != -1) {
            ESP_LOGI(TAG, "Shutting down socket and restarting...");
            shutdown(sock, 0);
            close(sock);
        }
    }
    vTaskDelete(NULL);
}

在代码中设置为循环发送次数为5的倍数后,设置为阻塞,直到接收到数据后,再设置为非阻塞。

4、实验

在这里插入图片描述
可以看到,esp32连接到ap后,ap分配了192.168.2.3的地址,创建socket后连接到了192.168.2.5的服务器。

欢迎关注微信公众号【物联网思考】,获取资料。在这里插入图片描述

  • 2
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
以下是用Arduino写一个ESP32作为TCP客户端连接TCP服务器的示例代码: ```C++ #include <WiFi.h> #include <WiFiClient.h> // 定义WiFi网络的名称和密码 const char* ssid = "your_SSID"; const char* password = "your_PASSWORD"; // 定义TCP服务器的IP地址和端口号 const char* serverIP = "192.168.1.100"; const int serverPort = 8080; void setup() { Serial.begin(115200); // 连接到WiFi网络 WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(1000); Serial.println("Connecting to WiFi..."); } Serial.println("Connected to WiFi."); // 创建TCP客户端 WiFiClient client; // 连接TCP服务器 if (client.connect(serverIP, serverPort)) { Serial.println("Connected to TCP server."); // 向TCP服务器发送数据 client.println("Hello, TCP server!"); // 等待TCP服务器的响应 while (client.available()) { String response = client.readStringUntil('\n'); Serial.println("Received response from TCP server: " + response); } // 关闭TCP连接 client.stop(); } else { Serial.println("Failed to connect to TCP server."); } } void loop() { // 无需执行任何操作 } ``` 在代码中,首先定义了WiFi网络的名称和密码以及TCP服务器的IP地址和端口号。然后使用`WiFi`库连接到WiFi网络,并创建一个`WiFiClient`对象作为TCP客户端。接下来,调用`connect()`方法连接TCP服务器,并向服务器发送数据。最后,使用`available()`方法等待TCP服务器的响应,并在收到响应后关闭TCP连接。 请注意,此代码仅用于示例目的。在实际应用中,您可能需要添加错误处理和重试逻辑,以确保程序在网络故障或服务器故障时能够正确处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

freemote

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值