嵌软开发:CMake 到 Socket 通信

目录

🌼前言

🎂周会技术回顾

1.1 .cc文件

2.1 .cfa 文件

3.1 CMake 简单示例

4.1 .sh 文件 

5.1 bin 文件

6.1 .so 文件

7.1 override 关键字

8.1 enum class

9.1 static_cast

10.1 socket 通信


 

🌼前言

目前,新的架构,会取代旧的技术架构,应用到新产品上

但是这个新的架构,是 C 为主的,目前业务,需要将其中的多个模块,全面重构为 C++

需要对 C++,CMake 有所了解

同时,C 中复杂的互相调用,重构时会有较大风险,需要先弄清楚代码逻辑

作为一个每天打杂的实习生,需要利用上班摸鱼时间,以及每天 8 点后下班时间

输出技术博客,完成对当天周会,或任务的复盘

以下对部分基础知识,进行回顾

CMake核心教程_【骠姚校尉】的博客-CSDN博客 

👆以上,18篇 CMake 实战,可以认真学习下

本博客主要目的,不是全部背熟记住,而是需要的时候,你知道这个东西怎么用,为什么会这样,以便快速利用文档,AI 进行查阅,提高后续开发效率 

并且公司目前全面普及 cursor 了,使用公司内部的免费 cursor,对项目代码进行全面解读 

🎂周会技术回顾

socket 通信,CMake,.cc .cfa .sh .bin .so 文件类型,override,enum,static_cast

1.1 .cc文件

与 .cpp 完全等价 

代码示例 

// camera_controller.cc - 摄像头控制器实现
#include <iostream>
#include <string>
#include "camera_controller.h"

namespace camera { // 避免命名冲突,camera::CameraController

// 摄像头控制器类
class CameraController {
private:
    int camera_id_;           // 摄像头ID
    bool is_initialized_;     // 是否已初始化
    std::string device_path_; // 设备路径

public:
    // 构造函数:初始化摄像头ID
    CameraController(int id) : camera_id_(id), is_initialized_(false) {
        device_path_ = "/dev/video" + std::to_string(id);
    }

    // 初始化摄像头设备
    bool Initialize() {        
        if (camera_id_ >= 0 && camera_id_ < 4) {
            is_initialized_ = true;
            return true;
        }
        return false;
    }

    // 开始视频流
    void StartStreaming() {
        if (!is_initialized_) {
            return;
        }
        std::cout << "Starting video stream from camera " << camera_id_ << std::endl;
    }

    // 析构函数:清理资源
    ~CameraController() {
        if (is_initialized_) {
        }
    }
};

} // namespace camera

编译与链接

# -c: 只编译不链接,生成目标文件
g++ -c camera_controller.cc -o camera_controller.o

# -o: 指定输出文件名
g++ camera_controller.cc main.cc -o camera_app

# -std=c++17: 使用C++17标准,-O2: 开启优化
g++ -std=c++17 -O2 camera_controller.cc -o camera_app

2.1 .cfa 文件

项目的配置文件

# camera.cfa - 摄像头配置文件
[camera_0]
resolution=1920x1080  # 分辨率
fps=30               # 帧率
format=YUV420        # 图像格式

[camera_1]
resolution=3840x2160
fps=60
format=RGB24

3.1 CMake 简单示例

makefile 示例 

# 设置CMake的最低版本要求
cmake_minimum_required(VERSION 3.10)

# 设置项目名称
project(SimpleCameraApp)

# 设置C++编译器选项,开启所有警告并设置优化等级为2
set(CMAKE_CXX_FLAGS "-Wall -O2")

# 设置链接选项,链接pthread库
set(CMAKE_EXE_LINKER_FLAGS "-lpthread")

# 添加可执行文件,指定源文件
add_executable(camera_app camera.cc main.cc)

# 自定义目标:特定平台编译
add_custom_target(platform_5555
    COMMAND ${CMAKE_COMMAND} -E echo "Build completed for platform 5555"
    COMMAND ${CMAKE_COMMAND} --build . --target clean
    COMMAND ${CMAKE_COMMAND} --build .
    COMMENT "Building for platform 5555"
)

# 添加清理目标
add_custom_target(clean
    COMMAND ${CMAKE_COMMAND} --build . --target clean
    COMMENT "Cleaning build files"
)

# 声明伪目标
add_custom_target(all
    COMMAND ${CMAKE_COMMAND} --build .
    COMMENT "Building all targets"
)

编译

# 创建一个构建目录,用于存放构建文件
mkdir build
# 进入构建目录
cd build

# 运行CMake,指定源代码目录(通常是上一级目录)
cmake ..

# 使用make命令构建项目
make

# 使用make命令调用清理目标,删除构建文件
make clean

# 使用make命令调用特定平台的构建目标
make platform_5555

# 并行编译,使用4个线程
make -j4

4.1 .sh 文件 

脚本示例

#!/bin/bash
# build.sh - 自动化编译脚本

# 设置变量
TARGET=5555
BUILD_DIR=build

# 创建构建目录(如果不存在)
if [ ! -d "$BUILD_DIR" ]; then
    mkdir $BUILD_DIR
fi

# 进入构建目录
cd $BUILD_DIR

# 执行编译
echo "开始编译目标:$TARGET"
make $TARGET

# 检查编译是否成功
if [ $? -eq 0 ]; then
    echo "编译成功!"
else
    echo "编译失败,请检查错误信息。"
    exit 1
fi

脚本执行

chmod +x build.sh    # 赋予执行权限
./build.sh           # 执行脚本

5.1 .bin 文件

二进制可执行文件

生成 bin 文件

# 假设你已经用 g++ 或 arm-linux-gnueabihf-g++ 等工具编译 .cc 文件
# 生成了 firmware.elf 文件
# .elf 是 Linux 和嵌入式系统常用的可执行文件格式(Executable and Linkable Format)
# 包含调试信息和符号表
# Windows 下常见的是 .exe 文件,但嵌入式和 Linux 下一般是 .elf

# 下面这条命令将 .elf 文件转换为 .bin 文件,.bin 是纯二进制格式,适合烧录到硬件设备
arm-linux-gnueabihf-objcopy -O binary firmware.elf firmware.bin

# 参数解释:
# arm-linux-gnueabihf-objcopy :交叉工具链中的 objcopy 工具,用于文件格式转换
# -O binary                  :指定输出格式为 binary(纯二进制)
# firmware.elf               :输入文件(可执行文件,含调试信息)
# firmware.bin               :输出文件(纯二进制文件,适合烧录)

操作 bin 文件

// bin_example.cc - 读取并显示二进制文件前8个字节
// 说明:.cc 是 C++ 源代码文件,编译后会生成可执行文件(在 Linux 下通常是 .elf 格式,Windows 下是 .exe)。
// 在嵌入式开发中,.elf 文件是包含调试信息的可执行文件,常用于后续生成 .bin 固件文件。

#include <iostream>
#include <fstream>

int main() {
    // 打开二进制文件 firmware.bin
    std::ifstream file("firmware.bin", std::ios::binary);
    if (!file) {
        std::cout << "无法打开文件" << std::endl;
        return 1;
    }
    char buffer[8];
    // 读取前8个字节
    file.read(buffer, 8);
    std::cout << "前8个字节:";
    for (int i = 0; i < file.gcount(); ++i) {
        printf("%02X ", (unsigned char)buffer[i]);
    }
    std::cout << std::endl;
    return 0;
}

6.1 .so 文件

动态链接库文件(Unix 下是 .so,Windows 下是 .dll)

动态加载一个共享库 libcamera.so,获取其中的函数指针,并调用这些函数来操作摄像头设备。通过动态加载共享库,程序可以在运行时决定加载哪个库

共享库示例

// camera_lib.cc - 一个简单的摄像头共享库实现
// 说明:本文件将被编译为 .so 动态库,供其他程序调用

#include <iostream>

extern "C" { // 按 C 语言规则链接
    // 打开摄像头,返回摄像头ID
    int camera_open(int id) {
        std::cout << "打开摄像头,ID: " << id << std::endl;
        return id;
    }

    // 开始摄像头预览
    void camera_start_preview(int id) {
        std::cout << "摄像头 " << id << " 开始预览" << std::endl;
    }

    // 关闭摄像头
    void camera_close(int id) {
        std::cout << "关闭摄像头,ID: " << id << std::endl;
    }
}

编译共享库

# 编译 camera_lib.cc 为共享库 libcamera.so
# -fPIC: 生成位置无关代码,-shared: 生成动态库
g++ -fPIC -shared -o libcamera.so camera_lib.cc

使用共享库

// main.cc - 动态加载并使用 libcamera.so
// 说明:演示如何在程序中动态加载 .so 文件并调用其中的函数

#include <iostream>
#include <dlfcn.h>

int main() {
    // 加载共享库
    void* handle = dlopen("./libcamera.so", RTLD_LAZY);
    if (!handle) {
        std::cerr << "无法加载库: " << dlerror() << std::endl;
        return 1;
    }

    // 获取函数指针
    // 定义函数指针类型,用于匹配 libcamera.so 中的函数签名
    typedef int (*open_func)(int);
    typedef void (*preview_func)(int);
    typedef void (*close_func)(int);

    // 使用 dlsym 函数获取 libcamera.so 中的函数指针
    // dlsym 的第一个参数是 dlopen 返回的句柄,第二个参数是函数名
    open_func camera_open = (open_func)dlsym(handle, "camera_open");
    preview_func camera_start_preview = (preview_func)dlsym(handle, "camera_start_preview");
    close_func camera_close = (close_func)dlsym(handle, "camera_close");

    // 调用库函数
    int cam_id = camera_open(1);
    camera_start_preview(cam_id);
    camera_close(cam_id);

    // 卸载库
    dlclose(handle);
    return 0;
}

编译 + 运行主程序

# 编译主程序,-ldl 用于链接动态库加载相关函数
g++ -o main main.cc -ldl

# 运行前需保证 libcamera.so 在当前目录或 LD_LIBRARY_PATH 路径下
./main

7.1 override 关键字

虚函数重写,安全保障

(基类指针或引用,通过动态绑定,调用子类的虚函数的实现)

示例

// override_demo.cc - override关键字演示
#include <iostream>

// 基类:设备
class Device {
public:
    virtual ~Device() = default;
    
    // 虚函数:启动设备
    virtual void Start() {
        std::cout << "设备启动" << std::endl;
    }
    
    // 虚函数:获取设备信息
    virtual std::string GetInfo() const {
        return "通用设备";
    }
};

// 派生类:摄像头
class Camera : public Device {
public:
    // 使用override确保正确重写虚函数
    void Start() override {
        std::cout << "摄像头启动" << std::endl;
    }
    
    // 重写获取信息函数
    std::string GetInfo() const override {
        return "摄像头设备";
    }
};

int main() {
    Camera cam;
    Device* device = &cam;  // 多态
    
    device->Start();                              // 调用Camera::Start()
    std::cout << device->GetInfo() << std::endl; // 调用Camera::GetInfo()
    
    return 0;
}

保障

// 1. 防止拼写错误
// void Stat() override;                   // 编译错误:应该是Start,拼写错了
    
// 2. 防止参数类型错误
// void SetMode(float mode) override;      // 编译错误:参数类型不匹配
    
// 3. 防止参数数量错误
// void Configure(int id) override;        // 编译错误:参数个数不对
    

// 4. 防止const修饰符错误
// std::string GetInfo() override;         // 编译错误:缺少const

// 基类的 GetInfo() 函数后面有 const,表示这个函数承诺不会修改对象
// 派生类重写时,必须保持相同的 const 修饰符
// 如果基类是 const 函数,派生类忘记加 const,override 会检测到这个错误并报编译错误
// 这确保了函数签名的完全一致性
    

// 5. 防止返回类型错误
// void GetStatus() const override;        // 编译错误:返回类型不匹配
    
// 6. 防止基类函数被删除后的错误
// void DeletedFunction() override;        // 编译错误:基类中不存在此虚函数

8.1 enum class

强类型枚举

示例

// enum_demo.cc - enum class演示
#include <iostream>
#include <cstdint> // 用于 uint8_t

// 传统枚举的问题
enum OldStatus {
    IDLE,     // 全局作用域,容易命名冲突;隐式转换,存在安全问题
    RUNNING
};

// 强类型枚举解决问题
enum class CameraStatus : uint8_t {  // 指定底层类型为 uint8_t
    Idle = 0,      // 空闲状态
    Running = 1,   // 运行状态
    Error = 255    // 错误状态
};

enum class DeviceType : int {
    Unknown = 0,   // 未知设备
    Camera = 1,    // 摄像头
    Microphone = 2 // 麦克风
};

// 枚举转换函数
std::string StatusToString(CameraStatus status) {
    switch (status) {
        case CameraStatus::Idle: return "Idle";       // 空闲状态
        case CameraStatus::Running: return "Running"; // 运行状态
        case CameraStatus::Error: return "Error";     // 错误状态
        default: return "Unknown";                    // 未知状态
    }
}

// 使用示例
int main() {
    // 类型安全,必须指定作用域
    CameraStatus status = CameraStatus::Idle;  // 摄像头状态设置为 Idle
    DeviceType type = DeviceType::Camera;      // 设备类型设置为 Camera

    // 不能隐式转换为 int
    // int value = status;  // 编译错误
    int value = static_cast<int>(status);  // 必须显式转换

    // 输出状态字符串
    std::cout << "Status: " << StatusToString(status) << std::endl;
    std::cout << "Status value: " << value << std::endl;

    // 类型安全的比较
    if (status == CameraStatus::Idle) {
        std::cout << "Camera is idle" << std::endl;
    }

    return 0;
}

9.1 static_cast<int>

类型安全转换

// static_cast_demo.cc - static_cast演示
#include <iostream>

enum class Priority : int {
    Low = 1,       // 低优先级
    Medium = 5,    // 中优先级
    High = 10      // 高优先级
};

// 类型转换示例
class TypeCastDemo {
public:
    // 基本类型转换
    static void BasicCast() {
        int int_val = 42;
        float float_val = static_cast<float>(int_val);  // int转float
        double double_val = static_cast<double>(int_val); // int转double
    }
    
    // 枚举类转换
    static void EnumCast() {
        Priority pri = Priority::High;
        int pri_value = static_cast<int>(pri);  // 枚举转int
        
        std::cout << "Priority value: " << pri_value << std::endl;
        
        // int转枚举(需谨慎)
        Priority new_pri = static_cast<Priority>(5);
        std::cout << "New priority: " << static_cast<int>(new_pri) << std::endl;
    }
    
    // 指针转换
    static void PointerCast() {
        int value = 100;
        void* void_ptr = &value;                           // int*转void*
        int* int_ptr = static_cast<int*>(void_ptr);        // void*转int*
    }
};

int main() {
    TypeCastDemo::BasicCast();
    
    TypeCastDemo::EnumCast();
    
    TypeCastDemo::PointerCast();
    
    return 0;
}

10.1 socket 通信

TCP 服务器

// tcp_server.cc - 简单的TCP服务器
// 说明:服务器就像一个餐厅,等待客户(客户端)来点餐(发送请求)
#include <iostream>
#include <sys/socket.h>    // socket相关函数
#include <netinet/in.h>    // 网络地址结构
#include <unistd.h>        // close函数
#include <cstring>         // strlen函数

// 简单的TCP服务器类
class SimpleTCPServer {
private:
    int server_fd_;        // 服务器的socket文件描述符(像餐厅的门牌号)
    int port_;            // 监听的端口号(像餐厅的地址)
    bool is_running_;     // 服务器是否在运行

public:
    // 构造函数:设置服务器监听的端口
    SimpleTCPServer(int port) : port_(port), is_running_(false) {}
    
    // 启动服务器
    bool Start() {
        // 1. 创建socket(相当于开一家餐厅)
        // socket(协议族, socket类型, 协议)
        // AF_INET: IPv4协议族, SOCK_STREAM: TCP协议, 0: 自动选择协议
        server_fd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (server_fd_ == -1) {
            std::cerr << "创建socket失败" << std::endl;
            return false;
        }
        
        // 2. 设置地址复用(允许重复使用同一个地址)
        int opt = 1;
        // setsockopt(socket描述符, 协议级别, 选项名, 选项值指针, 选项值长度)
        // SOL_SOCKET: socket级别, SO_REUSEADDR: 地址复用选项
        setsockopt(server_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        
        // 3. 绑定地址和端口(给餐厅确定地址)
        struct sockaddr_in address;
        address.sin_family = AF_INET;           // 使用IPv4协议
        address.sin_addr.s_addr = INADDR_ANY;   // 监听所有网络接口(0.0.0.0)
        address.sin_port = htons(port_);        // htons: 主机字节序转网络字节序
        
        // bind(socket描述符, 地址结构指针, 地址结构长度)
        if (bind(server_fd_, (struct sockaddr*)&address, sizeof(address)) < 0) {
            std::cerr << "绑定地址失败" << std::endl;
            return false;
        }
        
        // 4. 开始监听(餐厅开始营业,等待客人)
        // listen(socket描述符, 最大排队连接数)
        // 3表示最多排队3个客户等待连接
        if (listen(server_fd_, 3) < 0) {
            std::cerr << "监听失败" << std::endl;
            return false;
        }
        
        is_running_ = true;
        std::cout << "服务器在端口 " << port_ << " 开始监听..." << std::endl;
        return true;
    }
    
    // 接受客户端连接(等待客人进餐厅)
    void AcceptConnections() {
        while (is_running_) {
            struct sockaddr_in client_addr;  // 客户端地址信息
            socklen_t client_len = sizeof(client_addr);
            
            // 5. 接受一个客户端连接(客人进来了)
            // accept(服务器socket, 客户端地址结构指针, 客户端地址长度指针)
            // 返回新的socket描述符,专门用于与这个客户端通信
            int client_fd = accept(server_fd_, (struct sockaddr*)&client_addr, &client_len);
            if (client_fd < 0) {
                std::cerr << "接受连接失败" << std::endl;
                continue;  // 继续等待下一个客户
            }
            
            std::cout << "客户端已连接" << std::endl;
            
            // 6. 处理这个客户端的请求(服务客人)
            HandleClient(client_fd);
            
            // 7. 服务完毕,关闭连接(客人离开)
            // close(文件描述符) - 关闭socket连接
            close(client_fd);
            std::cout << "客户端已断开连接" << std::endl;
        }
    }
    
    // 处理客户端请求(具体服务客人)
    void HandleClient(int client_fd) {
        char buffer[1024] = {0};  // 接收数据的缓冲区(像记录客人点的菜)
        
        // 接收客户端发送的数据
        // recv(socket描述符, 接收缓冲区, 缓冲区大小, 标志位)
        // client_fd: 客户端socket, buffer: 存放数据的缓冲区
        // sizeof(buffer): 缓冲区大小, 0: 无特殊标志
        int bytes_read = recv(client_fd, buffer, sizeof(buffer), 0);
        if (bytes_read > 0) {
            std::cout << "收到消息: " << buffer << std::endl;
            
            // 发送回复给客户端
            const char* response = "你好,我是服务器!";
            // send(socket描述符, 发送数据指针, 数据长度, 标志位)
            // client_fd: 客户端socket, response: 要发送的数据
            // strlen(response): 数据长度, 0: 无特殊标志
            send(client_fd, response, strlen(response), 0);
        }
    }
    
    // 停止服务器(餐厅关门)
    void Stop() {
        is_running_ = false;
        if (server_fd_ != -1) {
            // close(文件描述符) - 关闭服务器socket
            close(server_fd_);
        }
        std::cout << "服务器已停止" << std::endl;
    }
    
    // 析构函数
    ~SimpleTCPServer() {
        Stop();
    }
};

TCP 客户端

// tcp_client.cc - 简单的TCP客户端
// 说明:客户端就像顾客,主动去餐厅(服务器)点餐(发送请求)
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>     // inet_pton函数
#include <unistd.h>
#include <cstring>

// 简单的TCP客户端类
class SimpleTCPClient {
private:
    int client_fd_;         // 客户端的socket文件描述符
    std::string server_ip_; // 服务器IP地址(餐厅地址)
    int server_port_;       // 服务器端口号

public:
    // 构造函数:设置要连接的服务器地址和端口
    SimpleTCPClient(const std::string& ip, int port) 
        : server_ip_(ip), server_port_(port), client_fd_(-1) {}
    
    // 连接到服务器(顾客走进餐厅)
    bool Connect() {
        // 1. 创建socket(准备去餐厅)
        // socket(协议族, socket类型, 协议)
        // AF_INET: IPv4协议族, SOCK_STREAM: TCP协议, 0: 自动选择协议
        client_fd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (client_fd_ == -1) {
            std::cerr << "创建socket失败" << std::endl;
            return false;
        }
        
        // 2. 设置服务器地址信息
        struct sockaddr_in server_addr;
        server_addr.sin_family = AF_INET;                           // IPv4协议
        server_addr.sin_port = htons(server_port_);                 // htons: 主机字节序转网络字节序
        
        // inet_pton(地址族, IP字符串, 二进制地址结构)
        // AF_INET: IPv4, server_ip_.c_str(): IP字符串, &server_addr.sin_addr: 输出地址
        inet_pton(AF_INET, server_ip_.c_str(), &server_addr.sin_addr);
        
        // 3. 连接服务器(走进餐厅)
        // connect(socket描述符, 服务器地址结构指针, 地址结构长度)
        if (connect(client_fd_, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "连接服务器失败" << std::endl;
            return false;
        }
        
        std::cout << "已连接到服务器 " << server_ip_ << ":" << server_port_ << std::endl;
        return true;
    }
    
    // 发送消息给服务器(向服务员点餐)
    bool SendMessage(const std::string& message) {
        if (client_fd_ == -1) {
            std::cerr << "未连接到服务器" << std::endl;
            return false;
        }
        
        // 发送数据
        // send(socket描述符, 发送数据指针, 数据长度, 标志位)
        // client_fd_: 客户端socket, message.c_str(): 消息字符串
        // message.length(): 消息长度, 0: 无特殊标志
        ssize_t bytes_sent = send(client_fd_, message.c_str(), message.length(), 0);
        if (bytes_sent < 0) {
            std::cerr << "发送消息失败" << std::endl;
            return false;
        }
        
        std::cout << "已发送: " << message << std::endl;
        return true;
    }
    
    // 接收服务器的响应(收到餐厅的回复)
    std::string ReceiveResponse() {
        if (client_fd_ == -1) {
            return "";
        }
        
        char buffer[1024] = {0};  // 接收缓冲区
        
        // recv(socket描述符, 接收缓冲区, 缓冲区大小, 标志位)
        // client_fd_: 客户端socket, buffer: 存放接收数据的缓冲区
        // sizeof(buffer): 缓冲区大小(1024字节), 0: 无特殊标志
        ssize_t bytes_received = recv(client_fd_, buffer, sizeof(buffer), 0);
        
        if (bytes_received > 0) {
            // string(字符数组, 长度) - 根据接收到的字节数创建字符串
            std::string response(buffer, bytes_received);
            std::cout << "收到回复: " << response << std::endl;
            return response;
        }
        
        return "";
    }
    
    // 断开连接(离开餐厅)
    void Disconnect() {
        if (client_fd_ != -1) {
            // close(文件描述符) - 关闭客户端socket
            close(client_fd_);
            client_fd_ = -1;
            std::cout << "已断开与服务器的连接" << std::endl;
        }
    }
    
    // 析构函数
    ~SimpleTCPClient() {
        Disconnect();
    }
};

客户端与服务器通信

// socket_demo.cc - 演示客户端和服务器通信
#include <iostream>
#include <thread>           // 多线程支持
#include <chrono>           // 时间相关

int main() {
    // 在单独的线程中启动服务器(就像在后台开一家餐厅)
    // std::thread(lambda函数) - 创建新线程执行lambda函数
    std::thread server_thread([]() {
        SimpleTCPServer server(8080);  // 在8080端口开餐厅
        if (server.Start()) {
            server.AcceptConnections();   // 开始接待客人
        }
    });
    
    // 等待服务器启动完成(等餐厅准备好)
    // sleep_for(时间间隔) - 当前线程休眠指定时间
    // milliseconds(100) - 100毫秒
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    
    // 创建客户端并连接服务器(顾客去餐厅)
    // SimpleTCPClient(IP地址, 端口号)
    // "127.0.0.1": 本机IP地址, 8080: 服务器端口号
    SimpleTCPClient client("127.0.0.1", 8080);
    if (client.Connect()) {
        // 发送消息并接收回复
        client.SendMessage("你好,服务器!");
        client.ReceiveResponse();
        client.Disconnect();
    }
    
    // 注意:实际应用中需要优雅地停止服务器线程
    // 这里为了演示简单,没有处理线程的正确关闭
    return 0;
}

编译

# 编译服务器程序
# -std=c++11: 使用C++11标准(支持线程)
# -pthread: 链接线程库(pthread)
g++ -std=c++11 -pthread tcp_server.cc -o server

# 编译客户端程序
g++ -std=c++11 -pthread tcp_client.cc -o client

# 编译演示程序(包含服务器和客户端)
g++ -std=c++11 -pthread socket_demo.cc -o socket_demo

# 运行方式1:分别运行服务器和客户端
./server &    # &符号表示后台运行服务器
./client      # 前台运行客户端

# 运行方式2:运行演示程序
./socket_demo

可以把所有代码放一个 .cc,一次编译即可

# 创建一个完整的文件 complete_socket.cc,包含:
# - SimpleTCPServer类
# - SimpleTCPClient类  
# - main函数
# 然后一次编译
g++ -std=c++11 -pthread complete_socket.cc -o socket_demo
./socket_demo
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

千帐灯无此声

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值