Acceptor类:用于接收新的TCP连接,该类是内部class,供TcpServer使用,生命期由TcpServer控制

类成员:

class Acceptor : boost::noncopyable
{
public:
typedef boost::function<void (int sockfd,
const InetAddress&)> NewConnectionCallback; Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport);
~Acceptor(); void setNewConnectionCallback(const NewConnectionCallback& cb)
{ newConnectionCallback_ = cb; } bool listenning() const { return listenning_; }
void listen(); private:
//调用accept()接受新连接,并回调用户callback
void handleRead();
//acceptChannel_所属loop对象
EventLoop* loop_;
//此socket是listen socket
Socket acceptSocket_;
//channel对象监测上述socket上的readable事件,并在channel对象的hanleEvent方法中回调handleRead(),handleRead会调用accept来接受新连接
Channel acceptChannel_;
NewConnectionCallback newConnectionCallback_;
bool listenning_;
int idleFd_;
};
//构造函数调用socket() 、bind(),即创建TCP服务端的传统步骤
//socket() bind() listen()任何一个步骤出错都会造成程序终止,故没有错误处理
//sockets::createNonblockingOrDie创建非阻塞的socket
Acceptor::Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport)
: loop_(loop),
acceptSocket_(sockets::createNonblockingOrDie(listenAddr.family())),
acceptChannel_(loop, acceptSocket_.fd()),
listenning_(false),
idleFd_(::open("/dev/null", O_RDONLY | O_CLOEXEC))
{
assert(idleFd_ >= );
acceptSocket_.setReuseAddr(true);
acceptSocket_.setReusePort(reuseport);
acceptSocket_.bindAddress(listenAddr);
acceptChannel_.setReadCallback(
boost::bind(&Acceptor::handleRead, this));
}
//调用listen(),监听listen_fd,当有新连接到达时,acceptChannel_会处理
void Acceptor::listen()
{
loop_->assertInLoopThread();
listenning_ = true;
acceptSocket_.listen();
acceptChannel_.enableReading();
}
//accept策略
//参考acceptable strategies for improving web server performance
//回调函数,在acceptChannel_的handleEvent方法中被调用,接受客户端连接
void Acceptor::handleRead()
{
loop_->assertInLoopThread();
InetAddress peerAddr;
//FIXME loop until no more
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= )
{
// string hostport = peerAddr.toIpPort();
// LOG_TRACE << "Accepts of " << hostport;
//当在handleRead建立新的客户连接后,会调用这个回调函数,我觉得它的作用是处理客户的业务逻辑,问题是什么时候设置这个回调函数(在TcpServer的构造函数中设置)
if (newConnectionCallback_)
{
newConnectionCallback_(connfd, peerAddr);
}
else
{
sockets::close(connfd);
}
}
else
{
LOG_SYSERR << "in Acceptor::handleRead";
// Read the section named "The special problem of
// accept()ing when you can't" in libev's doc.
// By Marc Lehmann, author of livev.
//本进程的文件描述符已经达到上限,由于没有socket文件描述符来表示这个连接,就无法close它.若epoll_wait是LT,则每次调用都会立刻返回,因为新连接还等待处理
//准备一个空闲的文件描述符,在这种情况下,先关闭这个空闲的fd,然后accept拿到新socket连接的描述符,随后close它,再重新打开一个空闲文件给该空闲文件描述符
if (errno == EMFILE)
{
::close(idleFd_);
idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
::close(idleFd_);
idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
}
}
}

TcpServer类:管理accept获得的tcp连接.TcpServer是供用户直接使用的,生命期由用户控制.

///TcpServer内部使用Acceptor来获得新连接的fd,它保存用户提供的connectionCallback和MessageCallback,在新建TcpConnection的
///时候会原样传给后者,TcpServer持有目前存活的TcpConnection的shared_ptr(定义为TcpConnectionPtr)
///在新连接到达时,Acceptor会回调newConnection(),后者会创建TcpConnection对象conn,把它加入ConnectionMap,设置好callback,再调用
///conn->connectEstablished(),其中会回调用户提供的ConnectionCallback.
class TcpServer : boost::noncopyable
{
public:
/// Starts the server if it's not listenning.
///
/// It's harmless to call it multiple times.
/// Thread safe.
void start(); /// 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, const InetAddress& peerAddr);
/// Thread safe.
void removeConnection(const TcpConnectionPtr& conn);
/// Not thread safe, but in loop
void removeConnectionInLoop(const TcpConnectionPtr& conn);
//key是TcpConnection对象的名字
typedef std::map<string, TcpConnectionPtr> ConnectionMap; EventLoop* loop_; // the acceptor loop
const string ipPort_;
const string name_;
boost::scoped_ptr<Acceptor> acceptor_; // avoid revealing Acceptor
boost::shared_ptr<EventLoopThreadPool> threadPool_;
ConnectionCallback connectionCallback_;
MessageCallback messageCallback_;
WriteCompleteCallback writeCompleteCallback_;
ThreadInitCallback threadInitCallback_;
AtomicInt32 started_;
// always in loop thread
int nextConnId_;
ConnectionMap connections_;
};
//新的客户连接建立后,会调用该函数,sockfd是新连接的fd,peerAddr是客户地址
//该函数会创建TcpConnection对象conn,建立对象名字到对象的映射,设置好conn上的回调函数,最后调用TcpConnection类中的connectEstablished方法
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
EventLoop* ioLoop = threadPool_->getNextLoop();
char buf[];
snprintf(buf, sizeof buf, "-%s#%d", ipPort_.c_str(), nextConnId_);
++nextConnId_;
string connName = name_ + buf; LOG_INFO << "TcpServer::newConnection [" << name_
<< "] - new connection [" << connName
<< "] from " << peerAddr.toIpPort();
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(ioLoop,
connName,
sockfd,
localAddr,
peerAddr));
connections_[connName] = conn;
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
}

muduo尽量让依赖是单项的,TcpServer会用到Acceptor,但Acceptor并不知道TcpServer的存在。TcpServer会创建TcpConnection,但TcpConnection并不知道TcpServer的存在

TcpConnection类:

//作用是为刚建立的客户连接conn提供channel对象进行管理,TcpConnection使用Channel来获得socket上的IO事件
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
//当前状态得是未建立连接
assert(state_ == kConnecting);
//将当前状态设置为已建立连接
setState(kConnected);
channel_->tie(shared_from_this());
channel_->enableReading(); connectionCallback_(shared_from_this());
} //TcpConnection断开连接的实现
//handleRead检查read的返回值,根据返回值分别调用messageCallback_、handleClose、handleError
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = ;
////使用buffer来读取数据
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > )
{
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
else if (n == )
{
handleClose();
}
else
{
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
//closeCallback_是TcpServer在newConnection函数中注册的,是TcpServer::removeConnection方法.TcpServer::removeConnection方法把当前TcpConnection从ConnectionMap中移除,然后调用TcpConnection::connectDestroyed
//TcpConnection::connectDestroyed()设置当前TcpConnection的channel对象不再监听任何事件,然后移除该channel对象。
void TcpConnection::handleClose()
{
loop_->assertInLoopThread();
LOG_TRACE << "fd = " << channel_->fd() << " state = " << stateToString();
assert(state_ == kConnected || state_ == kDisconnecting);
// we don't close fd, leave it to dtor, so we can find leaks easily.
setState(kDisconnected);
channel_->disableAll(); TcpConnectionPtr guardThis(shared_from_this());
connectionCallback_(guardThis);
// must be the last line
closeCallback_(guardThis);
}
//设置当前TcpConnection的channel对象不再监听任何事件,然后移除该channel对象。
void TcpConnection::connectDestroyed()
{
loop_->assertInLoopThread();
if (state_ == kConnected)
{
setState(kDisconnected);
channel_->disableAll(); connectionCallback_(shared_from_this());
}
channel_->remove();
}

http://www.ccvita.com/515.html

使用Linuxepoll模型,水平触发模式;当socket可写时,会不停的触发socket可写的事件,如何处理?

第一种最普遍的方式:
需要向socket写数据的时候才把socket加入epoll,等待可写事件。
接受到可写事件后,调用write或者send发送数据。
当所有数据都写完后,把socket移出epoll。

这种方式的缺点是,即使发送很少的数据,也要把socket加入epoll,写完后在移出epoll,有一定操作代价。

一种改进的方式:
开始不把socket加入epoll,需要向socket写数据的时候,直接调用write或者send发送数据。如果返回EAGAIN,把socket加入epoll,在epoll的驱动下写数据,全部数据发送完毕后,再移出epoll。

这种方式的优点是:数据不多的时候可以避免epoll的事件处理,提高效率。

muduo采用level trigger,因此我们只在需要时才关注writable事件,否则就会造成busy loop

TcpConnection发送数据:

两个难点:关注writable事件的时机、发送数据的速度高于对方接收数据的速度,会造成数据在本地内存中堆积

第二个难点的解决方案:设置一个callback highWaterMarkCallback,如果输出缓冲的长度超过用户指定的大小,就会触发回调

//sendInLoop会先尝试直接发送数据,如果一次发送完毕就不会启用WriteCallback,如果只发送了部分数据,则把剩余的数据放入outputBuffer_,并
//开始关注writable事件,以后在handlerWrite()中发送剩余的数据
void TcpConnection::sendInLoop(const void* data, size_t len)
{
loop_->assertInLoopThread();
ssize_t nwrote = ;
size_t remaining = len;
bool faultError = false;
if (state_ == kDisconnected)
{
LOG_WARN << "disconnected, give up writing";
return;
}
// if no thing in output queue, try writing directly
if (!channel_->isWriting() && outputBuffer_.readableBytes() == )
{
nwrote = sockets::write(channel_->fd(), data, len);
if (nwrote >= )
{
remaining = len - nwrote;
if (remaining == && writeCompleteCallback_)
{
loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
}
}
else // nwrote < 0
{
nwrote = ;
if (errno != EWOULDBLOCK)
{
LOG_SYSERR << "TcpConnection::sendInLoop";
if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
{
faultError = true;
}
}
}
} assert(remaining <= len);
if (!faultError && remaining > )
{
size_t oldLen = outputBuffer_.readableBytes();
if (oldLen + remaining >= highWaterMark_
&& oldLen < highWaterMark_
&& highWaterMarkCallback_)
{
loop_->queueInLoop(boost::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
}
outputBuffer_.append(static_cast<const char*>(data) + nwrote, remaining);
if (!channel_->isWriting())
{
channel_->enableWriting();
}
}
} //当socket可写时,发送outputBuffer_中的数据,一旦发送完毕,立刻停止观察writable事件,避免busy loop
void TcpConnection::handleWrite()
{
loop_->assertInLoopThread();
if (channel_->isWriting())
{
ssize_t n = sockets::write(channel_->fd(),
outputBuffer_.peek(),
outputBuffer_.readableBytes());
if (n > )
{
outputBuffer_.retrieve(n);
if (outputBuffer_.readableBytes() == )
{
channel_->disableWriting();
if (writeCompleteCallback_)
{
loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
}
if (state_ == kDisconnecting)
{
shutdownInLoop();
}
}
}
else
{
LOG_SYSERR << "TcpConnection::handleWrite";
// if (state_ == kDisconnecting)
// {
// shutdownInLoop();
// }
}
}
else
{
LOG_TRACE << "Connection fd = " << channel_->fd()
<< " is down, no more writing";
}
}

http://blog.csdn.net/luojiaoqq/article/details/12780051

TCP网络库:Acceptor、TcpServer、TcpConnection的更多相关文章

  1. Muduo网络库源代码分析(六)TcpConnection 的生存期管理

    TcpConnection是使用shared_ptr来管理的类,由于它的生命周期模糊.TcpConnection表示已经建立或正在建立的连接.建立连接后,用户仅仅须要在上层类如TcpServer中设置 ...

  2. Linux多线程服务端编程:使用muduo C++网络库

    内容推荐本 书主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread.这 ...

  3. muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor

    目录 muduo网络库学习笔记(五) 链接器Connector与监听器Acceptor Connector 系统函数connect 处理非阻塞connect的步骤: Connetor时序图 Accep ...

  4. 公布一个基于 Reactor 模式的 C++ 网络库

    公布一个基于 Reactor 模式的 C++ 网络库 陈硕 (giantchen_AT_gmail) Blog.csdn.net/Solstice 2010 Aug 30 本文主要介绍 muduo 网 ...

  5. muduo网络库架构总结

    目录 muduo网络库简介 muduo网络库模块组成 Recator反应器 EventLoop的两个组件 TimerQueue定时器 Eventfd Connector和Acceptor连接器和监听器 ...

  6. 长文梳理muduo网络库核心代码、剖析优秀编程细节

    前言 muduo库是陈硕个人开发的tcp网络编程库,支持Reactor模型,推荐大家阅读陈硕写的<Linux多线程服务端编程:使用muduo C++网络库>.本人前段时间出于个人学习.找工 ...

  7. muduo网络库使用心得

    上个月看了朋友推荐的mudo网络库,下完代码得知是国内同行的开源作品,甚是敬佩.下了mudo使用手冊和035版的代码看了下结构,感觉是一个比較成熟并且方便使用的网络库.本人手头也有自己的网络库,尽管不 ...

  8. Mudo C++网络库第六章学习笔记

    muduo网络库简介 高级语言(Java, Python等)的Sockects库并没有对Sockects API提供更高层的封装, 直接用它编写程序很容易掉到陷阱中: 网络库的价值还在于能方便地处理并 ...

  9. poco网络库分析,教你如何学习使用开源库

    Poco::Net库中有 FTPClient HTML HTTP HTTPClient HTTPServer ICMP Logging Mail Messages NetCore NTP OAuth ...

随机推荐

  1. jsdk之微信分享流程

    .步骤一:绑定域名 先登录微信公众平台进入“公众号设置”的“功能设置”里填写“JS接口安全域名”. 备注:登录后可在“开发者中心”查看对应的接口权限. .步骤二:引入JS文件 在需要调用JS接口的页面 ...

  2. C#运用存储过程新增一条记录并返回自动生成的ID

    @Hcy黄灿奕:http://blog.sina.com.cn/iihcy 前言: 1.存储过的好处: 存储过程相对于其他的数据库访问方法有以下的优点: (1)重复使用.存储过程可以重复使用,从而可以 ...

  3. 洛谷 P1569 [USACO11FEB]属牛的抗议Generic Cow Prote…

    题目描述 Farmer John's N (1 <= N <= 100,000) cows are lined up in a row and numbered 1..N. The cow ...

  4. 【原创】DESTOON做中英双语言(多语言)切换版本具体详解

    第一次发原创好激动,该注意点什么? 在开发过程中用户有许多要求,比如这个多语言切换就是一个需求. 首先讲解一下DESTOON(DT)后台系统如何做这个中英.甚至多语言切换的这个功能. DT本身不自带多 ...

  5. 修改phpadmin中的默认超时时间

    登录后1440秒未活动后总是自动退出,一天还要登录多次,终于有时间来解决这个问题了,感觉是session超时,结果在网上search了下,找到解决办法啦,哈哈哈,在此做个笔记: phpmyadmin在 ...

  6. Python应该怎样实现快速入门?

    作为一名Python爱好者,我也想跟大家分享分享我自学Python的一些小经验.搬来你的小板凳,听听看吧.也许,你会很有收获,也许你也走上了自学Python的不归路.开讲啦~ 首先,你要有自信心,要明 ...

  7. tcp案例之文件下载器

    文件下载器客户端 import socket def main(): # 1.创建一个tcp socket tcp_client_socket=socket.socket(socket.AF_INET ...

  8. linux下nginx、php和mysql安装配置

    一.安装nginx 安装nginx yum install -y epel-release yum install nginx -y 查看nginx软件包包括了哪些文件 rpm -ql nginx 启 ...

  9. Python之trutle库-五角星

    Python之trutle库-五角星 #!/usr/bin/env python # coding: utf-8 # Python turtle库官方文档:https://docs.python.or ...

  10. LeetCode(56)Merge Intervals

    题目 Given a collection of intervals, merge all overlapping intervals. For example, Given [1,3],[2,6], ...