当前位置 博文首页 > freemote的博客:【玩转ESP32】9、ESP32 作为TCP客户端连接服务

    freemote的博客:【玩转ESP32】9、ESP32 作为TCP客户端连接服务

    作者:[db:作者] 时间:2021-07-05 09:54

    系列文章:

    【玩转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的服务器。

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

    cs