muduo网络库源码复现笔记(二十一):Acceptor类、InetAddress类、Sockets类、SocketsOps.cc

Muduo网络库简介

muduo 是一个基于 Reactor 模式的现代 C++ 网络库,作者陈硕。它采用非阻塞 IO 模型,基于事件驱动和回调,原生支持多核多线程,适合编写 Linux 服务端多线程网络应用程序。
muduo网络库的核心代码只有数千行,在网络编程技术学习的进阶阶段,muduo是一个非常值得学习的开源库。目前我也是刚刚开始学习这个网络库的源码,希望将这个学习过程记录下来。这个网络库的源码已经发布在GitHub上,可以点击这里阅读。目前Github上这份源码已经被作者用c++11重写,我学习的版本是没有使用c++11版本的。不过二者大同小异,核心思想是没有变化的。点这里可以看我的源代码。从笔记十七开始记录muduo的net库的实现过程。如果你需要看一下基础库(base)的复现过程,可以点击这里:muduo的base库实现过程。而网络库的笔记在这里:
muduo网络库源码复现笔记(十七):什么都不做的EventLoop
muduo网络库源码复现笔记(十八):Reactor的关键结构
muduo网络库源码复现笔记(十九):TimeQueue定时器
muduo网络库源码复现笔记(二十):EventLoop::runInloop()函数和EventLoopThread类

0 介绍

通过前面几节的内容,Reactor事件处理框架已经搭的差不多了。从本节开始,我们可以实现一个非阻塞的TCP网络编程库。首先是要封装Acceptor类作为服务器的监听者,但是封装之前我们要做许多预备工作。

1 SocektsOps

SocketsOps中对Sockets套接字Api做了一些封装,包括socket、bind、listen、accept、connect、close等。这些封装不难,阅读代码即可明白它的意思。如果你对这些Api不是很熟悉,可以看这篇文章。其中比较值得注意的是createNonblockingOrDie方法,它可以返回一个非阻塞和close-on-exec的文件描述符。

int sockets::createNonblockingOrDie()
{
     
#if VALGRIND
	int sockfd = ::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
	if(sockfd < 0)
	{
     
		LOG_SYSFATAL << "sockets::createNonBlockingOrDie";
	}
	setNonBlockingAndCloseOnExec(sockfd);
#else	 
	//after Linux 2.6.27
	int sockfd = ::socket(AF_INET,
				 SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC,IPPROTO_TCP);
	if(sockfd < 0)
	{
     
		LOG_SYSFATAL << "sockets::createNonBlockingOrDie";
	}
#endif
	return sockfd;
}

2 InetAddress类

InetAddress类实际上是对struct sockaddr_in的封装。看一下它的构造函数,可以只传入端口号,那么addr_的ip将会是本机(INADDR_ANY)。也可以传入ip和端口号来进行设置。由于muduo只支持IPv4,所以通信domain均为AF_INET。其余函数见注释。

class InetAddress : public muduo::copyable
{
     
public:

	//mostly uesd in TcpServer listening
	explicit InetAddress(uint16_t port);
	
	InetAddress(const StringPiece& ip,uint16_t port);

	InetAddress(const struct sockaddr_in& addr)
		:	addr_(addr)
	{
      }
	
	string toIp() const;//返回地址Ip(字符串形式)
	string toIpPort() const;//返回地址和端口号(字符串形式)

	string toHostPort() const __attribute__((deprecated))//弃用
	{
      return toIpPort(); }

	const struct sockaddr_in& getSockAddrInet() const {
     return addr_;}

	void setSockAddrInet(const struct sockaddr_in& addr) {
     addr_ = addr;}

	uint32_t ipNetEndian() const {
     return addr_.sin_addr.s_addr;}
	uint16_t portNetEndian() const {
     return addr_.sin_port;}

private:
	struct sockaddr_in addr_;
};

3 Socket类

Socket类是对文件描述符的封装,它的成员函数依然多是对SocketOps的调用。

class Socket : boost::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();

	//禁用 Nagle?
	void setTcpNoDelay(bool on);
	//端口复用
	void setReuseAddr(bool on);

	void setKeepAlive(bool on);
private:
	const int sockfd_;
};

4 Acceptor

Acceptor用于接受新的TCP连接,通过回调通知使用者。它由TcpServer(后面讲)使用。它的封装原理类似于TimerQueue,我们使用它的时候,首先将它注册到EvetLoop中,Acceptor的acceptChannel_将加入Eventloop的Poller中,至于我们就可以监听来自其他Socket的连接了。

class Acceptor : boost::noncopyable
{
     
public:
	typedef boost::function<void(int sockfd,
							const InetAddress&)> NewConnectionCallback;
	Acceptor(EventLoop* loop,const InetAddress& listenAddr);
	~Acceptor();

	void setNewConnectionCallback(const NewConnectionCallback& cb)
	{
      newConnectionCallback_ = cb; }

	bool listenning() const {
     return listenning_;}
	void listen();
private:
	void handleRead();
	
	EventLoop* loop_;
	Socket acceptSocket_;
	Channel acceptChannel_;
	NewConnectionCallback newConnectionCallback_;
	bool listenning_;
	int idleFd_;
};

handleRead是作为acceptChannel_的回调函数使用的,NewConnectionCallback是连接之后执行的回调。另外值得注意的是muduo对于文件描述符耗尽的情况的处理,它的方法是预先准备一个文件描述符idleFd_,如果出现耗尽的情况,则使用这个文件描述符接受,然后立刻断开连接,然后重新使用idleFd_打开空白文件。

void Acceptor::handleRead()
{
     
	loop_ -> assertInLoopThread();
	InetAddress peerAddr(0);
	int connfd = acceptSocket_.accept(&peerAddr);
	if(connfd >= 0)
	{
     
		if(newConnectionCallback_)
		{
     
			newConnectionCallback_(connfd,peerAddr);
		}
		else
		{
     
			sockets::close(connfd);
		}	
	}
	else
	{
     
		if(errno = EMFILE)
		{
     
			::close(idleFd_);
			idleFd_ = ::accept(acceptSocket_.fd(),NULL,NULL);
			::close(idleFd_);
			idleFd_ = ::open("/dev/null",O_RDONLY | O_CLOEXEC);
		}
	}
}

你可能感兴趣的:(muduo,网络,c++,linux,后端)