muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor

标签: muduo Connector Acceptor


本篇继续为前面封装的EventLoop添加事件,到现在共给EventLoop添加了两个fd,Timerfd,EventFd分别用于处理定时任务和通知事件.

今天添加的Acceptor会增加另一个fd,此fd是是一个socket,用于监听套接字连接.同时封装非组赛网络编程中的connect(2)的使用Connector.

Connector

在非阻塞网络编程中,发起连接的基本方式是调用connect(2),当socket变得可写时表明连接建立完毕,其中要处理各种类型的错误,我们把它封装为Connector class.

Connector 和 Acceptor 设计思路基本一致,只是Acceptor通过判断套接字是否可读来执行回调,而Connector是判断套接字是否可写来执行回调.

还有一点就是错误处理,socket可写不一定就是连接建立好了 , 当连接建立出错时,套接口描述符变成既可读又可写,这时我们可以通过调用getsockopt来得到套接口上待处理的错误(SO_ERROR).

其次非阻塞网络编程中connect(2)的sockfd是一次性的,一旦出错(比如对方拒绝连接),就无法恢复,只能关闭重来。但Connector是可以反复使用的, 因此每次尝试连接都要使用新的socket文件描述符和新的Channel对象。要注意的就是Channel的生命期管理了.

系统函数connect

   #include <sys/types.h>          /* See NOTES */
#include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);

sockfd 试图制作的一个连接到被绑定到addr指定地址的套接字。

addraddrlen 服务端地址和长度.

retrun:

成功 返回0 , 失败 返回 -1.

处理非阻塞connect的步骤:

第一步:创建非阻塞socket,返回套接口描述符;

第二步:connect(2)开始建立连接;

第三步:判断连接是否成功建立:

A:如果connect返回0,表示连接建立成功, 如果错误为EINPROGRESS 表示连接正在进行,可以等待select()变的可写,通过getsockopt()来来得到套接口上待处理的错误(SO_ERROR),连接是否建立成功.如果连接建立成功,这个错误值将是0,如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等).

B: EAGAIN、EADDRINUSE、EADDRNOTAVAIL、ECONNREFUSED、ENETUNREACH 像EAGAIN 这类表明本机临时端口暂时用完的错误、可以尝试重连。

C: EACCES、EPERM、EAFNOSUPPORT、EALREADY、EBADF、EFAULT、ENOTSOCK 其他真错误像无权限,协议错误,等直接关闭套接字.

Connector正是按这个步骤处理的连接.

暴露的接口只有start()和stop()

start()执行上述connect的步骤.

stop()关闭套接字,删除注册的通道,停止进行连接.

class Connector
{
public:
typedef std::function<void (int sockfd)> NewConnectionCallback; Connector(EventLoop* loop, const InetAddress& serverAddr);
~Connector(); void setNewConnectionCallback(const NewConnectionCallback& cb)
{ m_newConnectionCallBack = cb; } void start();// can be called in any thread
void stop(); // can be called in any thread private: enum States { kDisconnected, kConnecting, kConnected };
static const int kMaxRetryDelayMs = 30*1000;
static const int kInitRetryDelayMs = 500; void connect();
void connecting(int sockfd); void handleWrite();
void handleError(); void retry(int sockfd);
int removeAndResetChannel();
void resetChannel(); void setState(States s) { m_state = s; }
void startInLoop();
void stopInLoop(); EventLoop* p_loop;
int m_retryDelayMs;
InetAddress m_serverAddr; States m_state; std::unique_ptr<Channel> p_channel;
NewConnectionCallback m_newConnectionCallBack;
};

Connetor时序图

Connector::Connector(EventLoop* loop, const InetAddress& serverAddr)
:p_loop(loop),
m_serverAddr(serverAddr),
m_state(kDisconnected),
m_retryDelayMs(kInitRetryDelayMs)
{
LOG_DEBUG << "ctor[" << this << "]";
} Connector::~Connector()
{
LOG_DEBUG << "dtor[" << this << "]";
assert(!p_channel);
} void Connector::start()
{ p_loop->runInLoop(std::bind(&Connector::startInLoop, this));
} void Connector::startInLoop()
{
p_loop->assertInLoopThread();
assert(m_state == kDisconnected); connect();
} void Connector::stop()
{
p_loop->queueInLoop(std::bind(&Connector::stopInLoop, this));
} void Connector::stopInLoop()
{
p_loop->assertInLoopThread(); if(m_state == kConnecting)
{
int sockfd = removeAndResetChannel();
sockets::close(sockfd);
setState(kDisconnected);
}
} void Connector::connect()
{
int sockfd = sockets::createNonblockingOrDie(m_serverAddr.family());
int ret = sockets::connect(sockfd, m_serverAddr.getSockAddr());
int savedErrno = (ret == 0) ? 0 : errno; if(ret != 0) LOG_TRACE << "connect error ("<< savedErrno << ") : " << strerror_tl(savedErrno); switch(savedErrno)
{
case 0:
case EINPROGRESS: //Operation now in progress
case EINTR: //Interrupted system call
case EISCONN: //Transport endpoint is already connected
connecting(sockfd);
break; case EAGAIN:
case EADDRINUSE:
case EADDRNOTAVAIL:
case ECONNREFUSED:
case ENETUNREACH:
retry(sockfd);
LOG_SYSERR << "reSave Error. " << savedErrno;
break; case EACCES:
case EPERM:
case EAFNOSUPPORT:
case EALREADY:
case EBADF:
case EFAULT:
case ENOTSOCK:
LOG_SYSERR << "connect error in Connector::startInLoop " << savedErrno;
sockets::close(sockfd);
break; default:
LOG_SYSERR << "Unexpected error in Connector::startInLoop " << savedErrno;
sockets::close(sockfd);
// connectErrorCallback_();
break;
} } void Connector::connecting(int sockfd)
{
LOG_TRACE << "Connector::connecting] sockfd : " << sockfd;
setState(kConnecting);
assert(!p_channel);
p_channel.reset(new Channel(p_loop, sockfd));
p_channel->setWriteCallBack(std::bind(&Connector::handleWrite, this));
//p_channel->setErrorCallback() //enableWriting if Channel Writeable ,Connect Success.
p_channel->enableWriting();
} void Connector::retry(int sockfd)
{
sockets::close(sockfd);
setState(kDisconnected); LOG_INFO << "Connector::retry - Retry connecting to " << m_serverAddr.toIpPort()
<< " in " << m_retryDelayMs << " milliseconds. "; p_loop->runAfter(m_retryDelayMs/1000.0, std::bind(&Connector::startInLoop, this));
m_retryDelayMs = std::min(m_retryDelayMs * 2, kMaxRetryDelayMs);
} int Connector::removeAndResetChannel()
{
p_channel->disableAll();
p_channel->remove(); int sockfd = p_channel->fd(); p_loop->queueInLoop(std::bind(&Connector::resetChannel, this)); return sockfd;
} void Connector::resetChannel()
{
LOG_TRACE << "Connector::resetChannel()";
p_channel.reset();
} void Connector::handleWrite()
{
LOG_TRACE << "Connector::handleWrite "; if(m_state == kConnecting)
{
int sockfd = removeAndResetChannel();
int err = sockets::getSocketError(sockfd); if(err)
{
LOG_WARN << "Connector::handleWrite - SO_ERROR = "
<< err << " " << strerror_tl(err);
retry(sockfd);
}
/*else if (sockets::isSelfConnect(sockfd))
{ }*/
else
{
setState(kConnected);
m_newConnectionCallBack(sockfd);
} }
else
{
assert(m_state == kDisconnected);
} } void Connector::handleError()
{
LOG_ERROR << "Connector::handleError States " << m_state; if(m_state == kConnecting)
{
int sockfd = removeAndResetChannel();
int err = sockets::getSocketError(sockfd);
LOG_TRACE << "SOCK_ERROR = " << err << " " << strerror_tl(err);
retry(sockfd);
}
}

Acceptor

相较于Connector更简单,只要有socket可读,即可确认连接建立.

系统函数accept

#include <sys/types.h>          /* See NOTES */

include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

#define _GNU_SOURCE             /* See feature_test_macros(7) */

       #include <sys/socket.h>

int accept4(int sockfd, struct sockaddr *addr,

                   socklen_t *addrlen, int flags);

sockfd socket(2)创建的文件描述符, 且已被bind(2)绑定本地地址,listen(2)使能监听.

addr 用于填充远端套接字地址, 如果不需要知道远端地址,可以添NULL.

addrlen 用于填充远端地址大小.

flags

如果flags为0  等同于 accept.

SOCK_NONBLOCK  在新打开的文件描述符设置 O_NONBLOCK 标记。在 fcntl(2) 中保存这个标记可以得到相同的效果。

SOCK_CLOEXEC  在新打开的文件描述符里设置 close-on-exec (FD_CLOEXEC) 标记。参看在open(2)里关于 O_CLOEXEC标记的描述来了解这为什么有用。

int connfd = ::accept4(sockfd, (struct sockaddr *)(addr),

                         &addrlen, SOCK_NONBLOCK | SOCK_CLOEXEC);

 

flags 会对返回的fd  connfd  设置SOCK_NONBLOCK | SOCK_CLOEXEC 标记.

如果用于监听的文件描述符没有设置nonblocking标志,且监听队列上没有挂起的连接, accept()会阻塞直到有新的连接到来. 如果此socket设置了nonblocking标记,accept() 会立即返回失败并设置 error 为 EAGAIN or EWOULDBLOCK.

Socket的封装

Socket类封装一个套接字 fd 析构的时候close 管理套接字的生命期.

class Socket{
public:
explicit Socket(int sockfd) : m_sockfd(sockfd) { }
~Socket(); int fd() const { return m_sockfd; } void bindAddress(const InetAddress& localaddr);
void listen();
int accept(int sockfd, struct sockaddr_in6* addr); int accept(InetAddress* peeraddr); private:
const int m_sockfd;
};

Acceptor的封装

Acceptor的数据成员包含Socket和Channel,Acceptor的Socket是服务端的监听socket,Channel用于观察此socket上的readable事件.并回调Acceptor:: handleRead(),handleRead()会调用accept(2)来接受新连接, 并回调用户callback。

class Acceptor{
public:
typedef std::function<void (int sockfd, const InetAddress&)> NewConnectionCallBack; Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport = true);
~Acceptor(); void listen();
bool listenning() const { return m_listenning; } // get listen status. void setNewConnectionCallBack(const NewConnectionCallBack& cb) { m_newConnectionCallBack = cb; } private:
void handleRead(); //处理新到的连接. EventLoop* p_loop;
Socket m_acceptSocket;
Channel m_acceptChannel;
NewConnectionCallBack m_newConnectionCallBack;
bool m_listenning;
int m_idleFd;
};

Acceptor时序图.

Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
:p_loop(loop),
m_acceptSocket(sockets::createNonblockingOrDie(listenAddr.family())),
m_acceptChannel(loop, m_acceptSocket.fd()),
m_listenning(false),
m_idleFd(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
assert(m_idleFd >= 0);
m_acceptSocket.setReuseAddr(true);
m_acceptSocket.setReuseAddr(reuseport);
m_acceptSocket.bindAddress(listenAddr);
m_acceptChannel.setReadCallBack(
std::bind(&Acceptor::handleRead, this));
} Acceptor::~Acceptor()
{
m_acceptChannel.disableAll();
m_acceptChannel.remove();
::close(m_idleFd);
} void Acceptor::listen()
{
p_loop->assertInLoopThread();
m_listenning = true;
m_acceptSocket.listen();
m_acceptChannel.enableReading();
} void Acceptor::handleRead()
{
p_loop->assertInLoopThread();
InetAddress peerAddr;
int connfd = m_acceptSocket.accept(&peerAddr);
if(connfd >= 0)
{
if(m_newConnectionCallBack)
{
m_newConnectionCallBack(connfd, peerAddr);
}
else
{
sockets::close(connfd);
}
}
else
{
LOG_SYSERR << "in Acceptor::handleRead";
if(errno == EMFILE)
{
::close(m_idleFd);
m_idleFd = ::accept(m_acceptSocket.fd(), NULL, NULL);
::close(m_idleFd);
m_idleFd = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
}
}

简单测试程序

Acceptor

void newConnetion(int sockfd, const InetAddress& peeraddr)
{
LOG_DEBUG << "newConnetion() : accepted a new connection from";
::sockets::close(sockfd);
} int main()
{
InetAddress listenAddr(8888);
EventLoop loop;
Acceptor acceptor(&loop, listenAddr);
acceptor.setNewConnectionCallBack(newConnetion);
acceptor.listen(); loop.loop(); }

Connctor

EventLoop* g_loop;

void newConnetion(int sockfd)
{
LOG_DEBUG << "newConnetion() : Connected a new connection.";
sockets::close(sockfd);
g_loop->quit();
} int main()
{
EventLoop loop;
g_loop = &loop; InetAddress serverAddr("127.0.0.1", 8888);
Connector client(&loop, serverAddr);
client.setNewConnectionCallback(newConnetion);
client.start(); loop.loop(); }

运行日志

muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor的更多相关文章

  1. muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制

    目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...

  2. muduo网络库学习笔记(三)TimerQueue定时器队列

    目录 muduo网络库学习笔记(三)TimerQueue定时器队列 Linux中的时间函数 timerfd简单使用介绍 timerfd示例 muduo中对timerfd的封装 TimerQueue的结 ...

  3. muduo网络库学习笔记(10):定时器的实现

    传统的Reactor通过控制select和poll的等待时间来实现定时,而现在在Linux中有了timerfd,我们可以用和处理IO事件相同的方式来处理定时,代码的一致性更好. 一.为什么选择time ...

  4. muduo 网络库学习之路(一)

    前提介绍: 本人是一名大三学生,主要使用C++开发,兴趣是高性能的服务器方面. 网络开发离不开网络库,所以今天开始学一个新的网络库,陈老师的muduo库 我参考的书籍就是陈老师自己关于muduo而编著 ...

  5. muduo网络库学习之MutexLock类、MutexLockGuard类、Condition类、CountDownLatch类封装中的知识点

    一.MutexLock 类 class  MutexLock  :  boost::noncopyable 二.MutexLockGuard类 class  MutexLockGuard  :  bo ...

  6. Linux多线程服务端编程 使用muduo C++网络库 学习笔记 日志log

    代码来自陈硕开源代码库 muduo中 地址是https://github.com/chenshuo/muduo #pragma once #include <string> #define ...

  7. Struts2学习笔记五 拦截器

    拦截器,在AOP中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作.拦截是AOP的一种实现策略. Struts2中,拦截器是动态拦截Action调用的对象.它提供了一种机制可以使 ...

  8. python学习笔记(五):装饰器、生成器、内置函数、json

    一.装饰器 装饰器,这个器就是函数的意思,连起来,就是装饰函数,装饰器本身也是一个函数,它的作用是用来给其他函数添加新功能,比如说,我以前写了很多代码,系统已经上线了,但是性能比较不好,现在想把程序里 ...

  9. 网络协议学习笔记(五)套接字Socket

    概述 前面学习网络知识的时候写过一篇关于套接字的随笔见<JAVA SOCKET 详解>,现在本人正在系统的学习网络知识,现在除了温故知新之外,在详细的学习记录一下套接字的知识. Socke ...

随机推荐

  1. PHP 与 YAML

    PHP 与 YAML 这一段时间都没有写blog,并不是因为事情多,而是自己变懒了.看到新技术也不愿意深入思考其背后的原理,学习C++语言了近一个多月,由于学习方法有问题,并没有什么项目可以练手.靠每 ...

  2. MySQL Error Code文档手册---摘自MySQL官方网站

    This chapter lists the errors that may appear when you call MySQL from any host language. The first ...

  3. 前端工程构建工具之Yeoman

    一.Yeoman 简介 通常在开发新项目时我们都需要配置工程环境,开发目录,需要下载一些库.框架文件(如 jQuery.Backbone 等),配置编译环境(Less.Sass.Coffeescrip ...

  4. python中for、while循环、if嵌套的使用

    1.for循环字符串就是一个有序的字符序列for i in range(5):     print(i)定义一个死循环while True:     pass2.break和continue肯定需要和 ...

  5. ArcGIS API for JS4.7加载FeatureLayer,点击弹出信息并高亮显示

    我加载的是ArcGIS Server本地发布的FeatureService,ArcGIS API for JS4.7记载FeatureLayer时,在二维需要通过代码启用WebGL渲染,在三维模式下, ...

  6. Netty入门(九)空闲连接以及超时

    检测空闲连接和超时是为了及时释放资源.常见的方法是发送消息来测试一个不活跃的连接,通常称为“心跳”. Netty 提供了几个 ChannelHandler 来实现此目的,如下: 下面是 IdleSta ...

  7. [JSOI2009]球队收益

    题目 这题好神啊 我们发现一个球队的总比赛场数是确定的,设第\(i\)支球队一共进行了\(s_i\)场比赛 于是这个球队的收益就是\(c_i\times x^2+d_i(s_i-x)^2\) 我们拆开 ...

  8. springmvc与ajax交互常见问题

    这是我个人再编写博客系统的时候,因个人疏忽犯下的低级错误. 不过犯错是一件好事,有助于总结. 1.关于参数前加@RequestBody 如果是使用ajax交互时,必须要加上这个contentType: ...

  9. [转]VS2015+OpenCV3.3 GPU模块和opencv_contrib模块的编译以及采用CMake编译opencv_contrib时提示“No extra modules found in folder”问题的解决方案

    据官方说法,目前还不是太稳定的算法模块都在opencv_contrib里边,由于不稳定,所以不能在release版本里发行,只有在稳定以后才会放进release里边.但是这里边有很多我们经常要用的算法 ...

  10. VC++环境下单文档SDI与OpenGL多视图分割窗口的实现-类似3DMAX的主界面

    本文主要讲述如何在VC++环境下实现单文档SDI与OpenGL多视图分割窗口,最终的界面类似3DMAX的主界面.首先给出我实现的效果图: 整个实现过程网络上有很多零散的博文,请各位自行搜索,在基于对话 ...