cllient.cc
#include "comm.hpp"
#include "log.hpp"
using namespace std;
// 管理管道文件
int main()
{
Init init;
Log log;
// log.Enable(Onefile);
log.Enable(Onefile);
// 打开管道
int fd = open(FIFO_FILE, O_RDONLY); // 等待写入方打开之后,自己才会打开文件,向后执行, open 阻塞了!
if (fd < 0)
{
log(Fatal, "error string: %s, error code: %d", strerror(errno), errno);
exit(FIFO_OPEN_ERR);
}
log(Info, "server open file done, error string: %s, error code: %d", strerror(errno), errno);
log(Warning, "server open file done, error string: %s, error code: %d", strerror(errno), errno);
log(Fatal, "server open file done, error string: %s, error code: %d", strerror(errno), errno);
log(Debug, "server open file done, error string: %s, error code: %d", strerror(errno), errno);
// 开始通信
while (true)
{
char buffer[1024] = { 0 };
int x = read(fd, buffer, sizeof(buffer));
if (x > 0)
{
buffer[x] = 0;
cout << "client say# " << buffer << endl;
}
else if (x == 0)
{
log(Debug, "client quit, me too!, error string: %s, error code: %d", strerror(errno), errno);
break;
}
else
break;
}
close(fd);
return 0;
}
comm.hpp
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include<stdio.h>
#define FIFO_FILE "./myfifo"
#define MODE 0664
enum
{
FIFO_CREATE_ERR = 1,
FIFO_DELETE_ERR,
FIFO_OPEN_ERR
};
class Init
{
public:
Init()
{
// 创建管道
int n = mkfifo(FIFO_FILE, MODE);
if (n == -1)
{
perror("mkfifo");
exit(FIFO_CREATE_ERR);
}
}
~Init()
{
int m = unlink(FIFO_FILE);
if (m == -1)
{
perror("unlink");
exit(FIFO_DELETE_ERR);
}
}
};
log.hpp
#pragma once
// 防止头文件重复包含(编译预处理指令)
// 等价于#ifndef XXX_H#define XXX_H#endif,更简洁高效,仅支持C++11及以上
#pragma once
// 包含必要的头文件
#include <iostream> // 标准输入输出流(用于屏幕输出日志)
#include <time.h> // 时间相关函数(获取当前时间、格式化时间戳)
#include <stdarg.h> // 可变参数相关宏(处理日志的格式化输入,如printf风格参数)
#include <sys/types.h> // 系统类型定义(文件操作相关类型,如mode_t)
#include <sys/stat.h> // 文件状态相关函数(如判断文件是否存在)
#include <fcntl.h> // 文件控制相关宏(文件打开标志O_WRONLY、O_CREAT等)
#include <unistd.h> // 系统调用函数(write、close、access等文件操作)
#include <stdlib.h> // 标准库函数(如exit,但此处未直接使用,可能为后续扩展预留)
// 缓冲区大小宏定义:日志字符串的临时存储缓冲区大小(1KB)
// 用于存储格式化后的时间、日志级别、自定义信息等
#define SIZE 1024
// 日志级别宏定义:用整数枚举日志的严重程度,便于后续判断和字符串转换
#define Info 0 // 信息级日志(普通运行状态信息)
#define Debug 1 // 调试级日志(开发调试用,输出详细过程)
#define Warning 2 // 警告级日志(非致命错误,可能影响功能但不中断运行)
#define Error 3 // 错误级日志(功能异常,部分模块可能无法正常工作)
#define Fatal 4 // 致命级日志(严重错误,程序可能崩溃或无法继续运行)
// 日志输出方式宏定义:指定日志的输出目的地
#define Screen 1 // 输出到屏幕(标准输出std::cout)
#define Onefile 2 // 输出到单个文件(所有级别日志写入同一个文件)
#define Classfile 3 // 按级别分类输出到多个文件(每个级别对应一个独立文件)
// 默认日志文件名(当输出到单个文件或分类文件时,作为基础文件名)
#define LogFile "log.txt"
// 日志工具类:支持多目的地输出、日志级别区分、时间戳格式化、可变参数日志输入
class Log
{
public:
// 构造函数:初始化日志类的默认配置
Log()
{
printMethod = Screen; // 默认输出方式:屏幕输出
path = "./log/"; // 默认日志文件存储路径:当前目录下的log文件夹
}
// 日志输出方式设置接口
// 参数method:输出方式(取值为Screen/Onefile/Classfile,对应上面的宏定义)
void Enable(int method)
{
printMethod = method; // 更新成员变量,切换日志输出目的地
}
// 日志级别整数转字符串:将日志级别宏(如Debug)转为对应的字符串(如"Debug")
// 参数level:日志级别整数(Info/Debug/Warning/Error/Fatal)
// 返回值:对应的日志级别字符串(默认返回"None"表示无效级别)
std::string levelToString(int level)
{
switch (level)
{
case Info:
return "Info"; // 信息级 -> "Info"
case Debug:
return "Debug"; // 调试级 -> "Debug"
case Warning:
return "Warning"; // 警告级 -> "Warning"
case Error:
return "Error"; // 错误级 -> "Error"
case Fatal:
return "Fatal"; // 致命级 -> "Fatal"
default:
return "None"; // 无效级别 -> "None"
}
}
// (原代码中被注释的日志输出函数,保留注释说明)
// 功能:接收日志级别和格式化字符串,生成完整日志并输出
// 注:该函数已被operator()重载替代,此处为预留/废弃代码
// void logmessage(int level, const char *format, ...)
// {
// time_t t = time(nullptr); // 获取当前系统时间(秒级时间戳)
// struct tm *ctime = localtime(&t); // 转换为本地时间结构体(包含年、月、日、时、分、秒)
// char leftbuffer[SIZE]; // 存储日志前缀(级别+时间戳)的缓冲区
// // 格式化日志前缀:[级别][年-月-日 时:分:秒]
// snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
// ctime->tm_year + 1900, // tm_year是从1900年开始的偏移量,需+1900得到实际年份
// ctime->tm_mon + 1, // tm_mon是0-11的月份,需+1得到实际月份(1-12)
// ctime->tm_mday, // 当月日期(1-31)
// ctime->tm_hour, // 小时(0-23)
// ctime->tm_min, // 分钟(0-59)
// ctime->tm_sec); // 秒(0-59)
// // va_list s; // 可变参数列表对象(本质是字符指针,用于遍历可变参数)
// // va_start(s, format); // 初始化可变参数列表:从format参数的下一个参数开始
// char rightbuffer[SIZE]; // 存储自定义日志信息的缓冲区
// vsnprintf(rightbuffer, sizeof(rightbuffer), format, s); // 格式化可变参数到缓冲区(vsnprintf专门处理可变参数)
// // va_end(s); // 释放可变参数列表资源(避免内存泄漏)
// // 拼接完整日志字符串:前缀 + 自定义信息 + 换行符
// char logtxt[SIZE * 2];
// snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
// // printf("%s", logtxt); // 暂时打印(调试用)
// printLog(level, logtxt); // 调用实际的日志输出函数
// }
// 日志输出分发函数:根据当前设置的输出方式(printMethod),将日志输出到对应目的地
// 参数level:日志级别(用于分类文件输出)
// 参数logtxt:完整的日志字符串(已包含前缀+自定义信息+换行)
void printLog(int level, const std::string& logtxt)
{
switch (printMethod)
{
case Screen:
// 输出到屏幕:使用std::cout打印日志
std::cout << logtxt << std::endl;
break;
case Onefile:
// 输出到单个文件:调用printOneFile,写入默认日志文件LogFile
printOneFile(LogFile, logtxt);
break;
case Classfile:
// 按级别分类输出到多个文件:调用printClassFile,每个级别对应一个文件
printClassFile(level, logtxt);
break;
default:
// 无效输出方式:不做任何操作
break;
}
}
// 单个文件写入函数:将日志写入指定的单个文件(支持文件不存在时创建、追加写入)
// 参数logname:日志文件名(如LogFile定义的"log.txt")
// 参数logtxt:要写入的完整日志字符串
void printOneFile(const std::string& logname, const std::string& logtxt)
{
// 拼接完整文件路径:日志存储根路径(path) + 文件名(logname)
std::string _logname = path + logname;
// 打开文件:O_WRONLY(只写模式)、O_CREAT(文件不存在则创建)、O_APPEND(追加模式,避免覆盖原有日志)
// 0666:文件权限(所有者、组用户、其他用户均有读写权限,具体生效受umask影响)
int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);
if (fd < 0) // 打开文件失败(如路径不存在、权限不足),直接返回
return;
// 写入日志:将logtxt的字符串内容写入文件描述符fd
// logtxt.size():获取字符串长度(字节数)
write(fd, logtxt.c_str(), logtxt.size());
// 关闭文件:释放文件描述符资源(必须调用,否则会导致文件描述符泄漏)
close(fd);
}
// 按级别分类写入文件函数:为不同日志级别创建独立文件,调用printOneFile完成写入
// 参数level:日志级别(用于生成对应文件名)
// 参数logtxt:要写入的完整日志字符串
void printClassFile(int level, const std::string& logtxt)
{
std::string filename = LogFile; // 基础文件名(如"log.txt")
filename += "."; // 拼接分隔符(如"log.txt.")
filename += levelToString(level); // 拼接日志级别字符串(如"log.txt.Debug")
// 调用单个文件写入函数,将日志写入对应级别文件
printOneFile(filename, logtxt);
}
// 析构函数:日志类无动态分配资源(如堆内存、打开的文件),无需额外清理操作
~Log()
{
}
// 重载()运算符:使Log类对象可像函数一样调用,简化日志输出语法
// 功能:接收日志级别、格式化字符串、可变参数,生成完整日志并调用printLog输出
// 参数level:日志级别(Info/Debug/Warning/Error/Fatal)
// 参数format:日志格式化字符串(如"用户%d登录成功,IP:%s")
// 参数...:可变参数(对应format中的占位符,如%d、%s等)
void operator()(int level, const char* format, ...)
{
// 1. 获取并格式化时间戳和日志级别(日志前缀部分)
time_t t = time(nullptr); // 获取当前系统时间(秒级时间戳,time_t本质是long)
// localtime:将秒级时间戳转换为本地时间结构体tm(包含年、月、日、时、分、秒等)
// 注意:localtime是线程不安全函数,多线程环境下建议使用localtime_r(Linux)
struct tm* ctime = localtime(&t);
char leftbuffer[SIZE]; // 存储日志前缀的缓冲区(级别+时间戳)
// 格式化日志前缀:[级别][年-月-日 时:分:秒]
snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]",
levelToString(level).c_str(), // 日志级别字符串(如"Debug")
ctime->tm_year + 1900, // 实际年份 = tm_year(1900偏移)+ 1900
ctime->tm_mon + 1, // 实际月份 = tm_mon(0-11)+ 1
ctime->tm_mday, // 当月日期(1-31)
ctime->tm_hour, // 小时(0-23)
ctime->tm_min, // 分钟(0-59)
ctime->tm_sec); // 秒(0-59)
// 2. 处理可变参数(自定义日志信息部分)
va_list s; // 可变参数列表对象(用于遍历可变参数,本质是char*指针)
va_start(s, format); // 初始化可变参数列表:从format参数的下一个参数开始遍历
char rightbuffer[SIZE]; // 存储格式化后的自定义日志信息
// vsnprintf:格式化可变参数到缓冲区
// 参数:目标缓冲区、缓冲区大小、格式化字符串、可变参数列表
// 优点:避免缓冲区溢出(第二个参数限制写入长度)
vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
va_end(s); // 释放可变参数列表资源(必须调用,否则可能导致内存泄漏或野指针)
// 3. 拼接完整日志字符串(前缀 + 自定义信息 + 换行符)
char logtxt[SIZE * 2]; // 拼接后的日志缓冲区(大小2*SIZE,避免前缀+自定义信息超出长度)
snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);
// 4. 调用日志输出分发函数,将日志输出到指定目的地
printLog(level, logtxt);
}
private:
int printMethod; // 日志输出方式(存储Screen/Onefile/Classfile,由Enable函数设置)
std::string path; // 日志文件存储根路径(默认./log/,可后续扩展为可配置)
};
// 可变参数函数示例:计算n个整数的和(用于演示stdarg.h的使用,与日志类功能无关)
// 参数n:可变参数的个数(必须显式传入,因为可变参数列表无法自动获取个数)
// 参数...:n个int类型的参数(如sum(3, 1, 2, 3)表示计算1+2+3)
// 返回值:n个整数的和
// int sum(int n, ...)
// {
// va_list s; // 可变参数列表对象(char*类型,用于指向可变参数的起始地址)
// va_start(s, n); // 初始化:s指向第一个可变参数(n的下一个参数)
// int sum = 0;
// while(n)
// {
// // va_arg(s, int):获取当前可变参数(类型为int),并将s指向-next-个可变参数
// sum += va_arg(s, int);
// // 示例:printf("hello %d, hello %s, hello %c, hello %d,", 1, "hello", 'c', 123);
// // 需显式知道每个参数的类型,否则会出现类型不匹配错误
// n--; // 剩余参数个数减1,直到处理完所有n个参数
// }
// va_end(s); // 清理:将s置为NULL,释放可变参数列表资源(避免野指针)
// return sum;
// }
server.cc
#include <iostream>
#include "comm.hpp"
using namespace std;
int main()
{
int fd = open(FIFO_FILE, O_WRONLY);
if (fd < 0)
{
perror("open");
exit(FIFO_OPEN_ERR);
}
cout << "client open file done" << endl;
string line;
while (true)
{
cout << "Please Enter@ ";
getline(cin, line);
write(fd, line.c_str(), line.size());
}
close(fd);
return 0;
}