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 的完整步骤
- 关键系统调用的使用细节:
socket
、bind
、sendto
、recvfrom
- 地址结构
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
。
- domain:
-
可选优化(快速重启端口):
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_INET
、sin_port
必须htons
、sin_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(×tamp);struct tm* tm_time = localtime(×tamp);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:自行车
> 本附录覆盖项目目录下与本示例相关的全部源码与资源,便于直接编译运行与对照阅读。