当前位置: 首页 > news >正文

Linux 上手 UDP Socket 程序编写(含完整具体demo)

文章目录

  • Linux 上手 UDP Socket 程序编写(含完整具体demo)
    • 1. 核心技术点
    • 2. UDP 通信模型速览
    • 3. 构建服务端:步骤与 API
      • 3.1 创建套接字 socket
      • 3.2 填充地址并绑定 bind
      • 3.3 循环收包 recvfrom → 处理 → 回包 sendto
      • 3.4 注入业务回调(示例)
    • 4. 构建客户端:步骤与 API
      • 4.1 创建套接字 socket
      • 4.2 组织对端地址、发送 sendto
      • 4.3 接收回复 recvfrom
    • 5. 字典 Demo 的实现(作为业务回调接入)
      • 5.1 字典文件与数据结构
      • 5.2 加载流程:读取 → 切分 → 裁剪 → 写入 map
      • 5.3 查询接口与不命中返回
      • 5.4 将业务回调注入服务端
    • 6. 模块设计与职责划分
    • 7. 关键函数与细节清单
    • 8. 测试与运行
    • 9. 常见坑位速查
    • 10. 结语
    • 附录:完整代码
        • 文件: `test/Dict.hpp`
        • 文件: `test/MyUdpServer.hpp`
        • 文件: `test/MyUdpServer.cc`
        • 文件: `test/MyUdpClient.cc`
        • 文件: `test/Makefile`
        • 文件: `test/Log.hpp`
        • 文件: `test/Mutex.hpp`
        • 文件: `test/dictionary.txt`

Linux 上手 UDP Socket 程序编写(含完整具体demo)

本文核心讲解“如何用 UDP socket 进行通信”,包括每一步该调用哪些系统调用、参数怎么填、如何设计服务器和客户端的模块与边界、常见坑点与增强方式。字典查询只是业务载体,重点在 UDP 的通信。

1. 核心技术点

  • UDP 的通信模型与适用场景
  • 服务端/客户端从 0 到 1 的完整步骤
  • 关键系统调用的使用细节:socketbindsendtorecvfrom
  • 地址结构 sockaddr_in、字节序转换、IP 转换函数
  • 缓冲区处理、错误处理、阻塞与非阻塞、超时与重试
  • 可扩展的架构设计与实战建议

2. UDP 通信模型速览

  • 无连接(connectionless):没有三次握手,直接收发。开销低、时延小。
  • 不可靠(unreliable):可能丢包、乱序、重复。应用层需要容错(超时、重试、去重)。
  • 保留消息边界(message-oriented):一次 sendto 对应一次 recvfrom,天然“报文”语义,不用像 TCP 处理粘包/拆包。
  • 适合“体量小、请求-应答、可容忍丢包”的场景。本示例查询一次、返回一次,正合适。

3. 构建服务端:步骤与 API

3.1 创建套接字 socket

文件: test/MyUdpServer.hpp

void Init(){//1.创建套接字_sockfd = socket(AF_INET, SOCK_DGRAM, 0);if(_sockfd < 0){LOG(LogLevel::FATAL) << "socket error" ;exit(1);}LOG(LogLevel::INFO) << "socket create success, sockfd: " << _sockfd ;
  • 重要说明:

    • domain: AF_INET(IPv4);如需 IPv6 用 AF_INET6
    • type: SOCK_DGRAM(UDP 数据报);TCP 则是 SOCK_STREAM
    • protocol: 一般填 0,由内核根据 type 推断。
    • 返回值: 成功为非负 fd;失败 -1 并设置 errno(如 EMFILE/ENFILE 资源耗尽)。
    • UDP 默认阻塞模式;如需非阻塞,后续用 fcntl 设置 O_NONBLOCK
  • 可选优化(快速重启端口):

int yes = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
  • SO_REUSEADDR:允许在 TIME_WAIT 等场景快速复用端口,对 UDP 也常用;须在 bind 前设置。

3.2 填充地址并绑定 bind

文件: test/MyUdpServer.hpp

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;

文件: test/MyUdpServer.hpp

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;
  • 重要说明:
    • sockaddr_in 字段:sin_family=AF_INETsin_port 必须 htonssin_addr.s_addr 可设 INADDR_ANY(所有本地网卡)。
    • htons/ntohs 端口字节序转换;IP 推荐用 inet_pton(现代、安全),此处演示 INADDR_ANY
    • bind 将本地地址:端口与套接字关联,服务端必须显式 bind 固定端口。
    • 常见错误:EADDRINUSE 端口占用、EACCES 低号端口权限不足。

3.3 循环收包 recvfrom → 处理 → 回包 sendto

文件: test/MyUdpServer.hpp

while(_isrunning){char buffer[1024];//1.不断收(读)消息ssize_t rec_msg = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr* )&peer, &len);if(rec_msg > 0){int peer_port = ntohs(peer.sin_port);std::string peer_ip = inet_ntoa(peer.sin_addr);buffer[rec_msg] = 0;std::string result = _func(buffer);LOG(LogLevel::DEBUG) << "buffer:" << buffer << " from " << peer_ip << ":" << peer_port;// 2.发消息ssize_t snd_sz = sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);}
}
  • 重要说明(recvfrom):

    • 原型:ssize_t recvfrom(int fd, void* buf, size_t len, int flags, struct sockaddr* src, socklen_t* addrlen)
    • 阻塞读:无数据会阻塞(可设置 SO_RCVTIMEO 或改非阻塞)。
    • 返回值:本次数据报长度;若 len 小于数据报,超出部分被丢弃(UDP 保持报文边界,不会“分多次收”)。
    • sizeof(buffer)-1:为字符串结尾 '\0' 预留一字节。
    • peer:回填对端地址,后续回包直接使用。
    • inet_ntoa 返回静态缓冲区的字符串,非线程安全;多线程推荐 inet_ntop
  • 重要说明(sendto):

    • 原型:ssize_t sendto(int fd, const void* buf, size_t len, int flags, const struct sockaddr* dst, socklen_t addrlen)
    • UDP 要么整报成功,要么失败(不会部分发送)。
    • 典型错误:EAGAIN(非阻塞且缓冲区满)、EMSGSIZE(报文过大)、ENETUNREACH/EHOSTUNREACH(网络不可达)。
    • flags 常为 0;除非需要 MSG_DONTWAIT 等特殊行为。
  • 业务解耦:网络层只负责“收/发”,实际处理交给回调 _func

3.4 注入业务回调(示例)

文件: test/MyUdpServer.cc

std::unique_ptr<MyUdpServer> usvr = std::make_unique<MyUdpServer>(port, [&dict](const std::string &message){return dict.Translate(message);
});
usvr->Init();
usvr->Start();
  • 重要说明:
    • 以函数对象注入业务逻辑(字典翻译/回显/计算器均可),不侵入网络层。
    • 更换业务=替换回调,网络收发与生命周期管理不变。

4. 构建客户端:步骤与 API

4.1 创建套接字 socket

文件: test/MyUdpClient.cc

int sockfd = socket (AF_INET,SOCK_DGRAM,0);
if(sockfd < 0){std::cerr << "socket error" << std::endl;return 2;
}
  • 重要说明:
    • 客户端通常无需显式 bind;首次 sendto 时内核自动分配临时端口(ephemeral port)。
    • 如需固定本地端口/网卡(多播/防火墙策略等),可主动 bind 到指定本地地址:端口。

4.2 组织对端地址、发送 sendto

文件: test/MyUdpClient.cc

std::string input;
std::cout << "Please Enter# ";
std::getline(std::cin,input);sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(atoi(argv[2]));
dest_addr.sin_addr.s_addr = inet_addr(argv[1]);int send_size = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr));
  • 重要说明:
    • dest_addr 必须是“服务端”的地址;客户端不需要 connect 也能直接 sendto
    • 发送长度必须用实际数据长度:input.size(),绝不要用 sizeof(std::string)(那是类型大小)。
    • inet_addr 将点分十进制 IP 转为网络序整数;更推荐 inet_pton(AF_INET, argv[1], &dest_addr.sin_addr)(更健壮)。

4.3 接收回复 recvfrom

文件: test/MyUdpClient.cc

char recv_buf[1024];
sockaddr_in peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);
int recv_size = recvfrom(sockfd,recv_buf,sizeof(recv_buf)-1,0,(struct sockaddr*)&peer_addr,&peer_addr_len);
if(recv_size > 0){recv_buf[recv_size] = '\0';std::cout << "server# " << recv_buf << std::endl;
}
  • 重要说明:
    • 仍然要用返回值补 '\0',避免打印脏数据。
    • peer_addr 给出实际回应者(在多播/任播/多服务端测试时很有用)。
    • 想要接收超时,可 setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, ...);想非阻塞,用 fcntl + select/epoll

5. 字典 Demo 的实现(作为业务回调接入)

本节只聚焦“字典查询”如何作为业务层接入到 UDP 通信中,帮助你理解网络层与应用层的协作关系。

5.1 字典文件与数据结构

  • 文件:test/dictionary.txt,每行 key:value
  • 结构:unordered_map<string,string>,平均 O(1) 查询。
  • 默认路径:
    文件: test/Dict.hpp
const std::string default_dict_path = "./dictionary.txt";

5.2 加载流程:读取 → 切分 → 裁剪 → 写入 map

文件: test/Dict.hpp

bool Load(){std::ifstream ifs(_path);if(!ifs.is_open()){return false;}std::string line;while(std::getline(ifs, line)){// 忽略空行if(line.empty()) continue;// 查找分隔符std::size_t pos = line.find(':');if(pos == std::string::npos) continue;std::string key = line.substr(0, pos);std::string value = line.substr(pos + 1);// 去除首尾空白auto trim = [](std::string &s){auto not_space = [](int ch){ return !std::isspace(ch); };s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space));s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end());};trim(key);trim(value);if(!key.empty() && !value.empty()){_dict[key] = value;}}return true;
}
  • 重要说明:
    • std::getline 每次读取一行(不含换行符),适合按行解析。
    • find(':') 只取第一个冒号分隔:支持 定义:内容:附注 这类行时会把后缀全部并入 value
    • substr(pos + 1) 从冒号右侧取值(见当前文件第 31 行),避免把冒号包含进 value
    • trim 使用 std::isspace 去除首尾空白,容忍文件中多余空格/Tab;如编译器报未声明,请 #include <cctype>
    • unordered_map 提供均摊 O(1) 插入/查询;若需有序遍历可换 std::map

5.3 查询接口与不命中返回

文件: test/Dict.hpp

std::string Translate(const std::string &word){auto it = _dict.find(word);if(it != _dict.end()){return it->second;}return "not found";
}
  • 重要说明:
    • 完全匹配、大小写敏感;可在装载或查询时统一 tolower 实现不敏感匹配。
    • 未命中返回 "not found",服务端原样返回给客户端。

5.4 将业务回调注入服务端

文件: test/MyUdpServer.cc

std::unique_ptr<MyUdpServer> usvr = std::make_unique<MyUdpServer>(port, [&dict](const std::string &message){return dict.Translate(message);
});
usvr->Init();
usvr->Start();
  • 重要说明:
    • 这体现“网络 I/O”与“业务处理”分层:更换业务无需触碰 UDP 细节。
    • 回调签名 std::function<std::string(const std::string&)>,输入/输出均为文本,便于替换其他业务。

6. 模块设计与职责划分

  • 网络层(UDP 收发):
    • 负责 socket/bind/recvfrom/sendto,不涉及业务。
    • 把“收到的消息”和“对端地址”传给业务层处理。
  • 业务层(翻译/计算/路由):
    • 输入消息字符串,输出要回的字符串。
    • 在本示例中以回调形式注入网络层,解耦清晰。

文件: test/MyUdpServer.cc

std::unique_ptr<MyUdpServer> usvr = std::make_unique<MyUdpServer>(port, [&dict](const std::string &message){return dict.Translate(message);
});
usvr->Init();
usvr->Start();

7. 关键函数与细节清单

  • socket: socket(AF_INET, SOCK_DGRAM, 0);失败返回 -1,查 errno
  • bind: 绑定本地地址:端口;失败多为端口占用或权限问题。
  • sendto/recvfrom: 保留报文边界;UDP 不会“部分发送”,要么整报成功要么失败。
  • htons/ntohs、htonl/ntohl: 端口/地址的主机序 ↔ 网络序。
  • inet_addr/inet_ntoa(或 inet_pton/inet_ntop): 文本 ↔ 网络序地址;inet_ntoa 非线程安全。
  • bzero/memset: 清零结构体,避免未初始化字段带来未定义行为。
  • 超时: setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, ...) 设置接收超时(阻塞模式下生效)。
  • 非阻塞: fcntl(fd, F_SETFL, O_NONBLOCK) 配合 select/poll/epoll

示例:接收超时 2s(阻塞模式)

timeval tv{.tv_sec = 2, .tv_usec = 0};
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));

8. 测试与运行

  • 编译:
cd /root/linux/test
make
  • 启动服务端(如 8080):
./myudpserver 8080
  • 启动客户端并交互:
./myudpclient 127.0.0.1 8080
  • 输入 apple,观察请求-应答链路(验证 UDP 收发、长度、字节序、地址填充)。

9. 常见坑位速查

  • 发送长度错用 sizeof(std::string) → 正确是 input.size()
  • 忘记 htons/ntohs,导致端口错乱
  • recvfrom 后未补 \0,输出脏字符
  • 客户端不 bind 正常;首发 sendto 自动绑定临时端口
  • 字典只是业务层;用回显也能完整验证 UDP 收发

10. 结语

本文把 UDP 的核心系统调用与工程实践一网打尽,示例中的“字典查询”只是把回调接上,让你更直观看见“收到一条请求 → 处理 → 回一条响应”的完整链路。掌握这些 API 的使用细节与常见坑,你就可以自信地实现自己的 UDP 小服务,并按需扩展到更复杂的场景。

  • 核心流程:socket →(服务端 bind)→ sendto/recvfrom
  • 关键细节:正确填 sockaddr_in、字节序转换、长度与缓冲区处理
  • 工程建议:业务与网络解耦、非阻塞与超时机制、应用层容错与可观测性

附录:完整代码

文件: test/Dict.hpp
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<map>
#include<unordered_map>
#include<algorithm>
#include<fstream>const std::string default_dict_path = "./dictionary.txt";class Dict
{
public:Dict(const std::string &path = default_dict_path):_path(path){}bool Load(){std::ifstream ifs(_path);if(!ifs.is_open()){return false;}std::string line;while(std::getline(ifs, line)){// 忽略空行if(line.empty()) continue;// 查找分隔符std::size_t pos = line.find(':');if(pos == std::string::npos) continue;std::string key = line.substr(0, pos);std::string value = line.substr(pos + 1);// 去除首尾空白auto trim = [](std::string &s){auto not_space = [](int ch){ return !std::isspace(ch); };s.erase(s.begin(), std::find_if(s.begin(), s.end(), not_space));s.erase(std::find_if(s.rbegin(), s.rend(), not_space).base(), s.end());};trim(key);trim(value);if(!key.empty() && !value.empty()){_dict[key] = value;}}return true;}std::string Translate(const std::string &word){auto it = _dict.find(word);if(it != _dict.end()){return it->second;}return "not found";}private:std::string _path;std::unordered_map<std::string, std::string> _dict;
};
文件: test/MyUdpServer.hpp
#pragma once#include <iostream>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string>
#include <strings.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include <functional>using namespace LogModule;
using func_t = std::function<std::string(const std::string&)>;const int defaultfd = -1;class MyUdpServer{
public:MyUdpServer(uint16_t port, func_t func):_sockfd(defaultfd),//   _ip(ip),_port(port),_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 create success, sockfd: " << _sockfd ;//绑定socket信息,ip和端口//填充sockaddr_in结构体struct sockaddr_in local;bzero(&local,sizeof(local)); //一个用于内存初始化的函数,主要功能是将指定内存区域的所有字节设置为 0。local.sin_family = AF_INET;//将端口从本地格式转换成网络序列local.sin_port = htons(_port);//将IP从string类型转化为4个字节存储,再转化为网络序列://转成in_addr_t类型 inet_addr (const char *cp)// local.sin_addr.s_addr = inet_addr(_ip.c_str());// 不用上面的绑定具体ip,使用INADDR_ANY,表示绑定所有网卡local.sin_addr.s_addr = INADDR_ANY;//绑定套接字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(){//因为udp不用管链接,一直管收发就好了_isrunning = true;struct sockaddr_in peer;socklen_t len = sizeof(peer);while(_isrunning){char buffer[1024];//1.不断收(读)消息ssize_t rec_msg = recvfrom(_sockfd, buffer, sizeof(buffer)-1, 0, (struct sockaddr* )&peer, &len);//sizeof(buffer)-1 是为了留一个位置将来添加 \nif(rec_msg > 0){//说明收到了消息int peer_port = ntohs(peer.sin_port);std::string peer_ip = inet_ntoa(peer.sin_addr);buffer[rec_msg] = 0;std::string result = _func(buffer);LOG(LogLevel::DEBUG) << "buffer:" << buffer << " from " << peer_ip << ":" << peer_port;// 2.发消息// std::string snd_msg = "receive message from server:";// snd_msg += buffer;// ssize_t snd_sz = sendto(_sockfd, snd_msg.c_str(), snd_msg.size(), 0, (struct sockaddr *)&peer, len);ssize_t snd_sz = sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr *)&peer, len);}}}private:int _sockfd;uint16_t _port;  // 就是unsigned short int类型重定义了// std::string _ip; // 这里使用字符串风格存储点分十进制的ip(比如1234.1.2.3)后需要转换成网络字节序bool _isrunning;func_t _func;   //  服务器回调函数,用来对数据进行处理
};
文件: test/MyUdpServer.cc
#include "MyUdpServer.hpp"
#include "Dict.hpp"
#include <memory>
#include <iostream>// ./myudpserver ip port
int main(int argc, char* argv[]){if(argc != 2){std::cerr << "Usage:" << argv[0] << " + port " << std::endl;return 1;}// std::string ip = argv[1];uint16_t port = std::stoi(argv[1]);Enable_Console_Log_Strategy();//字典对象提供翻译功能Dict dict;dict.Load();std::unique_ptr<MyUdpServer> usvr = std::make_unique<MyUdpServer>(port, [&dict](const std::string &message){return dict.Translate(message);});usvr->Init();usvr->Start();return 0;
}
文件: test/MyUdpClient.cc
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>// ./myudpclient server_ip server_port
int main(int argc, char* argv[]){if(argc != 3){std::cerr << "Usage:" << argv[0] << " + server_ip + server_port + " << std::endl;return 1;}//1.创建套接字int sockfd = socket (AF_INET,SOCK_DGRAM,0);if(sockfd < 0){std::cerr << "socket error" << std::endl;return 2;}//2.绑定。//客户端需要进行绑定,但是不需要我们手动显式地进行bind绑定//当首次发送消息的时候,os会自动给客户端进行绑定。//端口号采用随机端口号while(1){std::string input;std::cout << "Please Enter# ";std::getline(std::cin,input);sockaddr_in dest_addr;dest_addr.sin_family = AF_INET;dest_addr.sin_port = htons(atoi(argv[2]));dest_addr.sin_addr.s_addr = inet_addr(argv[1]);socklen_t addrlen = sizeof(dest_addr);int send_size = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr*)&dest_addr, addrlen);char recv_buf[1024];sockaddr_in peer_addr;socklen_t peer_addr_len = sizeof(peer_addr);int recv_size = recvfrom(sockfd,recv_buf,sizeof(recv_buf)-1,0,(struct sockaddr*)&peer_addr,&peer_addr_len);if(recv_size > 0){recv_buf[recv_size] = '\0';std::cout << "server# " << recv_buf << std::endl;}}return 0;
}
文件: test/Makefile
.PHONY: all clean# 定义生成的可执行文件目标
all: myudpserver myudpclient# 编译服务端
myudpserver: MyUdpServer.ccg++ -o $@ $^ -std=c++17# 编译客户端
myudpclient: MyUdpClient.ccg++ -o $@ $^ -std=c++17# 清理生成的可执行文件
clean:rm -f myudpserver myudpclient
文件: test/Log.hpp
#pragma once
#include<iostream>
#include<sstream>
#include<string>
#include<ctime>
#include<unistd.h>#include<pthread.h>#include"Mutex.hpp"namespace LogModule
{enum class LogLevel
{DEBUG = 0,INFO,WARNING,ERROR,FATAL
};const char* LogLevelToString(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 "UNKNOWN";}
}class Log
{
public:Log(LogLevel level, const char* file_name, int line): _level(level), _file_name(file_name), _line(line){}~Log(){std::string log_str = __Stream.str();time_t timestamp;time(&timestamp);struct tm* tm_time = localtime(&timestamp);char time_buffer[128] = {0};snprintf(time_buffer, sizeof(time_buffer), "%04d-%02d-%02d %02d:%02d:%02d", tm_time->tm_year + 1900, tm_time->tm_mon + 1, tm_time->tm_mday, tm_time->tm_hour, tm_time->tm_min, tm_time->tm_sec);//拼接std::stringstream _log_stream;_log_stream << "[" << time_buffer << "] ";_log_stream << "[" << LogLevelToString(_level) << "] ";_log_stream << "[" << getpid() << "] ";_log_stream << "[" << _file_name << "] ";_log_stream << "[" << _line  << "] - ";_log_stream << log_str;std::string log_string = _log_stream.str();//输出if(_consoleswitch)std::cout << log_string << std::endl;else if(_fileswitch){_mutex.Lock();FileWriteLog(log_string);_mutex.Unlock();}}
public:std::ostream& Stream(){ return __Stream; }
private:void FileWriteLog(const std::string& log_string, bool create_new_file = false){const std::string default_log_file = "./log.txt";//打开文件FILE* fp = nullptr;if(create_new_file){fp = fopen(default_log_file.c_str(), "w");}else{fp = fopen(default_log_file.c_str(), "a");}if(fp == nullptr){std::cerr << "FileWriteLog::open " << default_log_file << " error" << std::endl;return;}//写入,换行log_string;fprintf(fp, "%s\n", log_string.c_str());//关闭文件fclose(fp);}private:LogLevel _level;const char* _file_name;int _line;std::stringstream __Stream;
private://控制输出方式static bool _consoleswitch;static bool _fileswitch;//文件锁static Mutex _mutex;
};bool Log::_consoleswitch = true;
bool Log::_fileswitch = false;
Mutex Log::_mutex;Log LogMessage(LogLevel level, const char* file_name, int line)
{return Log(level, file_name, line);
}#define LOG(level) LogMessage(level, __FILE__, __LINE__).Stream()//5种日志输出策略
void Enable_Console_Log_Strategy()
{Log::_consoleswitch = true;Log::_fileswitch = false;
}
void Enable_File_Log_Strategy()
{Log::_consoleswitch = false;Log::_fileswitch = true;
}
void Enable_Dual_Log_Strategy()
{Log::_consoleswitch = true;Log::_fileswitch = true;
}
void Disable_Log_Strategy()
{Log::_consoleswitch = false;Log::_fileswitch = false;
}// #ifdef DEBUG
// #define LOG(level) DebugLog(level, __FILE__, __LINE__)
// #else
// #define LOG(level) 1 ? (void) 0 : LogMessage(level, __FILE__, __LINE__).Stream()
// #endif}
文件: test/Mutex.hpp
#pragma once#include <iostream>
#include <pthread.h>class Mutex
{
public:Mutex(){       pthread_mutex_init(&_mutex, nullptr);}~Mutex(){pthread_mutex_destroy(&_mutex);}void Lock(){pthread_mutex_lock(&_mutex);}void Unlock(){pthread_mutex_unlock(&_mutex);}
private:pthread_mutex_t _mutex;
};class LockGuard
{
public:LockGuard(Mutex& mutex):_mutex(mutex){_mutex.Lock();}~LockGuard(){_mutex.Unlock();}
private:Mutex& _mutex;
};
文件: test/dictionary.txt
apple:苹果
banana:香蕉
orange:橙子
grape:葡萄
watermelon:西瓜
strawberry:草莓
peach:桃子
pear:梨
pineapple:菠萝
mango:芒果
lemon:柠檬
cherry:樱桃
blueberry:蓝莓
kiwi:奇异果
tomato:西红柿
potato:土豆
carrot:胡萝卜
cucumber:黄瓜
onion:洋葱
garlic:大蒜
ginger:生姜
rice:大米
noodles:面条
bread:面包
milk:牛奶
egg:鸡蛋
meat:肉
beef:牛肉
pork:猪肉
chicken:鸡肉
fish:鱼
shrimp:虾
tofu:豆腐
vegetable:蔬菜
fruit:水果
water:水
tea:茶
coffee:咖啡
juice:果汁
sugar:糖
salt:盐
oil:油
pepper:胡椒
computer:电脑
phone:手机
keyboard:键盘
mouse:鼠标
screen:屏幕
internet:互联网
book:书
pen:笔
paper:纸
table:桌子
chair:椅子
window:窗户
door:门
house:房子
car:汽车
bus:公交车
bicycle:自行车

thread_mutex_t _mutex;
};

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


#### 文件: `test/dictionary.txt`

apple:苹果
banana:香蕉
orange:橙子
grape:葡萄
watermelon:西瓜
strawberry:草莓
peach:桃子
pear:梨
pineapple:菠萝
mango:芒果
lemon:柠檬
cherry:樱桃
blueberry:蓝莓
kiwi:奇异果
tomato:西红柿
potato:土豆
carrot:胡萝卜
cucumber:黄瓜
onion:洋葱
garlic:大蒜
ginger:生姜
rice:大米
noodles:面条
bread:面包
milk:牛奶
egg:鸡蛋
meat:肉
beef:牛肉
pork:猪肉
chicken:鸡肉
fish:鱼
shrimp:虾
tofu:豆腐
vegetable:蔬菜
fruit:水果
water:水
tea:茶
coffee:咖啡
juice:果汁
sugar:糖
salt:盐
oil:油
pepper:胡椒
computer:电脑
phone:手机
keyboard:键盘
mouse:鼠标
screen:屏幕
internet:互联网
book:书
pen:笔
paper:纸
table:桌子
chair:椅子
window:窗户
door:门
house:房子
car:汽车
bus:公交车
bicycle:自行车


> 本附录覆盖项目目录下与本示例相关的全部源码与资源,便于直接编译运行与对照阅读。
http://www.lryc.cn/news/621184.html

相关文章:

  • 基于SpringBoot+Vue的房屋匹配系统(WebSocket实时通讯、协同过滤算法、地图API、Echarts图形化分析)
  • css中container和media的用法和区别
  • 【Docker】安装kafka案例
  • BGP笔记及实验
  • Windows 11操作系统 Git命令执行速度慢
  • LLM 中 语音编码与文本embeding的本质区别
  • [论文阅读] 人工智能 + 软件工程 | 从模糊到精准:模块化LLM agents(REQINONE)如何重塑SRS生成
  • OpenCV图像处理2:边界填充与平滑滤波实战
  • 数据结构之顺序表相关算法题
  • latex 中破折号的输入
  • 【PCB设计经验】3D模型在线预览!效率便捷!
  • 【浅学】tflite-micro + ESP32S3 + VScode + ESP-IDF 基于例程快速实现自己的图像分类模型训练部署全流程
  • Python学习-----3.基础语法(2)
  • 异步同步,阻塞非阻塞,reactor/proactor
  • spring boot配置es
  • CPP模板编程
  • Redis7学习--持久化机制 RDB与AOF
  • 汽车生产线白皮书:稳联技术Profinet转Ethernet IP网关通信高效性
  • StarRocks优化统计分析
  • Redis入门到实战教程,深度透析redis
  • 零信任架构(Zero Trust Architecture, ZTA)(通过动态验证和最小权限控制,实现对所有访问请求的严格授权和持续监控)
  • Java应用架构实战指南:主流模式解析与Spring落地实践
  • diffusers库学习--pipeline,模型,调度器的基础使用
  • Docker exec进入容器命令的入门教程
  • 使用正则表达式 \s+ 作为分隔符处理字符串
  • 【cmake】编译cpp文件,安装MinGW
  • Python 进阶详解:正则表达式与 JSON —— 文本处理与数据交换的核心技能
  • K8s-持久化存储
  • 第1节:多模态大模型入门(多模态大模型基础教程)
  • 安装 Nginx