高并发服务器day5

C/C++ 实用封装:从零实现一个轻量级日志宏

在 C/C++ 开发中,直接按逻辑打印调试数据非常繁琐,且项目上线前难以统一清理。为了提高开发效率,我们需要封装一个独立的调试日志宏,实现输出格式化、自动附加源码信息,并通过日志等级控制打印内容的可见性。

1. 基础封装:定位文件与行号

初步的思路是利用编译器内置宏 __FILE____LINE__,在输出日志时自动带上所在的文件名和行号,方便快速定位问题代码:

cpp 复制代码
#define LOG(msg) fprintf(stdout, "[%s:%d] %s", __FILE__, __LINE__, msg)

int main() {
    // 调用示例
    LOG("Hello World");
}

局限性 :当前仅支持固定 %s 打印单个字符串。而在实际业务中,我们通常需要传入复杂的格式化字符串和不确定数量的参数。

2. 支持不定参数:__VA_ARGS__ 及其陷阱

为了接收不确定数量的参数,我们可以引入不定参符号 ...,并在展开时使用 __VA_ARGS__

cpp 复制代码
#define LOG(format, ...) fprintf(stdout, "[%s:%d] " format, __FILE__, __LINE__, __VA_ARGS__)

遇到问题 : 当只有格式化字符、没有不定参数时(例如 LOG("hello");),上述宏展开后,__LINE__ 后面会多出一个逗号,导致编译报错。

解决方案 : 引入 ##__VA_ARGS__## 的作用是在不定参数为空时,自动消除前面多余的逗号。顺便在末尾加上换行符 \n

cpp 复制代码
#define LOG(format, ...) fprintf(stdout, "[%s:%d] " format "\n", __FILE__, __LINE__, ##__VA_ARGS__)

3. 引入时间戳与多行宏封装

在排查复杂的状态流转时,时间戳是必不可少的。因为获取时间涉及多行代码,我们需要使用 do { ... } while(0) 结构将宏包裹为一个独立的作用域,并使用 \ 进行行尾转义(续行):

cpp 复制代码
#define LOG(format, ...) do{\
    time_t t = time(NULL);\
    struct tm *ltm = localtime(&t);\
    char tmp[32] = {0};\
    strftime(tmp, 31, "%H:%M:%S", ltm);\
    fprintf(stdout, "[%s %s:%d] " format "\n", tmp, __FILE__, __LINE__, ##__VA_ARGS__);\
}while(0)

4. 终极形态:日志等级过滤

为了让日志系统在"调试"和"生产"环境下灵活切换,我们可以引入日志等级(Log Level)。通过比对当前调用等级与系统设定的全局等级,决定是否拦截打印。

利用 do { ... } while(0) 的特性,我们还可以直接用 break 语句实现优雅的条件拦截。

完整代码实现如下:

cpp 复制代码
#include <iostream>
#include <time.h>

// 1. 定义日志等级枚举
#define INF 0
#define DBG 1
#define ERR 2

// 2. 设置全局日志等级阈值
#define LOG_LEVEL DBG

// 3. 核心宏逻辑(包含等级校验与时间戳生成)
#define LOG(level, format, ...) do{\
    if (level < LOG_LEVEL) break;\
    time_t t = time(NULL);\
    struct tm *ltm = localtime(&t);\
    char tmp[32] = {0};\
    strftime(tmp, 31, "%H:%M:%S", ltm);\
    fprintf(stdout, "[%s %s:%d] " format "\n", tmp, __FILE__, __LINE__, ##__VA_ARGS__);\
}while(0)

// 4. 封装上层快捷调用宏
#define INF_LOG(format, ...) LOG(INF, format, ##__VA_ARGS__)
#define DBG_LOG(format, ...) LOG(DBG, format, ##__VA_ARGS__)
#define ERR_LOG(format, ...) LOG(ERR, format, ##__VA_ARGS__)

int main() {
    // 测试调用
    INF_LOG("这条普通信息等级低于 DBG,将被拦截过滤");
    DBG_LOG("模块初始化成功,当前分配端口:%d", 8080);
    ERR_LOG("内存分配失败!");
    
    return 0;
}

至此,我们就完成了一个轻量级、无第三方依赖且支持等级控制的 C/C++ 日志宏封装,能够充分满足日常底层模块和服务的调试需求。

🚀 深入浅出网络编程:Socket 套接字核心操作与进阶属性配置

在网络编程中,套接字(Socket)是我们进行网络通信的基石。无论是搭建高并发的服务器,还是编写轻量级的客户端,掌握套接字的核心操作与底层属性都至关重要。

本文将系统梳理 Socket 模块的基本操作流程,并重点解析两个在实际开发中必不可少的进阶属性:端口重用非阻塞模式

一、 套接字核心生命周期与操作概览

根据我们在开发中最常用的步骤,套接字的操作可以清晰地划分为"建立连接"、"数据收发"和"高级配置"三大类。以下是套接字核心操作的整理:

操作类别 核心动作 功能说明 适用端
基础初始化 创建套接字 在内核中创建一个 Socket 对象,获取文件描述符 服务端 / 客户端
绑定地址信息 将套接字与指定的 IP 地址和端口(Port)进行绑定 服务端
开始监听 使套接字进入被动监听状态,等待客户端连接请求 服务端
连接管理 向服务器发起连接 根据 IP 和端口,主动向服务器发起 TCP 三次握手 客户端
获取新连接 从已完成连接的队列中取出一个新的客户端连接 (Accept) 服务端
创建一个服务端连接 封装上述服务端初始化流程的代码逻辑 服务端
创建一个客户端连接 封装上述客户端初始化流程的代码逻辑 客户端
数据交互 接收数据 从套接字缓冲区读取网络数据 服务端 / 客户端
发送数据 将本地数据写入套接字缓冲区并发送至网络 服务端 / 客户端
资源释放 关闭套接字 结束通信,回收文件描述符和相关资源 服务端 / 客户端
进阶配置 开启地址端口重用 设置套接字选项,允许复用处于 TIME_WAIT 状态的端口 服务端为主
设置为非阻塞 设置套接字阻塞属性,避免 I/O 操作挂起整个进程 服务端 / 客户端

二、 进阶避坑指南:为什么我们需要设置套接字属性?

在基础的"收发数据"之外,实际生产环境中我们经常会遇到"端口被占用"或"进程卡死"的问题。这需要通过配置套接字选项(Socket Options)来解决。

1. 突破 TIME_WAIT 限制:开启地址端口重用

痛点场景:

当一个链接绑定了 IP 地址和端口后,主动断开连接的一方 会进入 TIME_WAIT 状态。这是一种 TCP 协议的保护机制,用来确保网络中残留的数据包消散。在这个状态下,套接字不会被立即释放,对应的 IP 和端口依然被系统占用。

这主要是为了保护网络通信的可靠性(如防止旧连接的数据包干扰新连接),但对于服务器而言却是致命的------如果服务器意外崩溃并尝试立即重启 ,会因为端口仍然被占用而报 Address already in use 错误,导致无法立即恢复服务。

解决方案:设置端口重用 (SO_REUSEADDR)

通过调用 setsockopt 设置套接字选项,开启"地址端口重用"。这允许服务器在重启时,直接绑定并复用处于 TIME_WAIT 状态的端口,保障服务的高可用性。

2. 拒绝进程假死:设置非阻塞属性 (Non-blocking)

痛点场景:

默认情况下,套接字的读写操作(如 recvaccept)是阻塞模式(Blocking)的。这意味着如果缓冲区里暂时没有数据,或者没有新的连接请求,程序就会一直停在原地等待。如果在单线程下运行,整个进程就会像"卡死"一样,什么都做不了。

解决方案:设置为非阻塞 (Non-blocking)

将套接字的阻塞属性修改为非阻塞

设置后,如果尝试读取数据但数据尚未准备好,系统会立即返回一个错误(如 EAGAIN)而不是让进程挂起。这使得我们可以通过循环或结合 I/O 多路复用(如 epoll/select),实现循环读取直到读取完毕,大大提升了单进程处理多任务的并发效率。

总结

掌握基础的创建、绑定、监听和收发流程,能让你写出能跑的 Demo;而理解 TIME_WAIT 保护机制并合理使用端口重用非阻塞设置,才能让你写出健壮、工业级的网络程序。

大多数服务器绑定主机所有网卡的ip,所以缺省ip

accept第二个参数可以带出客户端信息,如果不需要传null

相关推荐
our_times1 小时前
【硬核实战】当机器人走进厨房:Java后端如何重构物联网并发与状态一致性`。
java·重构·机器人
一路向北North1 小时前
Spring AI(5) :对话机器人-会话记忆
java·后端·spring
明日清晨1 小时前
printf(“%p\n“,&((struct A *)NULL)->m_float)的原理
linux·服务器·前端
William_cl2 小时前
WinForms EF Core 上下文频繁释放引发的异常排查与规范方案(MES 项目实录)
服务器·数据库·oracle
kingwebo'sZone2 小时前
C#事件声明办法
java·前端·c#
冻感糕人~2 小时前
大模型学习指南:收藏这份AI Agent四层工程地图(小白程序员必备)
java·大数据·人工智能·学习·大模型·agent·大模型学习
许彰午2 小时前
01-Oracle 19c静默安装全流程
数据库·oracle
许彰午2 小时前
02-Oracle连接池配置实战
数据库·oracle
Flandern11112 小时前
HTTPS(TLS 1.3)完整流程
数据库·网络协议·计算机网络·https