muduo::Connector、TcpClient分析
来源:互联网 发布:为人谋而不忠乎的读音 编辑:程序博客网 时间:2024/05/17 02:16
- Connector
- TcpClient
Connector
Connector用来发起连接。在非阻塞网络中,主动发起连接比被动接收连接更为复杂,因为要考虑错误处理,还要考虑重试。
主要难点在于
1、socket是一次性的,一旦出错无法恢复;只能关闭重来。使用新的fd后,用新的channel。
2、错误代码与acce(2)不同。及时是socket可写,也不意味着已经成功建立连接,还需要用getsockopt(sockfd, SOL_SOCKET, SO_ERROR, ……)再次确认。
3、重试的间隔时间应该逐渐延长,直至back-off。重试使用了EventLoop::runAfter,防止Connector在定时器到时前析构,在Connector的析构函数中要注销定时器。
4、要防止自连接发生。
对于第2点,这里解释一下。非阻塞的socket调用connect后会立即返回,这时三次握手还在进行。这时可以用poll/epoll来检查socket。
当连接成功时,socket变为可写。
当连接失败时,socket变为可读可写。
因此还需要再次确认一下。还有其他方法再次确认:
1、调用getpeername,如果调用失败,返回ENOTCONN,表示连接失败。
2、调用read,长度参数为0,如果read失败,表示connect失败。
3、再调用connect一次,其应该失败,如果错误是EISCONN,表示套接字已建立而且连接成功。
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 }; static const int kMaxRetryDelayMs = 30*1000;//最大重试延迟 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); int removeAndResetChannel(); void resetChannel(); EventLoop* loop_;//所属的EventLoop InetAddress serverAddr_;//server地址 bool connect_; // atomic States state_; // FIXME: use atomic variable boost::scoped_ptr<Channel> channel_; NewConnectionCallback newConnectionCallback_; int retryDelayMs_;};
Connector.cc
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_->queueInLoop(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); }}void Connector::connect()//建立连接{ int sockfd = sockets::createNonblockingOrDie();//创建sockfd 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; 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();}void Connector::connecting(int sockfd){ setState(kConnecting); assert(!channel_);//这里设置channel。因为有了sockfd后才可以设置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();}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;}void Connector::resetChannel()//reset后channel_为空{ channel_.reset();}void Connector::handleWrite()//可写不一定表示已经建立连接{ LOG_TRACE << "Connector::handleWrite " << state_; if (state_ == kConnecting) { int sockfd = removeAndResetChannel();//移除channel。Connector中的channel只管理建立连接阶段。连接建立后,交给TcoConnection管理。 int err = sockets::getSocketError(sockfd);//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); }}void Connector::handleError(){ LOG_ERROR << "Connector::handleError state=" << state_; if (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)//重新尝试连接{ 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
Connector类补单独使用,它封装在类TcpClient中。一个Connector对应一个TcpClient,Connector用来建立连接,建立成功后把控制交给TcpConnection,因此TcpClient中也封装了一个TcpConnection。
class Connector;typedef boost::shared_ptr<Connector> ConnectorPtr;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& nameArg); ~TcpClient(); // force out-line dtor, for scoped_ptr members. void connect(); void disconnect(); void stop(); TcpConnectionPtr connection() const { MutexLockGuard lock(mutex_); return connection_; } EventLoop* getLoop() const { return loop_; } bool retry() const; void enableRetry() { retry_ = true; } const string& name() const { return name_; } /// 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; }#ifdef __GXX_EXPERIMENTAL_CXX0X__ void setConnectionCallback(ConnectionCallback&& cb) { connectionCallback_ = std::move(cb); } void setMessageCallback(MessageCallback&& cb) { messageCallback_ = std::move(cb); } void setWriteCompleteCallback(WriteCompleteCallback&& cb) { writeCompleteCallback_ = std::move(cb); }#endif private: /// Not thread safe, but in loop void newConnection(int sockfd); /// Not thread safe, but in loop void removeConnection(const TcpConnectionPtr& conn); EventLoop* loop_; ConnectorPtr connector_; // avoid revealing Connector const string name_; ConnectionCallback connectionCallback_; MessageCallback messageCallback_; WriteCompleteCallback writeCompleteCallback_; bool retry_; // atomic bool connect_; // atomic // always in loop thread int nextConnId_; mutable MutexLock mutex_; TcpConnectionPtr connection_; // @GuardedBy mutex_};
TcpClient.cc
void removeConnection(EventLoop* loop, const TcpConnectionPtr& conn){ loop->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn));}void removeConnector(const ConnectorPtr& connector){ //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){ 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; bool unique = false; { MutexLockGuard lock(mutex_); unique = connection_.unique(); conn = connection_; } if (conn) { assert(loop_ == conn->getLoop()); // 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)); if (unique) { conn->forceClose(); } } else { connector_->stop(); // FIXME: HACK loop_->runAfter(1, boost::bind(&detail::removeConnector, connector_)); }}void TcpClient::connect(){ // FIXME: check state LOG_INFO << "TcpClient::connect[" << name_ << "] - connecting to " << connector_->serverAddress().toIpPort(); connect_ = true; connector_->start();//开始连接}void TcpClient::disconnect(){ connect_ = false; { MutexLockGuard lock(mutex_); if (connection_) { connection_->shutdown(); } }}void TcpClient::stop(){ connect_ = false; connector_->stop();}void TcpClient::newConnection(int sockfd)//新连接创建后,用TcpConnection接管连接{ 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_,//把新连接创建为TcpConnection 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 TcpClient::removeConnection(const TcpConnectionPtr& conn){ loop_->assertInLoopThread(); assert(loop_ == conn->getLoop()); { MutexLockGuard lock(mutex_); assert(connection_ == conn); connection_.reset(); } loop_->queueInLoop(boost::bind(&TcpConnection::connectDestroyed, conn)); if (retry_ && connect_) { LOG_INFO << "TcpClient::connect[" << name_ << "] - Reconnecting to " << connector_->serverAddress().toIpPort(); connector_->restart(); }}
可以使用TcpClient写一个Echo客户端。用一个channel监听键盘的输入事件,有了输入就发送。
echoClient.h
#include <muduo/net/TcpClient.h>#include <muduo/net/Channel.h>#include <muduo/net/TcpConnection.h>#include <muduo/base/Timestamp.h>#include <muduo/net/EventLoop.h>using namespace muduo;using namespace muduo::net;class EchoClient{public: EchoClient(EventLoop* loop, const InetAddress& serverAddr, const string& nameArg); void start(); void send();private: void onConnection(const TcpConnectionPtr& conn); void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time); TcpClient client_; Channel channel_;};
echoClient.cpp
#include "echoClient.h"#include <muduo/net/Buffer.h>#include <muduo/net/InetAddress.h>#include <boost/bind.hpp>#include <unistd.h>#include <iostream>EchoClient::EchoClient(EventLoop* loop, const InetAddress& serverAddr, const string& nameArg): client_(loop, serverAddr, nameArg), channel_(loop, STDIN_FILENO) { client_.setConnectionCallback(boost::bind(&EchoClient::onConnection, this, _1)); client_.setMessageCallback(boost::bind(&EchoClient::onMessage, this, _1, _2, _3)); channel_.enableReading(); channel_.setReadCallback(boost::bind(&EchoClient::send, this));}void EchoClient::onConnection(const TcpConnectionPtr& conn){ if(conn->connected()) { std::cout<<"Connect to"<<conn->peerAddress().toIpPort()<<" successfully"<<std::endl; } else std::cout<<"Connect to"<<conn->peerAddress().toIpPort()<<" failed"<<std::endl;}void EchoClient::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time){ std::cout<<"Receive :"<<buf->retrieveAllAsString()<<std::endl;}void EchoClient::send(){ string msg; std::cin>>msg; Buffer buf; buf.append(msg); client_.connection()->send(&buf);}void EchoClient::start(){ client_.connect();}
main.cpp
#include "echoClient.h"int main(){ EventLoop loop; InetAddress serverAddr("127.0.0.1", 8000); EchoClient client(&loop, serverAddr, "echoClient"); client.start(); loop.loop(); return 0;}
- muduo::Connector、TcpClient分析
- muduo网络库学习之EventLoop(七):TcpClient、Connector
- muduo库的Connector以及TcpClient的使用
- TcpClient connector(39)
- c++ poco Connector tcpclient测试用例
- muduo源码分析--详解muduo多线程模型
- muduo源码分析---EventLoopThread
- muduo源码分析--TcpServer
- muduo::Thread类分析
- muduo::Logging、LogStream分析
- muduo::FileUtil、LogFile分析
- muduo::BlockingQueue、BoundedBlockingQueue分析
- muduo::ThreadPoll分析
- muduo::EventLoop分析
- muduo:Channel、Poller分析
- muduo::EventLoopThread、EventLoopThreadPool分析
- muduo::Buffer分析
- muduo::Acceptor、TcpServer分析
- [数论]小于n且与n互素的个数(欧拉函数)
- HDU 4417 Super Mario // 线段树
- Python绘制excel表格到邮件正文区并发送脚本实例
- 另类总结BRD,MRD,PRD到底是干嘛用的,有啥区别。
- Linux开关机相关命令
- muduo::Connector、TcpClient分析
- 高负载web服务器内核参数调整
- C语言中的未定义行为
- 苹果开发 笔记(59)UIButton
- Matrix详解
- MySQL优化方案
- JAVA class文件中的符号引用
- Http协议和安卓开发
- 设计模式在游戏中的应用--模板方法(七)