前言
五个简单TCP示例,分别是discard,daytime,time,echo, chargen,这些功能描述如下:
discard: 丢弃所有收到的数据。
daytime:服务端accept连接之后,以字符串形式发送当前时间,然后主动断开连接。
time:服务端accept连接之后,以二进制形式发送当前时间,然后主动断开连接,我们需要一个客户程序来把收到的时间转换为字符串。
echo:回显服务,把收到的数据发送回客户端。
chargen:服务端accept连接之后,不停地发送测试数据。
在<<UNIX网络编程卷一>>中有提到,参考第二章2.12标准英特网服务
五个简单TCP
1. discard
比较简单,只关心"三个半事件"中的"消息/数据到达"事件,事件处理函数如下:
void DiscardServer::onMessage(const TcpConnectionPtr& conn,
Buffer* buf,
Timestamp time)
{
string msg(buf->retrieveAllAsString());
LOG_INFO << conn->name() << " discards " << msg.size()
<< " bytes received at " << time.toString();
}
运行:
服务端:
root@ubuntu:/opt/muduo/examples/simple/discard# ./a.out
20220528 17:05:13.611973Z 62510 INFO pid = 62510 - main.cc:13
20220528 17:05:22.777054Z 62510 INFO TcpServer::newConnection [DiscardServer] - new connection [DiscardServer-0.0.0.0:2009#1] from 192.168.1.14:57380 - TcpServer.cc:80
20220528 17:05:22.777182Z 62510 INFO DiscardServer - 192.168.1.14:57380 -> 192.168.1.14:2009 is UP - discard.cc:25
20220528 17:05:25.590716Z 62510 INFO DiscardServer-0.0.0.0:2009#1 discards 1 bytes received at 1653757525.590698 - discard.cc:35
20220528 17:05:25.798819Z 62510 INFO DiscardServer-0.0.0.0:2009#1 discards 1 bytes received at 1653757525.798808 - discard.cc:35
20220528 17:05:26.020168Z 62510 INFO DiscardServer-0.0.0.0:2009#1 discards 1 bytes received at 1653757526.020155 - discard.cc:35
20220528 17:05:26.234700Z 62510 INFO DiscardServer-0.0.0.0:2009#1 discards 1 bytes received at 1653757526.234687 - discard.cc:35
20220528 17:05:26.425043Z 62510 INFO DiscardServer-0.0.0.0:2009#1 discards 1 bytes received at 1653757526.425030 - discard.cc:35
20220528 17:05:30.313801Z 62510 INFO DiscardServer-0.0.0.0:2009#1 discards 4 bytes received at 1653757530.313788 - discard.cc:35
客户端使用netcat的nc命令模拟:
root@ubuntu:/home/lvch# nc 192.168.1.14 2009
123
2.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();
}
}
运行:
root@ubuntu:/opt/muduo/examples/simple/daytime# ./a.out
20220528 17:10:57.295966Z 62523 INFO pid = 62523 - main.cc:13
20220528 17:11:04.130141Z 62523 INFO TcpServer::newConnection [DaytimeServer] - new connection [DaytimeServer-0.0.0.0:2013#1] from 192.168.1.14:49562 - TcpServer.cc:80
20220528 17:11:04.130248Z 62523 INFO DaytimeServer - 192.168.1.14:49562 -> 192.168.1.14:2013 is UP - daytime.cc:26
`客户端:
root@ubuntu:/home/lvch# nc 192.168.1.14 2013
20220528 17:11:04.130295
3. echo
相对discard多增加一个conn->send(msg);
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);
}
4. time
time协议与daytime即为类似,只不过它返回的不是日期时间字符串,而是一个32-bit整数,表示从1970-01-01 00:00:00Z到现在的秒数。当然这个协议有“2038年问题”。服务端只需要关注"三个半事件"中的
"连接已建立"事件,事件处理函数:
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::hostToNetwork32(static_cast<int32_t>(now));
conn->send(&be32, sizeof be32);
conn->shutdown();
}
}
客户端:
#include "muduo/base/Logging.h"
#include "muduo/net/Endian.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/InetAddress.h"
#include "muduo/net/TcpClient.h"
#include <utility>
#include <stdio.h>
#include <unistd.h>
using namespace muduo;
using namespace muduo::net;
class TimeClient : noncopyable
{
public:
TimeClient(EventLoop* loop, const InetAddress& serverAddr)
: loop_(loop),
client_(loop, serverAddr, "TimeClient")
{
client_.setConnectionCallback(
std::bind(&TimeClient::onConnection, this, _1));
client_.setMessageCallback(
std::bind(&TimeClient::onMessage, this, _1, _2, _3));
// client_.enableRetry();
}
void connect()
{
client_.connect();
}
private:
EventLoop* loop_;
TcpClient client_;
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)
{
if (buf->readableBytes() >= sizeof(int32_t))
{
const void* data = buf->peek();
int32_t be32 = *static_cast<const int32_t*>(data);
buf->retrieve(sizeof(int32_t));
time_t time = sockets::networkToHost32(be32);
Timestamp ts(implicit_cast<uint64_t>(time) * Timestamp::kMicroSecondsPerSecond);
LOG_INFO << "Server time = " << time << ", " << ts.toFormattedString();
}
else
{
LOG_INFO << conn->name() << " no enough data " << buf->readableBytes()
<< " at " << receiveTime.toFormattedString();
}
}
};
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid();
if (argc > 1)
{
EventLoop loop;
InetAddress serverAddr(argv[1], 2037);
TimeClient timeClient(&loop, serverAddr);
timeClient.connect();
loop.loop();
}
else
{
printf("Usage: %s host_ip\n", argv[0]);
}
}
5. chargen
协议比较特殊,只发送不接收数据。而且,它发送的数据的速度不能快过客户端接收的速度,因此需要关注"消息/数据发送完毕"事件(onWriteComplete)
`#include "examples/simple/chargen/chargen.h"
#include "muduo/base/Logging.h"
#include "muduo/net/EventLoop.h"
#include <stdio.h>
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));
if (print)
{
loop->runEvery(3.0, std::bind(&ChargenServer::printThroughput, this));
}
string line;
for (int i = 33; i < 127; ++i)
{
line.push_back(char(i));
}
line += line;
for (size_t i = 0; i < 127-33; ++i)
{
message_ += line.substr(i, 72) + '\n';
}
}
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_);
}
}
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();
}
void ChargenServer::onWriteComplete(const TcpConnectionPtr& conn)
{
transferred_ += message_.size();
conn->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;
}