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

发布于:2025-08-15 ⋅ 阅读:(12) ⋅ 点赞:(0)

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 是为了留一个位置将来添加 \n
            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.发消息
                // 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.cc
	g++ -o $@ $^ -std=c++17

# 编译客户端
myudpclient: MyUdpClient.cc
	g++ -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:自行车


> 本附录覆盖项目目录下与本示例相关的全部源码与资源,便于直接编译运行与对照阅读。

网站公告

今日签到

点亮在社区的每一天
去签到