先看下TcpServer连接建立/处理的时序图
1:当loop()函数监听到通道acceptChannel_有事件到来,即listen套接字可读时
2:acceptChannel_->handleEvent()
3:Acceptor::handleRead()
4:handleRead()函数中又调用了accept()接收客户端的请求
5:连接建立后,会调用注册的回调函数newConnection()
6:在回调newConnection()函数中,使用返回的sockfd先创建channel_,再用sockfd+channel_构造一个TcpConnection对象用于与client通信(channel_是TcpConnection的成员变量,监听读写事件用于通信)
7:调用了TcpConnection::connectEstablished()函数,在connectEstablished函数中,又回调了用户注册的connCb函数
channel_->enableReading(); //关注读事件
connectionCallback_(shared_from_this()); //执行回调函数connectionCallback_
typedef std::map<string, TcpConnectionPtr> ConnectionMap;
EventLoop* loop_; //the acceptor loop,即mainEVentLoop
const string ipPort_; //端口
const string name_; //服务器的名字
std::unique_ptr<Acceptor> acceptor_; //功能:socket、bind、listen
std::shared_ptr<EventLoopThreadPool> threadPool_; //一个线程池
//回调函数
ConnectionCallback connectionCallback_; //连接到来
MessageCallback messageCallback_; //消息到来
WriteCompleteCallback writeCompleteCallback_;
ThreadInitCallback threadInitCallback_;
AtomicInt32 started_;
int nextConnId_; //
ConnectionMap connections_; //哈希表,保存着每一个客户端连接
① EventLoop* loop_
是mainEventLoop
② std::shared_ptr
线程池,一个mainEventLoop+多个subEventLoop
③ std::unique_ptr
用于接收新的连接
④ typedef std::map
类型是一个哈希表,它的每一个元素,即
//构造函数、析构函数
TcpServer(EventLoop* loop,
const InetAddress& listenAddr,
const string& nameArg,
Option option = kNoReusePort);
~TcpServer(); // force out-line dtor, for std::unique_ptr members.
//获得成员变量的值
const string& ipPort() const { return ipPort_; } //端口
const string& name() const { return name_; } //服务名称
EventLoop* getLoop() const { return loop_; } //mainEventLoop
//set成员变量的值
//设置subEventLoop的个数
void setThreadNum(int numThreads);
{
assert(0 <= numThreads);
threadPool_->setThreadNum(numThreads);
}
//set成员变量(回调函数)的值
void setConnectionCallback(const ConnectionCallback& cb)
{ connectionCallback_ = cb; }
void setMessageCallback(const MessageCallback& cb)
{ messageCallback_ = cb; }
void setWriteCompleteCallback(const WriteCompleteCallback& cb)
{ writeCompleteCallback_ = cb; }
//开启所有的EventLoop && 开始listen客户端的连接请求
void start();
{
if (started_.getAndSet(1) == 0)
{
//EventLoopThreadPool::start(___)
//1.创建numThreads个线程,每个子线程都创建一个事件循环subEventLoop
//2.每个subEventLoop都开启事件循环,即调用loop()
threadPool_->start(threadInitCallback_);
//3.执行Acceptor::listen()
assert(!acceptor_->listenning());
loop_->runInLoop(
std::bind(&Acceptor::listen, get_pointer(acceptor_)));
//get_pointer:返回原生指针
}
}
TcpServer::TcpServer(EventLoop* loop,
const InetAddress& listenAddr,
const string& nameArg,
Option option)
: loop_(CHECK_NOTNULL(loop)), //检查loop不是空指针
ipPort_(listenAddr.toIpPort()),
name_(nameArg),
//创建acceptor,用于listen、accept
acceptor_(new Acceptor(loop, listenAddr, option == kReusePort)),
//线程池,多个事件循环mainEventLoop+subEventLoop
threadPool_(new EventLoopThreadPool(loop, name_)),
connectionCallback_(defaultConnectionCallback),
messageCallback_(defaultMessageCallback),
nextConnId_(1)
{
//当一个新的连接到来时后,执行回调函数TcpServer::newConnection
//_1 文件描述符 _2 对等方的地址
acceptor_->setNewConnectionCallback(
std::bind(&TcpServer::newConnection, this, _1, _2));
}
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr)
{
loop_->assertInLoopThread();
//1.按照RR方式,查找ioLoop
EventLoop* ioLoop = threadPool_->getNextLoop();
// 生成唯一的name
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));
//2.在ioLoop上,创建一个TcpConnection对象
TcpConnectionPtr conn(new TcpConnection(ioLoop,
connName,
sockfd,
localAddr,
peerAddr));
//3.将这个对象加入到ConnectionMap哈希表中
connections_[connName] = conn;
//4.使用TcpServer类的成员变量(回调函数),给TcpConnection的成员变量赋值
conn->setConnectionCallback(connectionCallback_);
conn->setMessageCallback(messageCallback_);
conn->setWriteCompleteCallback(writeCompleteCallback_);
conn->setCloseCallback(std::bind(&TcpServer::removeConnection, this, _1));
//5.调用conn->connectEstablished()
ioLoop->runInLoop(std::bind(&TcpConnection::connectEstablished, conn));
}
void TcpConnection::connectEstablished()
{
loop_->assertInLoopThread();
assert(state_ == kConnecting);
setState(kConnected);
channel_->tie(shared_from_this());
channel_->enableReading(); //关注读事件
//执行回调函数connectionCallback_
connectionCallback_(shared_from_this());
}
① 提供一个XXXServer类
② 在该类中包含一个TcpServer对象
③ 实现三个半事件,即:只需要bind并重写onConnection、onMessage、onWriteComplete函数
代码存放在muduo\examples\simple下
该代码,它实际上是一个discard服务。但目前它永远不会关闭socket,即永远走不到else分支,在遇到对方断开连接的时候会陷入busy-loop。该问题留在后面章节解决
#include
#include
#include
#include
#include
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
int numThreads = 0;
class EchoServer
{
public:
EchoServer(EventLoop* loop, const InetAddress& listenAddr)
: loop_(loop),
server_(loop, listenAddr, "EchoServer")
{
server_.setConnectionCallback(
std::bind(&EchoServer::onConnection, this, _1));
server_.setMessageCallback(
std::bind(&EchoServer::onMessage, this, _1, _2, _3));
server_.setThreadNum(numThreads);
}
void start()
{
server_.start();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
LOG_TRACE << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
LOG_INFO << conn->getTcpInfoString();
conn->send("hello\n");
}
void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time)
{
//接收readable中所有的数据,转换成string
string msg(buf->retrieveAllAsString());
LOG_TRACE << conn->name() << " recv " << msg.size() << " bytes at " << time.toString();
//根据msg的内容,执行相应的动作
if (msg == "exit\n")
{
conn->send("bye\n");
conn->shutdown();
}
if (msg == "quit\n")
{
loop_->quit();
}
conn->send(msg); //将msg回射回去
}
EventLoop* loop_;
TcpServer server_;
};
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid() << ", tid = " << CurrentThread::tid();
LOG_INFO << "sizeof TcpConnection = " << sizeof(TcpConnection);
if (argc > 1)
{
numThreads = atoi(argv[1]);
}
bool ipv6 = argc > 2;
EventLoop loop;
InetAddress listenAddr(2000, false, ipv6);
EchoServer server(&loop, listenAddr);
server.start();
loop.loop();
}
客户端连接上之后,服务器将不断的给客户端发送大流量的数据,服务器并每隔3秒统计一次吞吐量
chargen.h头文件
#ifndef MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H
#define MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H
#include
// RFC 864
class ChargenServer
{
public:
ChargenServer(muduo::net::EventLoop* loop,
const muduo::net::InetAddress& listenAddr,
bool print = false);
void start();
private:
void onConnection(const muduo::net::TcpConnectionPtr& conn);
void onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp time);
void onWriteComplete(const muduo::net::TcpConnectionPtr& conn);
void printThroughput();
muduo::net::TcpServer server_;
muduo::string message_;
int64_t transferred_;
muduo::Timestamp startTime_;
};
#endif // MUDUO_EXAMPLES_SIMPLE_CHARGEN_CHARGEN_H
chargen.c实现文件
#include "chargen.h"
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
ChargenServer::ChargenServer(EventLoop* loop,
const InetAddress& listenAddr,
bool print)
: server_(loop, listenAddr, "ChargenServer"),
transferred_(0),
startTime_(Timestamp::now())
{
server_.setConnectionCallback(
std::bind(&ChargenServer::onConnection, this, _1));
server_.setMessageCallback(
std::bind(&ChargenServer::onMessage, this, _1, _2, _3));
server_.setWriteCompleteCallback(
std::bind(&ChargenServer::onWriteComplete, this, _1));
//生成数据:每行72个字符,总共94行
string line;
for (int i = 33; i < 127; ++i)
{
line.push_back(char(i)); //line: 33,34,35,... ...,126
}
line += line; //line: 33,34,35,... ...,126,33,34,35,... ...,126
for (size_t i = 0; i < 127-33; ++i)
{
//line.substr(i, 72),表示substr获得字符串line中,从第i位开始的长度为72的字符串
message_ += line.substr(i, 72) + '\n';
}
/*
message_:类型string
33,34,35,... ...,126,\n
34,35,36,... ...,33,\n
35,36,37,... ...,34,\n
...
...
126,33,34,... ...,125,\n
*/
if (print) //每隔3s调用printThroughput函数打印吞吐量
{
loop->runEvery(3.0, std::bind(&ChargenServer::printThroughput, this));
}
}
void ChargenServer::start()
{
server_.start();
}
void ChargenServer::onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << "ChargenServer - " << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
if (conn->connected())
{
conn->setTcpNoDelay(true);
conn->send(message_);
}
}
//整块message_数据发送完毕后,会回调onWriteComplete函数
void ChargenServer::onWriteComplete(const TcpConnectionPtr& conn)
{
transferred_ += message_.size();
conn->send(message_); //继续调用send发送message_
}
void ChargenServer::printThroughput() //计算并打印吞吐量
{
Timestamp endTime = Timestamp::now();
double time = timeDifference(endTime, startTime_);
printf("%4.3f MiB/s\n", static_cast<double>(transferred_)/time/1024/1024);
transferred_ = 0;
startTime_ = endTime;
}
void ChargenServer::onMessage(const TcpConnectionPtr& conn,
Buffer* buf,
Timestamp time)
{
string msg(buf->retrieveAllAsString());
LOG_INFO << conn->name() << " discards " << msg.size()
<< " bytes received at " << time.toString();
}
main.c主程序文件
#include "chargen.h"
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
int main()
{
LOG_INFO << "pid = " << getpid();
EventLoop loop;
InetAddress listenAddr(2019);
ChargenServer server(&loop, listenAddr, true);
server.start();
loop.loop();
}
chargenclient.c
仅仅是将服务器发来的数据,调用buf->retrieveAll()取走,并没有打印(因为打印会降低吞吐量)
#include
#include
#include
#include
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
class ChargenClient : noncopyable
{
public:
ChargenClient(EventLoop* loop, const InetAddress& listenAddr)
: loop_(loop),
client_(loop, listenAddr, "ChargenClient")
{
client_.setConnectionCallback(
std::bind(&ChargenClient::onConnection, this, _1));
client_.setMessageCallback(
std::bind(&ChargenClient::onMessage, this, _1, _2, _3));
// client_.enableRetry();
}
void connect()
{
client_.connect();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << conn->localAddress().toIpPort() << " -> "
<< conn->peerAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
if (!conn->connected())
loop_->quit();
}
void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime)
{
buf->retrieveAll();
}
EventLoop* loop_;
TcpClient client_;
};
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid();
if (argc > 1)
{
EventLoop loop;
InetAddress serverAddr(argv[1], 2019);
ChargenClient chargenClient(&loop, serverAddr);
chargenClient.connect();
loop.loop();
}
else
{
printf("Usage: %s host_ip\n", argv[0]);
}
}