ESP8266/ESP32 Socket编程(2)查看并修改socket的配置选项(option)

引言:

网络编程中最常用的就是Socket编程,即网络套接字编程。Socket API不仅提供了连网、接收数据的接口,也提供了灵活地查看、修改Socket option,即配置选项的接口。想充分发挥Socket编程的作用,了解这些配置选项将是你开发出更好用的网络工程代码。

1.代码:

下面是查看、修改socket相关配置选项的代码,其中的代码都是可以化为己用的,我尽量添加了相关注释:

实验用的代码模板是ESP8266 RTOS-SDKv3.3以上版本的Socket TCP Example,开发板是ESP8266DevkitC.

/* 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";

/* use to get the socket options */
union val {
  int				i_val;
  long				l_val;
  struct linger		linger_val;
  struct timeval	timeval_val;
} val;

static char	*sock_str_flag(union val *, int);
static char	*sock_str_int(union val *, int);
static char	*sock_str_linger(union val *, int);
static char	*sock_str_timeval(union val *, int);

struct sock_opts {
  const char	   *opt_str;
  int		opt_level;
  int		opt_name;
  char   *(*opt_val_str)(union val *, int);
} sock_opts[] = {
	{ "SO_KEEPALIVE",		SOL_SOCKET,	SO_KEEPALIVE,	sock_str_flag },
    { "SO_LINGER",			SOL_SOCKET,	SO_LINGER,		sock_str_linger },
	{ "SO_RCVBUF",			SOL_SOCKET,	SO_RCVBUF,		sock_str_int },
	{ "SO_SNDBUF",			SOL_SOCKET,	SO_SNDBUF,		sock_str_int },
	{ "SO_RCVTIMEO",		SOL_SOCKET,	SO_RCVTIMEO,	sock_str_timeval },
	{ "SO_SNDTIMEO",		SOL_SOCKET,	SO_SNDTIMEO,	sock_str_timeval },
	{ "SO_REUSEADDR",		SOL_SOCKET,	SO_REUSEADDR,	sock_str_flag }
};

/* Some Conversion function ,include checkopts3 */
static char	strres[128];

static char	*
sock_str_flag(union val *ptr, int len)
{
/* *INDENT-OFF* Attention:ESP32 long type is 4Bytes,long long type is 8Bytes*/
	if (len != sizeof(long long)){
		snprintf(strres, sizeof(strres), "size (%d) not sizeof(int)", len);
    }
	else
		snprintf(strres, sizeof(strres),
				 "%s", (ptr->l_val == 0) ? "off" : "on");
	return(strres);
/* *INDENT-ON* */
}

static char	*
sock_str_int(union val *ptr, int len)
{
	if (len != sizeof(long long))
		snprintf(strres, sizeof(strres), "size (%d) not sizeof( long long)", len);
	else
		snprintf(strres, sizeof(strres), "%ld", ptr->l_val);
	return(strres);
}

static char	*
sock_str_linger(union val *ptr, int len)
{
	struct linger	*lptr = &ptr->linger_val;

	if (len != sizeof(struct linger))
		snprintf(strres, sizeof(strres),
				 "size (%d) not sizeof(struct linger)", len);
	else
		snprintf(strres, sizeof(strres), "l_onoff = %d, l_linger = %d",
				 lptr->l_onoff, lptr->l_linger);
	return(strres);
}

static char	*
sock_str_timeval(union val *ptr, int len)
{
	struct timeval	*tvptr = &ptr->timeval_val;

	if (len != sizeof(struct timeval))
		snprintf(strres, sizeof(strres),
				 "size (%d) not sizeof(struct timeval)", len);
	else
		snprintf(strres, sizeof(strres), "%ld sec, %ld usec",
				 tvptr->tv_sec, tvptr->tv_usec);
	return(strres);
}/*end for checkopt*/


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");
/* Now, let's get the socket's option */
        socklen_t			len;
	    struct sock_opts	*ptr;

	for (ptr = sock_opts; ptr->opt_str != NULL; ptr++) {
		printf("%s: ", ptr->opt_str);
		if (ptr->opt_val_str == NULL)
			printf("(undefined)\n");
		else {
            len = sizeof(val);
			if (getsockopt(listen_sock, ptr->opt_level, ptr->opt_name,
						   &val, &len) == -1) {
				printf("getsockopt error\n");
			} else {
				printf("default = %s\n", (*ptr->opt_val_str)(&val, len));
			}
        }
    }
/* Now ,let's set some options*/
            // ptr = &sock_opts[4];
            struct timeval set_my_timeval = {10,20};
            len = sizeof(struct timeval);
            if (setsockopt(listen_sock, SOL_SOCKET, SO_RCVTIMEO,
						   &set_my_timeval, len) == -1) {
				printf("getsockopt error\n");
			} else {
				printf("setoption_accept_timeout = %s\n", sock_str_timeval((union val *)(&set_my_timeval), len));
			}
/*check the changed value*/
            vTaskDelay(100);
            ptr = &sock_opts[4];
            if (getsockopt(listen_sock, ptr->opt_level, ptr->opt_name,
						   &val, &len) == -1) {
				printf("getsockopt error\n");
			} else {
				printf("nowoption_accept_timeout = %s\n", (*ptr->opt_val_str)(&val, len));
			}

/* bind() listen() */
        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");
/* Now, let's get the socket's option again */
	for (ptr = sock_opts; ptr->opt_str != NULL; ptr++) {
		printf("%s: ", ptr->opt_str);
		if (ptr->opt_val_str == NULL)
			printf("(undefined)\n");
		else {
            len = sizeof(val);
			if (getsockopt(sock, ptr->opt_level, ptr->opt_name,
						   &val, &len) == -1) {
				printf("getsockopt error\n");
			} else {
				printf("default = %s\n", (*ptr->opt_val_str)(&val, len));
			}
        }
    }
/* Now ,let's set some options again*/
            // ptr = &sock_opts[4];
            set_my_timeval.tv_sec = 12;
            set_my_timeval.tv_usec = 12;
            len = sizeof(struct timeval);
            if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
						   &set_my_timeval, len) == -1) {
				printf("getsockopt error\n");
			} else {
				printf("setoption_recv_timeout= %s\n", sock_str_timeval((union val *)(&set_my_timeval), len));
			}
/*check the changed value*/
            vTaskDelay(100);
            ptr = &sock_opts[4];
            if (getsockopt(sock, ptr->opt_level, ptr->opt_name,
						   &val, &len) == -1) {
				printf("getsockopt error\n");
			} else {
				printf("nowoption_ recv_timeout= %s\n", (*ptr->opt_val_str)(&val, len));
			}
// /*set a socket as nonbloacking */
//             int flags;
//             if ((flags = fcntl(sock, F_GETFL, 0)) < 0)
//                 printf("error");
//             flags |= O_NONBLOCK;
//             if ((fcntl(sock, F_SETFL, flags)) < 0)
//                 printf("error");


        while (1) {
            set_my_timeval.tv_sec = 12;
            set_my_timeval.tv_usec = 12;
            len = sizeof(struct timeval);
            if (setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO,
						   &set_my_timeval, len) == -1) {
				printf("getsockopt error\n");
			} else {
				printf("setoption = %s\n", sock_str_timeval((union val *)(&set_my_timeval), len));
			}
            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;
                }
            }
        }

        if (sock != -1) {
        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);
}

2.实验结果分析:

由于实验结果较长,我分为以下几个部分分别分析,以得出一些有用的结论。

(1)开发板编译烧录上述程序后,打印信息如下,首先是Socket原来的一些option的值。此外,我修改了其SO_RCVTIMEO的值,其原来是0 sec, 0usec.我设置的值是10 sec, 20usec. 再次查询其值是10 sec, 0usec(因为该开发板计时精度有限,因此自动舍去了微妙级别的数值20usec),证明修改成功。同时注意到,开发板上运行的TCP Server的IP地址为192.168.47.100.

 (2)剩下的实验结果如下:

在Ubuntu下打开一个Terminal终端,输入如下命令连接开发板上的TCP Server:

nc  192.168.47.100  3333(注意将命令中的IP替换为你的串口打印的IP地址和端口号,不了解的可以参考我的上一篇博客,Socket编程(1))。连接后,开发板打印信息如下:

 连接成功提示“Socket accept".之后打印出当前Socket的配置选项值。此外,需要提醒的是在(1)中设置的SO_RCVTIMEO的值是10sec,这意味这accept()必须在10秒内收到连接请求,超出10秒,就会抛出异常,终止程序。因此必须在10秒内输入上述nc命令,否则accept()就会在超出时间后,停止程序,提示超时异常。(这点可以试一试,看看实验结果就明白了)

最最重要的是,我们在上述代码中生成了两个套接字:Listen_sock、sock,前者经过Socket()产生,后者经过accept()产生,尽管accept()函数生成的新的套接字sock是向accept()传入Listen_sock加工而来的,但,sock并未继承Listen的option,这点可以通过比较(1)、(2)打印的结果得来,显而易见,sock的初始SO_RCVTIMEO是0sec 0usec,并不是(1)中设置的10sec.

实际上,Socket()生成的套接字、accept()生成的套接字是完全不同的属性,前者属性默认是发起连接的,后者的属性是接收连接请求的,因此,在设置Socket的option时,务必区分开来这两个套接字的属性。

从上面的打印信息来看,我再一次修改了SO_RCVTIMEO的值为12sec。那么这个12sec将影响接下来的哪些行为呢?它将影响的是recv()函数的行为,即12秒内服务器必须收到客户端发来的数据,否则就会触发超时异常,程序终止。

假设你在12秒内在终端输入以下信息:

向开发板的Server端发送消息“you got me", 开发板会回复同样的“you got me"作为回复。

(3)接到上述数据后,开发板的串口输出信息如下,提示,受到11Byetes的数据,以及收到的数据内容。

同样的,接收完数据后,将再一次进入循环,等待12秒内下一次数据的到来,如果没有数据发来,就会打印红色部分的串口信息这一段信息,可自行分析,因为未设置Socket的地址/端口可复用,因此触发超时异常就会停止程序运行。

值得一提的是,Socket的SO_RCVTIMEO选项,在accept()之前设置为10sec,其决定的是accept()的超时时间,而在accept()之后设置为12sec,其决定的是recv()的超时时间。此外,Socket的计时机制是累计计时,除非重新设置超时(即程序中的setsockopt()语句)或者有超时异常抛出,否则超时到达,不会重新开始计时。因此在循环中为使得每次recv()都反复设置超时时间为12sec,程序中将setsockopt()语句写在循环体内的recv()函数前。

最后,接收超时选项SO_RCVTIMEO作为Socket的配置选项中最基本的option,用的实在是多,但上述通过Setsockopt()语句的设置超时值的方法有些繁琐,下面我将再写一篇博客,介绍Socket编程中的利器-Select()的用法,通过Select机制设置Socket的接收超时时间。

猜你喜欢

转载自blog.csdn.net/wangyx1234/article/details/107960011