Connector类
Connector只负责建立socket连接,不负责创建TcpConnection对象,它的newConnectionCallback回调的参数是socket文件描述符.Muduo中的Connector带有自动重连的功能,并且重试的事件间隔逐渐延长,直至30s.与被动连接的Acceptor类相比,缺少一个acceptSocket_ 成员,因为Connector 是创建一个新的sockfd 并connect 它.因为我们这的Connector是主动发起连接.
调用函数的时序为:Connector::start->Connector::stopInLoop->Connector::connect.
其中的Connector::connect()函数:
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: // 非阻塞套接字,未连接成功返回码是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); // 不能重连,关闭sockfd
break;
default:
LOG_SYSERR << "Unexpected error in Connector::startInLoop " << savedErrno;
sockets::close(sockfd);
// connectErrorCallback_();
break;
}
}
其中会根据savedError来选择适当的执行函数,或者retry()或者connecting().
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_->enableWriting(); // 让Poller关注可写事件
}
当调用此函数时,sockfd便处于可写的状态(内核缓冲区不为满),而且poller关注可写的事件,触发回调Connector::handleWrite()函数,在handleWrite()里面需要removeAndResetChannel(),因此此时连接建立,故不用再关注channel的可写事件,最终会执行 channel_.reset(); 即把channel析构了.连接成功后调用newConnectionCallback_(sockfd).
void Connector::handleWrite()
{
LOG_TRACE << "Connector::handleWrite " << state_;
if (state_ == kConnecting)
{
int sockfd = removeAndResetChannel(); // 从poller中移除关注,并将channel置空
// socket可写并不意味着连接一定建立成功
// 还需要用getsockopt(sockfd, SOL_SOCKET, SO_ERROR, ...)再次确认一下。
int err = sockets::getSocketError(sockfd);
......
else // 连接成功
{
setState(kConnected);
if (connect_)
{
newConnectionCallback_(sockfd); // 回调
}
}
}
}
测试用例
#include "Connector.h"
#include "EventLoop.h"
#include
muduo::EventLoop* g_loop;
void connectCallback(int sockfd)
{
printf("connected.\n");
g_loop->quit();
}
int main(int argc, char* argv[])
{
muduo::EventLoop loop;
g_loop = &loop;
muduo::InetAddress addr("127.0.0.1", 9981);
muduo::ConnectorPtr connector(new muduo::Connector(&loop, addr));
connector->setNewConnectionCallback(connectCallback);
connector->start();
loop.loop();
}
运行结果:
20191013 11:29:40.428847Z 14273 DEBUG Connector ctor[0x1e76f40] - Connector.cc:31
-1
115
20191013 11:29:40.429139Z 14273 WARN Channel::handle_event() POLLHUP - Channel.cc:50
20191013 11:29:40.429157Z 14273 ERROR Connector::handleError - Connector.cc:190
20191013 11:29:40.429223Z 14273 INFO Connector::retry - Retry connecting to 127.0.0.1:9981 in 500 milliseconds. - Connector.cc:205
一次连接不成功,会一直反复尝试,直到连接成功.
实际上,Connector类一般不会单独使用,一般是作为TcpClient的成员,其与TcpServer类较为相似(都有newConnection和removeConnection这两个成员函数),但每个TCPClient只管理一个TcpConnection,加上Connector之后,具备了TcpConnection断开之后重新连接的功能.还有一点不同是在于TcpServer可以有多个Reactor,即mainReactor_Threadpool(subReactor)模式,但TcpClient只能有一个事件循环Eventloop,即也只能有一个Reactor,由它来处理TcpConnection上的事件.当然我们可以多开几个TcpClient一起绑定到同一个EventLoop对象上,也就是多个TcpConnection.
TcpClient类
数据成员:
typedef boost::shared_ptr ConnectorPtr;
ConnectorPtr connector_; // 用于主动发起连接
TcpConnectionPtr connection_; // Connector连接成功以后,得到一个TcpConnection,只管理一个TcpConnection
在构造函数中:
//设置连接成功回调函数
connector_->setNewConnectionCallback(
boost::bind(&TcpClient::newConnection, this, _1));
主动发起连接成功之后,会运行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));
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_
}
另外一点,TcpServer或者TcpClient对象,通过调用setxxxxCallback()函数来注册回调函数,最终都会实际上设置的是TcpConnection对象的xxxxCallback_成员,这些函数会在有事件发生时被调用,比如OnConnection()和OnMessage().
测试用例
服务端:echo回射服务器
#include
#include
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
class TestServer
{
public:
TestServer(EventLoop* loop,
const InetAddress& listenAddr)
: loop_(loop),
server_(loop, listenAddr, "TestServer")
{
server_.setConnectionCallback(
boost::bind(&TestServer::onConnection, this, _1));
server_.setMessageCallback(
boost::bind(&TestServer::onMessage, this, _1, _2, _3));
}
void start()
{
server_.start();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
printf("onConnection(): new connection [%s] from %s\n",
conn->name().c_str(),
conn->peerAddress().toIpPort().c_str());
}
else
{
printf("onConnection(): connection [%s] is down\n",
conn->name().c_str());
}
}
void onMessage(const TcpConnectionPtr& conn,
Buffer* buf,
Timestamp receiveTime)
{
string msg1 = buf->retrieveAllAsString();
string msg(msg1);
printf("onMessage(): received %zd bytes from connection [%s] at %s\n",
msg.size(),
conn->name().c_str(),
receiveTime.toFormattedString().c_str());
conn->send(msg);
}
EventLoop* loop_;
TcpServer server_;
};
int main()
{
printf("main(): pid = %d\n", getpid());
InetAddress listenAddr(8888);
EventLoop loop;
TestServer server(&loop, listenAddr);
server.start();
loop.loop();
}
客户端程序:
#include
#include
#include
#include
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
class TestClient
{
public:
TestClient(EventLoop* loop, const InetAddress& listenAddr)
: loop_(loop),
client_(loop, listenAddr, "TestClient"),
stdinChannel_(loop, 0)
{
client_.setConnectionCallback(
boost::bind(&TestClient::onConnection, this, _1));
client_.setMessageCallback(
boost::bind(&TestClient::onMessage, this, _1, _2, _3));
//client_.enableRetry();
// 标准输入缓冲区中有数据的时候,回调TestClient::handleRead
stdinChannel_.setReadCallback(boost::bind(&TestClient::handleRead, this));
stdinChannel_.enableReading(); // 关注可读事件
}
void connect()
{
client_.connect();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
printf("onConnection(): new connection [%s] from %s\n",
conn->name().c_str(),
conn->peerAddress().toIpPort().c_str());
}
else
{
printf("onConnection(): connection [%s] is down\n",
conn->name().c_str());
}
}
void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time)
{
string msg1 = buf->retrieveAllAsString();
string msg(msg1);
printf("onMessage(): recv a message [%s]\n", msg.c_str());
LOG_TRACE << conn->name() << " recv " << msg.size() << " bytes at " << time.toFormattedString();
}
// 标准输入缓冲区中有数据的时候,回调该函数
void handleRead()
{
char buf[1024] = {0};
fgets(buf, 1024, stdin);
buf[strlen(buf)-1] = '\0'; // 去除\n
client_.connection()->send(buf);
}
EventLoop* loop_;
TcpClient client_;
Channel stdinChannel_; // 标准输入Channel
};
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid();
EventLoop loop;
InetAddress serverAddr("127.0.0.1", 8888);
TestClient client(&loop, serverAddr);
client.connect();
loop.loop();
}
分别运行两个程序,并在客户端中输入"aaaa",会被回射回来,运行结果:
服务端输出:
20191013 12:03:25.884639Z 19542 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51
main(): pid = 19542
20191013 12:03:25.884858Z 19542 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20191013 12:03:25.884905Z 19542 TRACE EventLoop EventLoop created 0x7FFEBA471BD0 in thread 19542 - EventLoop.cc:76
20191013 12:03:25.884924Z 19542 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20191013 12:03:25.885039Z 19542 TRACE updateChannel fd = 6 events = 3 - EPollPoller.cc:104
20191013 12:03:25.885059Z 19542 TRACE loop EventLoop 0x7FFEBA471BD0 start looping - EventLoop.cc:108
20191013 12:03:28.221151Z 19542 TRACE poll 1 events happended - EPollPoller.cc:65
20191013 12:03:28.221476Z 19542 TRACE printActiveChannels {6: IN } - EventLoop.cc:271
20191013 12:03:28.221549Z 19542 INFO TcpServer::newConnection [TestServer] - new connection [TestServer:0.0.0.0:8888#1] from 127.0.0.1:53952 - TcpServer.cc:93
20191013 12:03:28.221599Z 19542 DEBUG TcpConnection TcpConnection::ctor[TestServer:0.0.0.0:8888#1] at 0x12E2680 fd=8 - TcpConnection.cc:65
20191013 12:03:28.221617Z 19542 TRACE newConnection [1] usecount=1 - TcpServer.cc:111
20191013 12:03:28.221646Z 19542 TRACE newConnection [2] usecount=2 - TcpServer.cc:113
20191013 12:03:28.221664Z 19542 TRACE connectEstablished [3] usecount=6 - TcpConnection.cc:238
20191013 12:03:28.221673Z 19542 TRACE updateChannel fd = 8 events = 3 - EPollPoller.cc:104
onConnection(): new connection [TestServer:0.0.0.0:8888#1] from 127.0.0.1:53952
20191013 12:03:28.221717Z 19542 TRACE connectEstablished [4] usecount=6 - TcpConnection.cc:243
20191013 12:03:28.221729Z 19542 TRACE newConnection [5] usecount=2 - TcpServer.cc:123
20191013 12:03:29.640547Z 19542 TRACE poll 1 events happended - EPollPoller.cc:65
20191013 12:03:29.640608Z 19542 TRACE printActiveChannels{8: IN } - EventLoop.cc:271
20191013 12:03:29.640623Z 19542 TRACE handleEvent [6] usecount=2 - Channel.cc:67
onMessage(): received 4 bytes from connection [TestServer:0.0.0.0:8888#1] at 20191013 12:03:29.640545
20191013 12:03:29.640733Z 19542 TRACE handleEvent [12] usecount=2 - Channel.cc:69
^C
输出中fd = 6是监听套接字,fd=8是返回的已连接套接字,连接建立调用OnConnection(),因为客户端输入两串数据,fd=8产生两次可读事件,调用两次onMessage().
客户端输出:
20191013 12:03:28.220602Z 19543 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51
20191013 12:03:28.220775Z 19543 INFO pid = 19543, tid = 19543 - TcpClient_test.cc:77
20191013 12:03:28.220821Z 19543 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20191013 12:03:28.220869Z 19543 TRACE EventLoop EventLoop created 0x7FFD9CF15DB0 in thread 19543 - EventLoop.cc:76
20191013 12:03:28.220893Z 19543 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20191013 12:03:28.220924Z 19543 DEBUG Connector ctor[0xFCE4A0] - Connector.cc:33
20191013 12:03:28.220942Z 19543 INFO TcpClient::TcpClient[TestClient] - connector 0xFCE4A0 - TcpClient.cc:72
20191013 12:03:28.220957Z 19543 TRACE updateChannel fd = 0 events = 3 - EPollPoller.cc:104
20191013 12:03:28.220980Z 19543 INFO TcpClient::connect[TestClient] - connecting to 127.0.0.1:8888 - TcpClient.cc:106
20191013 12:03:28.221114Z 19543 TRACE updateChannel fd = 6 events = 4 - EPollPoller.cc:104
20191013 12:03:28.221146Z 19543 TRACE loop EventLoop 0x7FFD9CF15DB0 start looping - EventLoop.cc:108
20191013 12:03:28.221165Z 19543 TRACE poll 1 events happended - EPollPoller.cc:65
20191013 12:03:28.221423Z 19543 TRACE printActiveChannels {6: OUT } - EventLoop.cc:271
20191013 12:03:28.221441Z 19543 TRACE handleWrite Connector::handleWrite 1 - Connector.cc:169
20191013 12:03:28.221458Z 19543 TRACE updateChannel fd = 6 events = 0 - EPollPoller.cc:104
20191013 12:03:28.221479Z 19543 TRACE removeChannel fd = 6 - EPollPoller.cc:147
20191013 12:03:28.221553Z 19543 DEBUG TcpConnection TcpConnection::ctor[TestClient:127.0.0.1:8888#1] at 0xFCE6F0 fd=6 - TcpConnection.cc:65
20191013 12:03:28.221585Z 19543 TRACE connectEstablished [3] usecount=3 - TcpConnection.cc:238
20191013 12:03:28.221594Z 19543 TRACE updateChannel fd = 6 events = 3 - EPollPoller.cc:104
onConnection(): new connection [TestClient:127.0.0.1:8888#1] from 127.0.0.1:8888
20191013 12:03:28.221653Z 19543 TRACE connectEstablished [4] usecount=3 - TcpConnection.cc:243
aaaa
20191013 12:03:29.640345Z 19543 TRACE poll 1 events happended - EPollPoller.cc:65
20191013 12:03:29.640415Z 19543 TRACE printActiveChannels {0: IN } - EventLoop.cc:271
20191013 12:03:29.640769Z 19543 TRACE poll 1 events happended - EPollPoller.cc:65
20191013 12:03:29.640819Z 19543 TRACE printActiveChannels {6: IN } - EventLoop.cc:271
20191013 12:03:29.640834Z 19543 TRACE handleEvent [6] usecount=2 - Channel.cc:67
onMessage(): recv a message [aaaa]
20191013 12:03:29.640894Z 19543 TRACE onMessage TestClient:127.0.0.1:8888#1 recv 4 bytes at 20191013 12:03:29.640768 - TcpClient_test.cc:58
20191013 12:03:29.640914Z 19543 TRACE handleEvent [12] usecount=2 - Channel.cc:69
20191013 12:03:32.560680Z 19543 TRACE poll 1 events happended - EPollPoller.cc:65
20191013 12:03:32.560750Z 19543 TRACE printActiveChannels {6: IN } - EventLoop.cc:271
20191013 12:03:32.560768Z 19543 TRACE handleEvent [6] usecount=2 - Channel.cc:67
20191013 12:03:32.560795Z 19543 TRACE handleClose fd = 6 state = 2 - TcpConnection.cc:369
20191013 12:03:32.560805Z 19543 TRACE updateChannel fd = 6 events = 0 - EPollPoller.cc:104
onConnection(): connection [TestClient:127.0.0.1:8888#1] is down
20191013 12:03:32.560843Z 19543 TRACE handleClose [7] usecount=3 - TcpConnection.cc:377
20191013 12:03:32.560865Z 19543 TRACE handleClose [11] usecount=3 - TcpConnection.cc:380
20191013 12:03:32.560874Z 19543 TRACE handleEvent [12] usecount=2 - Channel.cc:69
20191013 12:03:32.560883Z 19543 TRACE removeChannel fd = 6 - EPollPoller.cc:147
20191013 12:03:32.560897Z 19543 DEBUG ~TcpConnection TcpConnection::dtor[TestClient:127.0.0.1:8888#1] at 0xFCE6F0 fd=6 - TcpConnection.cc:72
20191013 12:03:42.561752Z 19543 TRACE poll nothing happended - EPollPoller.cc:74
20191013 12:03:52.571864Z 19543 TRACE poll nothing happended - EPollPoller.cc:74
由前面的分析可知,一个进程默认打开0(输入),1(输出),2(错误)文件描述符,该例子中0为标准输入,且mainReactor中:epollfd_ = 3; timerfd_ = 4; wakeupFd_ = 5; sockfd_ = 6; idleFd_ = 7;,fd=6是客户端连接的套接字,刚开始连接成功,fd=6可写事件发生,但马上把connector的channel移除关注并析构,并构造TcpConnection。在命令行输入一串数据,标准输入可读事件发生,等服务器回射回来,fd=6可读事件发生,调用OnMessage().我们首先ctrl+c 掉服务器,客户端发现此连接已经down掉,就会析构TcpConnection,顺便关闭套接字,当然事件循环还在继续,因为如前面所说,有可能EventLoop绑定了多个TcpClient。