ESP8266 - AP模式TCP SERVER 多客户端链接

一、记录

  1. 使用官方RTOS_SDK 3.2 版本,例子为 ESP8266_RTOS_SDK\examples\protocols\sockets\tcp_server
  2. 官方例子中,有一个问题,若客户端连接后,关闭连接,8266server端会不停报 Unable to create socket 错误
    原因:客户端close后,只关闭了客户端fd,没有关闭server端listen的fd,而又跳出了大循环,导致重新简历server端的sock,导致错误
    修复方法:在listen 后 加入一个大的 for循环
  3. 我这里改成兼容非阻塞方式,采用select进行操作
    这里参考了一个非阻塞的实现方法 : 非阻塞实现方式

特别的注意点:

(1).fd的属性可以不进行NO_BLOCK的配置
(2).struct timeval tv; tv.tv_usec注意不能小于1000,否则系统一会就崩溃 这里的单位是us

/* BSD Socket API Example

   This example code is in the Public Domain (or CC0 licensed, at your option.)

   Unless required by applicable law or agreed to in writing, this
   software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
   CONDITIONS OF ANY KIND, either express or implied.
*/
#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>

#include "xzh_wifi.h"

#define PORT 10000

static const char *TAG = "example";

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");
		
		
		#if 0
		for(;;)
		{

	#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) {
				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);
				//close(listen_sock);
				//vTaskDelay(500/portTICK_PERIOD_MS);
			}
		}
		#else
			
		
		fd_set readfds, testfds; 
		
		int client_sockfd;
		uint client_len;
		
		struct sockaddr_in sourceAddr;
		
		int result; 
		
		FD_ZERO(&readfds); 
		FD_SET(listen_sock, &readfds);//将服务器端socket加入到集合中

		while(1) 
		{
			char ch; 
			int fd; 
			int nread; 
			testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量 
			//printf("server waiting\n"); 
	 
			/*无限期阻塞,并测试文件描述符变动 */
			struct timeval tv;
			tv.tv_sec = 0;
			tv.tv_usec = 10*1000; //10MS  注意不能小于1000,否则系统一会就崩溃
			result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) &tv); //FD_SETSIZE:系统默认的最大文件描述符
			//result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) &0); //若 tv是 0 完全阻塞
			if(result < 1) 
			{ 
				//perror("server5"); 
				//exit(1); 
			} 
	 
			/*扫描所有的文件描述符*/
			for(fd = 0; fd < FD_SETSIZE; fd++) 
			{
				/*找到相关文件描述符*/
				if(FD_ISSET(fd,&testfds)) 
				{ 
				  /*判断是否为服务器套接字,是则表示为客户请求连接。*/
					if(fd == listen_sock) 
					{ 
						client_len = sizeof(sourceAddr); 
						client_sockfd = accept(listen_sock, 
						(struct sockaddr *)&sourceAddr, &client_len); 
						FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
						printf("adding client on fd %d\n", client_sockfd); 
					} 
					/*客户端socket中有数据请求时*/
					else 
					{ 
						#if 0
						ioctl(fd, FIONREAD, &nread);//取得数据量交给nread
						/*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
						if(nread == 0) 
						{ 
							//close(fd); 
							//FD_CLR(fd, &readfds); //去掉关闭的fd
							//printf("removing client on fd %d\n", fd); 
						} 
						/*处理客户数据请求*/
						else 
						{ 
							read(fd, &ch, 1); 
							//sleep(5); 
							printf("serving client on fd %d\n", fd); 
							ch++; 
							write(fd, &ch, 1); 
						} 
						#else
						char rx_buffer[128];
						int len = recv(fd, rx_buffer, sizeof(rx_buffer) - 1, 0);
						if (len < 0) 
						{
							ESP_LOGE(TAG, "recv failed: errno %d", errno);
							//break;
						}
						// Connection closed
						else if (len == 0) 
						{
							ESP_LOGI(TAG, "Connection closed");
							close(fd); 
							FD_CLR(fd, &readfds); //去掉关闭的fd
							printf("serving client on fd %d\n", fd); 
							//break;
						}
						else
						{
							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);		
						}
						#endif
						
						
					} 
				} 
			} 
		}

		#endif
    }
    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());
	xzh_wifi_ap_init("XZH_ESP8266","xuanzihao");
	
	

    xTaskCreate(tcp_server_task, "tcp_server", 4096, NULL, 5, NULL);
}


关于AP模式,注意这里在 xzh_wifi_ap_init 中 采用了 xEventGroupWaitBits ,来进行阻塞,等待AP建好(WIFI_EVENT_AP_START事件发生时)

//AP部分

static void ip_event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    switch (event_id) {
        case IP_EVENT_AP_STAIPASSIGNED:
            //xEventGroupSetBits(wifi_event_group, CONNECTED_BIT);
			ESP_LOGI(TAG, "IP_EVENT_AP_STAIPASSIGNED\r\n \r\n");
            break;
        default:
            break;
    }
    return;
}


static void wifi_event_handler(void* arg, esp_event_base_t event_base,
                                int32_t event_id, void* event_data)
{
    switch (event_id) {
		case WIFI_EVENT_AP_STACONNECTED:
		{
			wifi_event_ap_staconnected_t* event = (wifi_event_ap_staconnected_t*) event_data;
			ESP_LOGI(TAG, "station "MACSTR" join, AID=%d",
					 MAC2STR(event->mac), event->aid);
			break;
		}
		case WIFI_EVENT_AP_STADISCONNECTED:
		{
			wifi_event_ap_stadisconnected_t* event = (wifi_event_ap_stadisconnected_t*) event_data;
			ESP_LOGI(TAG, "station "MACSTR" leave, AID=%d",
					 MAC2STR(event->mac), event->aid);
			break;
		}
		case WIFI_EVENT_AP_START:
		{
			ESP_LOGI(TAG, "WIFI_EVENT_AP_START\r\n \r\n");
			xEventGroupSetBits(s_connect_event_group, GOT_IPV4_BIT);
			break;
		}
        default:
            break;
    }
    return;
}


esp_err_t xzh_set_ap_info(const char *ssid, const char *passwd)
{
    strncpy(s_connection_name, ssid, sizeof(s_connection_name));
    strncpy(s_connection_passwd, passwd, sizeof(s_connection_passwd));
	
    wifi_config_t wifi_config = { 0 };

    strncpy((char *)&wifi_config.ap.ssid, s_connection_name, 32);
    strncpy((char *)&wifi_config.ap.password, s_connection_passwd, 64);
	
	wifi_config.ap.authmode = WIFI_AUTH_WPA2_PSK;

   // ESP_LOGI(TAG, "Connecting to %s...", wifi_config.sta.ssid);
   // ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));

    return ESP_OK;
}

esp_err_t xzh_wifi_ap_init(const char *ssid, const char *passwd)
{
    if (s_connect_event_group != NULL) {
        return ESP_ERR_INVALID_STATE;
    }

    s_connect_event_group = xEventGroupCreate();
	

    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    //ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, &on_wifi_disconnect, NULL));
    //ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &on_got_ip, NULL));  
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &ip_event_handler, NULL));
	
    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
	

	
    wifi_config_t wifi_config = { 0 };
	
	

    strncpy((char *)&wifi_config.ap.ssid, ssid, 32);
    strncpy((char *)&wifi_config.ap.password, passwd, 64);
	
	wifi_config.ap.authmode = WIFI_AUTH_WPA2_PSK;
	
	wifi_config.ap.max_connection = 2;

    ESP_LOGI(TAG, "SSID:%s PASS:%s", wifi_config.ap.ssid,wifi_config.ap.password);
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
	
    ESP_ERROR_CHECK(esp_wifi_start());
	
	xEventGroupWaitBits(s_connect_event_group, CONNECTED_BITS, true, true, portMAX_DELAY);

	return ESP_OK;

}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值