源码可以从陈硕的github上下载到,位置在muduo-master\examples\ace\ttcp
TTCP是一个传统的测试TCP性能的工具,它主要测试两个机器之间TCP的吞吐量,在应用层模拟消息传递的过程——客户端发送一个包,服务端对包进行确认,并且在确认之前客户端不发送下一个包。可以设置发包的数量和每个包的大小,程序计算传输所有包所用的时间,然后计算出带宽。
操作流程图
以上发送的message都是在应用层中体现的。当一个client发起ttcp的请求时,首先会向server发送一个SessionMessage,里边主要包含了即将发送的数据包个数和每个包的长度。PayloadMessage便是实际的数据,里边包含了一个表示长度的length,和实际的数据data,其中data使用了柔性数组。
main.cc
#include
#include
int main(int argc, char* argv[])
{
Options options; //用来保存输入的参数信息
if (parseCommandLine(argc, argv, &options)) //把命令行输入的参数保存到结构体中
{
if (options.transmit) //让发送端和接收端分别执行对应的函数
{
transmit(options);
}
else if (options.receive)
{
receive(options);
}
else
{
assert(0);
}
}
}
common.h
#pragma once
#include
#include
struct Options
{
uint16_t port; //端口
int length; //报文长度
int number; //报文数量
bool transmit, receive, nodelay; //发送端,接收端,是否在传输层禁用negla算法
std::string host; //主机名
Options()
: port(0), length(0), number(0),
transmit(false), receive(false), nodelay(false)
{
}
};
bool parseCommandLine(int argc, char* argv[], Options* opt); //解析命令
struct sockaddr_in resolveOrDie(const char* host, uint16_t port); //通过域名和端口解析出服务端的sockaddr
struct SessionMessage //本次请求的信息
{
int32_t number; //报文数量
int32_t length; //报文长度
} __attribute__ ((__packed__));
struct PayloadMessage //消息报文
{
int32_t length; //长度
char data[0]; //数据
};
void transmit(const Options& opt); //发送端
void receive(const Options& opt); //接收端
ttcp_blocking.cc
#include
#include
#include
#undef NDEBUG
#include
#include
#include
#include
#include
#include
static int acceptOrDie(uint16_t port) //一次只服务一个客户端,创建一个listenfd收到连接就close,返回clientsock
{
int listenfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
assert(listenfd >= 0);
int yes = 1;
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)))
{
perror("setsockopt");
exit(1);
}
struct sockaddr_in addr;
muduo::memZero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listenfd, reinterpret_cast(&addr), sizeof(addr)))
{
perror("bind");
exit(1);
}
if (listen(listenfd, 5))
{
perror("listen");
exit(1);
}
struct sockaddr_in peer_addr;
muduo::memZero(&peer_addr, sizeof(peer_addr));
socklen_t addrlen = 0;
int sockfd = ::accept(listenfd, reinterpret_cast(&peer_addr), &addrlen);
if (sockfd < 0)
{
perror("accept");
exit(1);
}
::close(listenfd);
return sockfd;
}
static int write_n(int sockfd, const void* buf, int length) //写length个字符,返回写成功的字符个数
{
int written = 0;
while (written < length)
{
ssize_t nw = ::write(sockfd, static_cast(buf) + written, length - written);
if (nw > 0)
{
written += static_cast(nw);
}
else if (nw == 0)
{
break; // EOF
}
else if (errno != EINTR)
{
perror("write");
break;
}
}
return written;
}
static int read_n(int sockfd, void* buf, int length) //读legnth个字符,返回读到的字符个数
{
int nread = 0;
while (nread < length)
{
ssize_t nr = ::read(sockfd, static_cast(buf) + nread, length - nread);
if (nr > 0)
{
nread += static_cast(nr);
}
else if (nr == 0)
{
break; // EOF
}
else if (errno != EINTR)
{
perror("read");
break;
}
}
return nread;
}
void transmit(const Options& opt) //发送端执行
{
struct sockaddr_in addr = resolveOrDie(opt.host.c_str(), opt.port); //输入域名端口,返回表示server的sockaddr
printf("connecting to %s:%d\n", inet_ntoa(addr.sin_addr), opt.port);
int sockfd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
assert(sockfd >= 0);
int ret = ::connect(sockfd, reinterpret_cast(&addr), sizeof(addr)); //连接服务端
if (ret)
{
perror("connect");
printf("Unable to connect %s\n", opt.host.c_str());
::close(sockfd);
return;
}
printf("connected\n");
muduo::Timestamp start(muduo::Timestamp::now()); //记录当前时间
struct SessionMessage sessionMessage = { 0, 0 }; //填写信息报文
sessionMessage.number = htonl(opt.number);
sessionMessage.length = htonl(opt.length);
if (write_n(sockfd, &sessionMessage, sizeof(sessionMessage)) != sizeof(sessionMessage))//如果没有全部发送直接退出
{
perror("write SessionMessage");
exit(1);
}
const int total_len = static_cast(sizeof(int32_t) + opt.length); //计算数据报文长度
PayloadMessage* payload = static_cast(::malloc(total_len));
assert(payload);
payload->length = htonl(opt.length);
for (int i = 0; i < opt.length; ++i) //填充数据
{
payload->data[i] = "0123456789ABCDEF"[i % 16];
}
double total_mb = 1.0 * opt.length * opt.number / 1024 / 1024; //计算大小
printf("%.3f MiB in total\n", total_mb);
for (int i = 0; i < opt.number; ++i) //发收数据
{
int nw = write_n(sockfd, payload, total_len);
assert(nw == total_len);
int ack = 0;
int nr = read_n(sockfd, &ack, sizeof(ack)); //每个包发出之后要等收到对应的确认再发下一个
assert(nr == sizeof(ack));
ack = ntohl(ack);
assert(ack == opt.length);
}
::free(payload);
::close(sockfd);
double elapsed = timeDifference(muduo::Timestamp::now(), start); //输出结果
printf("%.3f seconds\n%.3f MiB/s\n", elapsed, total_mb / elapsed);
}
void receive(const Options& opt)
{
int sockfd = acceptOrDie(opt.port); //接受客户端连接
struct SessionMessage sessionMessage = { 0, 0 };
if (read_n(sockfd, &sessionMessage, sizeof(sessionMessage)) != sizeof(sessionMessage))//读消息报文
{
perror("read SessionMessage");
exit(1);
}
sessionMessage.number = ntohl(sessionMessage.number);
sessionMessage.length = ntohl(sessionMessage.length);
printf("receive number = %d\nreceive length = %d\n",
sessionMessage.number, sessionMessage.length);
const int total_len = static_cast(sizeof(int32_t) + sessionMessage.length);
PayloadMessage* payload = static_cast(::malloc(total_len)); //申请对应大小的缓冲区
assert(payload);
for (int i = 0; i < sessionMessage.number; ++i) //读数据发ack
{
payload->length = 0;
if (read_n(sockfd, &payload->length, sizeof(payload->length)) != sizeof(payload->length))
{
perror("read length");
exit(1);
}
payload->length = ntohl(payload->length);
assert(payload->length == sessionMessage.length);
if (read_n(sockfd, payload->data, payload->length) != payload->length)
{
perror("read payload data");
exit(1);
}
int32_t ack = htonl(payload->length);
if (write_n(sockfd, &ack, sizeof(ack)) != sizeof(ack))
{
perror("write ack");
exit(1);
}
}
::free(payload);
::close(sockfd);
}