主动发起连接比被动接受连接要复杂些,一方面是错误处理麻烦,另一方面是要考虑重试。Muduo封装了Connector class 用于客户端的发起连接,它只负责建立socket,创建好的socket交由TcpConnection进行维护管理。
Connector的设计难点:
Connector只对外开放了四个接口。
Connector.h
class Connector : boost::noncopyable,
public boost::enable_shared_from_this<Connector>
{
public:
typedef boost::function<void (int sockfd)> 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 };
//最大重连时间30s
static const int kMaxRetryDelayMs = 30*1000;
//初始重连时间500ms
static const int kInitRetryDelayMs = 500;
//当前连接状态
void setState(States s) { state_ = s; }
//开始连接
void startInLoop();
//停止连接
void stopInLoop();
//连接
void connect();
//正在连接
void connecting(int sockfd);
//写处理调用
void handleWrite();
//出错调用
void handleError();
//重试
void retry(int sockfd);
//移除和释放Channel
int removeAndResetChannel();
//释放Channel
void resetChannel();
//IO处理服务
EventLoop* loop_;
//服务端地址
InetAddress serverAddr_;
//连接状态位
bool connect_; // atomic
States state_; // FIXME: use atomic variable
//用于连接服务端的Channel
boost::scoped_ptr<Channel> channel_;
//连接成功以后要调用的回调
NewConnectionCallback newConnectionCallback_;
//重连间隔
int retryDelayMs_;
};
Connector.cc
const int Connector::kMaxRetryDelayMs;
//Connector初始化流程
//a、传入IO服务线程。
//b、传入服务端地址。
//c、连接动作置位false。
//d、当前连接状态为断开。
//e、重连时间初始化为500ms。
Connector::Connector(EventLoop* loop, const InetAddress& serverAddr)
: loop_(loop),
serverAddr_(serverAddr),
connect_(false),
state_(kDisconnected),
retryDelayMs_(kInitRetryDelayMs)
{
LOG_DEBUG << "ctor[" << this << "]";
}
//连接释放
Connector::~Connector()
{
LOG_DEBUG << "dtor[" << this << "]";
assert(!channel_);
}
//开始连接
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();
}
else
{
LOG_DEBUG << "do not connect";
}
}
void Connector::stop()
{
connect_ = false;
loop_->runInLoop(boost::bind(&Connector::stopInLoop, this)); // FIXME: unsafe
// FIXME: cancel timer
}
void Connector::stopInLoop()
{
loop_->assertInLoopThread();
if (state_ == kConnecting)
{
setState(kDisconnected);
int sockfd = removeAndResetChannel();
retry(sockfd);
}
}
//连接,要处理三种状态。
//a、正在连接过程。
//b、重连。
//c、连接失败。
void Connector::connect()
{
//创建非阻塞套接字
int sockfd = sockets::createNonblockingOrDie();
//发起连接
int ret = sockets::connect(sockfd, serverAddr_.getSockAddrInet());
int savedErrno = (ret == 0) ? 0 : errno;
switch (savedErrno)
{
case 0:
case EINPROGRESS://非阻塞套接字,返回此状态,表示三次握手正在连接中
case EINTR:
case EISCONN: //连接成功
connecting(sockfd);
break;
//以下五种状态表示要重连
case EAGAIN:
case EADDRINUSE:
case EADDRNOTAVAIL:
case ECONNREFUSED:
case ENETUNREACH:
retry(sockfd);
break;
//余下状态直接关闭socket,不再进行连接
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::restart()
{
loop_->assertInLoopThread();
setState(kDisconnected);
retryDelayMs_ = kInitRetryDelayMs;
connect_ = true;
startInLoop();
}
//正在连接的处理。
//这里给正在连接的socket创建一个Channel去处理。
void Connector::connecting(int sockfd)
{
setState(kConnecting);
assert(!channel_);
channel_.reset(new Channel(loop_, 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();
}
//释放用于连接的Channel。
int Connector::removeAndResetChannel()
{
channel_->disableAll();
channel_->remove();
int sockfd = channel_->fd();
// Can't reset channel_ here, because we are inside Channel::handleEvent
loop_->queueInLoop(boost::bind(&Connector::resetChannel, this)); // FIXME: unsafe
return sockfd;
}
//释放Channel
void Connector::resetChannel()
{
channel_.reset();
}
//这里为什么要对错误码做判断?
//连接成功客户端会收到写事件。
//a、释放掉用于连接的Channel。
//b、有异常就重连。
//c、连接成功,检查下是否是自连接。(分析下什么是自连接。)
//d、正常的话就连接成功了。
//e、如果当前不是连接状态,就关闭此Socket
void Connector::handleWrite()
{
LOG_TRACE << "Connector::handleWrite " << state_;
if (state_ == kConnecting)
{
//释放掉用于创建连接的Channel
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))
{
LOG_WARN << "Connector::handleWrite - Self connect";
retry(sockfd);
}
else
{
setState(kConnected);
if (connect_)
{
newConnectionCallback_(sockfd);
}
else
{
sockets::close(sockfd);
}
}
}
else
{
// what happened?
assert(state_ == kDisconnected);
}
}
//出现错误时,
//a、重置用于连接的Channel。
//b、重连。
void Connector::handleError()
{
LOG_ERROR << "Connector::handleError";
assert(state_ == kConnecting);
int sockfd = removeAndResetChannel();
int err = sockets::getSocketError(sockfd);
LOG_TRACE << "SO_ERROR = " << err << " " << strerror_tl(err);
retry(sockfd);
}
//重连的时间翻倍
void Connector::retry(int sockfd)
{
//关闭当前socket
sockets::close(sockfd);
//当前连接设置为断开
setState(kDisconnected);
if (connect_)
{
LOG_INFO << "Connector::retry - Retry connecting to " << serverAddr_.toIpPort()
<< " in " << retryDelayMs_ << " milliseconds. ";
//加入到定时器中
loop_->runAfter(retryDelayMs_/1000.0,
boost::bind(&Connector::startInLoop, shared_from_this()));
retryDelayMs_ = std::min(retryDelayMs_ * 2, kMaxRetryDelayMs);
}
else
{
LOG_DEBUG << "do not connect";
}
TcpClient是相对于TcpServer来说的,是个客户端。TcpClient用Connector去建立连接。连接建立好以后,用TcpConnection去维护管理连接。每个TcpConnection只管理一个TcpConnection。
TcpClient.h
class TcpClient : boost::noncopyable
{
public:
// TcpClient(EventLoop* loop);
// TcpClient(EventLoop* loop, const string& host, uint16_t port);
TcpClient(EventLoop* loop,
const InetAddress& serverAddr,
const string& name);
~TcpClient(); // force out-line dtor, for scoped_ptr members.
void connect(); //连接
void disconnect(); //断开连接
void stop(); //停止
//获取连接变量
TcpConnectionPtr connection() const
{
MutexLockGuard lock(mutex_);
return connection_;
}
//重试
bool retry() const;
//使能重试状态
void enableRetry() { retry_ = true; }
//设置链路的连接断开回调
/// Set connection callback.
/// Not thread safe.
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }
//设置链路的接受消息回调
/// Set message callback.
/// Not thread safe.
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }
//设置写完成回调
/// Set write complete callback.
/// Not thread safe.
void setWriteCompleteCallback(const WriteCompleteCallback& cb)
{ writeCompleteCallback_ = cb; }
private:
//创建新连接
/// Not thread safe, but in loop
void newConnection(int sockfd);
//移除连接
/// Not thread safe, but in loop
void removeConnection(const TcpConnectionPtr& conn);
//IO主循环
EventLoop* loop_;
//负责客户端连接建立的类
ConnectorPtr connector_; // avoid revealing Connector
//客户端名称
const string name_;
//三个回调函数
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
WriteCompleteCallback writeCompleteCallback_;
//重试状态
bool retry_; // atmoic
//连接状态
bool connect_; // atomic
// always in loop thread
//下一个连接ID
int nextConnId_;
//可修改锁,用于管理TcpConnection
mutable MutexLock mutex_;
//连接建立好的类
TcpConnectionPtr connection_; // @BuardedBy mutex_
};
TcpClient.cc
namespace detail
{
//移除已经建立好的连接
void removeConnection(EventLoop* loop, const TcpConnectionPtr& conn)
{
loop->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));
}
//移除负责建立连接的实体
void removeConnector(const ConnectorPtr& connector)
{
//connector->
}
}
}
}
//TcpClient的实例化流程
//a、传入负责IO的事件循环。
//b、服务端地址。
//c、客户端名称。
//d、检测Loop是否空。
//e、创建负责建立连接的Connector。
//f、赋值客户端名称。
//g、默认连接回调。
//h、默认消息回调。
//i、重试状态false。
//j、进行连接状态true。
//k、下一个连接ID为1。
TcpClient::TcpClient(EventLoop* loop,
const InetAddress& serverAddr,
const string& name)
: loop_(CHECK_NOTNULL(loop)),
connector_(new Connector(loop, serverAddr)),
name_(name),
connectionCallback_(defaultConnectionCallback),
messageCallback_(defaultMessageCallback),
retry_(false),
connect_(true),
nextConnId_(1)
{
//当连接上服务端以后,调用的回调
connector_->setNewConnectionCallback(
boost::bind(&TcpClient::newConnection, this, _1));
// FIXME setConnectFailedCallback
LOG_INFO << "TcpClient::TcpClient[" << name_
<< "] - connector " << get_pointer(connector_);
}
TcpClient::~TcpClient()
{
LOG_INFO << "TcpClient::~TcpClient[" << name_
<< "] - connector " << get_pointer(connector_);
TcpConnectionPtr conn;
{
MutexLockGuard lock(mutex_);
conn = connection_;
}
if (conn)
{
//如果TcpConnection还在,那就对其资源进行释放。释放资源有
//a、detail::removeConnection->TcpConnection::connectDestroyed 会将Channel从Poll中移除掉。
// FIXME: not 100% safe, if we are in different thread
CloseCallback cb = boost::bind(&detail::removeConnection, loop_, _1);
loop_->runInLoop(
boost::bind(&TcpConnection::setCloseCallback, conn, cb));
}
else
{
//如果TcpConnection还未有值,说明connector正在连接中,
//这个时候调用connector->stop去停止连接,释放Channel和
//关闭用于连接的socket。
connector_->stop();
// FIXME: HACK
loop_->runAfter(1, boost::bind(&detail::removeConnector, connector_));
}
}
//连接时所做的事
//a、将连接状态置位true。
//b、开始连接。
void TcpClient::connect()
{
// FIXME: check state
LOG_INFO << "TcpClient::connect[" << name_ << "] - connecting to "
<< connector_->serverAddress().toIpPort();
connect_ = true;
connector_->start();
}
//断开连接所做的事
//a、连接状态置位false。
//b、关闭已经存在的连接。
//b-1、如果连接还在,就调用TcpConnection::shutdown关闭本端的写,
//但继续接受服务端已发送过来的数据,即已进入对方的协议栈和在网络中的数据。
void TcpClient::disconnect()
{
connect_ = false;
{
MutexLockGuard lock(mutex_);
if (connection_)
{
connection_->shutdown();
}
}
}
//停止客户端
//a、连接状态置位false
//b、释放Connector中的资源(Channel和Socket)
void TcpClient::stop()
{
connect_ = false;
connector_->stop();
}
//当客户端调用Connector连接上服务端之后,会销毁掉Connector。
//销毁掉Connector之后,将建立好连接的sockfd给TcpConnection,
//由TcpConnection负责已经建立好连接的维护工作。
void TcpClient::newConnection(int sockfd)
{
///1、准备新连接的初始化信息。
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
TcpConnectionPtr conn(new TcpConnection(loop_,
connName,
sockfd,
localAddr,
peerAddr));
///2、设置连接的回调函数
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
boost::bind(&TcpClient::removeConnection, this, _1)); // FIXME: unsafe
///3、保存新建立的TcpConnection。
{
MutexLockGuard lock(mutex_);
connection_ = conn;
}
conn->connectEstablished();
}
//移除连接
void TcpClient::removeConnection(const TcpConnectionPtr& conn)
{
loop_->assertInLoopThread();
assert(loop_ == conn->getLoop());
{
//释放被管理对象的所有权,若存在。
MutexLockGuard lock(mutex_);
assert(connection_ == conn);
connection_.reset();
}
//销毁TcpConnection
loop_->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));
if (retry_ && connect_)
{
LOG_INFO << "TcpClient::connect[" << name_ << "] - Reconnecting to "
<< connector_->serverAddress().toIpPort();
//Connector已经被释放了,怎么还能restart?
connector_->restart();
}
}
连接建立主要由Connector负责。大致流程如下
注意点:
Connector建立好连接之后,会释放用于连接的Channel,然后将socket回调给TcpClient,由TcpClient创建TcpConnection去维护这个socket。
这里有两层意思:
第二种的断开连接大致调用方法如下:
TcpClient::disconnect() -> TcpConnection::shutdown() -> TcpConnection::shutdownInLoop() ->
Socket::shutdownWrite()。
先关闭本端的写,继续接收对端已经在路上的数据。等收到对端的关闭时,再释放Channel和TcpConnect等和网络操作相关资源。
TcpClient要想自动重连,要调用TcpClient::enableRetry()来触发。
大致流程就是TcpClient收到对端的连接关闭时,会销毁之前用于连接的TcpConnection,重新通过Connector来建立连接连接,剩下的流程就和连接建立流程一样了。
这里要说下TCP的同时打开概念。两个应用程序同时彼此执行主动打开的情况是有可能的,尽管发生的可能性极小。每一方必须发送SYN,且这些SYN必须传递给对方。交互流程状态图如下:
而自连接是TCP同时打开的一种特殊情况,要客户端和服务端都跑在同一个网络设备上才会出现且在服务端挂掉,或者服务端本身未启动的情况下即服务端端口未被监听。
出现的原因如下:
一条TCP连接由四元组(源IP、源端口、目的IP、目的端口)来决定,在Muduo连接过程中,源IP、目的IP、目的端口都是确定的,唯一不确定的是源端口。如果此时服务端还未启动,或者服务端异常挂掉了,如果系统选择的源端口与目的端口相同,那么Client和Server(实际上不存在Server这个实体)就是相同的TCP实体。举个列子:
Muduo解决办法是连接建立好以后,判断下连接两端的IP和端口是不是都一样,一样的话就断开重连。
bool sockets::isSelfConnect(int sockfd)
{
struct sockaddr_in localaddr = getLocalAddr(sockfd);
struct sockaddr_in peeraddr = getPeerAddr(sockfd);
return localaddr.sin_port == peeraddr.sin_port
&& localaddr.sin_addr.s_addr == peeraddr.sin_addr.s_addr;
}
参考连接:
http://blog.coderhuo.tech/2018/05/11/tcp_establish_release/