muduo网络库核心模块TcpServer以epoll+线程池的方式实现,封装了和Linux线程、网络socket相关的十几个API,并没有考虑可移植性,只能在Linux环境下编译安装。
我的开发环境为Ubuntu系统,项目编译用CMake环境编译,g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0。

muduo运用到了很多现代C++的编程技术和多线程编程理念,其中一些类对象、容器、智能指针、函数对象、绑定器使用了巧妙的设计非常值得我们学习。
比如在muduo中有很多类都会以私有继承的方式继承这样一个类:

这个类把拷贝构造函数和赋值运算符重载函数都delete掉了,构造析构采用的是默认的方式。
所有继承自noncopyable的类,在使用拷贝构造或者赋值的方式创建对象时,都会调用noncopyable的拷贝构造和赋值运算符重载,而这两种方式都被delete了,这使得所有继承noncopyable的类都不允许拷贝构造和赋值,可以帮我们省去很多重复的代码。
重写我们自己的noncopyable.h:
#pragma once
//限制继承该类的类可以正常构造和析构,不能进行拷贝构造和赋值
class noncopyable
{
public:
noncopyable(const noncopyable&) = delete;
noncopyable& operator=(const noncopyable&) = delete;
protected:
noncopyable() = default;
~noncopyable() = default;
};
日志对于一个系统来说还是非常重要的,当软件运行以后,日志的输出可以帮我们定位很多问题,日志是我们定位处理问题最佳的途径了,我的日志类以单例对象的形式定义,且具有noncopyable属性。
Logger.h
#pragma once
#include
#include "noncopyable.h"
//LOG_INFO("%s %d",arg1,arg2)
#define LOG_INFO(LogmsgFormat, ...) \
do \
{ \
Logger &logger = Logger::instance(); \
logger.setLogLevel(INFO); \
char buf[1024] = {0}; \
snprintf(buf, 1024, LogmsgFormat, ##__VA_ARGS__); \
logger.log(buf); \
}while(0)
#define LOG_ERROR(LogmsgFormat, ...) \
do \
{ \
Logger &logger = Logger::instance(); \
logger.setLogLevel(INFO); \
char buf[1024] = {0}; \
snprintf(buf, 1024, LogmsgFormat, ##__VA_ARGS__); \
logger.log(buf); \
}while(0)
#define LOG_FATAL(LogmsgFormat, ...) \
do \
{ \
Logger &logger = Logger::instance(); \
logger.setLogLevel(INFO); \
char buf[1024] = {0}; \
snprintf(buf, 1024, LogmsgFormat, ##__VA_ARGS__); \
logger.log(buf); \
exit(-1); \
}while(0)
#ifdef MUDEBUG
#define LOG_DEBUG(LogmsgFormat, ...) \
do \
{ \
Logger &logger = Logger::instance(); \
logger.setLogLevel(INFO); \
char buf[1024] = {0}; \
snprintf(buf, 1024, LogmsgFormat, ##__VA_ARGS__); \
logger.log(buf); \
}while(0)
#else
#define LOG_DEBUG(LogmsgFormat, ...)
#endif
//定义日志的级别 INFO(跟踪过程) ERROR(不影响程序执行的错误) FATAL(致命错误) DEBUG(调试信息)
enum LogLevel
{
INFO, //普通信息
ERROR, //错误信息
FATAL, //core信息
DEBUG //调试信息
};
//日志类
class Logger : noncopyable
{
public:
//获取日志单例对象
static Logger& instance();
//设置日志级别
void setLogLevel(int level);
//写日志
void log(std::string msg);
private:
int logLevel_; //日志级别
Logger(){} //单例私有构造函数
};
Logger.cc
#include "Logger.h"
#include "Timestamp.h"
#include
//获取日志单例对象
Logger& Logger::instance()
{
static Logger logger;
return logger;
}
//设置日志级别
void Logger::setLogLevel(int level)
{
logLevel_ = level;
}
//写日志 格式:[级别信息] time : msg
void Logger::log(std::string msg)
{
//打印日志类型
switch (logLevel_)
{
case INFO:
std:: cout << "[INFO]";
break;
case ERROR:
std:: cout << "[ERROR]";
break;
case FATAL:
std:: cout << "[FATAL]";
break;
case DEBUG:
std:: cout << "[DEBUG]";
break;
default:
break;
}
//打印时间
std::cout << Timestamp::now().toString() << " : " << msg << std::endl;
}
日志需要输出当前时间,再来看muduo的时间类。因为在muduo中,时间类Timestamp并不是其核心设计,我们就简单实现就好了。
Timestamp.h
#pragma once
#include
#include
class Timestamp
{
public:
Timestamp();
//explicit表示不接受隐式转换
explicit Timestamp(int64_t microSecondsSinceEpoch);
static Timestamp now();
std::string toString() const;
private:
int64_t microSecondsSinceEpoch_;
};
Timestamp.cc
#include "Timestamp.h"
#include
Timestamp::Timestamp()
:microSecondsSinceEpoch_(0)
{}
Timestamp::Timestamp(int64_t microSecondsSinceEpoch)
:microSecondsSinceEpoch_(microSecondsSinceEpoch)
{}
Timestamp Timestamp::now()
{
return Timestamp(time(NULL));
}
std::string Timestamp::toString() const
{
char buf[128] = {0};
tm * tm_time = localtime(µSecondsSinceEpoch_);
snprintf(buf,128,"%4d/%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);
return buf;
}
//时间类测试
// #include
// int main()
// {
// std::cout <
// }
muduo的InetAddress类封装了socket地址类型sockaddr_in,IPv4和IPv6都支持,我们只需实现IPv4的封装即可。
InetAddress.h
#pragma once
#include
#include
#include
class InetAddress
{
public:
explicit InetAddress(uint16_t port = 0, std::string ip = "127.0.0.1");
explicit InetAddress(const sockaddr_in &addr)
:addr_(addr)
{}
std::string toIp() const;
std::string toIpPort() const;
uint16_t toPort() const;
const sockaddr_in* getSockAddr() const {return &addr_;}
void setSockAddr(const sockaddr_in &addr) { addr_ = addr; }
private:
sockaddr_in addr_;
};
InetAddress.cc
#include "InetAddress.h"
#include
#include
#include
InetAddress::InetAddress(uint16_t port, std::string ip)
{
bzero(&addr_, sizeof addr_ );
addr_.sin_family = AF_INET;
addr_.sin_port = htons(port);
addr_.sin_addr.s_addr = inet_addr(ip.c_str());
}
std::string InetAddress::toIp() const
{
char buf[64] = {0};
::inet_ntop(AF_INET, &addr_.sin_addr,buf,sizeof buf );
return buf;
}
std::string InetAddress::toIpPort() const
{
char buf[64] = {0};
::inet_ntop(AF_INET, &addr_.sin_addr,buf,sizeof buf );
size_t end = strlen(buf);
uint16_t port = ntohs(addr_.sin_port);
sprintf(buf+end, ":%u", port);
return buf;
}
uint16_t InetAddress::toPort() const
{
return ntohs(addr_.sin_port);
}
// 测试
// int main()
// {
// InetAddress addr(8080);
// std::cout << addr.toIpPort() <
// return 0;
// }
muduo的Socket类封装了Socket类型和操作以及设置Tcp选项相关操作。
重写Socket.h:
#pragma once
#include "noncopyable.h"
class InetAddress;
class Socket : noncopyable
{
public:
explicit Socket(int sockfd)
:sockfd_(sockfd)
{}
~Socket();
int fd() const { return sockfd_; }
void bindAddress(const InetAddress &localaddr);
void listen();
int accept(InetAddress* peeraddr);
void shutdownWrite();
void setTcpNoDelay(bool on);
void setReuseAddr(bool on);
void setReusePort(bool on);
void setKeepAlive(bool on);
private:
const int sockfd_;
};
重写Socket.cc:
#include "Socket.h"
#include "Logger.h"
#include "InetAddress.h"
#include
#include
#include
#include
#include
Socket::~Socket()
{
close(sockfd_);
}
void Socket::bindAddress(const InetAddress &localaddr)
{
if(0 != bind(sockfd_, (sockaddr*)localaddr.getSockAddr(), sizeof(sockaddr_in)))
{
LOG_FATAL("bind sockfd:%d fail \n", sockfd_);
}
}
void Socket::listen()
{
if(0 != ::listen(sockfd_, 1024))
{
LOG_FATAL("listen sockfd:%d fail \n", sockfd_);
}
}
int Socket::accept(InetAddress* peeraddr)
{
/**
* 1、accept函数参数不合法
* 2、对返回的connfd没有设置非阻塞
* Reactor模型 one loop per thread =》 poller + nonblocking IO
sockaddr_in addr;
bzero(&addr, sizeof addr);
socklen_t len;
int connfd = ::accept(sockfd_, (sockaddr*)&addr, &len);
if(connfd >= 0)
{
peeraddr->setSockAddr(addr);
}
return connfd;
*/
sockaddr_in addr;
bzero(&addr, sizeof addr);
socklen_t len = sizeof addr;
int connfd = ::accept4(sockfd_, (sockaddr*)&addr, &len, SOCK_NONBLOCK | SOCK_CLOEXEC);
if(connfd >= 0)
{
peeraddr->setSockAddr(addr);
}
return connfd;
}
void Socket::shutdownWrite()
{
if(::shutdown(sockfd_, SHUT_WR) < 0)
{
LOG_ERROR("socket::shotdownWrite error");
}
}
void Socket::setTcpNoDelay(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof optval);
}
void Socket::setReuseAddr(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
}
void Socket::setReusePort(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof optval);
}
void Socket::setKeepAlive(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof optval);
}
前期准备完成,接下来就是muduo核心模块的编写了。