您的位置:首页 > 编程语言

ESP8266/ESP32 Socket编程(3)select基本用法-设置Socket接收超时时间

2020-08-17 11:10 99 查看

引言:

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

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐