echo:回显服务,把收到的数据发回客户端。
discard:丢弃所有收到的数据。
chargen:服务端accept连接之后,不停地发送测试数据。
daytime:服务端accept连接之后,以字符串形式发送当前时间,然后主动断开连接。
time:服务端accept连接之后,以二进制形式发送当前事件,然后主动断开连接;需要一个客户程序把收到的时间转换为字符串。
discard
最简单的长连接TCP应用层协议,只需要关注“三个半事件”中的“消息/数据到达”事件,事件处理函数如下:
void DiscardServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp time)
{
string mas(buf->retrieveAllAsString());
LOG_INFO << conn->name() << " discards " << msg.size() << " bytes received at " << time.toString();
}
daytime
daytime是短连接协议,在发送完当前事件后,服务端主动断开连接。只需要关注“三个半事件”中的“连接已建立”事件,事件处理函数如下:
void DaytimeServer::onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << "DaytimeServer - " << conn->peerAddress().toIpPort() << " -> " <<
conn->localAddress().toIpPort() << " is " << (conn->connected() ? "UP" : "DOWN");
if(conn->connected())
{
conn->send(Timestamp::now().toFormattedString() + "\n");
conn->shutdown();
}
}
time
time协议返回一个32-bit整数,表示从1970-01-01 00:00:00Z 到现在的秒数。只需要关注“连接已建立”事件,事件处理函数如下:
void TimeServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
{
LOG_INFO << "TimeServer - " << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << " is " << (conn->connected() ? "UP" : "DOWN");
if(conn->connected()) //连接建立之后开始发送时间
{
time_t now = ::time(NULL); //取当前时间
int32_t be32 = sockets::hostToNetword32(static_cast(now)); //转换为网络字节序
conn->send(&be32, sizeof(be32);
conn->shutdown():
}
}
time客户端:因为time服务端发送的是二进制数据,我们编写一个客户端来解析并打印收到的4个字节数据。只需要关注“消息/数据到达”事件,事件处理函数如下:
void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime)
{
if(buf->readableBytes() >= sizeof(int32_t))
{
const void* data = buf->peek();
int32_t be32 = *static_cast(data):
buf->retrieve(sizeof(int32_t)):
time_t time = sockets::networkToHost32(be32);
Timestamp ts(time* Timestamp::kMicroSecondPerSecond):
LOG_INFO << "Server time = " << time << ", " << ts.toFormattedString();
}
else
{
LOG_INFO << conn->name() << " no enough data " << buf->readableBytes()
<< " at " << receiveTime.toFormattedString();
}
}
echo
echo是我们遇到的第一个双向的协议:服务端把客户端发过来的数据原封不动地传回去。只需要关注“消息/数据到达"事件,事件处理函数:
void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp time)
{
muduo::string msg(buf->retrieveAllAsString());
LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, "
<< "data received at " << time.toString();
conn->send(msg):
}
这段代码不是行回显服务,而是有一点数据就发送一点数据。避免客户端恶意地不发送换行字符,而服务端又必须缓存已经收到的数据,导致服务器内存暴涨。
chargen
Chargen协议很特殊,只发送数据,不接受数据。只需要关注”三个半事件“中的半个”消息/数据发送完毕“事件,事件处理函数:
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_); //在连接建立时发送第一次数据
}
}
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();
}
vond ChargenServer::onWriteComplete(const TcpConnectionPtr& conn)
{
transferred_ += message_.size();
conn->send(message_); //继续发送数据
}
前面五个程序都用到了EventLoop,这其实是个Reactor,用于注册和分发IO事件。muduo遵循one loop per thread模型,多个服务端和客户端共享同一个EventLoop,也可以分配到多个EventLoop以发挥多核多线程的好处。
这里我们把五个服务端用同一个EventLoop跑起来:
int main()
{
LOG_INFO << "pid = " << getpid();
EventLoop loop; //one loop shared by multiple servers
ChargenServer chargenServer(&loop, InetAddress(2019));
chargenServer.start();
DaytimeServer daytimeServer(&loop, InetAddress(2013));
daytimeServer.start();
DiscardServer discardServer(&loop, InetAddress(2009));
discardServer.start();
EchoServer echoServer(&loop, InetAddress(2007));
echoServer.start();
TimeServer timeServer(&loop, InetAddress(2037));
timeServer.start();
loop.loop();
}
这个例子充分展示了Reactor模式复用线程的能力,让一个单线程程序同时具备多个网络功能。