Windows下网络编与ESP8266-WiFi通信(win32-API)
一、前言
络编程是指编写程序使不同计算机之间能够通过网络进行通信和数据交换。网络编程涉及使用网络协议和编程接口来建立、管理和终止网络上的数据通信。在这一领域中,TCP/IP协议族是核心组成部分,尤其TCP(传输控制协议)是面向连接的协议,为数据包在网络上传输提供可靠的保障,确保数据的准确性和顺序性。TCP客户端与TCP服务器是网络通信模型中的两个角色:服务器监听特定的端口,等待客户端的连接请求;一旦连接建立,双方即可进行双向通信。

在Windows下创建TCP服务器涉及使用Windows Socket(Winsock)API,这是一个用于网络编程的接口,允许应用程序通过TCP/IP协议栈发送和接收数据。
网络编程涵盖了客户端和服务器的交互机制。在这一模型中,服务器通常处于被动监听状态,等待客户端主动发起连接请求。一旦连接建立,服务器与客户端便能通过TCP协议进行可靠的数据交换。TCP协议通过三次握手建立连接,四次挥手断开连接,确保数据的有序传输和完整性检查。
在Windows环境下,创建TCP服务器涉及以下几个关键步骤:
- 初始化Winsock:使用
WSAStartup()函数初始化Winsock库,这是网络编程前的必要步骤。 - 创建套接字:使用
socket()函数创建一个套接字,它将成为服务器与客户端通信的端点。 - 绑定套接字:使用
bind()函数将套接字与本地IP地址和端口号关联。服务器通常绑定到一个固定的端口,以便客户端可以发现并连接。 - 监听连接:使用
listen()函数将套接字置于监听状态,准备接受来自客户端的连接请求。 - 接受连接:使用
accept()函数等待并接受客户端的连接请求。当客户端连接时,accept()会返回一个新的套接字,用于与特定客户端通信。 - 读写数据:使用
recv()和send()函数(或recvfrom()和sendto()在UDP情况下)读取和发送数据。在TCP连接中,数据以流的形式传输,无需关注数据包的边界。 - 关闭连接:当通信完成后,使用
closesocket()函数关闭套接字,释放资源。 - 清理Winsock:使用
WSACleanup()函数清理Winsock库。

ESP8266 WiFi模块是一款由乐鑫科技(Espressif Systems)推出的低成本、高性能的无线通信模块,专为物联网(IoT)应用设计。该模块内置了Tensilica L106超低功耗32位微控制器,拥有80MHz的主频,集成了Wi-Fi 802.11 b/g/n标准的无线网络功能,支持多种加密方式,如WEP、WPA/WPA2、WPA-PSK、WPA2-PSK等。ESP8266因其体积小、功耗低、价格便宜以及集成度高,迅速成为了物联网开发者的首选解决方案之一。
ESP8266模块支持TCP/IP协议栈,这意味着它可以作为TCP客户端或服务器,与其他设备进行网络通信。开发者可以利用ESP8266的AT指令集,或直接使用SDK进行固件开发,从而实现数据的传输与接收。除了TCP,ESP8266也支持UDP、HTTP、HTTPS、MQTT等多种网络协议,这使得它能够在各种网络环境中灵活应用。
由于ESP8266的强大功能和易用性,它已经成为许多物联网项目的基础,无论是业余爱好者还是专业开发者,都能够快速构建起具有网络连接能力的智能设备。
下面是一个C语言代码示例,展示如何在Windows下创建一个TCP服务器,等待ESP8266 WiFi模块的连接,并与之通信:
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>#pragma comment(lib, "ws2_32.lib")int main() {WSADATA wsaData;SOCKET serverSocket;sockaddr_in serverAddress;int addrLen = sizeof(serverAddress);char buffer[1024];// 初始化WinsockWSAStartup(MAKEWORD(2, 2), &wsaData);// 创建套接字serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 设置地址和端口serverAddress.sin_family = AF_INET;serverAddress.sin_port = htons(8080);serverAddress.sin_addr.s_addr = htonl(INADDR_ANY);// 绑定套接字bind(serverSocket, (SOCKADDR*)&serverAddress, sizeof(serverAddress));// 开始监听listen(serverSocket, SOMAXCONN);// 接受连接SOCKET clientSocket = accept(serverSocket, (SOCKADDR*)&serverAddress, &addrLen);// 通信while (1) {int bytesReceived = recv(clientSocket, buffer, 1024, 0);if (bytesReceived > 0) {buffer[bytesReceived] = '\0';printf("Received: %s\n", buffer);send(clientSocket, buffer, bytesReceived, 0);}else {break;}}// 关闭连接closesocket(clientSocket);closesocket(serverSocket);// 清理WinsockWSACleanup();return 0;
}
通过上述步骤和示例代码,创建了一个能够等待ESP8266 WiFi模块连接的TCP服务器,实现了基本的数据收发功能。对于初学者而言,理解网络编程的基础概念,如TCP协议的工作原理和Winsock API的使用,是学习ESP8266 WiFi编程的重要一步。掌握了这些知识后,可以更深入地探索物联网(IoT)项目,利用WiFi模块实现远程数据采集、监控以及其他智能应用。
二、实例代码
2.1 网络编程相关的函数
网络编程在Windows环境下主要依赖于Winsock(Windows Socket)API,是微软实现的基于Berkeley sockets API的一个版本,用于在Windows操作系统上进行网络编程。Winsock API提供了丰富的函数集,用于创建、配置、管理和关闭套接字(sockets),以及通过网络进行数据的发送和接收。
以下是几个核心的Winsock函数及其参数详解:
1. WSAStartup
功能:初始化Winsock DLL。
语法:
int WSAStartup(WORD wVersionRequired,LPWSADATA lpWSAData
);
参数:
wVersionRequired:指定需要的Winsock版本。lpWSAData:指向WSADATA结构的指针,用于返回Winsock DLL的信息。
2. WSACleanup
功能:卸载并关闭Winsock DLL。
语法:
int WSACleanup(void);
3. socket
功能:创建一个套接字。
语法:
SOCKET socket(int af,int type,int protocol
);
参数:
af:地址家族,如AF_INET(IPv4)或AF_INET6(IPv6)。type:套接字类型,如SOCK_STREAM(TCP)或SOCK_DGRAM(UDP)。protocol:协议类型,如IPPROTO_TCP或IPPROTO_UDP,通常可以设置为0。
4. bind
功能:将套接字绑定到一个本地地址。
语法:
int bind(SOCKET s,const struct sockaddr *name,int namelen
);
参数:
s:套接字描述符。name:指向sockaddr结构的指针,包含要绑定的地址和端口号。namelen:地址结构的大小。
5. listen
功能:将套接字置于监听状态,准备接受连接请求。
语法:
int listen(SOCKET s,int backlog
);
参数:
s:套接字描述符。backlog:连接队列的最大长度。
6. accept
功能:接受传入的连接请求,创建新的套接字用于通信。
语法:
SOCKET accept(SOCKET s,struct sockaddr *addr,int *addrlen
);
参数:
s:监听状态的套接字描述符。addr:指向sockaddr结构的指针,用于接收客户端地址信息。addrlen:指向整型变量的指针,用于指定和返回地址结构的大小。
7. connect
功能:主动发起连接到远程主机。
语法:
int connect(SOCKET s,const struct sockaddr *name,int namelen
);
参数:
s:套接字描述符。name:指向sockaddr结构的指针,包含远程地址和端口。namelen:地址结构的大小。
8. send 和 recv
功能:发送和接收数据。
语法:
int send(SOCKET s,const char *buf,int len,int flags
);int recv(SOCKET s,char *buf,int len,int flags
);
参数:
s:套接字描述符。buf:指向数据缓冲区的指针。len:数据长度。flags:控制选项,通常为0。
9. closesocket
功能:关闭套接字。
语法:
int closesocket(SOCKET s
);
参数:
s:要关闭的套接字描述符。
10. gethostbyname
功能:将主机名转换为IP地址。
语法:
struct hostent *gethostbyname(const char *name
);
参数:
name:主机名或域名。
11. inet_addr
功能:将点分十进制IP地址字符串转换为网络字节序的二进制格式。
语法:
in_addr_t inet_addr(const char *cp
);
参数:
cp:点分十进制IP地址字符串。
以上函数构成了网络编程的基础,它们使得在Windows平台上进行网络通信变得可能。正确理解和使用这些函数是开发网络应用程序的关键。
2.2 创建一个TCP服务器
开发环境:在Windows下安装一个VS即可。我当前采用的版本是VS2020。
在Windows环境下,创建一个能够处理多个客户端连接的TCP服务器通常需要使用多线程。下面是一个使用C语言和Winsock库实现的多线程TCP服务器的示例代码。服务器将监听客户端的连接请求,每当有新的客户端连接时,服务器将启动一个新的线程来处理与该客户端的通信,读取并打印客户端发送的消息。
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>#pragma comment(lib, "ws2_32.lib")#define DEFAULT_PORT 8080
#define BACKLOG 10
#define BUFFER_SIZE 1024void ErrorHandling(const char* message);
void ClientHandler(SOCKET clientSocket);int main() {WSADATA wsaData;SOCKET serverSocket;SOCKET clientSocket;struct sockaddr_in serverAddr;struct sockaddr_in clientAddr;int addrLen = sizeof(clientAddr);int result;// 初始化Winsockif (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {ErrorHandling("WSAStartup() failed!");}// 创建服务器套接字serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (serverSocket == INVALID_SOCKET) {ErrorHandling("socket() failed!");}// 准备服务器地址结构serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);serverAddr.sin_port = htons(DEFAULT_PORT);// 绑定套接字到地址if (bind(serverSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {ErrorHandling("bind() failed!");}// 监听连接if (listen(serverSocket, BACKLOG) == SOCKET_ERROR) {ErrorHandling("listen() failed!");}printf("Server is listening on port %d...\n", DEFAULT_PORT);// 主循环,等待客户端连接while (1) {clientSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &addrLen);if (clientSocket == INVALID_SOCKET) {ErrorHandling("accept() failed!");}printf("Client connected: %s:%d\n",inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));// 创建新线程处理客户端HANDLE threadHandle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ClientHandler, (LPVOID)clientSocket, 0, NULL);if (threadHandle == NULL) {ErrorHandling("CreateThread() failed!");}CloseHandle(threadHandle);}// 清理closesocket(serverSocket);WSACleanup();return 0;
}void ClientHandler(SOCKET clientSocket) {char buffer[BUFFER_SIZE];int bytesReceived;// 读取客户端数据while ((bytesReceived = recv(clientSocket, buffer, BUFFER_SIZE, 0)) > 0) {buffer[bytesReceived] = '\0'; // 确保字符串以空字符结尾printf("Received from client: %s\n", buffer);}if (bytesReceived == 0) {printf("Client disconnected.\n");} else if (bytesReceived == SOCKET_ERROR) {ErrorHandling("recv() failed!");}// 关闭客户端套接字closesocket(clientSocket);
}void ErrorHandling(const char* message) {printf("%s\n", message);WSACleanup();exit(1);
}
在上面的代码中,main()函数初始化Winsock库,创建并配置服务器套接字,然后开始监听客户端的连接请求。每当有新的客户端连接,main()函数就调用CreateThread()来创建一个新的线程执行ClientHandler()函数。ClientHandler()函数负责接收并打印客户端发送的消息,直到客户端断开连接或发生错误。
2.3 创建TCP客户端连接服务器
开发环境:在Windows下安装一个VS即可。我当前采用的版本是VS2020。
创建一个TCP客户端,使其能够连接到指定的服务器并在连接成功后定期发送消息,可以通过使用Winsock库在C语言中实现。
下面是一个示例代码,展示如何创建一个TCP客户端,连接到服务器,并每隔一定时间(本例中为5秒)向服务器发送一条消息。
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <windows.h>
#include <string.h>#pragma comment(lib, "ws2_32.lib")#define SERVER_IP "127.0.0.1"
#define SERVER_PORT 8080
#define MESSAGE "Hello from the client!"
#define BUFFER_SIZE 1024
#define SEND_INTERVAL 5000 // 5 seconds in millisecondsvoid ErrorHandling(const char* message);int main() {WSADATA wsaData;SOCKET clientSocket;struct sockaddr_in serverAddr;char buffer[BUFFER_SIZE];int bytesSent;int result;// Initialize Winsockif (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {ErrorHandling("WSAStartup() failed!");}// Create a socketclientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);if (clientSocket == INVALID_SOCKET) {ErrorHandling("socket() failed!");}// Prepare server address structureserverAddr.sin_family = AF_INET;serverAddr.sin_port = htons(SERVER_PORT);serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);// Connect to the serverif (connect(clientSocket, (SOCKADDR*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR) {ErrorHandling("connect() failed!");}printf("Connected to server %s:%d\n", SERVER_IP, SERVER_PORT);// Main loop for sending messageswhile (1) {// Send message to serverbytesSent = send(clientSocket, MESSAGE, strlen(MESSAGE), 0);if (bytesSent == SOCKET_ERROR) {ErrorHandling("send() failed!");}printf("Message sent: %s\n", MESSAGE);// Sleep for the specified interval before sending next messageSleep(SEND_INTERVAL);}// Cleanupclosesocket(clientSocket);WSACleanup();return 0;
}void ErrorHandling(const char* message) {printf("%s\n", message);WSACleanup();exit(1);
}
在上述代码中,main()函数先初始化Winsock库,创建一个套接字,使用connect()函数连接到指定的服务器。一旦连接成功,进入一个无限循环,每隔5秒使用send()函数向服务器发送一条消息。消息的内容是静态定义的字符串MESSAGE。
如果服务器不在同一台机器上,要将SERVER_IP替换为服务器的实际IP地址。 SEND_INTERVAL常量定义了发送消息的时间间隔,单位为毫秒。
相关文章:
Windows下网络编与ESP8266-WiFi通信(win32-API)
一、前言 络编程是指编写程序使不同计算机之间能够通过网络进行通信和数据交换。网络编程涉及使用网络协议和编程接口来建立、管理和终止网络上的数据通信。在这一领域中,TCP/IP协议族是核心组成部分,尤其TCP(传输控制协议)是面向…...
【Golang】golang安装一些依赖包时总是失败
Golang安装一些依赖包失败: 比如安装gin包:go get -u github.com/gin-gonic/gin 可能会报错:连接网络失败、超时等 这时可能需要修改go的环境配置,修改代理即可: go env -w GO111MO…...
ubuntu如何监控Xvfb虚拟显示器
在Ubuntu中监控Xvfb显示器主要涉及到使用VNC服务器来远程访问这个环境。以下是一些基本步骤: 安装Xvfb和相关工具: 使用apt安装Xvfb和x11vnc,x11vnc是一个VNC服务器,可以远程访问Xvfb创建的虚拟桌面环境。 sudo apt-get install xvfb sudo ap…...
小型需求管理软件盘点:8款功能强大的工具
本文介绍了以下8款工具:PingCode、Worktile、易得云、Ping、燃草、Gitee、Monday.com、Slack。 在现代企业管理中,需求管理一直是个让人头疼的问题,特别是对于小型企业来说,选择一款合适的需求管理软件往往比想象中更复杂。如果选…...
Labelme的安装与使用教程
文章目录 一、Labelme是什么?二、安装步骤1.新建虚拟环境2.安装Labelme3.Labelme的使用 三、json2yolo 一、Labelme是什么? Labelme是一个用于图像标注的开源工具,可以实现图像标注、语义分割、实例分割等。 本文记录一下labelme的安装与使…...
C#基础:数据库中使用Linq作分组处理(反射/直接分组)
目录 一、使用反射分组 二、不使用反射分组 三、调用示例 四、代码demo 一、使用反射分组 private static List<GroupList<T>> GetGroupList<T>(List<T> entities, string groupByProperty) {// 获取分组字段的类型var propertyInfo typeof(T).…...
Revite二次开发_使用WPF和WebView2制作一个访问网站的窗口
如果想在revit里打开网页,可以使用WebView2来实现,下面是一个代码示例。 也尝试过使用CefSharp,但由于Revit本身也使用了CefSharp,所以需要根据不同的Revit版本选择适合的CefSharp版本,比较麻烦,所以最好还…...
Java Spring Boot 连接数据库
要在Java Spring Boot应用程序中连接数据库,您需要遵循以下步骤: 1. 添加数据库依赖项:在您的Spring Boot项目中的pom.xml文件中添加数据库依赖项,例如MySQL或PostgreSQL等。例如,如果您要连接MySQL数据库,…...
Java面试八股之消息队列中推模式和拉模式分别有哪些使用场景
消息队列中推模式和拉模式分别有哪些使用场景 消息队列的推模式(Push)和拉模式(Pull)各有不同的使用场景和优缺点。下面我会详细介绍这两种模式及其适用场景: 推模式(Push) 特点:…...
springboot jar是如何启动的
我们先来看一个项目的打完包后的MANIFEST.MF文件: Manifest‐Version: 1.0 Implementation‐Title: spring‐learn Implementation‐Version: 0.0.1‐SNAPSHOT Start‐Class: com.tulingxueyuan.Application Spring‐Boot‐Classes: BOOT‐INF/classes/ Spring‐Bo…...
Android 12系统源码_屏幕设备(二)DisplayAdapter和DisplayDevice的创建
前言 在Android 12系统源码_屏幕设备(一)DisplayManagerService的启动这篇文章中我们具体分析了DisplayManagerService 的启动流程,本篇文章我们将在这个的基础上具体来分析下设备屏幕适配器的创建过程。 一、注册屏幕适配器 系统是在Disp…...
常用Mysql命令
前言 本文列举了一些常见的mysql操作 正文 一、连接和登录 MySQL 1. 使用命令行登录 MySQL 注意:需要将mysql的bin目录导入到环境变量中 mysql -u 用户名 -p示例: mysql -u root -p执行上述命令后,系统会提示输入密码,输入…...
IDEA Debug工具
一、Debug工具栏 自定义debug工具栏:先把debug程序运行起来->右击->配置 常用的工具: 二、DeBug常用图标详解 三、DeBug实践操作 常规Debug:略。 Stream Chain:处理流式语句 Reset Frame:重置方法入栈 …...
ARM64的汇编资源
最近在写一本ARM64的教材,所以在晚上查找了一下相关资源,都是免费开源的,不包括盗版书籍。 Exploring AArch64 assembler Roger Ferrer Ibez的博客文章,写在2016-2017年,内容简单充实,适合入门。 《ARM6…...
实验室安全分级分类管理系统在高校中的具体应用
盛元广通高校实验室安全分级分类管理系统的构建,旨在通过科学合理的管理手段,提高实验室的安全水平,保障师生的人身安全,防止实验事故的发生。这一系统通常包括实验室安全等级评估、分类管理、风险控制、安全教育与培训、应急响应…...
使用 prerenderRoutes 进行预渲染路由
title: 使用 prerenderRoutes 进行预渲染路由 date: 2024/8/20 updated: 2024/8/20 author: cmdragon excerpt: prerenderRoutes 函数是 Nuxt 3 中一个强大的工具,它能够帮助开发者优化页面加载速度和改善用户体验。通过使用 prerenderRoutes,你能够灵活地指定需要预渲染的…...
【深度解析】WRF-LES与PALM微尺度气象大涡模拟
查看原文>>>【深度解析】WRF-LES与PALM微尺度气象大涡模拟 针对微尺度气象的复杂性,大涡模拟(LES)提供了一种无可比拟的解决方案。微尺度气象学涉及对小范围内的大气过程进行精确模拟,这些过程往往与天气模式、地形影响和…...
redis事件机制
redis服务器是一个由事件驱动(死循环)的程序,它总共就干两件事: 文件事件:利用I/O复用机制,监听Socket等文件描述符发生的事件,如网络请求时间事件:定时触发的事件,负责完成redis内部定时任务&…...
【C++】模拟实现vector
可以把vector看作升级版的数组,可采用下标进行访问,非常高效,大小可动态改变,会自动扩容,数据存储在堆空间上。 VECROR 成员变量、函数及模板总览构造函数和析构函数无参构造函数构造n个元素大小的空间并初始化通过某个…...
【CAN-IDPS】汽车网关信息安全要求以及实验方法
《汽车网关信息安全技术要求及试验方法》是中国的一项国家标准,编号为GB/T 40857-2021,于2021年10月11日发布,并从2022年5月1日起开始实施 。这项标准由全国汽车标准化技术委员会(TC114)归口,智能网联汽车分会(TC114SC34)执行,主管部门为工业和信息化部。 该标准主要…...
基于FPGA的PID算法学习———实现PID比例控制算法
基于FPGA的PID算法学习 前言一、PID算法分析二、PID仿真分析1. PID代码2.PI代码3.P代码4.顶层5.测试文件6.仿真波形 总结 前言 学习内容:参考网站: PID算法控制 PID即:Proportional(比例)、Integral(积分&…...
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする
日语学习-日语知识点小记-构建基础-JLPT-N4阶段(33):にする 1、前言(1)情况说明(2)工程师的信仰2、知识点(1) にする1,接续:名词+にする2,接续:疑问词+にする3,(A)は(B)にする。(2)復習:(1)复习句子(2)ために & ように(3)そう(4)にする3、…...
2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
Cinnamon修改面板小工具图标
Cinnamon开始菜单-CSDN博客 设置模块都是做好的,比GNOME简单得多! 在 applet.js 里增加 const Settings imports.ui.settings;this.settings new Settings.AppletSettings(this, HTYMenusonichy, instance_id); this.settings.bind(menu-icon, menu…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据
微软PowerBI考试 PL300-在 Power BI 中清理、转换和加载数据 Power Query 具有大量专门帮助您清理和准备数据以供分析的功能。 您将了解如何简化复杂模型、更改数据类型、重命名对象和透视数据。 您还将了解如何分析列,以便知晓哪些列包含有价值的数据,…...
搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...
Go语言多线程问题
打印零与奇偶数(leetcode 1116) 方法1:使用互斥锁和条件变量 package mainimport ("fmt""sync" )type ZeroEvenOdd struct {n intzeroMutex sync.MutexevenMutex sync.MutexoddMutex sync.Mutexcurrent int…...
【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)
旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据!该数据集源自2025年4月发表于《地理学报》的论文成果…...
