muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor
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指定地址的套接字。
addr
和addrlen
服务端地址和长度.
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的更多相关文章
- muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制
目录 muduo网络库学习笔记(四) 通过eventfd实现的事件通知机制 eventfd的使用 eventfd系统函数 使用示例 EventLoop对eventfd的封装 工作时序 runInLoo ...
- muduo网络库学习笔记(三)TimerQueue定时器队列
目录 muduo网络库学习笔记(三)TimerQueue定时器队列 Linux中的时间函数 timerfd简单使用介绍 timerfd示例 muduo中对timerfd的封装 TimerQueue的结 ...
- muduo网络库学习笔记(10):定时器的实现
传统的Reactor通过控制select和poll的等待时间来实现定时,而现在在Linux中有了timerfd,我们可以用和处理IO事件相同的方式来处理定时,代码的一致性更好. 一.为什么选择time ...
- muduo 网络库学习之路(一)
前提介绍: 本人是一名大三学生,主要使用C++开发,兴趣是高性能的服务器方面. 网络开发离不开网络库,所以今天开始学一个新的网络库,陈老师的muduo库 我参考的书籍就是陈老师自己关于muduo而编著 ...
- muduo网络库学习之MutexLock类、MutexLockGuard类、Condition类、CountDownLatch类封装中的知识点
一.MutexLock 类 class MutexLock : boost::noncopyable 二.MutexLockGuard类 class MutexLockGuard : bo ...
- Linux多线程服务端编程 使用muduo C++网络库 学习笔记 日志log
代码来自陈硕开源代码库 muduo中 地址是https://github.com/chenshuo/muduo #pragma once #include <string> #define ...
- Struts2学习笔记五 拦截器
拦截器,在AOP中用于在某个方法或字段被访问之前,进行拦截,然后在之前或之后加入某些操作.拦截是AOP的一种实现策略. Struts2中,拦截器是动态拦截Action调用的对象.它提供了一种机制可以使 ...
- python学习笔记(五):装饰器、生成器、内置函数、json
一.装饰器 装饰器,这个器就是函数的意思,连起来,就是装饰函数,装饰器本身也是一个函数,它的作用是用来给其他函数添加新功能,比如说,我以前写了很多代码,系统已经上线了,但是性能比较不好,现在想把程序里 ...
- 网络协议学习笔记(五)套接字Socket
概述 前面学习网络知识的时候写过一篇关于套接字的随笔见<JAVA SOCKET 详解>,现在本人正在系统的学习网络知识,现在除了温故知新之外,在详细的学习记录一下套接字的知识. Socke ...
随机推荐
- TIDB单机多实例进程
TIDB节点: TIKV节点(tidb服务也有放在这里也有) tidb进程 tikv进程 当使用单机多实例(就是一个机器多个tikv的存储节点)的时候,每个实例都有对应的一个进程,这个进程号就是我们在 ...
- python reload(sys)找不到,name 'reload' is not defined和Python3异常-AttributeError: module 'sys' has no att
基于python3.6.1版本,在一个.py文件中,加入这3行:import requests, re, sysreload(sys)sys.setdefaultencoding("utf- ...
- Python glob.md
glob 即使glob API非常简单, 但这个模块包含了很多的功能. 在很多情况下, 尤其是你的程序需要寻找出文件系统中, 文件名匹配特定模式的文件时, 是非常有用的. 如果你需要包含一个特定扩展名 ...
- 异步IO的概念
同步IO是阻塞IO: 异步IO分为两种:1.主动查询是否有数据:2.被动监听是否有数据状态. https://www.cnblogs.com/euphie/p/6376508.html
- 2017-2018-2 20165318 实验四《Android程序设计》实验报告
2017-2018-2 20165318 实验四<Android程序设计>实验报告 一.实验报告封面 课程:Java程序设计 班级:1653班 姓名:孙晓暄 ...
- docker构建Java环境
FROM java:7 COPY . /usr/src/javaapp WORKDIR /usr/src/javaapp RUN javac HelloWorld.java CMD ["ja ...
- 人人都是产品经理<1.0>
用了大概2个月的时间,细细的读完了<人人都是产品经理>这本书,受益良多,期间也做了一些笔记,都在前面的博客————products系列中... 当然,更多的收获,还是沉滞在书中的注释,以及 ...
- Java中的单利模式介绍
单利模式:本来是不准备写的,但是最近发现好多公司面试时都会或多或少的提到单利模式,因此今天把单利模式拉出来说说. 定义:只包含一个被称为单例类的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且 ...
- pv,uv的意义
PV(page view),即页面浏览量,或点击量;通常是衡量一个网络新闻频道或网站甚至一条网络新闻的主要指标. 高手对pv的解释是,一个访问者在24小时(0点到24点)内到底看了你网站几个页面.这里 ...
- 所谓的液晶屏驱动IC是单独的IC还是在屏内就集成
所谓的液晶屏驱动IC是单独的IC还是在屏内就集成 时间:2016-12-05 作者:admin 其实无论什么液晶屏,想要正常工作必须包括两个人:玻璃屏+驱动IC:但是现在有一些液晶厂商他们不 ...