muduo::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源码分析之TcpServer模块
这次我们开始muduo源代码的实际编写,首先我们知道muduo是LT模式,Reactor模式,下图为Reactor模式的流程图[来源1] 然后我们来看下muduo的整体架构[来源1] 首先muduo有 ...
- muduo源码分析之Buffer
这一次我们来分析下muduo中Buffer的作用,我们知道,当我们客户端向服务器发送数据时候,服务器就会读取我们发送的数据,然后进行一系列处理,然后再发送到其他地方,在这里我们想象一下最简单的Echo ...
- muduo源码分析:组成结构
muduo整体由许多类组成: 这些类之间有一些依赖关系,如下:
- muduo源码分析之回调模块
这次我们主要来说说muduo库中大量使用的回调机制.muduo主要使用的是利用Callback的方式来实现回调,首先我们在自己的EchoServer构造函数中有这样几行代码 EchoServer(Ev ...
- muduo源码分析之muduo简单运用
今天不先实现muduo项目,我们先来看下muduo库的基本使用,只有了解了如何用,才能在写代码的时候知道自己写的找个函数是干嘛的,实际上是怎么使用的这个函数.首先说简单点,就是定义一个Server,设 ...
- muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor
目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...
- Tomcat源码分析 (二)----- Tomcat整体架构及组件
前言 Tomcat的前身为Catalina,而Catalina又是一个轻量级的Servlet容器.在美国,catalina是一个很美的小岛.所以Tomcat作者的寓意可能是想把Tomcat设计成一个优 ...
- Mina源码阅读笔记(四)—Mina的连接IoConnector1
上一篇写的是IoAcceptor是服务器端的接收代码,今天要写的是IoConnector,是客户端的连接器.在昨天,我们还留下一些问题没有解决,这些问题今天同样会产生,但是都要等到讲到session的 ...
- Tomcat相关面试题,看这篇就够了!保证能让面试官颤抖!
Tomcat相关的面试题出场的几率并不高,正式因为如此,很多人忽略了对Tomcat相关技能的掌握. 这次整理了Tomcat相关的系统架构,介绍了Server.Service.Connector.Con ...
随机推荐
- [OJ#15]TR #2 画心
[OJ#15]TR #2 画心 试题描述 渠是一名画师.渠有一支神奇的画笔,可以画尽因果. 渠要画一幅画,这幅画由N个线段组成,线段从1开始编号,第i条线段有一个特殊的因果值Ai. 由于画太长了,渠不 ...
- JStorm源代码阅读——消息的确认机制
Acker //Acker相当于一个bolt,用于处理事件 public class Acker implements IBolt { private RotatingMap<Object, A ...
- formData使用总结
1.formData基本使用 //可以从form元素初始化一个FormData对象,或者new一个空对象 var formData = new FormData([fromElement]); //可 ...
- [转] Makefile 基础 (2) —— Makefile 总述
该篇文章为转载,是对原作者系列文章的总汇加上标注. 支持原创,请移步陈浩大神博客:(最原始版本) http://blog.csdn.net/haoel/article/details/2886 我转自 ...
- Java-Pi的几种实现
1.无穷级数计算 p = 1 - 1/3 + 1/5 -1/7+..... π=4p 2.使用 Nilakantha 级数 π = 3 + 4/(2*3*4) - 4/(4*5*6) + 4/(6*7 ...
- [LeetCode] Rotate Image n-by-n矩阵顺时针旋转
You are given an n x n 2D matrix representing an image. Rotate the image by 90 degrees (clockwise). ...
- 41深入理解C指针之---指针与栈
一.借助第40指针与链表的相关内容,稍微修改即可: 1.定义头文件stack.h代码如下: #include <stdlib.h> #include <stdio.h> #if ...
- LNMP 之 Nginx负载均衡服务器
Nginx负载均衡服务器 参考教程:[千峰教育] 准备工作: 三台服务器,每台服务器都需要安装nginx. 1.关闭防火墙 service iptables stop2.关闭Selinux seten ...
- JProfile 9.2 linux安装及windows客户端远程监控
http://blog.csdn.net/fengzhou0920/article/details/52119039 1. 测试环境 服务器:Linux X64;tomcat 7.0;jd ...
- Android内存泄漏总结
内存泄漏问题老生常谈,很常见也很难根治,今天我在这里总结一下内存泄漏的原因和解决方法: 所谓内存泄漏,就是本该被回收的对象,由于某些原因不能被回收,继续占用堆内存的这种状态,导致的结果也是显而易见的, ...