ESP8266/ESP32 Socket编程(3)select基本用法-设置Socket接收超时时间
引言:
select()函数是Socket编程中实现I/O多路复用的基本函数,通过select机制,我们可以实现同时监控多个I/O描述符,控制多个I/O的输入输出。
作为基本的,我们可以通过select机制,代替上一篇博客的setsockopt()函数设置Socket的接收超时时间。作为开始我简单介绍下很好理解的select机制的基本应用方法,与相关的宏(或者函数)。
1.Select基本使用步骤:
(1)fd_set read_set;//声明待监控的集合read_set
(2)FD_ZERO(&read_set);//将待监控的集合read_set中的监控位全部变为0,即初始态。
(3)FD_SET(1, &read_set);//将集合read_set中的bit1位置1,即要监控文件描述符fd为1的I/0口。
(4)FD_SET(5, &read_set);//同上,将集合read_set中的bit5位置1,即要监控文件描述符fd为5的I/0口。(即我们监控的对象是fd为1,5的文件I/O)
(5)ret = select(5+1, &read_set, NULL, NULL, &timeout);//启动监控,若集合read_set中有可操作的文件描述符,即1,5可操作(即已就绪),就返回可操作的数量给ret.
(6)FD_ISSET(1, read_set);//测试是否是fd=1的文件可读了(可读的话,相应的bit位会在调用select时清零,我们不用管这种机制的实现,只要会用即可)
(7)FD_ISSET(5, read_set);//测试是否是fd=5的文件可读了
(8)如果1,5可读了,就执行对应的读操作。
(9)如果不想再监控fd=1的文件I/O,就调用FD_CLR(1, read_set),将其对应的监控bit位清零,select()就不会再监控之了。
(10)特别注意的是,调用select()函数被调用后,任何与未就绪读/写描述符相关的bit均被清0,为此,为保证,我们始终能够监控所关心的文件描述符(即通过FD_SET(fd, &read_set);将关心的bit置位为1),应总是在重新调用select()前,调用FD_SET(fd, &read_set);语句加入关心的文件描述符。
2.使用select设置Socket TCP接收超时时间
实验用的代码模板是ESP8266 RTOS-SDKv3.3以上版本的Socket TCP Example,开发板是ESP8266DevkitC.
[code]/* BSD Socket API Example*/ #include <string.h> #include <sys/param.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_log.h" #include "esp_netif.h" #include "esp_event.h" #include "protocol_examples_common.h" #include "nvs.h" #include "nvs_flash.h" #include "lwip/err.h" #include "lwip/sockets.h" #include "lwip/sys.h" #include <lwip/netdb.h> #define PORT CONFIG_EXAMPLE_PORT static const char *TAG = "example"; int readable_timeo(int fd, int sec); int Readable_timeo(int fd, int sec); int ret; int readable_timeo(int fd, int sec) { fd_set rset; struct timeval tv; FD_ZERO(&rset); FD_SET(fd, &rset); tv.tv_sec = sec; tv.tv_usec = 0; ESP_LOGI(TAG,"Timeout will occur after %d sec", sec); return(select(fd+1, &rset, NULL, NULL, &tv)); /* > 0 if descriptor is readable */ } /* end readable_timeo */ int Readable_timeo(int fd, int sec) { int n; if ( (n = readable_timeo(fd, sec)) < 0) ESP_LOGI(TAG,"readable_timeo error"); return(n); } static void tcp_server_task(void *pvParameters) { char rx_buffer[128]; char addr_str[128]; int addr_family; int ip_protocol; while (1) { #ifdef CONFIG_EXAMPLE_IPV4 struct sockaddr_in destAddr; destAddr.sin_addr.s_addr = htonl(INADDR_ANY); destAddr.sin_family = AF_INET; destAddr.sin_port = htons(PORT); addr_family = AF_INET; ip_protocol = IPPROTO_IP; inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1); #else // IPV6 struct sockaddr_in6 destAddr; bzero(&destAddr.sin6_addr.un, sizeof(destAddr.sin6_addr.un)); destAddr.sin6_family = AF_INET6; destAddr.sin6_port = htons(PORT); addr_family = AF_INET6; ip_protocol = IPPROTO_IPV6; inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1); #endif int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol); if (listen_sock < 0) { ESP_LOGE(TAG, "Unable to create socket: errno %d", errno); break; } ESP_LOGI(TAG, "Socket created"); int err = bind(listen_sock, (struct sockaddr *)&destAddr, sizeof(destAddr)); if (err != 0) { ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno); break; } ESP_LOGI(TAG, "Socket binded"); err = listen(listen_sock, 1); if (err != 0) { ESP_LOGE(TAG, "Error occured during listen: errno %d", errno); break; } ESP_LOGI(TAG, "Socket listening"); #ifdef CONFIG_EXAMPLE_IPV6 struct sockaddr_in6 sourceAddr; // Large enough for both IPv4 or IPv6 #else struct sockaddr_in sourceAddr; #endif uint addrLen = sizeof(sourceAddr); int sock = accept(listen_sock, (struct sockaddr *)&sourceAddr, &addrLen); if (sock < 0) { ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno); break; } ESP_LOGI(TAG, "Socket accepted"); while (1) { ret = Readable_timeo(sock, 10);//set timeout and add if if (ret > 0){ int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0); // Error occured during receiving if (len < 0) { ESP_LOGE(TAG, "recv failed: errno %d", errno); break; } // Connection closed else if (len == 0) { ESP_LOGI(TAG, "Connection closed"); break; } // Data received else { #ifdef CONFIG_EXAMPLE_IPV6 // Get the sender's ip address as string if (sourceAddr.sin6_family == PF_INET) { inet_ntoa_r(((struct sockaddr_in *)&sourceAddr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); } else if (sourceAddr.sin6_family == PF_INET6) { inet6_ntoa_r(sourceAddr.sin6_addr, addr_str, sizeof(addr_str) - 1); } #else inet_ntoa_r(((struct sockaddr_in *)&sourceAddr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1); #endif rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str); ESP_LOGI(TAG, "%s", rx_buffer); int err = send(sock, rx_buffer, len, 0); if (err < 0) { ESP_LOGE(TAG, "Error occured during sending: errno %d", errno); break; } } } else { ESP_LOGE(TAG, "timeout or error: ret of readable_timeo= %d", ret); break; } } if (sock != -1 || ret <= 0) { ESP_LOGE(TAG, "Shutting down socket and restarting..."); shutdown(sock, 0); close(sock); } } vTaskDelete(NULL); } void app_main() { ESP_ERROR_CHECK(nvs_flash_init()); ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_event_loop_create_default()); ESP_ERROR_CHECK(example_connect()); xTaskCreate(tcp_server_task, "tcp_server", 4096, NULL, 5, NULL); }
实现上主要是参考《UNIX 网络编程 卷1,套接字编程》一书相关介绍,修改了其中的
int readable_timeo(int fd, int sec);
int Readable_timeo(int fd, int sec);
两个函数,并在while()的第一句代码添加了设置超时的语句:ret = Readable_timeo(sock, 10);//set timeout
3.实验结果与分析:
(1)编译烧录上述程序,包含接收延时的TCP server程序就设置好了。运行该程序,其串口输出信息如下:
(2)在终端下运行以下命令,启动终端下建立的模拟TCP Client:(注意替换(1)中打印的自己的开发板IP地址)
(3)连接后,会提示Timeout will occur after 10 sec,即10秒后会触发接收超时,我们在10s内发送字符串“you got me"则终端就会收到开发板回复的消息“you got me",同时串口提示收到PC端的终端发来的消息,并再次提示接收超时Timeout will occur after 10 sec。
(4)如果,10秒内未向开发板发出任何消息,将触发接收超时,串口打印信息如下:
提示超时发生,并关闭开发板的Socket TCP Server.
(附:如果对上述nc命令以及建立TCP Server/Client不太熟悉,可以参考我博客的Socket编程(1)中的介绍)
当然,Select()仅仅用来设置Socket的接收超时时间实在是大材小用,作为I/O多路复用的基本实现机制,它的作用可是非同小可,下一篇博客,将介绍用select机制在ESP8266上实现单任务处理多客户端交互的实现,感兴趣的可以看一看哦。
- ESP8266/ESP32 Socket编程(4)Select进阶用法-服务器单任务实现多客户端交互
- 基于XSocket框架的socket编程技巧(设置连接的超时时间和最大空闲时间)
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- 基于XSocket框架的socket编程技巧(设置连接的超时时间和最大空闲时间)
- 基于XSocket框架的socket编程技巧(设置连接的超时时间和最大空闲时间)
- C++ 之Socket 编程 send rev 阻塞设置 阻塞超时时间
- C语言socket编程设置接收超时(Window&Linux)
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- linux网络编程之socket(十一):套接字I/O超时设置方法和用select实现超时
- PHP socket 编程中的超时设置
- VC socket Connect 超时时间设置
- linux非阻塞式socket编程之select()用法
- 初学Socket编程的坑~~~连接超时和服务器无法接收数据
- Windows 和 Linux下使用socket下载网页页面内容(可设置接收/发送超时)的代码
- socket为send和recv设置 4000 超时时间
- 关于http socket timeout 超时时间 未设置 导致线程一直在等待(线程饥饿),微信公众号开发过程遇到的。java
- socket的send、recv阻塞设置阻塞超时时间
- 【转】【win网络编程】socket中的recv阻塞和select的用法
- winsock编程如何设置发送与接收超时参数?
- [置顶] linux非阻塞式socket编程之select()用法