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.


  
  
  1. /* BSD Socket API Example*/
  2. #include <string.h>
  3. #include <sys/param.h>
  4. #include "freertos/FreeRTOS.h"
  5. #include "freertos/task.h"
  6. #include "esp_system.h"
  7. #include "esp_log.h"
  8. #include "esp_netif.h"
  9. #include "esp_event.h"
  10. #include "protocol_examples_common.h"
  11. #include "nvs.h"
  12. #include "nvs_flash.h"
  13. #include "lwip/err.h"
  14. #include "lwip/sockets.h"
  15. #include "lwip/sys.h"
  16. #include <lwip/netdb.h>
  17. #define PORT CONFIG_EXAMPLE_PORT
  18. static const char *TAG = "example";
  19. int readable_timeo(int fd, int sec);
  20. int Readable_timeo(int fd, int sec);
  21. int ret;
  22. int
  23. readable_timeo (int fd, int sec)
  24. {
  25. fd_set rset;
  26. struct timeval tv;
  27. FD_ZERO(&rset);
  28. FD_SET(fd, &rset);
  29. tv.tv_sec = sec;
  30. tv.tv_usec = 0;
  31. ESP_LOGI(TAG, "Timeout will occur after %d sec", sec);
  32. return( select(fd+ 1, &rset, NULL, NULL, &tv));
  33. /* > 0 if descriptor is readable */
  34. }
  35. /* end readable_timeo */
  36. int
  37. Readable_timeo (int fd, int sec)
  38. {
  39. int n;
  40. if ( (n = readable_timeo(fd, sec)) < 0)
  41. ESP_LOGI(TAG, "readable_timeo error");
  42. return(n);
  43. }
  44. static void tcp_server_task(void *pvParameters)
  45. {
  46. char rx_buffer[ 128];
  47. char addr_str[ 128];
  48. int addr_family;
  49. int ip_protocol;
  50. while ( 1) {
  51. #ifdef CONFIG_EXAMPLE_IPV4
  52. struct sockaddr_in destAddr;
  53. destAddr.sin_addr.s_addr = htonl(INADDR_ANY);
  54. destAddr.sin_family = AF_INET;
  55. destAddr.sin_port = htons(PORT);
  56. addr_family = AF_INET;
  57. ip_protocol = IPPROTO_IP;
  58. inet_ntoa_r(destAddr.sin_addr, addr_str, sizeof(addr_str) - 1);
  59. #else // IPV6
  60. struct sockaddr_in6 destAddr;
  61. bzero(&destAddr.sin6_addr.un, sizeof(destAddr.sin6_addr.un));
  62. destAddr.sin6_family = AF_INET6;
  63. destAddr.sin6_port = htons(PORT);
  64. addr_family = AF_INET6;
  65. ip_protocol = IPPROTO_IPV6;
  66. inet6_ntoa_r(destAddr.sin6_addr, addr_str, sizeof(addr_str) - 1);
  67. #endif
  68. int listen_sock = socket(addr_family, SOCK_STREAM, ip_protocol);
  69. if (listen_sock < 0) {
  70. ESP_LOGE(TAG, "Unable to create socket: errno %d", errno);
  71. break;
  72. }
  73. ESP_LOGI(TAG, "Socket created");
  74. int err = bind(listen_sock, ( struct sockaddr *)&destAddr, sizeof(destAddr));
  75. if (err != 0) {
  76. ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
  77. break;
  78. }
  79. ESP_LOGI(TAG, "Socket binded");
  80. err = listen(listen_sock, 1);
  81. if (err != 0) {
  82. ESP_LOGE(TAG, "Error occured during listen: errno %d", errno);
  83. break;
  84. }
  85. ESP_LOGI(TAG, "Socket listening");
  86. #ifdef CONFIG_EXAMPLE_IPV6
  87. struct sockaddr_in6 sourceAddr; // Large enough for both IPv4 or IPv6
  88. #else
  89. struct sockaddr_in sourceAddr;
  90. #endif
  91. uint addrLen = sizeof(sourceAddr);
  92. int sock = accept(listen_sock, ( struct sockaddr *)&sourceAddr, &addrLen);
  93. if (sock < 0) {
  94. ESP_LOGE(TAG, "Unable to accept connection: errno %d", errno);
  95. break;
  96. }
  97. ESP_LOGI(TAG, "Socket accepted");
  98. while ( 1) {
  99. ret = Readable_timeo(sock, 10); //set timeout and add if
  100. if (ret > 0){
  101. int len = recv(sock, rx_buffer, sizeof(rx_buffer) - 1, 0);
  102. // Error occured during receiving
  103. if (len < 0) {
  104. ESP_LOGE(TAG, "recv failed: errno %d", errno);
  105. break;
  106. }
  107. // Connection closed
  108. else if (len == 0) {
  109. ESP_LOGI(TAG, "Connection closed");
  110. break;
  111. }
  112. // Data received
  113. else {
  114. #ifdef CONFIG_EXAMPLE_IPV6
  115. // Get the sender's ip address as string
  116. if (sourceAddr.sin6_family == PF_INET) {
  117. inet_ntoa_r((( struct sockaddr_in *)&sourceAddr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);
  118. } else if (sourceAddr.sin6_family == PF_INET6) {
  119. inet6_ntoa_r(sourceAddr.sin6_addr, addr_str, sizeof(addr_str) - 1);
  120. }
  121. #else
  122. inet_ntoa_r((( struct sockaddr_in *)&sourceAddr)->sin_addr.s_addr, addr_str, sizeof(addr_str) - 1);
  123. #endif
  124. rx_buffer[len] = 0; // Null-terminate whatever we received and treat like a string
  125. ESP_LOGI(TAG, "Received %d bytes from %s:", len, addr_str);
  126. ESP_LOGI(TAG, "%s", rx_buffer);
  127. int err = send(sock, rx_buffer, len, 0);
  128. if (err < 0) {
  129. ESP_LOGE(TAG, "Error occured during sending: errno %d", errno);
  130. break;
  131. }
  132. }
  133. }
  134. else {
  135. ESP_LOGE(TAG, "timeout or error: ret of readable_timeo= %d", ret);
  136. break;
  137. }
  138. }
  139. if (sock != -1 || ret <= 0) {
  140. ESP_LOGE(TAG, "Shutting down socket and restarting...");
  141. shutdown(sock, 0);
  142. close(sock);
  143. }
  144. }
  145. vTaskDelete( NULL);
  146. }
  147. void app_main()
  148. {
  149. ESP_ERROR_CHECK( nvs_flash_init());
  150. ESP_ERROR_CHECK( esp_netif_init());
  151. ESP_ERROR_CHECK( esp_event_loop_create_default());
  152. ESP_ERROR_CHECK( example_connect());
  153. xTaskCreate(tcp_server_task, "tcp_server", 4096, NULL, 5, NULL);
  154. }

 实现上主要是参考《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上实现单任务处理多客户端交互的实现,感兴趣的可以看一看哦。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值