Muduo网络库
第一阶段:数据准备阶段 (Waiting for the data to be ready)这个阶段是指数据从网络到达网卡,再经过内核处理,直到**内核缓冲区(TCP 接收缓冲区)**中有足够的数据可读的过程。阻塞 (Blocking):用户线程发起调用后被挂起,什么都不干,直到数据准备好。非阻塞 (Non-blocking):如果数据没准备好,内核立即返回一个错误码(如 EAGAIN 或 EWO
基础知识
网络IO的两个阶段
第一阶段:数据准备阶段 (Waiting for the data to be ready)
这个阶段是指数据从网络到达网卡,再经过内核处理,直到**内核缓冲区(TCP 接收缓冲区)**中有足够的数据可读的过程。
阻塞 (Blocking):用户线程发起调用后被挂起,什么都不干,直到数据准备好。
非阻塞 (Non-blocking):如果数据没准备好,内核立即返回一个错误码(如 EAGAIN 或 EWOULDBLOCK)。用户线程不会被挂起,可以轮询或去做别的事。
关键点:这个阶段取决于文件描述符(fd)的属性设置。
第二阶段:数据读写阶段 (Copying data from the kernel to user space)
这个阶段是指数据已经到达内核缓冲区后,将其从内核态拷贝到用户态(应用程序内存)的过程。
同步 (Synchronous):用户线程亲自参与拷贝。在数据从内核缓冲区拷贝到用户态内存的过程中,用户线程是阻塞的(即便第一阶段是非阻塞的,拷贝这一步也得等)。
常见的同步 I/O:阻塞 I/O、非阻塞 I/O、I/O 复用(select/poll/epoll)、信号驱动 I/O。
异步 (Asynchronous):用户线程完全不参与拷贝。用户只需告诉内核:“把数据读完后放到这个数组里并通知我”。内核在后台完成“等待数据”和“拷贝数据”两个阶段,完成后再通知用户。
关键点:区分同步与异步的唯一标准是“谁负责把数据搬进用户房间”。如果内核搬,就是异步;如果用户自己动手搬,就是同步。
Node.js是高能的异步非阻塞高性能服务器
Unix的五种IO模型
-
阻塞 I/O (Blocking I/O)
这是最简单的模型。当进程调用一个 I/O 函数(如 recvfrom)时,该调用会一直等待,直到数据到达并被拷贝到用户态缓冲区为止。
第一阶段(等数据):进程阻塞,放弃 CPU 控制权。
第二阶段(拷贝):进程继续阻塞,直到拷贝完成。
特点:实现简单,但线程利用率极低。如果数据没来,线程就死等。
-
非阻塞 I/O (Non-blocking I/O)
进程通过设置文件描述符为非阻塞,如果内核缓冲区没有数据,函数会立即返回一个错误(如 EWOULDBLOCK),而不是挂起线程。
第一阶段(等数据):进程不断轮询(Polling)内核:数据好了吗?没好就立即返回,好就进入第二阶段。
第二阶段(拷贝):进程阻塞,手动完成数据拷贝。
特点:线程不会被挂起,可以做别的事,但轮询操作会大量消耗 CPU 资源。
-
I/O 复用 (I/O Multiplexing)
这是目前高性能服务器(如 Redis, Nginx)最常用的模型。进程调用 select、poll 或 epoll,这些函数可以同时监控多个文件描述符。
第一阶段(等数据):进程阻塞在 select 或 epoll_wait 调用上。一旦有一个或多个描述符就绪(有数据到了),函数返回。
第二阶段(拷贝):进程紧接着调用 recvfrom,阻塞并完成数据拷贝。
特点:单个线程可以同时处理成千上万个连接,虽然第一阶段仍是阻塞的,但它极大地提高了系统并发能力。



-
信号驱动 I/O (Signal Driven I/O)
进程通知内核:当数据准备好时,请给我发一个 SIGIO 信号。
第一阶段(等数据):进程不阻塞,继续执行其他任务。当数据准备好,内核发信号。
第二阶段(拷贝):进程在信号处理函数中调用 recvfrom,阻塞并完成数据拷贝。
特点:等待数据阶段是非阻塞的,但数据拷贝阶段依然是同步阻塞的。
-
异步 I/O (Asynchronous I/O)
这是真正的异步模型。进程发起 I/O 请求后立即返回,内核负责等待数据并将其直接拷贝到用户指定的缓冲区,全部完成后再通知进程。
第一阶段(等数据):内核自动处理,进程完全不感知,不阻塞。
第二阶段(拷贝):内核自动处理,进程完全不感知,不阻塞。
特点:两个阶段都不阻塞进程。这是性能最高的模型,但实现复杂度最高,在 Unix/Linux 下的成熟度(AIO)不如 I/O 复用。

Reactor

Muduo


简单的使用
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <iostream>
#include <string>
using std::cout;
using std::endl;
using namespace muduo;
using namespace muduo::net;
/*
* Muduo Server 开发一般步骤:
* 1. 组合 TcpServer 对象
* 2. 创建 EventLoop 事件循环对象的指针
* 3. 明确 TcpServer 构造函数参数
* 4. 注册回调函数 (连接建立/断开回调, 消息读写回调)
* 5. 设置线程数量
*/
class ChatServer
{
public:
ChatServer(EventLoop *loop,
const InetAddress &listenAddr,
const string &nameArg)
: server_(loop, listenAddr, nameArg), loop_(loop)
{
// 1. 给服务器注册用户连接的创建和断开回调
server_.setConnectionCallback(
std::bind(&ChatServer::onConnection, this, _1));
// 2. 给服务器注册用户读写事件回调
server_.setMessageCallback(
std::bind(&ChatServer::onMessage, this, _1, _2, _3));
// 3. 设置后端线程数量 (1个 I/O 线程,N个 工作线程)
server_.setThreadNum(4);
}
// 开启事件循环
void start()
{
server_.start();
}
private:
// 处理用户的连接创建和断开 (epoll listenfd accept)
void onConnection(const TcpConnectionPtr &conn)
{
if (conn->connected())
{
cout << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << " state:online" << endl;
}
else
{
cout << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << " state:offline" << endl;
conn->shutdown(); // close(fd)
}
}
// 处理用户的读写事件 (epoll connfd 读写事件)
// buf: 缓冲区 (Muduo封装好的,自动扩容)
// time: 接收到数据的时间信息
void onMessage(const TcpConnectionPtr &conn,
Buffer *buf,
Timestamp time)
{
string msg = buf->retrieveAllAsString(); // 把接收到的数据全部转成 string
cout << "收到消息: " << msg << " 长度: " << msg.size() << endl;
// 回显:把收到的消息原封不动发回去
conn->send(msg);
}
TcpServer server_; // #1
EventLoop *loop_; // #2
};
int main()
{
// 创建 EventLoop,相当于 epoll_create
EventLoop loop;
// 创建地址对象,监听 6000 端口
InetAddress addr(6000);
// 创建 Server 对象
ChatServer server(&loop, addr, "ChatServer");
// 启动 Server,相当于 bind + listen
server.start();
// 进入事件循环,相当于 epoll_wait (阻塞)
cout << "Server is running on port 6000..." << endl;
loop.loop();
return 0;
}
源码
noncopyable
class noncopyable
{
public:
noncopyable(const noncopyable&) = delete;
void operator=(const noncopyable&) = delete;
protected:
noncopyable() = default;
~noncopyable() = default;
};
- 当一个类不能被拷贝和赋值时可以继承这个noncopyable类,保证子类可以


日志代码的书写
Do-While-Zero
导致语句没有执行完全
由于习惯的分号结尾导致的语法错误
#pragma once
#include "./nocopyable.h"
#include <string>
#include <memory>
// 定义日志宏,方便使用,为什么方便?
/*
// 惨不忍睹的直接调用写法
char buffer[1024];
snprintf(buffer, sizeof(buffer), "User %s login failed, id: %d", userName.c_str(), userId); // 1. 手动格式化
Logger::instance().setLogLevel(INFO); // 2. 手动设置级别
Logger::instance().log(buffer); // 3. 发送日志
1. 使用宏定义,简化日志调用
LOG_INFO("User %s login failed, id: %d", userName.c_str(), userId);
*/
// LOG_INFO("%s %d",arg1,arg2)
#define LOG_INFO(logmsgFormat, ...) \
do \
{ \
Logger::instance().setLogLevel(INFO); \
char buf[1024] = {0}; \
snprintf(buf, sizeof(buf), logmsgFormat, ##__VA_ARGS__); \
Logger::instance().log(buf); \
} while (0)
/* 书写宏的注意事项
1. 宏定义尽量使用大写字母,避免和变量名冲突
2. 宏定义如果有多条语句,使用 do{...}while(0) 包裹,避免使用时出现语法错误
3. 有多行的宏定义,每行末尾使用反斜杠 \ 连接,而且其后面不能有任何字符,包括空格和注释
4. 宏定义中如果有参数,参数名前后不要加括号
为什么: #define SQUARE(x) ((x)*(x)) 比 #define SQUARE(x) (x)*(x) 好
5. ##__VA_ARGS__ 用于处理可变参数宏,表示宏参数列表中的所有参数
*/
#define LOG_ERROR(logmsgFormat, ...) \
do \
{ \
Logger::instance().setLogLevel(ERROR); \
char buf[1024] = {0}; \
snprintf(buf, sizeof(buf), logmsgFormat, ##__VA_ARGS__); \
Logger::instance().log(buf); \
} while (0)
#define LOG_FATAL(logmsgFormat, ...) \
do \
{ \
Logger::instance().setLogLevel(FATAL); \
char buf[1024] = {0}; \
snprintf(buf, sizeof(buf), logmsgFormat, ##__VA_ARGS__); \
Logger::instance().log(buf); \
} while (0)
// 由于debug的信息比较多,一般发布版本不需要打印debug信息
#ifdef NDEBUG
#define LOG_DEBUG(logmsgFormat, ...) \
do \
{ \
Logger::instance().setLogLevel(DEBUG); \
char buf[1024] = {0}; \
snprintf(buf, sizeof(buf), logmsgFormat, ##__VA_ARGS__); \
Logger::instance().log(buf); \
} while (0)
#else
#define LOG_DEBUG(logmsgFormat, ...) \
do \
{ \
} while (0)
#endif
enum LogLevel
{
INFO, // 普通信息
ERROR, // 错误信息
FATAL, // 致命错误信息
DEBUG // 调试信息
};
class Logger : noncopyable
{
public:
// 获取日志唯一的实例对象
Logger &instance();
// 设置日志级别
void setLogLevel(int level);
// 写日志
void log(std::string msg);
private:
int logLevel_;
Logger() {}
};
#include "./logger.h"
#include <iostream>
using std::cout;
using std::endl;
Logger &Logger::instance()
{
static Logger logger;
return logger;
}
void Logger::setLogLevel(int level)
{
logLevel_ = level;
}
// 写日志 [级别信息] 时间 信息
void Logger::log(std::string msg)
{
// 此处省略具体的日志写入实现
switch (logLevel_)
{
case INFO:
cout << "[INFO]: " << msg << endl;
break;
case ERROR:
cout << "[ERROR]: " << msg << endl;
break;
case FATAL:
cout << "[FATAL]: " << msg << endl;
break;
case DEBUG:
cout << "[DEBUG]: " << msg << endl;
break;
default:
cout << "[UNKNOWN]: " << msg << endl;
break;
}
// 打印时间和msg
cout << "print time: " << time(nullptr) << " msg: " << msg << endl;
}
更多推荐

所有评论(0)