Connector可以说是muduo库的连接器,负责客户端向服务器发起连接。实际上说白了就是封装了socket的connect操作。
Connector类的成员如下:
class Connector : boost::noncopyable,
public boost::enable_shared_from_this
{
public:
typedef boost::function NewConnectionCallback;
Connector(EventLoop* loop, const InetAddress& serverAddr);
~Connector();
void setNewConnectionCallback(const NewConnectionCallback& cb)
{ newConnectionCallback_ = cb; }
void start(); // can be called in any thread
void restart(); // must be called in loop thread
void stop(); // can be called in any thread
const InetAddress& serverAddress() const { return serverAddr_; }
private:
enum States { kDisconnected, kConnecting, kConnected };
static const int kMaxRetryDelayMs = 30*1000; //默认最大重连时间30000ms
static const int kInitRetryDelayMs = 500; //默认重连延迟时间500ms
void setState(States s) { state_ = s; }
void startInLoop();
void stopInLoop();
void connect();
void connecting(int sockfd);
void handleWrite();
void handleError();
void retry(int sockfd);
int removeAndResetChannel();
void resetChannel();
EventLoop* loop_; //所属的EventLoop
InetAddress serverAddr_; //服务器端的地址
bool connect_; // atomic
States state_; // FIXME: use atomic variable
boost::scoped_ptr channel_; //Connector所对应的Channel
NewConnectionCallback newConnectionCallback_; //连接成功回调函数
int retryDelayMs_; //重连延迟时间(单位ms)
};
构造函数是这样的:
Connector::Connector(EventLoop* loop, const InetAddress& serverAddr)
: loop_(loop),
serverAddr_(serverAddr),
connect_(false),
state_(kDisconnected),
retryDelayMs_(kInitRetryDelayMs) //初始化延时
{
LOG_DEBUG << "ctor[" << this << "]";
}
构造函数初始化了I/O线程,服务器地址,并设置为未连接状态以及初始化了重连延时时间。注意,这里的重连是指发起连接失败重连。
Connector启动流程是这样的:
//发起连接
void Connector::start()
{
connect_ = true;
loop_->runInLoop(boost::bind(&Connector::startInLoop, this)); // FIXME: unsafe
}
实际上调用了:
void Connector::startInLoop()
{
loop_->assertInLoopThread();
assert(state_ == kDisconnected);
if (connect_) //调用前必须connect_为true,start()函数中会这么做
{
connect(); //连接具体实现
}
else
{
LOG_DEBUG << "do not connect";
}
}
如果connect_为true,才真正开始连接,调用connect():
//连接实现
void Connector::connect()
{
//设置非阻塞,否则退出
int sockfd = sockets::createNonblockingOrDie(serverAddr_.family());
int ret = sockets::connect(sockfd, serverAddr_.getSockAddr());
int savedErrno = (ret == 0) ? 0 : errno;
switch (savedErrno) //检查错误码
{
case 0:
case EINPROGRESS: //非阻塞套接字,未连接成功返回码是EINPROGRESS表示正在连接
case EINTR:
case EISCONN: //连接成功
connecting(sockfd);
break;
case EAGAIN:
case EADDRINUSE:
case EADDRNOTAVAIL:
case ECONNREFUSED:
case ENETUNREACH:
retry(sockfd); //重连
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);
break;
}
}
这个函数虽然很长,但实际上就是设置套接字为非阻塞,然后底层调用socket的conenct()函数,进而判断errno采取相应的操作。
操作主要有三种情况,下面分三点讨论:
说明连接成功,调用connecting()函数,对成功的连接进行处理:
//如果连接成功
void Connector::connecting(int sockfd)
{
setState(kConnecting);
assert(!channel_);
//Channel与sockfd关联
channel_.reset(new Channel(loop_, sockfd));
//设置可写回调函数,这时候如果socket没有错误,sockfd就处于可写状态
channel_->setWriteCallback(
boost::bind(&Connector::handleWrite, this)); // FIXME: unsafe
//设置错误回调函数
channel_->setErrorCallback(
boost::bind(&Connector::handleError, this)); // FIXME: unsafe
// channel_->tie(shared_from_this()); is not working,
// as channel_ is not managed by shared_ptr
//关注可写事件
channel_->enableWriting();
}
总的来说,连接成功就是更改连接状态+设置各种回调函数+加入poller关注可写事件。
说明连接暂时失败,但是仍可能成功,需要重连。调用retry()函数:
//重连函数,采用back-off策略重连,也就是退避策略
//也就是重连时间逐渐延长,0.5s,1s,2s,...一直到30s
void Connector::retry(int sockfd)
{
sockets::close(sockfd); //先关闭连接
setState(kDisconnected);
if (connect_)
{
LOG_INFO << "Connector::retry - Retry connecting to " << serverAddr_.toIpPort()
<< " in " << retryDelayMs_ << " milliseconds. ";
//隔一段时间后重连,重新启用startInLoop
loop_->runAfter(retryDelayMs_/1000.0,
boost::bind(&Connector::startInLoop, shared_from_this()));
//间隔时间2倍增长
retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs);
}
else //超出最大重连时间后,输出连接失败
{
LOG_DEBUG << "do not connect";
}
}
3.彻底失败,返回errno为EACCES等错误码
这种情况只能关掉sockfd,因为再怎么试也成功不了的。
sockets::close(sockfd);
断开连接函数就不剖析了,相对比较简单,接下来看TcpClient类。
EventLoop* loop_;
ConnectorPtr connector_; // avoid revealing Connector
const string name_;
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
WriteCompleteCallback writeCompleteCallback_;
bool retry_; // atomic //是否重连,是指建立的连接成功后又断开是否重连。而Connector的重连是一直不成功是否重试的意思
bool connect_; // atomic
// always in loop thread
int nextConnId_; //name_+nextConnid_用于标识一个连接
mutable MutexLock mutex_;
TcpConnectionPtr connection_; // @GuardedBy mutex_ //Connector连接成功后,得到一个TcpConnection
需要注意的是,这里的重连是连接成功后又断开的重连,而不是Connector类的连不上一直尝试的重连。
构造函数:
TcpClient::TcpClient(EventLoop* loop,
const InetAddress& serverAddr,
const string& nameArg)
: loop_(CHECK_NOTNULL(loop)),
connector_(new Connector(loop, serverAddr)),
name_(nameArg),
connectionCallback_(defaultConnectionCallback),
messageCallback_(defaultMessageCallback),
retry_(false),
connect_(true),
nextConnId_(1)
{
//一旦连接建立连接,回调newConnection
connector_->setNewConnectionCallback(
boost::bind(&TcpClient::newConnection, this, _1));
}
构造函数创建了一个Connector,毕竟是TcpClient嘛,需要一个东西来发起连接。并且注册连接成功的回调函数。
它的使用方法,先connect:
void TcpClient::connect()
{
connect_ = true;
connector_->start();
}
底层就是调用我们上文中分析的Connector的一系列连接机制,向服务端发起连接。
连接成功后,就会调用自己的成员函数newConnection()函数:void TcpClient::newConnection(int sockfd)
{
loop_->assertInLoopThread();
InetAddress peerAddr(sockets::getPeerAddr(sockfd));
char buf[32];
snprintf(buf, sizeof buf, ":%s#%d", peerAddr.toIpPort().c_str(), nextConnId_);
++nextConnId_;
string connName = name_ + buf;
InetAddress localAddr(sockets::getLocalAddr(sockfd));
// FIXME poll with zero timeout to double confirm the new connection
// FIXME use make_shared if necessary
//创建一个TcpConnection对象,智能指针。
//根据Connector中的handleWrite()函数,连接建立后会把sockfd从poller中移除,以后不会再关注可写事件了
//否则会出现busy loop,因为已连接套接字一直处于可写状态
TcpConnectionPtr conn(new TcpConnection(loop_,
connName,
sockfd,
localAddr,
peerAddr));
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
boost::bind(&TcpClient::removeConnection, this, _1)); // FIXME: unsafe
{
MutexLockGuard lock(mutex_);
connection_ = conn; //保存TcpConnection
}
conn->connectEstablished(); //这里会关注可读事件,并且回调connectionCallback_,
}
该函数创建一个堆上局部TcpConnection对象,并用TcpClient的智能指针connection_保存起来,这样本函数中conn即便析构掉,connection_依然维护该连接。
然后设置各种回调函数。由于为了避免busy loop,在Connector中一旦连接成功,我们取消关注sockfd的可写事件。并且本函数使用conn->connectEstablished()内部会关注可读事件:
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread(); //断言处于loop线程
assert(state_ == kConnecting); //断言处于未连接状态
setState(kConnected); //将状态设置为已连接
channel_->tie(shared_from_this()); //将自身这个TcpConnection对象提升,由于是智能指针,所以不能直接用this
//shared_from_this()之后引用计数+1,为3,但是shared_from_this()是临时对象,析构后又会减一,
//而tie是weak_ptr并不会改变引用计数,所以该函数执行完之后引用计数不会更改
channel_->enableReading(); //一旦连接成功就关注它的可读事件,加入到Poller中关注
connectionCallback_(shared_from_this());
}
下面是连接断开的函数:
//连接断开
void TcpClient::removeConnection(const TcpConnectionPtr& conn)
{
loop_->assertInLoopThread();
assert(loop_ == conn->getLoop());
{
MutexLockGuard lock(mutex_);
assert(connection_ == conn);
connection_.reset(); //重置
}
//I/O线程中销毁
loop_->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));
if (retry_ && connect_) //是否发起重连
{
LOG_INFO << "TcpClient::connect[" << name_ << "] - Reconnecting to "
<< connector_->serverAddress().toIpPort();
//这里的重连是连接成功后断开的重连,所以实际上是重启
connector_->restart();
}
}