UDP Socket 编程从入门到实践:打造回显服务器、字典服务与聊天室

Linux Swap 子系统:架构、内存管理与虚拟化 Swap Linux Swap机制通过将不活跃内存页移至磁盘或压缩内存,扩展系统有效内存容量,缓解内存压力。传统架构中,逻辑Swap槽与物理存储绑定,导致资源浪费,尤其在使用Zswap等压缩技术时。为此,Vswap与Xswap提出虚拟化方案:前者以XArray实现灵活映射,支持多后端迁移;后者采用稀疏映射,简化管理但容量固定。二者在架构设计、资源记账与动态扩展上各有权衡,反映了内核开发中对性能、可维护性与灵活性的深层考量。 阅读详情

        在网络编程领域,UDP(用户数据报协议)以其无连接、低延迟的特性,在实时通信场景中有着广泛应用。本文将以实战为导向,从基础的 UDP 回显服务器入手,逐步升级到英译汉字典服务,最终实现多线程聊天室,并深入解析 UDP 编程的核心原理与关键技术,帮助开发者快速掌握 UDP Socket 编程精髓。

一、UDP 编程基础:核心概念与地址转换

在开始编码前,我们需要先理解 UDP 编程的底层逻辑和必备工具,这是后续开发的基石。

1.1 UDP 协议核心特性

UDP 作为传输层协议,与 TCP 的 “可靠连接” 不同,它具有以下特点:

        无连接:通信前无需建立三次握手,直接发送数据报

        不可靠:不保证数据有序到达,也不重传丢失的数据包

        面向数据报:数据以固定大小的 “数据报” 为单位传输

        低延迟:省去连接建立和确认机制,适合实时场景(如聊天、直播)

1.2 地址转换函数:解决 “人类可读” 与 “机器可识别” 的矛盾

IP 地址在代码中以 32 位整数(网络字节序)存储,但人类习惯用 “点分十进制”(如 192.168.1.1)表示。以下函数是连接两者的关键工具:

功能函数说明
字符串转 IP(IPv4)inet_addr(const char *strptr)将点分十进制字符串转为in_addr_t(网络字节序)
字符串转 IP(通用)inet_pton(int family, const char *strptr, void *addrptr)支持 IPv4(AF_INET)和 IPv6(AF_INET6),更通用
IP 转字符串(IPv4)inet_ntoa(struct in_addr inaddr)将in_addr转为点分十进制字符串,返回静态缓冲区地址
IP 转字符串(通用)inet_ntop(int family, const void *addrptr, char *strptr, size_t len)支持 IPv4/IPv6,需手动提供缓冲区,线程安全

注意点:inet_ntoa返回的是静态缓冲区地址,多线程调用会导致数据覆盖,推荐在多线程场景使用inet_ntop。

二、V1 版本:极简 UDP 回显服务器(Echo Server)

回显服务器是网络编程的 “Hello World”,功能很简单:接收客户端发送的消息,原样返回给客户端。通过这个案例,我们可以掌握 UDP 编程的基本流程。

2.1 核心设计:禁止拷贝与基础封装

为了避免对象拷贝导致的资源泄漏(如重复关闭 socket),首先定义一个nocopy基类,通过删除拷贝构造和赋值运算符实现 “禁止拷贝”:

// nocopy.hpp
#pragma once
class nocopy {
public:
    nocopy() = default;
    ~nocopy() = default;
    // 禁止拷贝构造和赋值
    nocopy(const nocopy&) = delete;
    const nocopy& operator=(const nocopy&) = delete;
};

2.2 服务器实现:三步完成 UDP 服务搭建

UDP 服务器的核心流程可总结为 “创建 socket → 绑定地址 → 循环收发数据”,具体代码如下:

// UdpServer.hpp
#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nocopy.hpp"

// 常量定义
const static uint16_t default_port = 8888;
const static int default_fd = -1;
const static int buffer_size = 1024;

class UdpServer : public nocopy {
public:
    UdpServer(uint16_t port = default_port) 
        : _port(port), _sock_fd(default_fd) {}

    // 1. 初始化:创建socket + 绑定地址
    void Init() {
        // 创建UDP socket(SOCK_DGRAM表示UDP)
        _sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sock_fd < 0) {
            perror("socket create failed");
            exit(1);
        }

        // 绑定地址(IP + 端口)
        struct sockaddr_in local_addr;
        memset(&local_addr, 0, sizeof(local_addr));
        local_addr.sin_family = AF_INET;         // IPv4
        local_addr.sin_port = htons(_port);      // 端口转为网络字节序
        local_addr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡IP

        if (bind(_sock_fd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {
            perror("bind failed");
            close(_sock_fd);
            exit(1);
        }
        std::cout << "Server init success, sock_fd: " << _sock_fd << std::endl;
    }

    // 2. 启动服务:循环收发数据
    void Start() {
        char buffer[buffer_size];
        while (true) { // 服务器常驻运行
            struct sockaddr_in client_addr;
            socklen_t client_addr_len = sizeof(client_addr);

            // 接收客户端消息
            ssize_t recv_len = recvfrom(
                _sock_fd, buffer, buffer_size - 1, 0,
                (struct sockaddr*)&client_addr, &client_addr_len
            );
            if (recv_len < 0) {
                perror("recvfrom failed");
                continue;
            }
            buffer[recv_len] = '\0'; // 确保字符串结束

            // 打印客户端信息与消息
            std::string client_ip = inet_ntoa(client_addr.sin_addr);
            uint16_t client_port = ntohs(client_addr.sin_port);
            std::cout << "[" << client_ip << ":" << client_port << "]# " << buffer << std::endl;

            // 回显消息给客户端
            sendto(
                _sock_fd, buffer, strlen(buffer), 0,
                (struct sockaddr*)&client_addr, client_addr_len
            );
        }
    }

    // 3. 析构:释放资源
    ~UdpServer() {
        if (_sock_fd != default_fd) {
            close(_sock_fd);
        }
    }

private:
    uint16_t _port;   // 服务端口
    int _sock_fd;     // UDP socket文件描述符
};

2.3 客户端实现:无需显式绑定的秘密

UDP 客户端无需像服务器那样 “绑定固定端口”,因为客户端只需随机端口即可(操作系统会自动分配)。客户端流程为 “创建 socket → 发送消息 → 接收回显”:

// UdpClient.cpp
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

void Usage(const std::string& process) {
    std::cout << "Usage: " << process << " server_ip server_port" << std::endl;
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        Usage(argv[0]);
        return 1;
    }

    // 解析服务器地址
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 1. 创建UDP socket
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0) {
        perror("socket create failed");
        return 1;
    }

    // 2. 填充服务器地址
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());

    // 3. 循环发送与接收
    std::string message;
    char buffer[1024];
    while (true) {
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);

        // 发送消息给服务器
        sendto(
            sock_fd, message.c_str(), message.size(), 0,
            (struct sockaddr*)&server_addr, sizeof(server_addr)
        );

        // 接收服务器回显
        socklen_t server_addr_len = sizeof(server_addr);
        ssize_t recv_len = recvfrom(
            sock_fd, buffer, 1023, 0,
            (struct sockaddr*)&server_addr, &server_addr_len
        );
        if (recv_len < 0) {
            perror("recvfrom failed");
            continue;
        }
        buffer[recv_len] = '\0';
        std::cout << "Server echo# " << buffer << std::endl;
    }

    close(sock_fd);
    return 0;
}

2.4 关键疑问:客户端为什么不需要显式 bind?

        服务器需要bind固定端口:因为客户端需要 “知道去哪里找服务器”(比如 HTTP 默认 80 端口)。

        客户端无需显式bind:操作系统会在客户端首次调用sendto时,自动分配一个随机端口并绑定。若手动bind固定端口,多客户端运行时会出现 “端口占用” 错误。

三、V2 版本:UDP 英译汉字典服务

在回显服务器基础上,我们将 “业务逻辑” 与 “网络通信” 解耦,通过回调函数实现字典查询功能,让代码更具扩展性。

3.1 字典核心:加载词库与查询

首先实现一个Dict类,负责从文件加载词库(dict.txt)并提供查询接口:

// Dict.hpp
#pragma once
#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>

const std::string sep = ": "; // 词库文件分隔符(如"apple: 苹果")

class Dict {
public:
    Dict(const std::string& dict_path) : _dict_path(dict_path) {
        LoadDict(); // 构造时加载词库
    }

    // 翻译接口:查询不到返回"Unknown"
    std::string Translate(const std::string& word) {
        auto iter = _dict.find(word);
        return iter != _dict.end() ? iter->second : "Unknown";
    }

private:
    // 加载词库文件到哈希表
    void LoadDict() {
        std::ifstream dict_file(_dict_path);
        if (!dict_file.is_open()) {
            perror("dict file open failed");
            return;
        }

        std::string line;
        while (std::getline(dict_file, line)) {
            if (line.empty()) continue;
            // 分割"单词: 释义"
            size_t sep_pos = line.find(sep);
            if (sep_pos == std::string::npos) continue;

            std::string word = line.substr(0, sep_pos);
            std::string meaning = line.substr(sep_pos + sep.size());
            _dict.emplace(word, meaning);
        }
        dict_file.close();
        std::cout << "Dict loaded, total words: " << _dict.size() << std::endl;
    }

private:
    std::string _dict_path;                     // 词库文件路径
    std::unordered_map<std::string, std::string> _dict; // 哈希表存储词库
};

词库文件dict.txt格式如下:

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
happy: 快乐的
hello: 你好

3.2 服务器改造:用回调函数解耦业务

修改UdpServer类,通过std::function定义回调函数类型,将 “网络通信” 与 “字典查询” 解耦:

// UdpServer.hpp(V2版本)
#pragma once
#include <iostream>
#include <string>
#include <functional> // 用于std::function
#include <sys/socket.h>
#include <netinet/in.h>
#include "nocopy.hpp"

// 定义回调函数类型:输入请求(单词),输出响应(释义)
using Callback = std::function<void(const std::string& req, std::string* resp)>;

class UdpServer : public nocopy {
public:
    // 构造时传入回调函数(业务逻辑)
    UdpServer(Callback callback, uint16_t port = 8888)
        : _callback(callback), _port(port), _sock_fd(-1) {}

    // 初始化(与V1一致,省略重复代码)
    void Init() { /* 同V1,创建socket + 绑定地址 */ }

    // 启动服务:接收请求 → 回调业务 → 发送响应
    void Start() {
        char buffer[1024];
        while (true) {
            struct sockaddr_in client_addr;
            socklen_t client_addr_len = sizeof(client_addr);

            // 1. 接收客户端请求(单词)
            ssize_t recv_len = recvfrom(
                _sock_fd, buffer, 1023, 0,
                (struct sockaddr*)&client_addr, &client_addr_len
            );
            if (recv_len < 0) continue;
            buffer[recv_len] = '\0';
            std::string req = buffer;

            // 2. 调用回调函数查询字典
            std::string resp;
            _callback(req, &resp);

            // 3. 发送释义给客户端
            sendto(
                _sock_fd, resp.c_str(), resp.size(), 0,
                (struct sockaddr*)&client_addr, client_addr_len
            );

            // 打印日志
            std::string client_ip = inet_ntoa(client_addr.sin_addr);
            uint16_t client_port = ntohs(client_addr.sin_port);
            std::cout << "[" << client_ip << ":" << client_port << "] query: " << req << " → " << resp << std::endl;
        }
    }

private:
    Callback _callback;  // 业务回调函数(字典查询)
    uint16_t _port;      // 服务端口
    int _sock_fd;        // UDP socket
};

3.3 主函数:组装服务与启动

在主函数中创建字典实例、定义回调函数,并启动服务器:

// Main.cpp
#include "UdpServer.hpp"
#include "Dict.hpp"
#include <memory>

// 全局字典实例(加载词库)
Dict g_dict("./dict.txt");

// 回调函数:调用字典查询
void DictCallback(const std::string& req, std::string* resp) {
    *resp = g_dict.Translate(req);
}

int main(int argc, char* argv[]) {
    if (argc != 2) {
        std::cout << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    uint16_t port = std::stoi(argv[1]);

    // 创建服务器实例(传入回调函数)
    std::unique_ptr<UdpServer> server = std::make_unique<UdpServer>(DictCallback, port);
    server->Init();
    server->Start();

    return 0;
}

3.4 测试效果

  1. 启动服务器:./dict_server 8888
  2. 启动客户端:./udp_client 127.0.0.1 8888
  3. 客户端输入 “apple”,服务器返回 “苹果”;输入 “test”,返回 “Unknown”。

四、V3 版本:多线程 UDP 聊天室

聊天室是 UDP 的典型应用场景,需要实现 “消息广播”(将一个客户端的消息发送给所有在线用户)和 “多线程并发”(处理多个客户端的消息)。

4.1 核心挑战与解决方案

        挑战 1:管理在线用户:需要记录所有在线客户端的地址(IP + 端口),用于消息广播。

        挑战 2:多线程安全:多个线程会同时读写 “在线用户列表”,需用互斥锁保护。

        挑战 3:并发处理消息:用线程池处理消息广播,避免主线程阻塞。

4.2 在线用户管理:线程安全的用户列表

首先扩展InetAddr类,增加地址比较和获取原生地址的接口,方便用户管理:

// InetAddr.hpp
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class InetAddr {
public:
    InetAddr(struct sockaddr_in& addr) : _addr(addr) {
        _ip = inet_ntoa(addr.sin_addr);
        _port = ntohs(addr.sin_port);
    }

    // 获取IP和端口
    std::string Ip() const { return _ip; }
    uint16_t Port() const { return _port; }

    // 获取原生sockaddr_in地址(用于sendto)
    const struct sockaddr_in& GetAddr() const { return _addr; }

    // 重载==:判断是否为同一用户(IP + 端口相同)
    bool operator==(const InetAddr& other) const {
        return _ip == other._ip && _port == other._port;
    }

private:
    std::string _ip;               // 客户端IP
    uint16_t _port;                // 客户端端口
    struct sockaddr_in _addr;      // 原生地址结构
};

4.3 服务器改造:线程池 + 消息广播

引入线程池(ThreadPool,实现略)处理消息广播任务,用互斥锁保护在线用户列表:

// UdpServer.hpp(V3版本)
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <pthread.h>
#include <functional>
#include <sys/socket.h>
#include <netinet/in.h>
#include "nocopy.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

// 任务类型:线程池执行的任务
using Task = std::function<void()>;

class UdpServer : public nocopy {
public:
    UdpServer(uint16_t port = 8888) : _port(port), _sock_fd(-1) {
        // 初始化互斥锁
        pthread_mutex_init(&_user_mutex, nullptr);
        // 初始化线程池(4个线程)
        ThreadPool<Task>::GetInstance()->Init(4);
    }

    // 初始化(创建socket + 绑定地址 + 启动线程池)
    void Init() {
        // 1. 创建socket(同V1)
        _sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sock_fd < 0) { perror("socket failed"); exit(1); }

        // 2. 绑定地址(同V1)
        struct sockaddr_in local_addr;
        memset(&local_addr, 0, sizeof(local_addr));
        local_addr.sin_family = AF_INET;
        local_addr.sin_port = htons(_port);
        local_addr.sin_addr.s_addr = INADDR_ANY;
        if (bind(_sock_fd, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) {
            perror("bind failed"); close(_sock_fd); exit(1);
        }

        // 3. 启动线程池
        ThreadPool<Task>::GetInstance()->Start();
        std::cout << "Chat room server init success, port: " << _port << std::endl;
    }

    // 添加在线用户(线程安全)
    void AddOnlineUser(const InetAddr& user) {
        pthread_mutex_lock(&_user_mutex);
        // 避免重复添加同一用户
        for (const auto& u : _online_users) {
            if (u == user) {
                pthread_mutex_unlock(&_user_mutex);
                return;
            }
        }
        _online_users.push_back(user);
        std::cout << "User [" << user.Ip() << ":" << user.Port() << "] online, total: " << _online_users.size() << std::endl;
        pthread_mutex_unlock(&_user_mutex);
    }

    // 消息广播:将消息发送给所有在线用户
    void Broadcast(const std::string& message) {
        pthread_mutex_lock(&_user_mutex);
        for (const auto& user : _online_users) {
            sendto(
                _sock_fd, message.c_str(), message.size(), 0,
                (struct sockaddr*)&user.GetAddr(), sizeof(user.GetAddr())
            );
        }
        pthread_mutex_unlock(&_user_mutex);
    }

    // 启动服务:接收消息 → 加入线程池广播
    void Start() {
        char buffer[1024];
        while (true) {
            struct sockaddr_in client_addr;
            socklen_t client_addr_len = sizeof(client_addr);

            // 1. 接收客户端消息
            ssize_t recv_len = recvfrom(
                _sock_fd, buffer, 1023, 0,
                (struct sockaddr*)&client_addr, &client_addr_len
            );
            if (recv_len < 0) continue;
            buffer[recv_len] = '\0';
            InetAddr user(client_addr);

            // 2. 添加用户到在线列表
            AddOnlineUser(user);

            // 3. 构造广播消息(带用户标识)
            std::string message = "[" + user.Ip() + ":" + std::to_string(user.Port()) + "]# " + buffer;

            // 4. 将广播任务加入线程池
            Task broadcast_task = std::bind(&UdpServer::Broadcast, this, message);
            ThreadPool<Task>::GetInstance()->PushTask(broadcast_task);
        }
    }

    ~UdpServer() {
        pthread_mutex_destroy(&_user_mutex);
        if (_sock_fd != -1) close(_sock_fd);
    }

private:
    uint16_t _port;                  // 服务端口
    int _sock_fd;                    // UDP socket
    std::vector<InetAddr> _online_users; // 在线用户列表
    pthread_mutex_t _user_mutex;     // 保护用户列表的互斥锁
};

4.4 客户端改造:多线程收发

客户端需要同时 “发送消息” 和 “接收广播”,因此用两个线程分别处理:

// UdpClient.cpp(V3版本)
#include <iostream>
#include <string>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 线程数据:传递socket和服务器地址
struct ThreadData {
    int sock_fd;
    struct sockaddr_in server_addr;
};

// 接收线程:持续接收服务器广播
void* RecvThread(void* arg) {
    ThreadData* data = (ThreadData*)arg;
    char buffer[1024];
    while (true) {
        socklen_t server_addr_len = sizeof(data->server_addr);
        ssize_t recv_len = recvfrom(
            data->sock_fd, buffer, 1023, 0,
            (struct sockaddr*)&data->server_addr, &server_addr_len
        );
        if (recv_len < 0) continue;
        buffer[recv_len] = '\0';
        std::cout << "\n" << buffer << std::endl; // 打印广播消息
        std::cout << "Please Enter# "; // 提示用户输入
        fflush(stdout); // 刷新缓冲区,避免输出卡顿
    }
    return nullptr;
}

// 发送线程:从标准输入读取消息并发送
void* SendThread(void* arg) {
    ThreadData* data = (ThreadData*)arg;
    std::string message;
    while (true) {
        std::cout << "Please Enter# ";
        std::getline(std::cin, message);

        // 发送消息给服务器
        sendto(
            data->sock_fd, message.c_str(), message.size(), 0,
            (struct sockaddr*)&data->server_addr, sizeof(data->server_addr)
        );
    }
    return nullptr;
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cout << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }

    // 解析服务器地址
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 创建UDP socket
    int sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sock_fd < 0) { perror("socket failed"); return 1; }

    // 填充服务器地址
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(server_port);
    server_addr.sin_addr.s_addr = inet_addr(server_ip.c_str());

    // 准备线程数据
    ThreadData data = {sock_fd, server_addr};

    // 创建收发线程
    pthread_t recv_tid, send_tid;
    pthread_create(&recv_tid, nullptr, RecvThread, &data);
    pthread_create(&send_tid, nullptr, SendThread, &data);

    // 等待线程结束(实际不会结束,常驻运行)
    pthread_join(recv_tid, nullptr);
    pthread_join(send_tid, nullptr);

    close(sock_fd);
    return 0;
}

4.5 测试效果

  1. 启动服务器:./chat_server 8888
  2. 启动多个客户端:./chat_client 127.0.0.1 8888
  3. 任一客户端发送消息,所有客户端都会收到该消息(带发送者 IP 和端口)。

五、总结与扩展

本文通过三个递进的案例,覆盖了 UDP Socket 编程的核心知识点:

  1. 基础流程:创建 socket → 绑定地址(服务器)→ 收发数据(recvfrom/sendto)。
  2. 关键技术:地址转换函数、禁止拷贝、回调函数解耦、线程池、互斥锁。
  3. 最佳实践:服务器绑定INADDR_ANY、客户端不手动bind、多线程场景用inet_ntop。

扩展方向

        可靠性优化:UDP 本身不可靠,可通过 “序列号”“重传机制” 实现可靠 UDP(如 QUIC 协议)。

        性能优化:增大接收缓冲区、使用内存池减少内存分配、优化线程池大小。

        功能扩展:聊天室添加用户昵称、消息撤回、文件传输等功能。

【2026年华为暑期实习(AI)-4月29日-第一题- 选择题】(题目+思路+Java&C++&Python解析+在线测试) 本文摘要涵盖了机器学习和深度学习中的多个核心概念与技术,包括非负矩阵分解、条件数、MobileNet创新点、LDA降维、大模型评测指标、半精度训练、多任务学习、GPU存储层次、正则化、张量并行等。通过12道单选题和8道多选题的形式,系统性地考察了相关理论原理、应用场景及工程实践要点。题目设计注重概念准确性(如LDA目标函数)、技术细节(如Megatron-LM的并行策略)与实际应用(如FP16训练动机),并包含概率统计(二项分布)和数值计算(微分误差分析)等数学基础内容,全面检验读者对AI领域关键知识的掌握 阅读详情

相关推荐

GB/T 27930-2023协议升级与CANoe仿真测试实践

本文深入解析GB/T 27930-2023协议的核心升级,包括A/B类系统分类与EVIN码引入,并详细指导如何使用CANoe搭建仿真测试环境、编写CAPL脚本及利用DLL库进行一致性验证与故障注入测试,助力开发人员确保电动汽车充电通信的合规性与稳定性。

y7z8a的博客 538

理解 Linux 动态库加载:LD_LIBRARY_PATH

flyfish

二分掌柜的 331

达梦慢sql和统计信息收集

各计划节点的执行顺序为:缩进越多的越先执行,同样缩进的上面的先执行,下面的后执行,上下的优先级高于内外。在建立索引的时候,要考虑索引的最左匹配原则(在使用 SQL 语句时,如果 where 部分的条件不符合最左匹配原则,可能导致索引失效,或者不能完全发挥建立的索引的功效);执行计划是 SQL 语句的执行方式,由查询优化器为语句设计的执行方式,交给执行器去执行。生产系统中,对于频繁变动的数据表,多次执行计划可能会出现不一致的问题,这往往是由于统计信息不准导致的,索引定期收集统计信息就很有必要了。

m0_37680131的博客 1714

Linux 磁盘满了怎么排查?df / du / find 三步定位大文件

Linux 磁盘满排查三步法:df -h 定位分区、du 逐层下钻、find 按大小和时间抓大文件,附 inode 耗尽、文件已删未释放、du 与 df 不一致等经典坑的解决方案。

weixin_38291577的博客 218

图形学基础|球谐光照(Spherical Harmonics Lighting)

球谐光照(Spherical Harmonics Lighting) 文章目录球谐光照(Spherical Harmonics Lighting)一、前言二、球谐函数2.1 基函数2.2 投影与重建2.3 应用三、漫反射环境光3.1 IrradianceMap3.2 数学推导3.3 实践参考博文 一、前言 在学习图形渲染的过程中,一直對球谐函数(球谐光照)有一点了解,但没有亲手实现过。 关于球谐函数的数学概念是比较复杂的,笔者也实在无法完全理解,这篇文章只能从做东西的角度来说,公式是如何和代码进行对应的。

【黑键】 1万+

XS5018A TX ISP芯片同轴高清模拟输出图像处理芯片用户指南

当看门狗开启且系统异常,看门狗模块得不到喂狗信号时,自动产生看门狗复位信号,复位​​​​​​​。分量上与人眼对光谱的响应通常是有偏差的,通常通过一个色彩校正矩阵校正光谱响应的交叉效。但实际上由于电路的作用,复位模块对整个芯片及各个功能模块的复位进行统一管理,负责产生各个模块的软复位信。每个区域前景色可以在 16 个色表中查找,单个区域的前景色统一,各个区域的前。较弱的坏点被放大成刺眼的白点和明显的黑点,强烈推荐开启动态坏点校正功能。四种制式,通过寄存器配置,可以很方便的实现四种制式的切换、且可以对截止。

2403_88922712的博客 1959

Linux-iostat命令详解

Linux命令学习记录

m0xin的博客 97

W5500 TCP服务器状态机详解:从SOCK_CLOSED到SOCK_ESTABLISHED的完整流程

本文深入解析W5500芯片作为TCP服务器时的状态机转换流程,从SOCK_CLOSED到SOCK_ESTABLISHED的完整链路实践。通过硬件协议栈架构分析、状态机全流程拆解及异常处理方案,帮助开发者构建高可靠性的嵌入式网络服务端,优化TCP连接性能与稳定性。

Jerry的专栏 1165

【Linux】APT 本地 deb 安装与 `-f` 参数总结笔记

apt install ./xx.deb 可自动解析并安装依赖,无需 -f;-f 仅用于修复已损坏的依赖状态,非首次安装时必需。apt install -f 可单独运行,修复 broken dependencies。两者职责不同:前者是安装与依赖解决,后者是故障修复。完全离线时需提前准备依赖包或本地源。

hknaruto的专栏 209

Vivado2023.2与Vitis协同开发:从Hello World到BOOT.BIN的完整流程解析

本文详细解析了使用Vivado 2023.2与Vitis协同开发ZYNQ/MPSoC平台的完整流程。从环境安装、硬件设计、软件编写、在线调试到生成独立的BOOT.BIN启动文件,手把手指导开发者完成从Hello World到系统固化的全步骤,并提供了常见问题的避坑指南,帮助初学者快速掌握软硬件协同开发的核心技能。

weixin_29197051的博客 176

SAN存储与协议,了解SCSI、FC和ISCSI

在前面我们介绍SAN存储及iSCSI协议的时候了解到SAN存储其实是一个CS架构的系统。存储系统本身就是整个系统的服务端。以IP-SAN(也就是基于iSCSI的SAN存储)为例,其整体架构如图1所示。 图1 CS架构 对于存储系统来说,通常是由专业的存储厂商提供,比较知名的如EMC、NetAPP、IBM和日立等等。国内存储厂商也迅速崛起,比较知名的如华为、宏杉、浪潮和同友等等。另外,还有一些小的存储厂商,他们往往集中在某个领域,比如专注文件系统或者块存储等等。 除了专业的存储设备外,其实Linux

shuningzhang的专栏 2684

Linux 服务器性能排查手册(2026):CPU、内存、磁盘IO、网络四大瓶颈定位

iptables 四表五链模型、firewalld zone 信任级别、云安全组与主机防火墙的关系与优先级,避坑改了防火墙把自己挡在服务器外面的翻车事故。

2502_91473995的博客 287

QT+QGIS跨平台编译(基于Qt,采用一套代码、一套框架,完成QGIS第三方库、核心库、应用程序的跨平台编译)

QT+QGIS跨平台编译。 基于Qt,采用一套代码、一套框架,完成QGIS第三方库、核心库、应用程序的跨平台编译。

LuckyHanMo的博客 4373

Linux rm 误删文件恢复实操教程

Linux使用rm删除文件时,并不会直接抹除磁盘上的数据,只是移除文件索引,让系统不再识别这块空间。只要没有写入新数据,原有扇区内容就会保留。新文件写入才有可能覆盖这些区域,一旦覆盖就很难找回。恢复的本质就是扫描磁盘,找到这些还存在、但索引丢失的数据条目,再把它们导出保存到其他分区。越早停止写入,恢复成功的概率越高。

海天数据恢复-winhex数据恢复教程 224

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Linux 磁盘 100% 但 du 查不出东西,五种情况逐个排

监控告警说根分区 100%,登录上去 du -sh /* 加起来只有 12GB,df 显示 40GB 已用。差了快 30GB,du 找不到任何东西。这种情况基本都是"文件被删了但进程还开着",但也可能是别的原因。这篇按可能性排一下顺序。

2408_89646393的博客 163

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

Linux sudo

如果输出 → 无 sudo 权限 如果输出 → 拥有全部 sudo 权限 辅助验证命令 ⚠️ 注意:在 wheel 组 ≠ 一定能 sudo,必须 开启 Ubuntu/Debian: ✅ 重要:修改组之后,用户要重新登录才生效! 或者执行 临时生效 看到这行(没有 #注释)才生效: 方式 2:visudo 直接单独给用户授权(精细化权限) 找到 ,下面添加一行: 自带语法校验,写错会告警,不要直接 vim 改 ,写错直接 sudo 炸掉! 想免密 su

fengliyang_的博客 178

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Windows/Linux下强实时运动控制内核(二):基于Qt的Delta机械手的开发应用

强实时运动控制内核MotionRT750下Qt开发Delta机械手的完整开发流程。

weixin_57350300的博客 240

Linux 网络基础详解:IP、子网、路由与 ip、ss 命令实战

建库建表、增删改查、连接查询(内连左连右连)、聚合函数、权限控制 GRANT/REVOKE,每条命令配可运行示例,零基础也能跟着敲会 SQL 基础。

2502_91473995的博客 610

Linux、Shell、MySQL 测试卷逐题细讲(下篇:MySQL)

本文为Linux、Shell、MySQL测试卷下半段MySQL部分逐题讲解,包含填空、选择与SQL分析三类题型。填空题覆盖COUNT、HAVING、DISTINCT、事务、约束、索引等核心语法;选择题辨析存储过程、索引类型、ACID、存储引擎、用户管理等考点。SQL大题围绕员工、部门、职位、地点四张关联表,考察多表连接、分组聚合、自连接、相关子查询、NULL处理等高频写法,标注LEFT JOIN统计计数、NULL判断、子查询别名等易错坑点,同时梳理试卷OCR识别带来的干扰问题,重点提示SQL实操中的常见失误

dongziwen1的博客 181

Linux 下 OpenCode 接入自定义大模型 API 教程:以 DeepSeek-V4.1-Flash-1017 为例

本文介绍在Linux下通过Python脚本配置OpenCode接入自定义大模型API(以DeepSeek-V4.1-Flash-1017为例),实现永久化、非侵入式配置。通过修改~/.config/opencode/opencode.json文件,添加OpenAI兼容服务商BLSC,注册模型并设置APIKey。支持命令行测试、默认模型设定及安全权限控制,操作简单且不影响原有配置。适用于各类支持OpenAI接口的自定义模型服务,提升开发灵活性与安全性。

qq_15060477的博客 121

【Linux 命令实战(02):文件与目录操作,掌握 pwd、ls、cd 三个核心命令】

命令作用cd /进入根目录cd ~进入当前用户家目录cd返回当前用户家目录cd …返回上一级目录cd …/…返回上两级目录cd -返回上一次所在目录进入指定目录命令核心用途常用参数pwd查看当前目录-L、-Pls查看文件和目录cd切换目录…、~、-Linux 文件系统的基本结构。绝对路径与相对路径的区别。Tab 自动补全。Shell 通配符。使用&&组合命令。这篇文章主要解决的是如何在 Linux 文件系统中快速定位目录。

一些微不足道的见解、希望能帮助各位 192

Linux Day29——HTTP与网络排障

*实测**:`traceroute -n -m 8 8.8.8.8` 的输出:第 1 跳 `172.17.144.1`(本机网关)、第 2 跳 `10.32.0.1`、第 3 跳 `10.50.255.253`、第 4 跳 `* * *`、第 5 跳 `221.226.71.193`、第 6 跳 `* * *`、第 7、8 跳有地址但夹着星号。`ping` 和 `traceroute` 都靠它。**实测**:`ss -lta` 看到三行 `domain`,加 `-n` 后变成 `53`。

2503_94114317的博客 151

Linux的渲染与显示系统链路

Linux渲染与显示链路核心为DRM/KMS + EGL直连模式,绕过传统X11/Wayland中间层,实现低延迟、高效率的直接帧提交。相比X11的粗粒度权限控制与Wayland的合成开销,该方案链路短、资源消耗低,但仅支持单进程独占显示,缺乏窗口管理与系统级兜底。EGL作为跨平台桥梁,连接OpenGL ES与原生显示系统,通过Display、Surface、Context三要素构建渲染环境。内核层DRM管理GPU资源,KMS配置显示模式,用户态GDM负责登录会话,共同构成从硬件到应用的完整图形栈。

微小的鱼的博客 205

【Linux】进程间通信的基础概念及基于匿名管道的进程池

前面我们介绍了进程的创建、退出和等待,接下来看看进程之间怎样传递数据。这篇从匿名管道入手,再用管道把父子进程联系起来,写一个简单的进程池小demo。而且我把发行版从CentOS切换成Ubuntu了,主要是CentOS已经不维护了,可惜了。。。

qeen87的博客 316

Linux 启动流程与故障修复:GRUB、systemd 与救援模式实战

密钥登录配置替代密码、sshd_config 禁 root 禁密码改端口、fail2ban 自动封爆破 IP、端口改不改的权衡,服务器被扫描爆破时的完整加固方案。

2502_91473995的博客 459

芯参谋(88):Linux 嵌入式系统开发指南

本文为面向MPU/Linux平台的用户态系统层开发指南,聚焦启动链各环节的可测量、可判据化工程实践。涵盖交叉工具链一致性、Bootloader交接、根文件系统构建与瘦身、init/systemd服务编排、进程与IO模型、网络接入、调试可观测性、实时性优化、安全加固、电源管理及OTA升级等核心内容。强调“启动时间、镜像大小、内存峰值、断电恢复”等量化指标,提出四条读者路径:从零搭建、启动优化、线上问题排查、实时性实现。以Linux 6.1/LTS内核、glibc2.36/musl1.2、systemd252等

qq520131714的专栏 465
上一篇: 深入理解计算机网络基础:从协议分层到 Socket 编程预备
下一篇: TCP Socket 编程实战:从单连接到线程池,打造高可用网络服务
cpparf
博客等级 码龄2年 48粉丝 65原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值