- 一般使用中发送消息由网络库完成,不会直接调用write或send;
1 使用TcpConnection::send需要注意
- send返回值为void,保证将数据发送给对方,用户不必关心调用send成功发送多少字节;
- send是非阻塞;
- send是线程安全,原子的;
- send(const void* mes, size_t len)可以发送任意字节序列;
- send(const StringPiece& meg)可发送string和const char*,StringPiece可传递字符串参数的class;
- send(Buffer*)以指针为参数,避免内存拷贝;
2 muduo文件传输实现
- 将文件发送给对方;
【版本一:一次将文件读入内存,在send】
- 该版本,每次建立连接啧需要读取一遍文件,内存消耗与(并发连接数x文件大小)成正比;
【版本二:解决版本一,每次发送固定大小的数据块】
- 每次发送固定大小的数据块;
- 若由其他程序修改该文件时,每次连接都会打开该文件,导致每个连接占用一个socket fd和file fd;则我们将整个程序都共享一个文件描述符,记录每个连接时的偏移量;
- 若客户端故意只发起连接,不接收数据,则我们使用需要去除空闲连接并设置最大连接数;
【版本三:使用智能指针】
- 用shared_ptr的custom deleter来减轻资源管理负担,使用FILE*生命周期和TCPconnet一样长;
为什么TcpConnectiom::shutdown没有直接关闭TCP连接
- muduo将主动关闭连接分成两步:
- 先关闭本地写端;
- 再关闭本地读端;
- muduo只提供shutdown,为了收发数据的完整性;
- 若对方发送了数据,则数据再路上,不会被漏收;
- 由于TCP fd可读可写,shutdownWrite关闭了写方向,保留读方向,若直接close则关闭读写;
- 发送完数据,即shutdownWrite,发送TCP FIN分节,对方读到0字节,后对方会关闭连接,muduo会读到0字节,后muduo关闭连接;
【shutdwon后,为什么muduo回调connection callback的时间间隔大约在round-triptime】
- 应该是避免数据还没用传输完成;
【muduo什何时真正close socket】
- 在TcpConnection对象析构时;
3 简易实现文件传输
server.cc
#include "server_transfer_file.h"
#include "sylar/util.h"
namespace sylar {
namespace tcp {
static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");
TransferFile::TransferFile(std::string filename, sylar::IOManager* worker
,sylar::IOManager* accept_worker)
: TcpServer(worker, accept_worker){
setName("TransferFile Server");
FSUtil::Realpath(filename, m_file);
}
void TransferFile::handleClient(Socket::ptr client) {
m_fp = fopen(m_file.c_str(), "r");
if(m_fp) {
SYLAR_LOG_INFO(g_logger) << "send to: [" << client->getRemoteAddress()->toString() << "] start...";
char buf[128] = {0};
// 获取文件长度
struct stat statbuf;
stat(m_file.c_str(), &statbuf);
m_len = statbuf.st_size;
// 发送头部信息
std::string tmp = m_file+ " " + std::to_string(m_len);
client->send(tmp.c_str(), tmp.size());
SYLAR_LOG_INFO(g_logger) << getName() << " send head msg: "
<< m_file.c_str() << " " << m_len;
// 若对方接收到文件且创建好文件
do {
if(!client->isConnected()) {
SYLAR_LOG_INFO(g_logger) << getName() << "close";
return;
}
int rt = client->recv(buf, sizeof(buf));
if(rt > 0) {
SYLAR_LOG_INFO(g_logger) << "Client Create Sucess";
break;
}
}while(true);
// 发送消息
do {
if(!client->isConnected()) {
SYLAR_LOG_INFO(g_logger) << getName() << "close";
return;
}
int rt = fread(m_buf, 1, sizeof(m_buf), m_fp);
if(rt <= 0) break;
rt = client->send(m_buf, rt);
SYLAR_LOG_INFO(g_logger) << "send to: [" << client->getRemoteAddress()->toString()
<< "] size: " << rt;
}while(true);
SYLAR_LOG_INFO(g_logger) << "send finish";
fclose(m_fp);
client->close();
}
}
}
}
server.h
#ifndef __SERVER_TRANSFER_FILE_H__
#define __SERVER_TRANSFER_FILE_H__
#include "sylar/log.h"
#include "sylar/tcp_server.h"
#include <string>
// 先传输文件的名称、长度,让对方能够在接收文件前先创建好文件
// 若文件
namespace sylar {
namespace tcp {
class TransferFile : public TcpServer {
public:
TransferFile(std::string filename = "", sylar::IOManager* worker = sylar::IOManager::GetThis()
,sylar::IOManager* accept_worker = sylar::IOManager::GetThis());
protected:
virtual void handleClient(Socket::ptr client) override;
private:
enum Status {
OK,
ERROR,
NONE
};
private:
size_t m_len;
std::string m_file;
FILE* m_fp = nullptr;
char m_buf[64 * 1024];
};
}
}
#endif
server.main
#include "sylar/tcpServer/server_transfer_file.h"
#include <iostream>
std::string filename = "";
void run() {
auto addr = sylar::Address::LookupAny("0.0.0.0:8033");
std::vector<sylar::Address::ptr> addrs;
addrs.push_back(addr);
sylar::tcp::TransferFile::ptr tcp_server(new sylar::tcp::TransferFile(filename));
std::vector<sylar::Address::ptr> fails;
while(!tcp_server->bind(addrs, fails)) {
sleep(2);
}
tcp_server->start();
}
int main(int argc, char** argv) {
if(argc > 1) {
filename = argv[1];
sylar::IOManager iom(2);
iom.schedule(run);
}else {
std::cout << "error" << std::endl;
}
return 0;
}
client.h
#ifndef __CLIENT_TRANSFER_FILE_H__
#define __CLIENT_TRANSFER_FILE_H__
#include "sylar/socket.h"
#include "sylar/address.h"
#include <memory>
namespace sylar {
namespace tcp{
class ClientTransfer {
public:
typedef std::shared_ptr<ClientTransfer> ptr;
ClientTransfer(sylar::Address::ptr addr);
void start();
~ClientTransfer();
private:
enum Status {
OK,
ERROR,
NONE
};
private:
Socket::ptr m_sock;
};
}
}
#endif
client.cc
#include "client_transfer_file.h"
#include "sylar/log.h"
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
namespace sylar {
namespace tcp {
static sylar::Logger::ptr g_logger = SYLAR_LOG_NAME("system");
ClientTransfer::ClientTransfer(sylar::Address::ptr addr) {
m_sock = sylar::Socket::CreateTCPSocket();
m_sock->connect(addr, 10);
}
void ClientTransfer::start() {
if(m_sock->isConnected()) {
char buf[128];
FILE* fd;
// 接收服务端的头部信息,创建文件
int rt = m_sock->recv(buf, sizeof(buf));
if(rt) {
char name[120];
int len = 0;
sscanf(buf, "%s %d", name, &len);
SYLAR_LOG_INFO(g_logger) << name << " " << len;
std::string newName = "ttttt.txt"; // 本地测试使用的文件名
fd = fopen(newName.c_str(), "w+");
fseek(fp, len, SEEK_SET); // 指定文件大小
if(!fd) return;
if(rt == -1){
exit(1);
}
// 发送OK提示对方可以发送文件内容
std::string tmp = std::to_string(OK);
int rt = m_sock->send(tmp.c_str(), tmp.size());
SYLAR_LOG_INFO(g_logger) << "send ok: " << rt;
}
do{
char bbuf[64 * 1024];
rt = m_sock->recv(bbuf, sizeof(bbuf));
SYLAR_LOG_INFO(g_logger) << "recv: " << rt;
if(rt <= 0) break;
rt = fwrite(bbuf, 1, sizeof(bbuf), fd);
if(rt <= 0) break;
}while(true);
}
m_sock->close();
}
ClientTransfer::~ClientTransfer() {
m_sock->close();
}
}
}
client.main
#include "sylar/tcpServer/client_transfer_file.h"
#include "sylar/iomanager.h"
#include <iostream>
void run() {
auto addr = sylar::Address::LookupAny("0.0.0.0:8033");
sylar::tcp::ClientTransfer::ptr client(new sylar::tcp::ClientTransfer(addr));
client->start();
}
int main(int argc, char** argv) {
sylar::IOManager iom(2);
iom.schedule(run);
return 0;
}