重写Muduo库实现核心模块的Git仓库
注:本文将重点剖析 Muduo
网络库的核心框架,深入探讨作者精妙的代码设计思路,并针对核心代码部分进行重写,将原本依赖 boost
的实现替换为原生的 C++11 语法。需要说明的是,本文并不打算对整个 Muduo
库进行完整的重写。Muduo库源码链接
在前几篇博客中,我们已经详细讲解了以下模块:
epoll
的事件分发器具体实现。EventLoop
提供专属线程。EventLoop
线程,实现负载均衡。这些模块构成了 Muduo
网络库的核心基础,并在高性能网络编程中扮演着不可或缺的角色。
网络模块的解析将分为两部分进行。在本篇文章中,我们将重点讲解以下模块:
而 TcpConnection
和 TcpServer
这两个模块会在后续文章中进行详细解析。这两个模块是 Muduo
网络库的核心所在,对理解其设计至关重要,请大家务必关注后续内容!
InetAddress
对IP和Port进行封装,并提供将其转化为string
的方法。
类图如下所示:
此类理解起来较为容易,故不做讲解。
class InetAddress
{
public:
InetAddress() = default;
InetAddress(u_int16_t port,std::string ip = "127.0.0.1");
explicit InetAddress(const sockaddr_in& addr) : addr_(addr){}
std::string toIp() const;
std::string toIpPort() const;
u_int16_t toPort() const;
const sockaddr* getSockAddr() const { return (sockaddr*)&addr_; }
void setSockAddr(const sockaddr_in& addr) { addr_ = addr;}
private:
struct sockaddr_in addr_;
};
Socket
对网络套接字进行了封装,提供了创建、管理和操作套接字的接口,并包含了一系列修改套接字选项的方法。此外,它还为网络编程中的常用操作(如绑定、监听、关闭等)提供了简洁易用的封装,从而简化了套接字的使用。
类图如下:
class Socket : noncopyable
{
public:
explicit Socket(int sockfd) : sockfd_(sockfd) {}
~Socket();
int fd() const { return sockfd_; }
void bindAddress(const InetAddress& localaddr);
void listen();
int accpet(InetAddress* peeraddr);
void shutdownWrite();
void setTcpNoDelay(bool on);
void setReuseAddr(bool on);
void setReusePort(bool on);
void setKeepAlive(bool on);
private:
const int sockfd_;
};
解析:
setTcpNoDelay
:禁止Nagle算法
setReuseAddr
:允许重用本地地址
setReusePort
:允许重用本地端口
setKeepAlive
:周期性测试连接是否存货
Socket::~Socket()
{
::close(sockfd_);
}
void Socket::bindAddress(InetAddress const &localaddr)
{
if( 0 != ::bind(sockfd_, localaddr.getSockAddr(), sizeof(sockaddr)))
{
LOG_FATAL("bind sockfd:%d fail", sockfd_);
}
}
void Socket::listen()
{
if(0 != ::listen(sockfd_, 1024))
{
LOG_FATAL("listen sockfd:%d fail", sockfd_);
}
}
int Socket::accpet(InetAddress *peeraddr)
{
sockaddr_in addr;
socklen_t len;
bzero(&addr, sizeof addr);
int connfd = accept(sockfd_, (sockaddr*)&addr, &len);
if(connfd >= 0)
{
peeraddr->setSockAddr(addr);
}
return connfd;
}
void Socket::shutdownWrite()
{
if(0 != ::shutdown(sockfd_, SHUT_WR))
{
LOG_ERROR("shutdownWrite error ");
}
}
void Socket::setTcpNoDelay(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY,
&optval, static_cast<socklen_t>(sizeof optval));
// FIXME CHECK
}
void Socket::setReuseAddr(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR,
&optval, static_cast<socklen_t>(sizeof optval));
// FIXME CHECK
}
void Socket::setReusePort(bool on)
{
#ifdef SO_REUSEPORT
int optval = on ? 1 : 0;
int ret = ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT,
&optval, static_cast<socklen_t>(sizeof optval));
if (ret < 0 && on)
{
LOG_ERROR("SO_REUSEPORT failed.");
}
#else
if (on)
{
LOG_ERROR << "SO_REUSEPORT is not supported.";
}
#endif
}
void Socket::setKeepAlive(bool on)
{
int optval = on ? 1 : 0;
::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE,
&optval, static_cast<socklen_t>(sizeof optval));
// FIXME CHECK
}
Acceptor
是运行在MainLoop
里负责监听并接受客户端的连接,并提供设置新连接回调函数的方法setNewConnectionCallback(const NewConnectionCallback& cb)
,以便客户端连接后可以调用此回调函数。类图如下:
class Acceptor : noncopyable
{
public:
using NewConnectionCallback = std::function<void(int, const InetAddress&)>;
Acceptor(EventLoop* loop, const InetAddress& ListenAddr, bool reuseport);
~Acceptor();
void setNewConnectionCallback(const NewConnectionCallback& cb)
{
NewConnectionCallback_ = std::move(cb);
}
bool listening() const { return listening_; }
void listen();
private:
void handleRead();
EventLoop* loop_;
Socket acceptSocket_;
Channel acceptChannel;
NewConnectionCallback NewConnectionCallback_;
bool listening_;
};
解析:
成员变量:
loop_
:MainLoop
,主线程中的 EventLoop
,负责管理和分发事件。acceptSocket_
:封装了监听套接字的 Socket
对象,用于接受新连接。acceptChannel_
:与监听套接字关联的 Channel
,负责将监听套接字的事件(如新连接到来)分发到注册的回调函数中处理。成员方法:
listen()
:开启监听套接字handleRead()
:注册到acceptChannel
的读回调函数,当有新连接到来时(读事件),执行此函数static int CreateNonblocking()
{
int sockfd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
if(sockfd < 0)
{
LOG_FATAL("%s:%s:%d listen socket create error:%d", __FILE__, __FUNCTION__, __LINE__, errno);
}
return sockfd;
}
Acceptor::Acceptor(EventLoop *loop, InetAddress const &ListenAddr,
bool reuseport) :
loop_(loop),
acceptSocket_(CreateNonblocking()),
acceptChannel(loop_, acceptSocket_.fd()),
listening_(false)
{
acceptSocket_.setReuseAddr(true);
acceptSocket_.setReusePort(true);
acceptSocket_.bindAddress(ListenAddr);
acceptChannel.setReadCallback(std::bind(&Acceptor::handleRead, this));
}
Acceptor::~Acceptor()
{
acceptChannel.disableAll();
acceptChannel.remove();
}
void Acceptor::listen()
{
listening_ = true;
acceptSocket_.listen();
acceptChannel.enableReading();
}
void Acceptor::handleRead()
{
InetAddress peerAddr;
int connfd = acceptSocket_.accpet(&peerAddr);
if(connfd >= 0)
{
if(NewConnectionCallback_)
{
NewConnectionCallback_(connfd, peerAddr);
}
else
{
::close(connfd);
}
}
else
{
LOG_ERROR("%s:%s:%d listen socket accept error:%d", __FILE__, __FUNCTION__, __LINE__, errno);
if(errno == EMFILE)
{
LOG_ERROR("%s:%s:%d listen socket limit error:%d", __FILE__, __FUNCTION__, __LINE__, errno);
}
}
}
到这里,InetAddress
、Socket
、Acceptor
这三个模组就讲完了,这三个模块内容不多,也不难,但是是网络模块的基础部分。下一节的TcpServer
和TcpConnection
是本系列的重中之重,请大家记得订阅!