先简单例子:
TcpServer
)时,创建Acceptor
,设置接收到客户端请求后执行的回调函数Acceptor
创建监听套接字,将监听套接字绑定到一个Channel
中,设置可读回调函数为Acceptor
的handleRead
Acceptor
的listen
函数创建监听套接字,同时将Channel
添加到Poller
中Channel
被激活,调用可读回调函数handleRead
调用TcpServer提供的回调函数(newConnection)
TcpServer
的回调函数中创建 TcpConnection
代表这个tcp连接,设置tcp连接各种回调函数(由用户提供给TcpServer)TcpServer
让tcp
连接所属线程调用TcpConnection
的connectEstablished
connectEstablished
开启对客户端套接字的Channel的可读监听,然后调用用户提供的回调函数Channel的回调函数就是根据被激活原因调用不同的回调函数, 这些回调函数是在TcpConnection创建时就被设置的
套接字就记录在Channel中
TcpConnection
在创建之初就会为Channel
设置回调函数
,如果套接字可读/可写/错误/关闭等就会执行TcpConnection的函数
向Poller注册自己的Channel
/* TcpConnection.cc */
channel_->setReadCallback(
std::bind(&TcpConnection::handleRead, this, _1));
channel_->setWriteCallback(
std::bind(&TcpConnection::handleWrite, this));
channel_->setCloseCallback(
std::bind(&TcpConnection::handleClose, this));
channel_->setErrorCallback(
std::bind(&TcpConnection::handleError, this));
Channel
的handleEvent
就是根据fd激活事件的不同,调用不同的fd回调函数
Channel其实就是一个事件类,保存fd和需要监听的事件,以及各种回调函数
void Channel::handleEvent(Timestamp receiveTime)
{
/*
* RAII,对象管理资源
* weak_ptr使用lock提升成shared_ptr,此时引用计数加一
* 函数返回,栈空间对象销毁,提升的shared_ptr guard销毁,引用计数减一
*/
std::shared_ptr<void> guard;
if (tied_)
{
guard = tie_.lock();
if (guard)
{
handleEventWithGuard(receiveTime);
}
}
else
{
handleEventWithGuard(receiveTime);
}
}
handleEventWithGuard
就是根据不同的激活原因调用不同的回调函数,这些回调函数都在TcpConnection
中
/*
* 根据被激活事件的不同,调用不同的回调函数
*/
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
eventHandling_ = true;
LOG_TRACE << reventsToString();
if ((revents_ & POLLHUP) && !(revents_ & POLLIN))
{
if (logHup_)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLHUP";
}
if (closeCallback_) closeCallback_();
}
if (revents_ & POLLNVAL)
{
LOG_WARN << "fd = " << fd_ << " Channel::handle_event() POLLNVAL";
}
if (revents_ & (POLLERR | POLLNVAL))
{
if (errorCallback_) errorCallback_();
}
if (revents_ & (POLLIN | POLLPRI | POLLRDHUP))
{
if (readCallback_) readCallback_(receiveTime);
}
if (revents_ & POLLOUT)
{
if (writeCallback_) writeCallback_();
}
eventHandling_ = false;
}
通过服务器在处理客户端连接请求时,为了不阻塞在accept
上, 会将监听套接字注册到IO复用函数中
那么当客户端请求连接时,监听套接字变为可读,然后就会在回调函数
中调用accept
接收客户端连接
muduo将这一部分封装成了Acceptor
类,用于执行接收客户端请求的任务
对TCP socket,bind,listen,accept的封装,将sockfd以Channel的形式注册到EventLoop的Poller中,检测到sockfd可读时接收套接字
主要就是将监听套接字变为可读的回调函数
//Acceptor.h文件
class Acceptor : noncopyable
{
public:
typedef std::function<void (int sockfd, const InetAddress&)> NewConnectionCallback;
Acceptor(EventLoop* loop, const InetAddress& listenAddr, bool reuseport);
~Acceptor();
void setNewConnectionCallback(const NewConnectionCallback& cb)
{ newConnectionCallback_ = cb; }
/* 由服务器TcpServer设置的回调函数,在接收完客户端请求后执行,
用于创建TcpConnection */
bool listenning() const { return listenning_; }
void listen();//调用listen函数,转为监听套接字,同时将监听套接字添加到Poller中
private:
void handleRead();//回调函数,当有客户端请求连接的时候执行(将监听套接字变为可读
EventLoop* loop_;//事件驱动主循环
Socket acceptSocket_;//封装socket的一些接口
Channel acceptChannel_;//Channel,保存sockfd,被添加到Poller中,等待被激活
/*
当有客户端连接时首先内部接收连接,然后调用用户提供的回调函数
客户端套接字和地址作为参数传入
*/
NewConnectionCallback newConnectionCallback_;
bool listenning_;
int idleFd_;//优雅关闭连接
};
服务器启动时占用一个空闲文件描述符/dev/null,作用是解决文件描述符耗尽的情况
原理如下:
当服务器端文件描述符耗尽,当客户端再次请求连接,服务器端由于没有可用文件描述符,会返回-1,同时errno为EMFILE,意为描述符到达hard limit,无可用描述符,此时服务器端accept函数在获取一个空闲文件描述符时就已经失败,还没有从内核tcp连接队列中取出tcp连接
这会导致监听套接字一直可读,因为tcp连接队列中一直有客户端的连接请求
所以服务器在启动时打开一个空闲描述符/dev/null(文件描述符),先站着’坑‘,当出现上面情况,accept返回-1时,**服务器暂时关闭idleFd_让出’坑’,此时就会多出一个空闲描述符,然后再次调用accept接收客户端请求,并close接收后的客户端套接字,优雅的告诉客户端关闭连接,**然后再将’坑’占上
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_ >= 0);
acceptSocket_.setReuseAddr(true);
/*
* setsockopt设置套接字选项SO_REUSEADDR,对于端口bind,如果这个地址/端口处于TIME_WAIT,也可bind成功
* int flag = 1;
* setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
*/
acceptSocket_.setReusePort(reuseport);
/*
* setsockopt设置套接字选项SO_REUSEPORT,作用是对于多核cpu,允许在同一个对上运行多个相同服务器
* 内核会采用负载均衡的的方式分配客户端的连接请求给某一个服务器
*/
acceptSocket_.bindAddress(listenAddr);
acceptChannel_.setReadCallback(
std::bind(&Acceptor::handleRead, this));
/* Channel设置读事件的回调函数,此时还没有开始监听这个Channel,需要调用Channel::enableReading() */
}
构造函数中为用于监听的套接字设置了SO_REUSEPORT
和SO_REUSEADDR
属性,一个是端口重用,一个是地址重用。
acceptChannel_
用于保存用于监听的套接字,绑定回调函数,在合适的时机注册到Poller上(调用listen时)
void listen();//调用listen函数,转为监听套接字,同时将监听套接字添加到Poller中
void Acceptor::listen()
{
loop_->assertInLoopThread();
listenning_ = true;
acceptSocket_.listen();
/*
* 开始监听Channel,也就是设置fd关心的事件(EPOLLIN/EPOLLOUT等),然后添加到Poller中
* Poller中保存着所有注册到EventLoop中的Channel
*/
acceptChannel_.enableReading();
}
当监听套接字可读时,调用accept
接收客户端请求,如果描述符耗尽,释放idleFd_
重新accept
,然后关闭,再占用idleFd_
⭐⭐⭐
void Acceptor::handleRead()
{
loop_->assertInLoopThread();
InetAddress peerAddr;
//FIXME loop until no more
int connfd = acceptSocket_.accept(&peerAddr);
if (connfd >= 0)
{
// string hostport = peerAddr.toIpPort();
// LOG_TRACE << "Accepts of " << hostport;
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 libev.
if (errno == EMFILE)//这就是资源耗尽了, 用idleFd_ 了
{
::close(idleFd_);
idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
::close(idleFd_);
idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
}
}
}
newConnectionCallback_
、TcpServer
、TcpConnection
之间回调函数中调用的newConnectionCallback_
函数是在Acceptor
创建之初由TcpServer
设置的(TcpServer表示服务器,内有一个监听类Acceptor
),
newConnectionCallback_
函数主要用于初始化一个TcpConnection
,一个TcpConnection对象
代表着一个tcp连接
TcpConnection
的定义主要都是写set*函数,成员变量比较多
//代码:TcpConnection.h文件
EventLoop* loop_;//事件循环驱动
const string name_;//每个tcp连接有一个独一无二的名字,建立连接时由TcpServer传入
StateE state_; // FIXME: use atomic variable
bool reading_;
// we don't expose those classes to client.
std::unique_ptr<Socket> socket_;//用于tcp连接的套接字
std::unique_ptr<Channel> channel_;//用于监听套接字的Channel
const InetAddress localAddr_;//本地<地址,端口>
const InetAddress peerAddr_;//客户端<地址,端口>, 都由TcpServer传入
ConnectionCallback connectionCallback_;//连接建立后/关闭后的回调函数,
//通常是由用户提供给TcpServer,然后TcpServer提供给TcpConnection
MessageCallback messageCallback_;//当tcp连接有消息通信时执行的回调函数,也是由用户提供
WriteCompleteCallback writeCompleteCallback_;
/*
* 写入tcp缓冲区之后的回调函数
* 通常是tcp缓冲区满然后添加到应用层缓冲区后,由应用层缓冲区写入内核tcp缓冲区
* 后执行,一般用户不关心这部分
*/
HighWaterMarkCallback highWaterMarkCallback_;
/* 高水位回调,设定缓冲区接收大小,如果应用层缓冲区堆积的数据大于某个给定值时调用 */
CloseCallback closeCallback_;
/*
* tcp连接关闭时调用的回调函数,由TcpServer设置,用于TcpServer将这个要关闭的TcpConnection从
* 保存着所有TcpConnection的map中删除
* 这个回调函数和TcpConnection自己的handleClose不同,后者是提供给Channel的,函数中会使用到
* closeCallback_
*/
size_t highWaterMark_;/* 高水位值 */
Buffer inputBuffer_;
Buffer outputBuffer_; // FIXME: use list as output buffer.
/* 输入输出缓冲区 */
boost::any context_;
// FIXME: creationTime_, lastReceiveTime_
// bytesReceived_, bytesSent_
};
typedef std::shared_ptr<TcpConnection> TcpConnectionPtr;
} // namespace net
} // namespace muduo
设置当fd就绪时调用的回调函数,Channel代表一个对fd事件的监听,主要是为Channel提供各种回调函数
TcpConnection::TcpConnection(EventLoop* loop,
const string& nameArg,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr)
: loop_(CHECK_NOTNULL(loop)),
name_(nameArg),
state_(kConnecting),
reading_(true),
socket_(new Socket(sockfd)),
channel_(new Channel(loop, sockfd)),
localAddr_(localAddr),
peerAddr_(peerAddr),
highWaterMark_(64*1024*1024)
{
/* 设置各种回调函数 */
channel_->setReadCallback(
std::bind(&TcpConnection::handleRead, this, _1));
channel_->setWriteCallback(
std::bind(&TcpConnection::handleWrite, this));
channel_->setCloseCallback(
std::bind(&TcpConnection::handleClose, this));
channel_->setErrorCallback(
std::bind(&TcpConnection::handleError, this));
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
socket_->setKeepAlive(true);
/*
* 设置KEEP-ALIVE属性,如果客户端很久没有和服务器通讯,tcp会自动判断客户端是否还处于连接(类似心跳包)
*
* int setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &sockopt, static_cast(sizeof(sockopt)));
*/
}
调用的是Channel
的hanleEvent
, 上文复习过了Channel
的hanleEvent
函数调用handleEventWithGuard
,根据不同激活原因调用不同回调函数
TcpServer
创建完TcpConnection
后,会设置各种回调,调用TcpConnection
的connectEstablished
函数,主要用于将Channel
添加到Poller
中,同时调用用户提供的连接建立成功后的回调函数
//TcpServer.cc
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
EventLoop* ioLoop = threadPool_->getNextLoop();//从事件驱动线程池中取出一个线程给TcpConnection
char buf[64];
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));//创建一个新的TcpConnection代表一个Tcp连接
connections_[connName] = conn;//添加到所有tcp连接的map中, 键是tcp连接特有的名字
//服务器名+客户端<地址,端口>
//为tcp连接设置回调函数(由用户提供)
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
std::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
/*
* 关闭回调函数,由TcpServer设置,作用是将这个关闭的TcpConnection从map中删除
* 当poll返回后,发现被激活的原因是EPOLLHUP,此时需要关闭tcp连接
* 调用Channel的CloseCallback,进而调用TcpConnection的handleClose,进而调用removeConnection
*/
ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
/*
* 连接建立后,调用TcpConnection连接建立成功的回调函数,这个函数会调用用户提供的回调函数
* 1.新建的TcpConnection所在事件循环是在事件循环线程池中的某个线程
* 2.所以TcpConnection也就属于它所在的事件驱动循环所在的那个线程
* 3.调用TcpConnection的函数时也就应该在自己所在线程调用
* 4.所以需要调用runInLoop在自己的那个事件驱动循环所在线程调用这个函数
*/
}
connectEstablished
handleRead
调用TcpServer提供的回调函数(newConnection)
TcpServer
的回调函数中创建 TcpConnection
代表这个tcp连接,设置tcp连接各种回调函数(由用户提供给TcpServer)TcpServer
让tcp
连接所属线程调用TcpConnection
的connectEstablished
connectEstablished
开启对客户端套接字的Channel的可读监听,然后调用用户提供的回调函数void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
channel_->tie(shared_from_this());//Channel中对TcpConnection的弱引用在这里设置
channel_->enableReading();//设置对可读事件的监听,同时将Channel添加到Poller中
connectionCallback_(shared_from_this());//用户提供的回调函数,在连接建立成功后调用
}
至此tcp连接建立完成,在用户提供的回调函数中,传入的参数便是这个TcpConnection的shared_ptr,用户可以使用TcpConnection::send
操作向客户端发送消息(放到后面)
有连接的建立就有连接的关闭,当客户端主动关闭(调用close)时,服务器端对应的Channel
被激活,激活原因为EPOLLHUP
,表示连接已关闭,此时会调用TcpConnection
的回调函数handleClose
,在这个函数中,TcpConnection
处理执行各种关闭动作,包括
Channel
从Poller
中移除TcpServer
提供的关闭回调函数,将自己从TcpServer
的tcp
连接map
中移除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());
// 此时当前的TcpConnection的引用计数为2,一个是guardThis,另一个在TcpServer的connections_中
connectionCallback_(guardThis);
// must be the last line
closeCallback_(guardThis);
/*
* closeCallback返回后,TcpServer的connections_(tcp连接map)已经将TcpConnection删除,引用计数变为1
* 此时如果函数返回,guardThis也会被销毁,引用计数变为0,这个TcpConnection就会被销毁
* 所以在TcpServer::removeConnectionInLoop使用bind将TcpConnection生命期延长,引用计数加一,变为2
* 就算guardThis销毁,引用计数仍然有1个
* 等到调用完connectDestroyed后,bind绑定的TcpConnection也会被销毁,引用计数为0,TcpConnection析构
*/
}
void TcpServer::removeConnection(const TcpConnectionPtr& conn)
{
// FIXME: unsafe
loop_->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
}
在TcpConnection
所在的事件驱动循环所在的线程执行删除工作, 因为需要操作TcpServer::connections_
,就需要传TcpServer
的this
指针到TcpConnection
所在线程, 会导致将TcpServer
暴露给TcpConnection
线程,也不具有线程安全性
TcpConnection
所在线程:在创建时从事件驱动循环线程池中选择的某个事件驱动循环线程TcpServer
所在线程:事件驱动循环线程池所在线程,不在线程池中TcpConnection
所在线程,因为它被激活,然后调用回调函数,都是在自己线程执行的removeConnection
的调用者TcpServer
的this
指针如今在TcpConnection
所在线程this
指针delele
了,或者改了什么东西,那么TcpServer
所在线程就会出错为什么不在TcpServer所在线程执行以满足线程安全性(TcpConnection就是由TcpServer所在线程创建的)
loop_->runInLoop(std::bind(&TcpServer::removeConnectionInLoop, this, conn));
removeConnectionInLoop
这个函数是线程安全的,因为是由TcpServer所在事件驱动循环调用的
void TcpServer::removeConnectionInLoop(const TcpConnectionPtr& conn)
{
loop_->assertInLoopThread();
LOG_INFO << "TcpServer::removeConnectionInLoop [" << name_
<< "] - connection " << conn->name();
size_t n = connections_.erase(conn->name());
(void)n;
assert(n == 1);
EventLoop* ioLoop = conn->getLoop();
ioLoop->queueInLoop(
std::bind(&TcpConnection::connectDestroyed, conn));
}
感谢https://blog.csdn.net/sinat_35261315/article/details/78343266