目录
0.简介
上一篇已讲述了服务端整个工作流程,本篇大致分析一下客户端。客户端相对简单,它只负责一个连接,成功连接到服务端后,即可进行正常的数据交互。
和服务端相对应的,客户端代码由TcpClient类发起,TcpClient里包含一个Connector(回想一下Acceptor),等连接建立后,也是依赖TcpConnection类做大部分事情。
1.主动连接
客户端连接过程如下:
来看一下Connector::connect(), 先创建一个非阻塞的套接口,调用系统函数去连接服务端:
int sockets::connect(int sockfd, const struct sockaddr* addr)
{
return ::connect(sockfd, addr, static_cast<socklen_t>(sizeof(struct sockaddr_in6)));
}
如果正常返回(注意:此时连接还没有成功),则进入connecting() 函数,否则尝试重连或者直接关闭套接口。
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:
case EINTR:
case EISCONN:
connecting(sockfd); //正在连接
break;
case EAGAIN:
case EADDRINUSE:
case EADDRNOTAVAIL:
case ECONNREFUSED:
case ENETUNREACH:
retry(sockfd); // 重连
break;
...
...
}
connecting() 主要是创建一个Channel(上一篇已提到Channel类的作用),设置好回调函数(write callback, error callback), 然后关注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();
}
以上操作执行之后,客户代码一般会调用EventLoop::loop()用于监控channel的可写事件。
2.连接成功
当channel变为可写时,执行Connector::handleWrite() 回调函数:
void Connector::handleWrite()
{
LOG_TRACE << "Connector::handleWrite " << state_;
if (state_ == kConnecting)
{
int sockfd = removeAndResetChannel(); // 当前channel已经没有利用价值了,delete it
// 套接口变成可写状态,不代表连接成功(也有可能是失败),所以还要再检查一次
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_)
{
// 到这里,说明连接真的成功了,调用TcpClient::newConnection 创建TcpConnection类对象
newConnectionCallback_(sockfd);
}
else
{
sockets::close(sockfd);
}
}
}
else
{
// what happened?
assert(state_ == kDisconnected);
}
}
最后再看一下TcpClient::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
// 创建类对象
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;
}
conn->connectEstablished(); // 定义如下
}
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
channel_->tie(shared_from_this());
channel_->enableReading(); // 监听可读事件
connectionCallback_(shared_from_this()); // 告诉用户连接成功了。
}
回忆一下TcpServer::newConnection() 最后一句:
ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
服务端会把connectEstablished()放到Tcpconnection所在的线程里执行,客户端由于对象都在主线程,所以可直接调用。
3.数据收发和关闭连接
由于都是使用了同一个类TcpConnection, 所以收发过程/关闭连接和服务端完全相同,不再详述,可参考