Windows下网络编与ESP8266-WiFi通信(win32-API)

举报
DS小龙哥 发表于 2024/08/26 14:15:30 2024/08/26
【摘要】 在这一领域中,TCP/IP协议族是核心组成部分,尤其TCP(传输控制协议)是面向连接的协议,为数据包在网络上传输提供可靠的保障,确保数据的准确性和顺序性。TCP客户端与TCP服务器是网络通信模型中的两个角色:服务器监听特定的端口,等待客户端的连接请求;一旦连接建立,双方即可进行双向通信。

一、前言

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

image-20240715144933330

在Windows下创建TCP服务器涉及使用Windows Socket(Winsock)API,这是一个用于网络编程的接口,允许应用程序通过TCP/IP协议栈发送和接收数据。

网络编程涵盖了客户端和服务器的交互机制。在这一模型中,服务器通常处于被动监听状态,等待客户端主动发起连接请求。一旦连接建立,服务器与客户端便能通过TCP协议进行可靠的数据交换。TCP协议通过三次握手建立连接,四次挥手断开连接,确保数据的有序传输和完整性检查。

在Windows环境下,创建TCP服务器涉及以下几个关键步骤:

  1. 初始化Winsock:使用WSAStartup()函数初始化Winsock库,这是网络编程前的必要步骤。

  2. 创建套接字:使用socket()函数创建一个套接字,它将成为服务器与客户端通信的端点。

  3. 绑定套接字:使用bind()函数将套接字与本地IP地址和端口号关联。服务器通常绑定到一个固定的端口,以便客户端可以发现并连接。

  4. 监听连接:使用listen()函数将套接字置于监听状态,准备接受来自客户端的连接请求。

  5. 接受连接:使用accept()函数等待并接受客户端的连接请求。当客户端连接时,accept()会返回一个新的套接字,用于与特定客户端通信。

  6. 读写数据:使用recv()send()函数(或recvfrom()sendto()在UDP情况下)读取和发送数据。在TCP连接中,数据以流的形式传输,无需关注数据包的边界。

  7. 关闭连接:当通信完成后,使用closesocket()函数关闭套接字,释放资源。

  8. 清理Winsock:使用WSACleanup()函数清理Winsock库。

image-20240715144839634

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];
​
    // 初始化Winsock
    WSAStartup(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);
​
    // 清理Winsock
    WSACleanup();
​
    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. sendrecv

功能:发送和接收数据。

语法

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 1024

void 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;

    // 初始化Winsock
    if (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 milliseconds

void ErrorHandling(const char* message);

int main() {
    WSADATA wsaData;
    SOCKET clientSocket;
    struct sockaddr_in serverAddr;
    char buffer[BUFFER_SIZE];
    int bytesSent;
    int result;

    // Initialize Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
        ErrorHandling("WSAStartup() failed!");
    }

    // Create a socket
    clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET) {
        ErrorHandling("socket() failed!");
    }

    // Prepare server address structure
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(SERVER_PORT);
    serverAddr.sin_addr.s_addr = inet_addr(SERVER_IP);

    // Connect to the server
    if (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 messages
    while (1) {
        // Send message to server
        bytesSent = 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 message
        Sleep(SEND_INTERVAL);
    }

    // Cleanup
    closesocket(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常量定义了发送消息的时间间隔,单位为毫秒。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。