ESP32 WIFI知识点

1.TCP客户端连接服务器
(1).基本流程wifi连接到sta,新建socket,连接到tcp server。
(2).相关API函数
创建socket

int socket(int domain,int type,int protocol)

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

连接

int connect(int s,const struct sockaddr *name,socklen_t namelen)

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

发送

ssize_t send(int s,const void *dataptr,size_t size,int flags)

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

接收

ssize_t recv(int s,void *mem,size_t len,int flags)

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

关闭连接

int shutdown(int s,int how)

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

关闭socket

close(int s)

s:套接字描述符。

控制套接口的模式

int ioctlsocket(int s,long cmd,void *argp)

s:套接字描述符; cmd:对套接口s的操作命令; argp:指向cmd命令所带参数的指针; 当cmd为FIONBIO时表示非阻塞,对应的argp为1时是非阻塞,为0时是阻塞。

设置套接口的选项

int setsockopt(int s,int level,int optname,const void *opval,socklen_t optlen)

s:套接口的描述字; level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6; optname:需设置的选项; optval:指针,指向存放选项待设置的新值的缓冲区; optlen:optval缓冲区长度; 对应的optname为SO_RCVTIMEO时表示接收超时,optval为超时时间,optlen为长度

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

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);
}

2.setsockopt参数详解
如果要从已经处于建立状态的套接字(一般用端口号和标识符区分)调用closesocket后继续重用套接字(一般不会立即关闭,要经历TIME_WAIT的过程):

(1).如果要从已经处于建立状态的套接字(一般用端口号和标识符区分)调用closesocket后继续重用套接字(一般不会立即关闭,要经历TIME_WAIT的过程):
  BOOL bReuseaddr=TRUE
  setsockopt(s,SOL_SOCKET,SO_REUSEADDR,(const char*) bReuseaddr,sizeof(BOOL));

(2).如果您希望在调用closesocket后强制关闭已连接的套接字,请不要体验
  时间等待程序:
  BOOL bDontLinger=FALSE
  setsockopt(s,SOL_SOCKET,SO_DONTLINGER,(const char*) bDontLinger,sizeof(BOOL));

(3).在SEND () send()、recv()的过程中,有时由于网络条件等原因,收发无法预期,设置了收发时限:
  int nNetTimeout=1000//1秒
  //发送时间限制
  setsockopt(socket,SOL _ S0CKET,SO_SNDTIMEO,(char *) nNetTimeout,sizeof(int));
  //接收时间限制
  setsockopt(socket,SOL _ S0CKET,SO_RCVTIMEO,(char *) nNetTimeout,sizeof(int));

(4).send()时,返回的是实际发出的字节(同步)或发送到socket buffer的字节(异步);系统默认状态是8688字节(约8.5k)收发一次;实际过程中收发数据量比较大,可以设置socket缓冲区,避免send () send()、recv()不断循环收发:
  //接收缓冲区
  int nRecvBuf=32 * 1024//设置为32K
  setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*) nRecvBuf,sizeof(int));
  //发送缓冲区
  int nSendBuf=32 * 1024//设置为32K
  setsockopt(s,SOL_SOCKET,SO_SNDBUF,(const char*) nSendBuf,sizeof(int));

(5).如果在发送数据时,希望从系统缓冲区到套接字缓冲区的复制不会影响程序的性能:
  int nZero=0;
  setsockopt(socket,SOL _ S0CKET,SO_SNDBUF,(char *) nZero,sizeof(nZero));

(6).同上,在recv()中完成上述函数(默认情况下,将socket缓冲区的内容复制到系统缓冲区):
  int nZero=0;
  setsockopt(socket,SOL _ S0CKET,SO_RCVBUF,(char *) nZero,sizeof(int));

(7).通常,当发送UDP数据报时,希望该套接字发送的数据具有广播特征:
  BOOL bBroadcast=TRUE
  setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*) bBroadcast,sizeof(BOOL));

(8).在客户端连接服务器的过程中,如果非阻塞模式的socket处于connect()过程中,可以设置connect()延迟,直到调用accpet()为止(该函数设置只在非阻塞过程中起显著作用,在阻塞的函数调用中不起作用)
  BOOL bConditionalAccept=TRUE
  setsockopt(s,SOL_SOCKET,SO_CONDITIONAL_ACCEPT,(const char *)bconditionaccept,sizeof(BOOL));

猜你喜欢

转载自blog.csdn.net/qizhi321123/article/details/128862965