linux笔记归纳13:Socket编程UDP

Socket编程UDP

目录

Socket编程UDP

一、接口介绍

1.1.socket函数

1.2.bind函数

1.3.inet_addr函数

1.4.recvfrom函数

1.5.sendto函数

1.6.本地环回

二、V1回显服务器

2.1.UdpServer.hpp

2.2.UdpServer.cc

2.3.UdpClient.cc

三、V2字典服务器

3.1.UdpServer.hpp

3.2.UdpServer.cc

3.3.UdpClient.cc

3.4.Dict.hpp

3.5.dictionary.txt

3.6.InetAddr.hpp

四、V3聊天服务器

4.1.UdpServer.hpp

4.2.UdpServer.cc

4.3.UdpClient.hpp

4.4.Route.hpp

4.5.InetAddr.hpp

4.6.ThreadPool.hpp

4.7.Thread.hpp

4.8.Cond.hpp

4.9.Mutex.hpp

4.10.Log.hpp

4.11.Makefile

五、补充内容

5.1.地址转换函数

5.2.remove_if

六、网络命令

6.1.netstat命令

6.2.Ping命令

6.3.pidof命令

七、Windows作为client访问Linux


一、接口介绍

1.1.socket函数

作用:创建一个通信的一端

参数1:域

  • 本地通信:AF_UNIX
  • 网络通信:AF_INET

参数2:套接字类型

  • UDP:SOCK_DGRAM(面向数据报)
  • TCP:SOCK_STREAM(面向字节流)

参数3:设置为0

返回值:

  • 返回成功:文件描述符
  • 返回失败:-1

1.2.bind函数

作用:给一个套接字(网络文件)绑定一个名字

参数1:文件描述符

参数2:sockaddr结构体(填充端口号和IP地址)

参数3:结构体大小

1.3.inet_addr函数

作用:将IP地址由字符串转为四字节

IP地址的格式转换

  • 字符串转四字节
  • 四字节转字符串

1.4.recvfrom函数

作用:收消息

参数1:文件描述符

参数2:缓冲区地址

参数3:缓冲区大小

参数4:0 - 阻塞式IO

对方不发送数据时,该进程一直在函数内阻塞(类似scanf)

服务端读取客户端的信息

  • 客户端发送的数据
  • 客户端的套接字信息

参数5:获取发送端的信息(输出型参数)

参数6:发送端结构体大小

返回值:实际收到的字节大小

1.5.sendto函数

作用:发消息

参数1:文件描述符(UDP socket 既可以读,也可以写,为全双工)

参数2:缓冲区地址

参数3:缓冲区大小

参数4:0 - 阻塞式IO

参数5:填充接收端的信息

参数6:接收端结构体大小

返回值:实际发送的字节大小

1.6.本地环回

客户端与服务器在同一台机器,客户端发送的数据不会被推送网络

而是在操作系统内部绕一圈,直接交给服务器,用来测试网络代码

  • 服务器绑定公网IP失败(公网IP没有配置到主机,无法被服务器绑定)
  • 服务器绑定本地环回,客户端用本地环回访问成功
  • 服务器绑定内网IP,客户端用内网IP访问成功
  • 服务端绑定内网IP,客户端用本地环回访问失败

如果显示地进行IP地址绑定,客户端访问时,必须使用服务器端绑定的IP地址信息

服务器不建议显示绑定特定的IP地址,不然只能接收这个IP地址对应的客户端消息

将服务器的IP地址设置为任意地址,就能被任意客户端(环回、内网、公网)访问

二、V1回显服务器

2.1.UdpServer.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"

using namespace LogModule;

using func_t = std::function<std::string(const std::string &)>;

const int defaultfd = -1;

class UdpServer
{
public:
    UdpServer(/*const std::string &ip,*/ uint16_t port, func_t func)
        : _sockfd(defaultfd), /* _ip(ip),*/ _port(port), _isrunning(false), _func(func)
    {
    }

    // 初始化服务器
    void Init()
    {
        // 1. 创建套接字(打开网络文件)
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << " socket success, sockfd : " << _sockfd;

        // 2. 显示绑定

        // 填充sockaddr_in结构体
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 初始化
        local.sin_family = AF_INET;   // 网络通信
        // 本地格式 → 网络序列
        local.sin_port = htons(_port); // 端口号
        // 点分十进制格式 → 四字节格式
        // 本地四字节格式 → 网络序列
        // 不建议绑定特定IP: local.sin_addr.s_addr = inet_addr(_ip.c_str()); 
        local.sin_addr.s_addr = INADDR_ANY; // 绑定任意IP地址才能被任意客户端访问

        // 服务端的IP和端口号必须众所周知并且不能轻易改变(类似: 110、120、119)
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(2);
        }
        LOG(LogLevel::INFO) << " bind success, sockfd : " << _sockfd;
    }

    // 启动服务器
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 缓冲区
            char buffer[1024];
            // 客户端信息
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            // 1. 收消息
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                // 网络序列 → 主机序列
                int peer_port = ntohs(peer.sin_port); // 客户端端口号
                // 网络序列 → 点分十进制
                std::string peer_ip = inet_ntoa(peer.sin_addr); // 客户端IP地址
                // 消息末尾加'\0'
                buffer[s] = 0;
                // 回调函数处理接收数据
                std::string result = _func(buffer);
                // 向客户端返回处理结果
                sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);
                // 消息内容 + 客户端信息
                LOG(LogLevel::DEBUG) << "[ " << peer_ip << " : " << peer_port << " ] " << buffer;
            }

            // 2. 发消息
            std::string echo_string = "server echo@ ";
            echo_string += buffer;
        }
    }

    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip;
    bool _isrunning;
    func_t _func; // 服务器回调函数
};

2.2.UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp"

std::string defaulthandler(const std::string &message)
{
    std::string hello = "hello, ";
    hello += message;
    return hello;
}

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

    // 获取IP
    // std::string ip = argv[1];

    // 获取端口
    uint16_t port = std::stoi(argv[1]);

    Enable_Console_Log_Strategy();
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(/*ip,*/ port, defaulthandler);
    usvr->Init();
    usvr->Start();

    return 0;
}

2.3.UdpClient.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    // 判断命令行参数
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }

    // 获取服务器IP地址
    std::string server_ip = argv[1];
    // 获取服务器端口号
    uint16_t server_port = std::stoi(argv[2]);

    // 1. 创建套接字(打开网络文件)
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 2;
    }

    // 2. 非显示绑定
    // 首次发送消息,OS会自动给客户端进行绑定
    // OS知道IP,并且随机生成端口号(类似: 普通的电话号码)
    // 为了避免客户端的端口发生冲突

    // 3. 填写服务器信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    while (true)
    {
        // 客户端向服务器发送消息
        std::string input;
        std::cout << "Please Enter# ";
        std::getline(std::cin, input);
        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
        (void)n;

        // 客户端从服务器接收消息
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if(m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }
    return 0;
}

三、V2字典服务器

3.1.UdpServer.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace LogModule;

using func_t = std::function<std::string(const std::string &, InetAddr &)>;

const int defaultfd = -1;

class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultfd), _port(port), _isrunning(false), _func(func)
    {
    }

    // 初始化服务器
    void Init()
    {
        // 1. 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << " socket success, sockfd : " << _sockfd;

        // 2. 显示绑定

        // 填充sockaddr_in结构体
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); // 初始化
        local.sin_family = AF_INET;   // 网络通信
        // 本地格式 → 网络序列
        local.sin_port = htons(_port); // 端口号
        // 点分十进制格式 → 四字节格式
        // 本地四字节格式 → 网络序列
        local.sin_addr.s_addr = INADDR_ANY; // 绑定任意IP地址才能被任意客户端访问

        // 服务端的IP和端口号必须众所周知并且不能轻易改变(类似: 110、120、119)
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(2);
        }
        LOG(LogLevel::INFO) << " bind success, sockfd : " << _sockfd;
    }

    // 启动服务器
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 缓冲区
            char buffer[1024];
            // 客户端信息
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);

            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                // 网络序列 → 主机序列
                // int peer_port = ntohs(peer.sin_port); // 客户端端口号
                // 网络序列 → 点分十进制
                // std::string peer_ip = inet_ntoa(peer.sin_addr); // 客户端IP地址
                InetAddr client(peer);
                // 消息末尾加'\0'
                buffer[s] = 0;
                // 回调函数处理接收数据
                std::string result = _func(buffer, client);
                // 向客户端返回处理结果
                sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);
            }
        }
    }

    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip;
    bool _isrunning;
    func_t _func; // 服务器回调函数
};

3.2.UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "UdpServer.hpp" // 网络通信
#include "Dict.hpp"      // 字典翻译

std::string defaulthandler(const std::string &message)
{
    std::string hello = "hello, ";
    hello += message;
    return hello;
}

// 翻译系统: 字符串当成英文单词, 把英文单词翻译成汉语

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

    // 获取端口
    uint16_t port = std::stoi(argv[1]);

    // 初始化日志
    Enable_Console_Log_Strategy();
    
    // 字典对象提供翻译功能
    Dict dict;
    dict.LoadDict(); // 加载字典

    // 服务对象提供通信功能
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, 
        [&dict](const std::string &word, InetAddr &cli)
        ->std::string
        {
            return dict.Translate(word, cli);
        });
    usvr->Init();
    usvr->Start();

    return 0;
}

3.3.UdpClient.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
    // 判断命令行参数
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }

    // 获取服务器IP地址
    std::string server_ip = argv[1];
    // 获取服务器端口号
    uint16_t server_port = std::stoi(argv[2]);

    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 2;
    }

    // 2. 非显示绑定

    // 3. 填写服务器信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));

    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    while (true)
    {
        // 客户端向服务器发送消息
        std::string input;
        std::cout << "Please Enter# ";
        std::getline(std::cin, input);
        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
        (void)n;

        // 客户端从服务器接收消息
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (m > 0)
        {
            buffer[m] = 0;
            std::cout << buffer << std::endl;
        }
    }
    return 0;
}

3.4.Dict.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <fstream>
#include <string>
#include <unordered_map>
#include "Log.hpp"
#include "InetAddr.hpp"

const std::string defaultdict = "./dictionary.txt";
const std::string sep = ": ";

using namespace LogModule;

class Dict
{
public:
    Dict(const std::string &path = defaultdict)
        : _dict_path(path)
    {
    }

    bool LoadDict()
    {
        // 打开文件输入流
        std::ifstream in(_dict_path);
        if (!in.is_open())
        {
            LOG(LogLevel::DEBUG) << "打开字典: " << _dict_path << " 错误";
            return false;
        }

        // 获取一行字符串
        std::string line;
        while (std::getline(in, line))
        {
            // apple: 苹果
            auto pos = line.find(sep);
            if (pos == std::string::npos)
            {
                LOG(LogLevel::WARNING) << "解析: " << line << " 失败";
                continue;
            }
            // 提取英文
            std::string english = line.substr(0, pos);
            // 提取中文
            std::string chinese = line.substr(pos + sep.size());
            if (english.empty() || chinese.empty())
            {
                LOG(LogLevel::WARNING) << "没有有效内容: " << line;
                continue;
            }

            // 建立映射, 插入哈希表
            _dict.insert(std::make_pair(english, chinese));
            LOG(LogLevel::DEBUG) << "加载: " << line;
        }

        // 关闭文件输入流
        in.close();
        return true;
    }

    std::string Translate(const std::string &word, InetAddr &client)
    {
        // 查找哈希表
        auto iter = _dict.find(word);
        if (iter == _dict.end())
        {
            LOG(LogLevel::DEBUG) << "进入翻译模块, " << client.Ip() << " : "<< client.Port() << "]# " << word << " -> None";
            return "None";
        }
        LOG(LogLevel::DEBUG) << "进入翻译模块, " << client.Ip() << " : "<< client.Port() << "]# " << word << " -> " << iter->second;
        return iter->second;
    }

    ~Dict()
    {
    }

private:
    std::string _dict_path;                             // 路径 + 文件名
    std::unordered_map<std::string, std::string> _dict; // 字典映射
};

3.5.dictionary.txt

cpp 复制代码
apple: 苹果
banana: ⾹蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
hello:
: 你好
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳


teacher: ⽼师
student: 学⽣
car: 汽⻋
bus: 公交⻋
love: 爱
hate: 恨
hello: 你好
goodbye: 再⻅
summer: 夏天
winter: 冬天

3.6.InetAddr.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 网络地址 <=> 主机地址

class InetAddr
{
public:
    InetAddr(struct sockaddr_in &addr)
        : _addr(addr)
    {
        // 网络序列 → 主机序列
        _port = ntohs(_addr.sin_port);
        // 网络序列 → 点分十进制
        _ip = inet_ntoa(_addr.sin_addr);
    }
    uint16_t Port()
    {
        return _port;
    }
    std::string Ip()
    {
        return _ip;
    }
    ~InetAddr()
    {
    }

private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

四、V3聊天服务器

4.1.UdpServer.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <functional>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace LogModule;

using func_t = std::function<void(int sockfd, const std::string &, InetAddr &)>;

const int defaultfd = -1;

class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        : _sockfd(defaultfd)
        , _port(port)
        , _isrunning(false)
        , _func(func)
    {}

    // 初始化服务器
    void Init()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error!";
            exit(1);
        }
        LOG(LogLevel::INFO) << " socket success, sockfd : " << _sockfd;

        // 显示绑定IP地址和端口号

        // 填充sockaddr_in结构体
        struct sockaddr_in local;           // 实例化
        bzero(&local, sizeof(local));       // 初始化
        local.sin_family = AF_INET;         // 网络通信
        local.sin_port = htons(_port);      // 端口号
        local.sin_addr.s_addr = INADDR_ANY; // IP地址 (绑定任意IP才能被任意客户端访问)

        // 服务端的IP和端口号必须众所周知并且不能轻易改变(类似: 110、120、119)
        int n = bind(_sockfd, (struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(2);
        }
        LOG(LogLevel::INFO) << " bind success, sockfd : " << _sockfd;
    }

    // 启动服务器
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 消息接收缓冲区
            char buffer[1024];
            // 客户端网络信息
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 接收客户端消息
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                // 客户端网络信息
                InetAddr client(peer);
                // 消息末尾加'\0'
                buffer[s] = 0;
                // 回调函数处理接收消息
                _func(_sockfd, buffer, client);
            }
        }
    }

    ~UdpServer()
    {}
private:
    int _sockfd;      // 网络文件描述符
    uint16_t _port;   // 端口号
    bool _isrunning;  // 运行标志位
    func_t _func;     // 服务器回调函数
};

4.2.UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "Route.hpp"
#include "ThreadPool.hpp"
#include "UdpServer.hpp"

using namespace ThreadPoolModule;

using task_t = std::function<void()>;

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

    // 服务器端口号
    uint16_t port = std::stoi(argv[1]);

    // 日志初始化
    Enable_Console_Log_Strategy();

    // 路由对象
    Route r;

    // 线程池对象
    auto tp = ThreadPool<task_t>::GetInstance();

    // 服务器对象(网络通信)
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port,
        /* UDP回调方法 */
        [&r, &tp](int sockfd, const std::string &message, InetAddr &peer)
        {
            // 路由对象中的消息路由方法
            auto t = std::bind(&Route::MessageRoute, &r, sockfd, message, peer);
            // 等价于:
            // auto t = [&r, sockfd, message, peer]()
            //          {
            //             r->MessageRoute(sockfd, message, peer)
            //          }

            // 将消息路由方法放入线程池
            tp->Enqueue(t);
        });

    usvr->Init(); // 初始化网络通信
    usvr->Start();// 开启网络通信

    return 0;
}

4.3.UdpClient.hpp

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Thread.hpp"

using namespace ThreadModlue;

int sockfd = 0;           // 客户端套接字
std::string server_ip;    // 服务器IP地址
uint16_t server_port = 0; // 服务器端口号
pthread_t id;             // 客户端接收消息的线程ID

void Recv()
{
    // 客户端从服务器接收消息
    while (true)
    {
        char buffer[1024];
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);

        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (m > 0)
        {
            buffer[m] = 0;
            std::cerr << buffer << std::endl; // fd = 2
        }
    }
}

void Send()
{
    // 填写服务器信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));

    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    const std::string online = "online";
    sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));

    // 客户端向服务器发送消息
    while (true)
    {
        std::string input;
        std::cout << "Please Enter# "; // fd = 1
        std::getline(std::cin, input); // fd = 0

        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
        (void)n;

        if (input == "QUIT")
        {
            // 取消客户端接收消息线程
            pthread_cancel(id);
            break;
        }
    }
}

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

    // 获取服务器IP地址
    server_ip = argv[1];
    // 获取服务器端口号
    server_port = std::stoi(argv[2]);

    // 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 2;
    }

    // 非显示绑定

    // 创建线程
    Thread recvr(Recv);
    Thread sender(Send);

    // 启动线程
    recvr.Start();
    sender.Start();

    // 获取客户端接收消息的线程ID
    id = recvr.Id();

    // 等待线程
    recvr.Join();
    sender.Join();

    // 如果没有发送消息, 接收消息会被阻塞
    // 使用多线程, 一个线程发送, 一个接收

    return 0;
}

4.4.Route.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Mutex.hpp"

using namespace LogModule;
using namespace MutexModule;

class Route
{
private:
    // 判断用户是否存在
    bool IsExist(InetAddr &peer)
    {
        for (auto &user : _online_user)
        {
            if (user == peer)
            {
                return true;
            }
        }
        return false;
    }

    // 新增用户
    void AddUser(InetAddr &peer)
    {
        LOG(LogLevel::INFO) << "新增一个在线用户: " << peer.StringAddr();
        _online_user.push_back(peer);
    }

    // 删除用户
    void DeleteUser(InetAddr &peer)
    {
        for (auto iter = _online_user.begin(); iter != _online_user.end(); iter++)
        {
            if (*iter == peer)
            {
                LOG(LogLevel::INFO) << "删除用户: " << peer.StringAddr() << " 成功";
                _online_user.erase(iter);
                break;
            }
        }
    }
public:
    Route()
    {}

    void MessageRoute(int sockfd, const std::string &message, InetAddr &peer)
    {
        // 加锁
        LockGuard lockguard(_mutex);
        
        // 首次发信息 → 用户登录
        if (!IsExist(peer))
        {
            AddUser(peer);
        }

        // 127.0.0.1:8080# 你好
        std::string send_message = peer.StringAddr() + "# " + message;

        // 将消息发给所有用户
        for (auto &user : _online_user)
        {
            sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr *)&user.NetAddr(), sizeof(user.NetAddr()));
        }

        // 用户退出消息
        if (message == "QUIT")
        {
            LOG(LogLevel::INFO) << "删除一个在线用户: " << peer.StringAddr();
            DeleteUser(peer);
        }
    }

    ~Route()
    {}
private:
    std::vector<InetAddr> _online_user; // 所有在线用户
    Mutex _mutex;                       // 互斥锁
};

4.5.InetAddr.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// 网络地址 <=> 主机地址

class InetAddr
{
public:
    InetAddr(struct sockaddr_in &addr)
        : _addr(addr)
    {
        // 网络序列 → 主机序列(端口号)
        _port = ntohs(_addr.sin_port);
        // 网络序列 → 点分十进制(IP地址)
        char ipbuffer[64];
        inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
        _ip = ipbuffer;
    }

    InetAddr(const std::string &ip, uint16_t port)
        : _ip(ip)
        , _port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        // 主机序列 → 网络序列(端口号)
        _addr.sin_port = htons(_port);
        // 点分十进制 → 网络序列(IP地址)
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
    }
    
    // 获取端口号
    uint16_t Port()
    {
        return _port;
    }

    // 获取IP地址
    std::string Ip()
    {
        return _ip;
    }
    
    // 获取网络地址结构体
    const struct sockaddr_in &NetAddr()
    {
        return _addr;
    }

    // 判断网路地址是否相同
    bool operator==(const InetAddr &addr)
    {
        return addr._ip == _ip && addr._port == _port;
    }

    // 获取IP地址 + 端口号
    std::string StringAddr()
    {
        return _ip + " : " + std::to_string(_port);
    }

    ~InetAddr()
    {}
private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

4.6.ThreadPool.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"

namespace ThreadPoolModule
{
    using namespace ThreadModlue;
    using namespace LogModule;
    using namespace MutexModule;
    using namespace CondModule;

    static const int gnum = 5;

    template <typename T>
    class ThreadPool
    {
    private:
        void WakeUpAllThread()
        {
            LockGuard lockfguard(_mutex);
            if (_sleepernum)
            {
                _cond.Broadcast();
            }
            LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
        }
        void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO) << "唤醒一个的休眠线程";
        }
        void Start()
        {
            if (_isrunning)
            {
                return;
            }
            _isrunning = true;
            for (auto &thread : _threads)
            {
                thread.Start();
                // LOG(LogLevel::INFO) << "start new thread success: " << thread.Name();
            }
        }
        ThreadPool(int num = gnum)
            : _num(num), _isrunning(false), _sleepernum(0)
        {
            for (int i = 0; i < num; i++)
            {
                _threads.emplace_back(
                    [this]()
                    {
                        HandlerTask();
                    });
                LOG(LogLevel::INFO) << "create new thread success";
            }
        }
        ThreadPool(const ThreadPool<T> &) = delete;
        ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

    public:
        // static修饰, 类不需要实例化就可以访问该函数
        static ThreadPool<T> *GetInstance()
        {
            // 双层判断, 提高获取单例的效率
            if (inc == nullptr)
            {
                // 加锁(防止多线程访问, 造成线程安全问题)
                LockGuard lockguard(_lock);
                LOG(LogLevel::DEBUG) << "获取单例...";
                if (inc == nullptr)
                {
                    LOG(LogLevel::DEBUG) << "首次使用单例, 创建...";
                    inc = new ThreadPool<T>();
                    inc->Start();
                }
            }
            return inc;
        }
        void Stop()
        {
            if (!_isrunning)
            {
                return;
            }
            _isrunning = false;
            // 唤醒所有线程
            WakeUpAllThread();
        }
        void Join()
        {
            for (auto &thread : _threads)
            {
                thread.Join();
            }
        }
        void HandlerTask()
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            while (true)
            {
                T t;
                {
                    LockGuard lockfguard(_mutex);
                    // 线程池退出时, 任务应该被取完
                    // 队列是否为空 && 线程池没有退出
                    while (_taskq.empty() && _isrunning)
                    {
                        _sleepernum++;
                        _cond.Wait(_mutex);
                        _sleepernum--;
                    }
                    // 内部线程被唤醒
                    if (!_isrunning && _taskq.empty())
                    {
                        LOG(LogLevel::INFO) << name << "退出了,线程池退出 && 任务队列为空";
                        break;
                    }
                    // 一定有任务
                    t = _taskq.front(); // 从任务队列中获取任务, 任务已经是线程私有
                    _taskq.pop();
                }
                t(); // 处理任务
            }
        }
        bool Enqueue(const T &in)
        {
            if (_isrunning)
            {
                LockGuard lockguard(_mutex);
                _taskq.push(in);
                if (_threads.size() == _sleepernum)
                {
                    WakeUpOne();
                }
                return true;
            }
            return false;
        }
        ~ThreadPool()
        {
        }

    private:
        std::vector<Thread> _threads; // 线程池
        int _num;                     // 线程个数
        std::queue<T> _taskq;         // 任务队列
        Cond _cond;                   // 条件变量
        Mutex _mutex;                 // 互斥锁
        bool _isrunning;              // 线程池状态
        int _sleepernum;              // 休眠线程数量

        static ThreadPool<T> *inc; // 单例指针
        static Mutex _lock;        // 全局锁
    };
    template <typename T>
    ThreadPool<T> *ThreadPool<T>::inc = nullptr;

    template <typename T>
    Mutex ThreadPool<T>::_lock;
};

4.7.Thread.hpp

cpp 复制代码
#ifndef _THREAD_H_
#define _THREAD_H_

#include <iostream>
#include <string>
#include <pthread.h>
#include <cstdio>
#include <cstring>
#include <functional>
#include <unistd.h>
#include "Log.hpp"

namespace ThreadModlue
{
    using namespace LogModule;
    static uint32_t number = 1;
    class Thread
    {
        using func_t = std::function<void()>;

    private:
        void EnableDetach()
        {
            _isdetach = true;
        }

        void EnableRunning()
        {
            _isrunning = true;
        }

        static void *Routine(void *args)
        {
            Thread *self = static_cast<Thread *>(args);

            self->EnableRunning();

            if (self->_isdetach)
            {
                self->Detach();
            }

            pthread_setname_np(self->_tid, self->_name.c_str());
            self->_func();

            return nullptr;
        }

    public:
        Thread(func_t func)
            : _tid(0), _isdetach(false), _isrunning(false), _res(nullptr), _func(func)
        {
            _name = "thread-" + std::to_string(number++);
        }

        void Detach()
        {
            if (_isdetach)
            {
                return;
            }
            if (_isrunning)
            {
                pthread_detach(_tid);
            }
            EnableDetach();
        }

        std::string Name()
        {
            return _name;
        }

        bool Start()
        {
            if (_isrunning)
            {
                return false;
            }

            // 创建新线程
            int n = pthread_create(&_tid, nullptr, Routine, this);
            if (n != 0)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        bool Stop()
        {
            if (_isrunning)
            {
                int n = pthread_cancel(_tid);
                if (n != 0)
                {
                    return false;
                }
                else
                {
                    _isrunning = false;
                    return true;
                }
            }
            return false;
        }

        void Join()
        {
            if (_isdetach)
            {
                return;
            }

            int n = pthread_join(_tid, &_res);
            if (n != 0)
            {
                LOG(LogLevel::DEBUG) << "Join线程失败";
            }
            else
            {
                LOG(LogLevel::DEBUG) << "Join线程成功";
            }
        }

        pthread_t Id()
        {
            return _tid;
        }
        
        ~Thread()
        {
        }

    private:
        pthread_t _tid;    // 线程ID
        std::string _name; // 线程名称
        bool _isdetach;    // 线程是否分离
        bool _isrunning;   // 线程是否运行
        void *_res;        // 新线程返回值
        func_t _func;      // 新线程的方法
    };
}

#endif

4.8.Cond.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <pthread.h>
#include "Mutex.hpp"

using namespace MutexModule;

namespace CondModule
{
    class Cond
    {
    public:
        Cond()
        {
            pthread_cond_init(&_cond, nullptr);
        }
        void Wait(Mutex &mutex)
        {
            int n = pthread_cond_wait(&_cond, mutex.Get());
            (void)n;
        }
        void Signal()
        {
            //唤醒在条件变量下等待的一个线程
            int n = pthread_cond_signal(&_cond);
            (void)n;
        }
        void Broadcast()
        {
            //唤醒在条件变量下等待的所有线程
             int n = pthread_cond_broadcast(&_cond);
             (void)n;
        }
        ~Cond()
        {
            pthread_cond_destroy(&_cond);
        }
    private:
        pthread_cond_t _cond;
    };
};

4.9.Mutex.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <pthread.h>

namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&_mutex, nullptr);
        }
        void Lock()
        {
            int n = pthread_mutex_lock(&_mutex);
            (void)n;
        }
        void unLock()
        {
            int n = pthread_mutex_unlock(&_mutex);
            (void)n;
        }
        ~Mutex()
        {
            pthread_mutex_destroy(&_mutex);
        }
        pthread_mutex_t *Get()
        {
            return &_mutex;
        }
    private:
        pthread_mutex_t _mutex;
    };

    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex)
            :_mutex(mutex)
        {
            _mutex.Lock();
        }
        ~LockGuard()
        {
            _mutex.unLock();
        }
    private:
        Mutex &_mutex;
    };
};

4.10.Log.hpp

cpp 复制代码
#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <string>
#include <filesystem> // C++17中 文件操作的相关封装
#include <fstream>
#include "Mutex.hpp"
#include <memory>
#include <unistd.h>
#include <sstream>
#include <ctime>
#include <cstdio>

namespace LogModule
{
    using namespace MutexModule;
    const std::string gsep = "\r\n";

    // 2. 刷新策略(策略模式: C++多态)

    // 策略基类
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;
        virtual void SyncLog(const std::string &message) = 0;
    };

    // 策略a: 显示器打印
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        ConsoleLogStrategy()
        {
        }

        void SyncLog(const std::string &message) override
        {
            // 加锁
            LockGuard lockguard(_mutex);

            // 打印日志
            std::cout << message << gsep;
        }

        ~ConsoleLogStrategy()
        {
        }

    private:
        Mutex _mutex;
    };

    // 缺省参数
    const std::string defaultpath = "./log";
    const std::string defaultfile = "my.log";

    // 策略b: 指定文件写入
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const std::string &path = defaultpath, const std::string &file = defaultfile)
            : _path(path), _file(file)
        {
            // 加锁
            LockGuard lockguard(_mutex);
            // 如果当前路径存在
            if (std::filesystem::exists(_path))
            {
                return;
            }
            // 如果当前路径不存在
            try
            {
                std::filesystem::create_directories(_path);
            }
            catch (const std::filesystem::filesystem_error &e)
            {
                std::cerr << e.what() << "\n";
            }
        }

        void SyncLog(const std::string &message) override
        {
            // 加锁
            LockGuard lockguard(_mutex);
            // "./log" + "/" + "my.log"
            std::string filename = _path + (_path.back() == '/' ? "" : "/") + _file;
            // 以追加的方式打开文件
            std::ofstream out(filename, std::ios::app);
            if (!out.is_open())
            {
                return;
            }
            // 写入日志
            out << message << gsep;
            // 关闭文件
            out.close();
        }

        ~FileLogStrategy()
        {
        }

    private:
        std::string _path; // 日志文件所在路径
        std::string _file; // 日志文件名称

        Mutex _mutex; // 互斥锁
    };

    // 形成完整日志 && 根据策略选择不同刷新方式

    // 1. 形成日志等级
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string LeveltoStr(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKNOW";
        }
    }

    // 2. 获取时间方法
    std::string GetTimeStamp()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr, &curr_tm);
        char timebuffer[128];
        snprintf(timebuffer, sizeof(timebuffer), "%4d-%02d-%02d %02d-%02d-%02d",
                 curr_tm.tm_year + 1900, curr_tm.tm_mon + 1, curr_tm.tm_mday,
                 curr_tm.tm_hour, curr_tm.tm_min, curr_tm.tm_sec);
        return timebuffer;
    }

    // 日志类
    class Logger
    {
    public:
        Logger()
        {
            // 默认使用显示器
            EnableConsoleLogStrategy();
        }

        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }

        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }

        // 内部类: 表示未来的一条日志
        class LogMessage
        {
        public:
            LogMessage(LogLevel &level, std::string &src_name, int line_number, Logger &logger)
                : _curr_time(GetTimeStamp()), _level(level), _pid(getpid()), _src_name(src_name), _line_number(line_number), _logger(logger)
            {
                // 日志左半部分
                std::stringstream ss;
                ss << "[" << _curr_time << "] "
                   << "[" << LeveltoStr(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _src_name << "] "
                   << "[" << _line_number << "] "
                   << "- ";
                _loginfo = ss.str();
            }
            template <typename T>
            LogMessage &operator<<(const T &info)
            {
                // 日志右半部分
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                if (_logger._fflush_strategy)
                {
                    _logger._fflush_strategy->SyncLog(_loginfo);
                }
            }

        private:
            std::string _curr_time; // 时间
            LogLevel _level;        // 等级
            pid_t _pid;             // 进程PID
            std::string _src_name;  // 文件名
            int _line_number;       // 行号
            std::string _loginfo;   // 一条完整的日志信息
            Logger &_logger;
        };

        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            return LogMessage(level, name, line, *this);
        }

        ~Logger()
        {
        }

    private:
        std::unique_ptr<LogStrategy> _fflush_strategy;
    };

    // 全局日志对象
    Logger logger;

// 使用宏简化用户操作, 获取文件名和行号
#define LOG(level) logger(level, __FILE__, __LINE__)
#define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()
#define Enbale_File_Log_Strategy() logger.EnableFileLogStrategy()
};

#endif

4.11.Makefile

cpp 复制代码
.PHONY:all
all:udpclient udpserver

udpclient:UdpClient.cc
	g++ -o $@ $^ -std=c++17 #-static

udpserver:UdpServer.cc
	g++ -o $@ $^ -std=c++17 

.PHONY:clean
clean:
	rm -f udpclient udpserver

五、补充内容

5.1.地址转换函数

  • 字符串转in_addr函数

pton(process to net)函数

cpp 复制代码
InetAddr(const std::string &ip, uint16_t port)
    : _ip(ip), _port(port)
{
    memset(&_addr, 0, sizeof(_addr));
    _addr.sin_family = AF_INET;
    // 主机序列 → 网络序列(端口号)
    _addr.sin_port = htons(_port);
    // 点分十进制 → 网络序列(IP地址)
    inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
}
  • in_addr转字符串函数

ntoa(net to ascii)函数

cpp 复制代码
InetAddr(struct sockaddr_in &addr)
    : _addr(addr)
{
    // 网络序列 → 主机序列
    _port = ntohs(_addr.sin_port);
    // 网络序列 → 点分十进制
    _ip = inet_ntoa(_addr.sin_addr);
}

注:inet_ntoa函数将返回结果存放在静态存储区,多线程多次调用会出现线程安全问题

ntop(net to process)函数

cpp 复制代码
InetAddr(struct sockaddr_in &addr)
    : _addr(addr)
{
    // 网络序列 → 主机序列(端口号)
    _port = ntohs(_addr.sin_port);
    // 网络序列 → 点分十进制(IP地址)
    char ipbuffer[64];
    inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(ipbuffer));
    _ip = ipbuffer;
}

5.2.remove_if

cpp 复制代码
#include <iostream>
#include <list>
#include <memory>
#include <algorithm>
int main()
{
    std::list<std::shared_ptr<int>> ls;
    ls.push_back(std::make_shared<int>(1));
    ls.push_back(std::make_shared<int>(2));
    ls.push_back(std::make_shared<int>(3));
    ls.push_back(std::make_shared<int>(4));
    ls.push_back(std::make_shared<int>(4));
    ls.push_back(std::make_shared<int>(4));
    ls.push_back(std::make_shared<int>(5));
    ls.push_back(std::make_shared<int>(6));
    for (auto &v : ls)
    {
        std::cout << *v << std::endl;
    }
    std::cout << "aa: " << ls.size() << std::endl;
    std::cout << "\n";
    // int a = 3;
    int a = 4;
    auto pos = remove_if(ls.begin(), ls.end(), [&a](const std::shared_ptr<int> &elem) -> bool
                         { return a == *elem; });
    ls.erase(pos, ls.end());
    std::cout << "aa: " << ls.size() << std::endl;
    for (auto &v : ls)
    {
        std::cout << *v << std::endl;
    }
    return 0;
}

六、网络命令

6.1.netstat命令

作用:查看网络状态

常用选项:

  • -a:显示所有
  • -l:只显示处于listen状态下的网络服务
  • -u:UDP协议
  • -t:TCP协议
  • -n:数字化数据
  • -p:携带进程相关信息

6.2.Ping命令

作用:检测网络连通性

常用选项:

-c:Ping的次数

6.3.pidof命令

作用:查看服务器的进程ID

七、Windows作为client访问Linux

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <thread>
#include <string>
#include <cstdlib>
#include <WinSock2.h> // windows的网络通信
#include <Windows.h>

#pragma warning(disable : 4996) // 屏蔽报错号

#pragma comment(lib, "ws2_32.lib") // windows的socket静态库

std::string serverip = "101.35.8.119"; // 填写你的云服务器ip
uint16_t serverport = 8080; // 填写你的云服务开放的端口号

int main()
{
	WSADATA wsd;
	// 初始化windows的socket静态库
	WSAStartup(MAKEWORD(2, 2), &wsd);

	struct sockaddr_in server;
	memset(&server, 0, sizeof(server));
	server.sin_family = AF_INET;
	// 主机序列 → 网络序列(端口号)
	server.sin_port = htons(serverport);
	// 点分十进制 → 网络序列(IP地址)
	server.sin_addr.s_addr = inet_addr(serverip.c_str());

	// 创建套接字
	SOCKET sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd == SOCKET_ERROR)
	{
		std::cout << "socker error" << std::endl;
		return 1;
	}

	std::string message;
	char buffer[1024];
	while (true)
	{
		// 给服务器发送消息
		std::cout << "Please Enter@ ";
		std::getline(std::cin, message);
		if (message.empty())
		{
			continue;
		}
		sendto(sockfd, message.c_str(), (int)message.size(), 0, (struct sockaddr*)&server, sizeof(server));
		
		// 从服务器接收消息
		struct sockaddr_in temp;
		int len = sizeof(temp);
		int s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
		if (s > 0)
		{
			buffer[s] = 0;
			std::cout << buffer << std::endl;
		}
	}

	// 关闭套接字
	closesocket(sockfd);
	// 清理windows的socket静态库
	WSACleanup();
	return 0;
}
相关推荐
bksczm1 小时前
Linux之应用层层协议认知(TCP协议编程模块化demo) —— 兼论守护进程机制
linux·运维·服务器
jiecy1 小时前
同在专网内,为什么有的通服务器有的不通?
运维·服务器
艾莉丝努力练剑1 小时前
【MYSQL】MYSQL学习的一大重点:视图
android·服务器·数据库·学习·mysql·面试·视图
三江番长 陀舍古帝1 小时前
细说SQL Server中的加密
运维·服务器·数据库
疯狂打码的少年1 小时前
【面向对象】设计模式概述(要素、分类:创建型/结构型/行为型)
笔记
爱和冰阔落1 小时前
【Linux】从匿名管道到进程池:任务派发、fd 继承 Bug 与完整实现
android·linux·运维·c++
懿路向前2 小时前
【HarmonyOS学习笔记】2026-08-05 | 端插件卡片绑定与跨上下文判断
笔记·学习·ai编程·harmonyos
Ivan CloudBay2 小时前
网站更新时为什么会进入维护模式?
运维·服务器·云服务器
DFT计算杂谈6 小时前
服务器通过pip安装Kimi Code CLI 和使用
运维·服务器·pip