下面就是利用muduo进行客户端与服务器端的实例:
网络编程关注三个半事件:连接建立、连接断开、消息到达这算三个,还有半个是消息发送完毕(之所以是半个,因为对于低流量的服务,通常不需要关注该事件)
所以,我们利用muduo编写自己的服务器时,需要有一个XXXServer类,在该类中包含一个TcpServer对象和一个EventLoop对象。这个类需要提供3个成员函数OnConnection、OnMessage、OnWriteComplete作为回调函数
#include <muduo/net/TcpServer.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/InetAddress.h>
#include <boost/bind.hpp>
#include <stdio.h>
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 msg(buf->retrieveAllAsString());
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();
}
分析:
1.server.starrt()也就是server_.start(),此时服务端启动
//该函数可以跨线程调用
//启动线程池
void TcpServer::start()
{
if (started_.getAndSet(1) == 0)
{
threadPool_->start(threadInitCallback_);
//判断是否处于监听状态
assert(!acceptor_->listenning());
loop_->runInLoop(
//进入&Acceptor::listen
boost::bind(&Acceptor::listen, get_pointer(acceptor_)));
}
}
2.执行Acceptor::listen(),此时服务器处于监听状态,并且注册被动连接通道,关注其可读事件
//监听
void Acceptor::listen()
{
loop_->assertInLoopThread();
listenning_ = true;
//监听
acceptSocket_.listen();
//关注可读事件
acceptChannel_.enableReading();
}
3.注册被动连接通道,关注其可读事件
//关注可读事件,把通道注册到EventLoop,从而注册到EventLoop所持有的poll对象中
void enableReading() { events_ |= kReadEvent; update(); }
void Channel::update()
{
addedToLoop_ = true;
//调用loop的updateChannel
loop_->updateChannel(this);
}
进而调用loop的updateChannel()
void EventLoop::updateChannel(Channel* channel)
{
assert(channel->ownerLoop() == this);
assertInLoopThread();
poller_->updateChannel(channel);
}
进而调用poller(有可能是PollPoller或者EPollPoller)的updateChannel()
//注册或者更新通道
//注册某个文件描述符的一些可读可写事件,更新就是原来关注可读可写,现在只关注可读
void PollPoller::updateChannel(Channel* channel)
{
//断言是在Loop线程中调用Poller
Poller::assertInLoopThread();
LOG_TRACE << "fd = " << channel->fd() << " events = " << channel->events();
//如果index_<0,位置还不知道,说明是一个新的通道
if (channel->index() < 0)
{
// a new one, add to pollfds_
assert(channels_.find(channel->fd()) == channels_.end());
struct pollfd pfd;
pfd.fd = channel->fd();
pfd.events = static_cast<short>(channel->events());
pfd.revents = 0;
pollfds_.push_back(pfd);
int idx = static_cast<int>(pollfds_.size())-1;
//现在是个已经存在的通道,idx不会为-1
channel->set_index(idx);
//key是文件描述符,value是channel
channels_[pfd.fd] = channel;
}
//更新一个已经存在的通道
else
{
// update existing one
assert(channels_.find(channel->fd()) != channels_.end());
assert(channels_[channel->fd()] == channel);
int idx = channel->index();
assert(0 <= idx && idx < static_cast<int>(pollfds_.size()));
struct pollfd& pfd = pollfds_[idx];
assert(pfd.fd == channel->fd() || pfd.fd == -channel->fd()-1);
pfd.fd = channel->fd();
pfd.events = static_cast<short>(channel->events());
pfd.revents = 0;
//将一个通道暂时更新为不关注事件,但不从Poller中移除该通道
if (channel->isNoneEvent())
{
// ignore this pollfd
//暂时忽略该文件描述符的事件
pfd.fd = -channel->fd()-1;
}
}
}
4.经过上面的步骤,就注册了被动连接通道的可读事件,loop::loop();开始事件循环,此时如果客户端有连接过来,poller_->poll()就会返回这个活跃的通道
//事件循环,该函数不能跨线程使用,只能在创建对象的线程中调用
void EventLoop::loop()
{
assert(!looping_);
//断言当前处于创建该对象的线程中
assertInLoopThread();
looping_ = true;
quit_ = false; // FIXME: what if someone calls quit() before loop() ?
LOG_TRACE << "EventLoop " << this << " start looping";
while (!quit_)
{
activeChannels_.clear();
//调用poll返回活动的通道,前一个参数是超时时间
pollReturnTime_ = poller_->poll(kPollTimeMs, &activeChannels_);
++iteration_;
if (Logger::logLevel() <= Logger::TRACE)
{
//打印活动通道
printActiveChannels();
}
// TODO sort channel by priority
eventHandling_ = true;
//遍历活动通道进行处理
for (ChannelList::iterator it = activeChannels_.begin();
it != activeChannels_.end(); ++it)
{
//当前的处理通道
currentActiveChannel_ = *it;
//调用handleEvent处理通道
currentActiveChannel_->handleEvent(pollReturnTime_);
}
currentActiveChannel_ = NULL;
eventHandling_ = false;
//其它线程或者当前IO线程添加的一些回调任务
//因为IO线程的设计比较灵活,IO线程也能执行一些IO任务
//I/O不是很繁忙的时候,IO线程就一直处于阻塞的状态,也就是不工作的状态
//我们就可以添加一些计算任务,让它来处理
doPendingFunctors();
}
5.调用Channel中的handleEvent处理活跃的通道
//当事件到来调用handleEvent处理
void Channel::handleEvent(Timestamp receiveTime)
{
boost::shared_ptr<void> guard;
if (tied_)
{
//提升,返回一个shared_ptr对象
guard = tie_.lock();
if (guard)
{
handleEventWithGuard(receiveTime);
}
}
else
{
handleEventWithGuard(receiveTime);
}
}
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;
}
6.此时是已连接通道中的可读事件发生,调用Acceptor注册的回调函数处理
Acceptor的构造函数中注册的回调函数
acceptChannel_.setReadCallback(
boost::bind(&Acceptor::handleRead, this));
进入handleRead()
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)
{
//把这个空闲的文件描述符关闭掉,腾出一个位置
::close(idleFd_);
idleFd_ = ::accept(acceptSocket_.fd(), NULL, NULL);
//接受完以后关闭掉
::close(idleFd_);
idleFd_ = ::open("/dev/null", O_RDONLY | O_CLOEXEC);
}
}
}
7.调用TcpServer中注册的回调函数,进行相应可读事件的处理
TcpServer的构造函数中注册的回调函数
//对accptor_设置一个回调函数
//Acceptor::handleRead函数中会回调用TcpServer::newConnection
//_1对应的是socket文件描述符,_2对应的是对等方的地址
acceptor_->setNewConnectionCallback(
boost::bind(&TcpServer::newConnection, this, _1, _2));
进入newConnection(),此时就创建了一个连接对象
//一个新的连接之后
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
//按照轮叫的方式选择一个EventLoop
EventLoop* ioLoop = threadPool_->getNextLoop();
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));
//将连接对象放到一个map容器中
connections_[connName] = conn;
对这个连接对象设置回调函数
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1)); // FIXME: unsafe
//不能够在当前线程中调用,应该让ioLoop所属的IO线程调用这个连接
ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn));
}
8.进入connectEstablished,因为有新的连接的建立,准备将已连接通道加入到Poller,关注其可读事件
//当连接到来的时候
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
//tie的参数是shared_ptr,shared_from_this()获得一个自身的share_ptr对象
channel_->tie(shared_from_this());
//TcpConnection所对应的通道加入到Poller关注
channel_->enableReading();
//这是用户的回调函数
connectionCallback_(shared_from_this());
}
9.注册已连接通道,关注其可读事件
代码和步骤3的代码一样
10.经过上面的步骤,就注册了已连接通道,关注了可读事件,loop::loop()一直在循环,此时如果客户端有发消息过来,poller_->poll()就会返回这个活跃的通道
代码和步骤4的代码一样
11.调用Channel中的handleEvent处理活跃的通道
代码和步骤5的代码一样
12.此时是已连接通道中的可读事件发生,调用TcpConnection注册的回调函数处理
TcpConnection的构造函数中注册的回调函数
//通道可读事件到来的时候,回调tcpConnection::handleRead,_1是事件发生时间
channel_->setReadCallback(
boost::bind(&TcpConnection::handleRead, this, _1));
进入handleRead(),此时就将socket中的数据接收到了缓冲区中,如果应用层注册了回调函数,则执行。
//当一个消息到来的时候,调用handleRead
void TcpConnection::handleRead(Timestamp receiveTime)
{
loop_->assertInLoopThread();
int savedErrno = 0;
//将数据接受到inputBuffer_这个缓冲区中
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0)
{
//shared_from_this()的作用是转为shared_ptr
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
}
//处理连接断开
else if (n == 0)
{
//返回为0的话,处理连接断开
handleClose();
}
//否则的话处理错误
else
{
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
调用inputBuffer_.readFd(channel_->fd,&savedErrno);
//从套接字读取数据,然后添加到缓冲区中
//结合栈上的空间,避免内存使用过大,提高内存使用率
//如果有5000个连接,每个连接就分配64K+64K的缓冲区的话,将占用640M内存
//而大多数时候,这些缓冲区使用率很低
ssize_t Buffer::readFd(int fd, int* savedErrno)
{
// saved an ioctl()/FIONREAD call to tell how much to read
//栈上的空间
char extrabuf[65536];
struct iovec vec[2];
const size_t writable = writableBytes();
//第一块缓冲区
vec[0].iov_base = begin()+writerIndex_;
vec[0].iov_len = writable;
//第二块缓冲区,这是栈上的空间
vec[1].iov_base = extrabuf;
vec[1].iov_len = sizeof extrabuf;
// when there is enough space in this buffer, don't read into extrabuf.
// when extrabuf is used, we read 128k-1 bytes at most.
const int iovcnt = (writable < sizeof extrabuf) ? 2 : 1;
const ssize_t n = sockets::readv(fd, vec, iovcnt);
if (n < 0)
{
*savedErrno = errno;
}
//第一块缓冲区足够容纳
else if (implicit_cast<size_t>(n) <= writable)
{
writerIndex_ += n;
}
//当前缓冲区不够容纳,因而数据被接受到了第二块缓冲区extrabuf,将其append至buffer
else
{
writerIndex_ = buffer_.size();
append(extrabuf, n - writable);
}
// if (n == writable + sizeof extrabuf)
// {
// goto line_30;
// }
return n;
}
13.执行应用层中消息到达回调函数
void onMessage(const TcpConnectionPtr& conn,
Buffer* buf,
Timestamp receiveTime)
{
//获取应用层缓冲区的数据
string msg(buf->retrieveAllAsString());
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_;
};
14.现在服务端已经收到了数据,而且还要执行conn->send(),转到send()函数
//线程安全,可以跨线程调用
void TcpConnection::send(const StringPiece& message)
{
if (state_ == kConnected)
{
if (loop_->isInLoopThread())
{
sendInLoop(message);
}
else
{
loop_->runInLoop(
boost::bind(&TcpConnection::sendInLoop,
this, // FIXME
message.as_string()));
//std::forward<string>(message)));
}
}
}
转入sendInLoop()
如果已连接通道没有关注可写事件并且发送缓冲区没有数据,直接write,此时有两种情况,如果把数据都写入了内核缓冲区,那么很方便,结束。如果发送了一部分,内核缓冲区满了,则需要将未发送到内核缓冲区的数据加入应用层缓冲区,因为应用层缓冲区有数据了,此时就需要关注已连接通道的可写事件了。
void TcpConnection::sendInLoop(const void* data, size_t len)
{
loop_->assertInLoopThread();
ssize_t nwrote = 0;
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
//通道没有关注可写事件并且发送缓冲区没有数据,直接write
if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0)
{
//channel_->fd()是已连接套接字
nwrote = sockets::write(channel_->fd(), data, len);
if (nwrote >= 0)
{
remaining = len - nwrote;
//写完了,回调writeCompleteCallback_
if (remaining == 0 && writeCompleteCallback_)
{
loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
}
}
else // nwrote < 0
{
nwrote = 0;
if (errno != EWOULDBLOCK)
{
LOG_SYSERR << "TcpConnection::sendInLoop";
if (errno == EPIPE || errno == ECONNRESET) // FIXME: any others?
{
faultError = true;
}
}
}
}
assert(remaining <= len);
//没有错误,并且还有未写完的数据(说明内核缓冲区满,要将未写完的数据添加到output buffer中)
if (!faultError && remaining > 0)
{
size_t oldLen = outputBuffer_.readableBytes();
if (oldLen + remaining >= highWaterMark_
&& oldLen < highWaterMark_
&& highWaterMarkCallback_)
{
loop_->queueInLoop(boost::bind(highWaterMarkCallback_, shared_from_this(), oldLen + remaining));
}
//将未发送完的数据添加到outputBuffer_
//这个时候outputBuffer_有数据了,需要关注POLLOUT事件
outputBuffer_.append(static_cast<const char*>(data)+nwrote, remaining);
if (!channel_->isWriting())
{
//关注POLLOUT事件
//POLLOUT事件触发的时候,回调handleWrite()
channel_->enableWriting();
}
}
}
15.已连接通道关注POLLOUT(可写)事件
void enableWriting() { events_ |= kWriteEvent; update(); }
然后重复步骤3的代码,不同的是,已连接通道早已注册,现在是要改变其关注的事件(加上可写事件)
16.此时,如果内核缓冲区有了空余,loop->loop()返回已连接通道
调用Channel中的handleEvent处理活跃的通道
代码和步骤5的代码一样
17.此时是已连接通道中的可写事件发生,调用TcpConnection注册的回调函数处理
TcpConnection的构造函数中注册的回调函数
//通道可写事件到来的时候,回调tcpConnection::handleWrite,_1是事件发生时间
channel_->setWriteCallback(
boost::bind(&TcpConnection::handleWrite, this));
进入handleWrite(),此时内核缓冲区中有空间了,可以将应用层缓冲区中的数据写入内核缓冲区了,此时有两种情况,内核缓冲区中的空余空间可以容纳应用层缓冲区中的数据了,那么将数据写入内核缓冲区后,就清空应用层发送缓冲区中的数据,并且停止已连接通道的可写事件。第二种情况是内核缓冲区还是只能容纳一部分,应用层缓冲区中还有数据,那么还需要继续关注可写通道中的POLLOUT事件,直到应用层缓冲区中的数据都发往内核缓冲区,结束。
//内核发送缓冲区有空间了,回调该函数,也就是POLLOUT事件触发了
void TcpConnection::handleWrite()
{
loop_->assertInLoopThread();
if (channel_->isWriting())
{
//将outputBuffer_缓冲区中的数据写入,不一定保证把数据都写入
ssize_t n = sockets::write(channel_->fd(),
outputBuffer_.peek(),
outputBuffer_.readableBytes());
if (n > 0)
{
outputBuffer_.retrieve(n);
//发送缓冲区已清空
if (outputBuffer_.readableBytes() == 0)
{
//停止关注可写事件,以免出现busy loop
channel_->disableWriting();
if (writeCompleteCallback_)
{
//应用层发送缓冲区已被清空,就会调用writeCompleteCallback_
loop_->queueInLoop(boost::bind(writeCompleteCallback_, shared_from_this()));
}
//发送缓冲区已清空并且连接状态是kDisconnecting,要关闭连接
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";
}
}
18.至此,整个过程就结束