本章实现一个发送文件的命令行小工具,启动时通过命令行参数指定发送的文件,启用端口侦听网络连接。每次有新连接进来就把文件内容完整的发送给对方。
这里一共提供三个版本
- 一次性把文件读入内存,一次性调用send发送数据。
- 一块一块地发送文件,减少内存使用
- 设计思路同2,但是使用智能指针share_ptr来管理文件描述符FILE*
代码位置:muduo-master/examples/filetransfer
版本一
void onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << "FileServer - " << conn->peerAddress().toIpPort() << " -> "
<< conn->localAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
if (conn->connected())
{
LOG_INFO << "FileServer - Sending file " << g_file
<< " to " << conn->peerAddress().toIpPort();
conn->setHighWaterMarkCallback(onHighWaterMark, 64*1024);
string fileContent = readFile(g_file); // 一次性读入文件的全部数据
conn->send(fileContent);
conn->shutdown();
LOG_INFO << "FileServer - done";
}
}
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid();
if (argc > 1)
{
g_file = argv[1];
EventLoop loop;
InetAddress listenAddr(2021);
TcpServer server(&loop, listenAddr, "FileServer");
// 接收新的连接
server.setConnectionCallback(onConnection);
server.start();
loop.loop();
}
else
{
fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]);
}
}
这个版本有一个明显的缺陷,就是内存消耗。这里可以看见,内存消耗与(并发数目*文件大小)呈正相关。如果文件很大,那么所占用的内存也就会很大,所以就有了版本二。
版本二
const int kBufSize = 64*1024;
void onWriteComplete(const TcpConnectionPtr& conn)
{
FILE* fp = boost::any_cast<FILE*>(conn->getContext());
char buf[kBufSize];
// 每次读取64kb的数据
size_t nread = ::fread(buf, 1, sizeof buf, fp);
if (nread > 0)
{
conn->send(buf, static_cast<int>(nread));
}
else
{
::fclose(fp);
fp = NULL;
conn->setContext(fp);
conn->shutdown();
LOG_INFO << "FileServer - done";
}
}
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid();
if (argc > 1)
{
g_file = argv[1];
EventLoop loop;
InetAddress listenAddr(2021);
TcpServer server(&loop, listenAddr, "FileServer");
server.setConnectionCallback(onConnection);
server.setWriteCompleteCallback(onWriteComplete); // send数据回调
server.start();
loop.loop();
}
else
{
fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]);
}
}
版本二采用了流水线的思路,每次读取64kb的数据量,等该数据发送完之后再发送下一个64kb的数据,直到把文件内容发送完。
这种方式每一种连接都会占用一个文件描述符,同时每个连接都会有socket fd,当连接数足够大时,会占用很多文件描述符,所以我们可以看看版本三。
版本三
const int kBufSize = 64*1024;
const char* g_file = NULL;
typedef boost::shared_ptr<FILE> FilePtr; // 智能指针
void onWriteComplete(const TcpConnectionPtr& conn)
{
const FilePtr& fp = boost::any_cast<const FilePtr&>(conn->getContext());
char buf[kBufSize];
size_t nread = ::fread(buf, 1, sizeof buf, get_pointer(fp));
if (nread > 0)
{
conn->send(buf, static_cast<int>(nread));
}
else
{
conn->shutdown();
LOG_INFO << "FileServer - done";
}
}
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid();
if (argc > 1)
{
g_file = argv[1];
EventLoop loop;
InetAddress listenAddr(2021);
TcpServer server(&loop, listenAddr, "FileServer");
server.setConnectionCallback(onConnection);
server.setWriteCompleteCallback(onWriteComplete);
server.start();
loop.loop();
}
else
{
fprintf(stderr, "Usage: %s file_for_downloading\n", argv[0]);
}
}
版本三的思路与版本二一致,但是在文件描述符上采用了一个智能指针,所有连接共享一个文件描述符,每一连接维护各自的偏移量,减轻代码资源管理的负担。