在《linux多线程服务端编程》的8.5节讲述了基于eventloop,channel,acceptor等底层的类构建TCP server的过程,以及TcpConnection的初步实现。这块的程序被各式各样的回调函数所充斥着,可读性不是太好。现在把相关程序的流程记录一下,方便以后的学习。
首先还是看应用程序(使用tcpserver类的程序):
#include "TcpServer.h"
#include "EventLoop.h"
#include "InetAddress.h"
#include <stdio.h>
void onConnection(const muduo::TcpConnectionPtr& conn)
{
if (conn->connected())
{
printf("onConnection(): new connection [%s] from %s\n",
conn->name().c_str(),
conn->peerAddress().toHostPort().c_str());
}
else
{
printf("onConnection(): connection [%s] is down\n",
conn->name().c_str());
}
}
void onMessage(const muduo::TcpConnectionPtr& conn,
muduo::Buffer* buf,
muduo::Timestamp receiveTime)
{
printf("onMessage(): received %zd bytes from connection [%s] at %s\n",
buf->readableBytes(),
conn->name().c_str(),
receiveTime.toFormattedString().c_str());
printf("onMessage(): [%s]\n", buf->retrieveAsString().c_str());
}
int main()
{
printf("main(): pid = %d\n", getpid());
muduo::InetAddress listenAddr(9981);
muduo::EventLoop loop;
muduo::TcpServer server(&loop, listenAddr);
server.setConnectionCallback(onConnection);
server.setMessageCallback(onMessage);
server.start();
loop.loop();
}
上述程序是使用Tcpserver类构建了一个简易的 tcp 服务器,然后写了两个简单的回调函数onConnection, onMessage。当新接收一个Tcp 连接时候,server会调用onconnection,而每次收到数据时server则会调用onmessage来完成业务逻辑。下面具体来分析tcp server处理请求的流程。
首先是构建一个server对象
muduo::TcpServer server(&loop, listenAddr);
loop 和 listenAddr被用于构造server对象,tcpserver的构造函数如下:
TcpServer::TcpServer(EventLoop* loop, const InetAddress& listenAddr)
: loop_(CHECK_NOTNULL(loop)),
name_(listenAddr.toHostPort()),
acceptor_(new Acceptor(loop, listenAddr)),
started_(false),
nextConnId_(1)
{
acceptor_->setNewConnectionCallback(
boost::bind(&TcpServer::newConnection, this, _1, _2));
}
如上所示,构造函数中会创建一个acceptor_对象来负责TCP请求的连接,Acceptor类自身的构造函数中会完成相应的socket准备工作,并且设定用于接收请求的channel回调函数 Acceptor::handleRead。
在Acceptor::handleRead的实现中,首先是调用accept()系统调用来接收连接,然后就会调用acceptor_->setNewConnectionCallback()方法,从而对应到tcpserver::newconnection中。
接下来是传递两个自定义回调函数onConnection,onMessage的句柄:
server.setConnectionCallback(onConnection);
server.setMessageCallback(onMessage);
这两个自定义的回调函数会在Tcpserver::newconnection中被使用,这个方法的源码:
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
char buf[32];
snprintf(buf, sizeof buf, "#%d", nextConnId_);
++nextConnId_;
std::string connName = name_ + buf;
LOG_INFO << "TcpServer::newConnection [" << name_
<< "] - new connection [" << connName
<< "] from " << peerAddr.toHostPort();
InetAddress localAddr(sockets::getLocalAddr(sockfd));
// FIXME poll with zero timeout to double confirm the new connection
TcpConnectionPtr conn(
new TcpConnection(loop_, connName, sockfd, localAddr, peerAddr));
connections_[connName] = conn;
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setCloseCallback(
boost::bind(&TcpServer::removeConnection, this, _1));
conn->connectEstablished();
}
在这个方法中会新创建一个TcpConnection对象,这个对象以后就负责管理这个刚刚创建好的tcp连接,而onConnection,onMessage这两个回调函数的句柄会被赋值给TcpConnection对象。最后在这个方法中会调用connectEstablished方法:
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
channel_->enableReading();
connectionCallback_(shared_from_this());
}
这个函数里面会调用channel_->enableReading(),从而将channel加入loop中,然后调用用户自定义的connectionCallback_。当然,这里的channel是在tcp_connection中新创建的chanel对象,并为其设置了新的回调函数方法,如下:
cpConnection::TcpConnection(EventLoop* loop,
const std::string& nameArg,
int sockfd,
const InetAddress& localAddr,
const InetAddress& peerAddr)
: loop_(CHECK_NOTNULL(loop)),
name_(nameArg),
state_(kConnecting),
socket_(new Socket(sockfd)),
channel_(new Channel(loop, sockfd)),
localAddr_(localAddr),
peerAddr_(peerAddr)
{
LOG_DEBUG << "TcpConnection::ctor[" << name_ << "] at " << this
<< " fd=" << sockfd;
channel_->setReadCallback(
boost::bind(&TcpConnection::handleRead, this, _1));
channel_->setWriteCallback(
boost::bind(&TcpConnection::handleWrite, this));
channel_->setCloseCallback(
boost::bind(&TcpConnection::handleClose, this));
channel_->setErrorCallback(
boost::bind(&TcpConnection::handleError, this));
}
接下来,loop会继续循环,此时TCP连接已经建立好了。下一步poll返回时,实际上就是要进行数据交互了,而完成数据交互的回调函数则是由TcpConnection定义的各种handle方法来实现。比如说tcpConnection::handleRead
void TcpConnection::handleRead(Timestamp receiveTime)
{
int savedErrno = 0;
ssize_t n = inputBuffer_.readFd(channel_->fd(), &savedErrno);
if (n > 0) {
messageCallback_(shared_from_this(), &inputBuffer_, receiveTime);
} else if (n == 0) {
handleClose();
} else {
errno = savedErrno;
LOG_SYSERR << "TcpConnection::handleRead";
handleError();
}
}
这个回调函数会先调用read系统调用读取数据到bufer,当读取的数据非量非0时,下一步就会调用messageCallback_这个用户自定义的函数来完成业务逻辑了,
以上就是整个程序的执行流程,对client而言,只需要定义并注册几个回调函数。底层的机制会完成相应的socket处理,并且在相应的时候比如连接建立时,有数据到来时调用用户自定义的函数来完成业务逻辑。