嵌入式上位机开发入门(三):TCP 编程 —— Server 端实现

目录

一、前言

大家好,这里是 Hello_Embed 。前两篇我们学习了网络通信的基本概念和常用 API,本篇将正式进入实战,编写一个完整的 TCP Server 程序。通过这个例子,你将理解 TCP 通信的完整流程,为后续编写客户端程序打下基础。

在开始编程之前需要事先安装好 VS Code 或 Visual Studio。


二、Windows 网络编程环境

对于 Windows 网络编程,需要使用特定的头文件和库文件。

必要的头文件和库配置:

cpp 复制代码
#include <WinSock2.h>
#include <ws2tcpip.h>
#pragma comment(lib,"ws2_32.lib")
#define close closesocket

初始化 WinSock:

cpp 复制代码
WSADATA wsa;
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
    return -1;  // 初始化失败
}

WSAStartup 必须在使用任何网络函数之前调用,用于初始化 Windows Socket 库。


三、TCP Server 通信流程

按照 TCP 通信流程,Server 端需要经过以下步骤:

1. open socket(socket)

创建套接字,返回文件描述符。

cpp 复制代码
int iSocketServer = (int)socket(AF_INET, SOCK_STREAM, 0);
// AF_INET: IPv4 协议族
// SOCK_STREAM: TCP 流式套接字

2. name the socket(bind)

绑定 IP 地址和端口号。

cpp 复制代码
struct sockaddr_in tSocketServerAddr;
tSocketServerAddr.sin_family = AF_INET;
tSocketServerAddr.sin_port = htons(SERVER_PORT);  // 端口号转换为网络字节序
tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;   // 监听所有网卡
memset(tSocketServerAddr.sin_zero, 0, 8);
bind(iSocketServer, (const struct sockaddr*)&tSocketServerAddr, sizeof(struct sockaddr));

3. listen for incoming connections(listen)

启动监听,等待客户端连接请求。

cpp 复制代码
listen(iSocketServer, BACKLOG);  // BACKLOG 是等待队列长度

4. accept client connections(accept)

接收客户端连接,返回新的套接字用于通信。

cpp 复制代码
int iAddrLen = sizeof(struct sockaddr);
struct sockaddr_in tSocketClientAddr;
int iSocketClient = (int)accept(iSocketServer, (struct sockaddr*)&tSocketClientAddr, &iAddrLen);

5. send and receive data(send/recv)

收发数据。

cpp 复制代码
int iRecvLen = recv(iSocketClient, (char*)ucRecvBuf, 1024, 0);
if(iRecvLen > 0) {
    // 处理接收到的数据
}

6. close connection(close)

关闭连接。

cpp 复制代码
close(iSocketClient);

四、完整代码实现

以下是一个完整的 TCP Server 程序,监听端口 8888,接收客户端数据并打印:

cpp 复制代码
#define _WIN32_WINNT 0x0600
#include<iostream>
#include<WinSock2.h>
#include<ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define close closesocket
#define SERVER_PORT 8888
#define BACKLOG 10

int main() {
    WSADATA wsa;
    int iSocketServer, iSocketClient, iAddrLen, iRecvLen, iClientNum = 0;
    struct sockaddr_in tSocketServerAddr, tSocketClientAddr;
    if(WSAStartup(MAKEWORD(2,2), &wsa) != 0) return -1;
    iSocketServer = (int)socket(AF_INET, SOCK_STREAM, 0);
    tSocketServerAddr.sin_family = AF_INET;
    tSocketServerAddr.sin_port = htons(SERVER_PORT);
    tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    memset(tSocketServerAddr.sin_zero, 0, 8);
    bind(iSocketServer, (const struct sockaddr*)&tSocketServerAddr, sizeof(struct sockaddr));
    listen(iSocketServer, BACKLOG);
    while (1) {
        iAddrLen = sizeof(struct sockaddr);
        iSocketClient = (int)accept(iSocketServer, (struct sockaddr*)&tSocketClientAddr, &iAddrLen);
        if(-1 != iSocketClient) {
            char ip[20];
            inet_ntop(AF_INET, &tSocketClientAddr.sin_addr, ip, 20);
            printf("client ip:%s, port:%d\n", ip, iClientNum++);
            while(1) {
                char ucRecvBuf[1024];
                iRecvLen = recv(iSocketClient, (char*)ucRecvBuf, 1024, 0);
                if(iRecvLen > 0) {
                    ucRecvBuf[iRecvLen] = '\0';
                    printf("Get Msg From Client: %d %s\n", iClientNum, ucRecvBuf);
                } else {
                    close(iSocketClient);
                    break;
                }
            }
        }
    }
    return 0;
}

关键注意事项:

  1. _WIN32_WINNT 宏定义的值 0x0600 表示 Windows Vista 或更高版本,这是 inet_ntop 函数在 Windows 上可用的最低版本要求。
  2. 确保这个宏定义在所有 Windows 头文件之前,否则可能不会生效。

运行效果:

先运行程序,再打开串口软件选择 TCP Client,然后设置远程 IP 与本地 IP 一致,Server 端口号 8888,可正常发送数据。


五、增加回复计数功能

在上面的基础上,增加一个功能:Server 接收到客户端消息后,自动回复一条计数消息。

cpp 复制代码
#define _WIN32_WINNT 0x0600
#include<iostream>
#include<WinSock2.h>
#include<ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
#define close closesocket
#define SERVER_PORT 8888
#define BACKLOG 10

int main() {
    WSADATA wsa;
    int iSocketServer, iSocketClient, iAddrLen, iRecvLen, iClientNum = 0, msg_cnt = 0;
    struct sockaddr_in tSocketServerAddr, tSocketClientAddr;
    if(WSAStartup(MAKEWORD(2,2), &wsa) != 0) return -1;
    iSocketServer = (int)socket(AF_INET, SOCK_STREAM, 0);
    tSocketServerAddr.sin_family = AF_INET;
    tSocketServerAddr.sin_port = htons(SERVER_PORT);
    tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
    memset(tSocketServerAddr.sin_zero, 0, 8);
    bind(iSocketServer, (const struct sockaddr*)&tSocketServerAddr, sizeof(struct sockaddr));
    listen(iSocketServer, BACKLOG);
    while (1) {
        iAddrLen = sizeof(struct sockaddr);
        iSocketClient = (int)accept(iSocketServer, (struct sockaddr*)&tSocketClientAddr, &iAddrLen);
        if(-1 != iSocketClient) {
            char ip[20];
            inet_ntop(AF_INET, &tSocketClientAddr.sin_addr, ip, 20);
            printf("client ip:%s, port:%d\n", ip, iClientNum++);
            msg_cnt = 0;
            while(1) {
                char ucRecvBuf[1024];
                iRecvLen = recv(iSocketClient, (char*)ucRecvBuf, 1024, 0);
                if(iRecvLen > 0) {
                    ucRecvBuf[iRecvLen] = '\0';
                    printf("Get Msg From Client: %d %s\n", iClientNum, ucRecvBuf);
                    snprintf(ucRecvBuf, 1024, "server msg %d", msg_cnt++);  // 生成回复消息
                    send(iSocketClient, ucRecvBuf, strlen(ucRecvBuf), 0);   // 发送回复
                } else {
                    close(iSocketClient);
                    break;
                }
            }
        }
    }
    return 0;
}

运行效果:

发送消息之后自动回复信息,计算消息数并累加。


六、总结

  • socket:创建套接字
  • bind:绑定 IP 和端口
  • listen:启动监听
  • accept:接收客户端连接
  • recv/send:收发数据
  • close:关闭连接

TCP Server 的核心是 "监听 → 接收 → 处理 → 回复",理解这个流程后,编写客户端程序就相对容易了。


七、结尾

本篇我们实现了一个完整的 TCP Server 程序,下一篇我们将学习 TCP 客户端编程,实现与 Server 的双向通信。

Hello_Embed 继续带你从原理到实践,掌握嵌入式上位机开发的核心技能,敬请关注~

相关推荐
Sakura_j_h_6 小时前
STM32F103 定时器的三种用法:PWM 输出、输入捕获与编码器接口
stm32·嵌入式·输入捕获·定时器·pwm·编码器·1024程序员节
Lxx_Zw6 小时前
机器周期与共地:时钟基准和电压参考基准
stm32·单片机·嵌入式硬件
解决小子6 小时前
【虚幻引擎】【C++】【GAS框架】构建角色基类、属性集、数值钳制
c++·笔记·游戏引擎·虚幻·gas·能力系统
treesforest7 小时前
游戏账号安全风险分层方案:IP信号分层与人机复核实践
tcp/ip·安全·游戏·1024程序员节
破芽7 小时前
learn-claude-code-s05-s11-学习笔记
笔记·学习
pipixia200337 小时前
C++ 面试高频:namespace 的 6 个知识点,从 using 到匿名命名空间
c语言·学习·算法·嵌入式·命名空间·c++面试·匿名命名空间
我叫洋洋8 小时前
用 Qoder(AI 编程助手)把 Cadence 16.6 从零配到能画板
人工智能·单片机·嵌入式硬件·电路·1024程序员节
流浪0018 小时前
计算机网络篇8:传输层——序列化与反序列化,TCP 面向字节流的报文边界由应用层自己切
网络协议·tcp/ip·计算机网络·1024程序员节·序列化反序列化·面向字节流·面向数据包
Zevalin爱灰灰9 小时前
Linux入门第一部分(Ubuntu入门篇) 第二章 浅尝Ubuntu(二)
linux·嵌入式·1024程序员节
幻智星科技9 小时前
多通道数据采集中collect_end信号的妙用
网络协议·架构·udp·硬件架构