boost::enable_shared_from_this 管理 Session 生命週期unsigned long long 突破 2GB 限制
#ifndef _FILE_INFO_H_
#define _FILE_INFO_H_
#if USE_ASIO_ALONE
#include <asio.hpp>
#else
#include <boost/asio.hpp>
namespace asio { using namespace boost::asio; using boost::system::error_code; }
#endif
struct File_info {
typedef unsigned long long Size_type;
Size_type filesize;
size_t filename_size;
File_info() : filesize(0), filename_size(0) {}
};
#endif
void sender(asio::io_service& io, const char* ip, unsigned port, const char* filename) {
typedef asio::ip::tcp TCP;
FILE* fp = fopen(filename, "rb");
if (!fp) { std::cerr << "cannot open file\n"; return; }
boost::shared_ptr<FILE> file_ptr(fp, fclose); // RAII 確保關閉
const size_t k_buffer_size = 32 * 1024;
char buffer[k_buffer_size];
File_info fi;
int fn_size = strlen(filename) + 1;
size_t header_size = sizeof(fi);
if (header_size + fn_size > k_buffer_size) { std::cerr << "File name too long\n"; return; }
fi.filename_size = fn_size;
fseek(fp, 0, SEEK_END);
fi.filesize = ftell(fp);
rewind(fp);
memcpy(buffer, &fi, header_size);
memcpy(buffer + header_size, filename, fn_size);
TCP::socket socket(io);
socket.connect(TCP::endpoint(asio::ip::address_v4::from_string(ip), port));
std::cout << "Sending: " << filename << " ";
size_t len = header_size + fn_size;
unsigned long long total = 0;
clock_t start = clock();
while (true) {
socket.send(asio::buffer(buffer, len), 0);
if (feof(fp)) break;
len = fread(buffer, 1, k_buffer_size, fp);
total += len;
}
double sec = double(clock() - start) / CLOCKS_PER_SEC;
double speed = total / (1024.*1024.) / sec;
std::cout << "Time: " << sec << "s Bytes: " << total << " Speed: " << speed << " MB/s\n";
}
class Session : public boost::enable_shared_from_this<Session> {
public:
typedef asio::ip::tcp TCP;
typedef asio::error_code Error;
typedef boost::shared_ptr<Session> Pointer;
typedef File_info::Size_type Size_type;
static Pointer create(asio::io_service& io) { return Pointer(new Session(io)); }
TCP::socket& socket() { return socket_; }
void start() {
clock_ = clock();
std::cout << "Client: " << socket_.remote_endpoint().address() << "\n";
async_receive_header();
}
~Session() {
if (fp_) fclose(fp_);
double sec = double(clock() - clock_) / CLOCKS_PER_SEC;
double speed = total_bytes_writen_ / (1024.*1024.) / sec;
std::cout << "Done. Time: " << sec << "s Bytes: " << total_bytes_writen_ << " Speed: " << speed << " MB/s\n";
}
private:
Session(asio::io_service& io) : socket_(io), fp_(NULL), total_bytes_writen_(0), clock_(0) {}
void async_receive_header() {
socket_.async_receive(asio::buffer(&file_info_, sizeof(file_info_)),
boost::bind(&Session::handle_header, shared_from_this(), asio::placeholders::error));
}
void handle_header(const Error& err) {
if (err) return print_error(err);
if (file_info_.filename_size > k_buffer_size) return print_error("Path too long");
asio::async_read(socket_, asio::buffer(buffer_, file_info_.filename_size),
boost::bind(&Session::handle_filename, shared_from_this(), asio::placeholders::error));
}
void handle_filename(const Error& err) {
if (err) return print_error(err);
const char* base = buffer_ + file_info_.filename_size - 1;
while (base >= buffer_ && *base != '\\' && *base != '/') --base;
++base;
std::cout << "Save as: " << base << "\n";
fp_ = fopen(base, "wb");
if (!fp_) return print_error("Open file failed");
async_receive_content();
}
void async_receive_content() {
socket_.async_receive(asio::buffer(buffer_, k_buffer_size),
boost::bind(&Session::handle_content, shared_from_this(),
asio::placeholders::error, asio::placeholders::bytes_transferred));
}
void handle_content(const Error& err, size_t bytes) {
if (err) {
if (err != asio::error::eof) return print_error(err);
if (total_bytes_writen_ != file_info_.filesize)
std::cerr << "Size mismatch: " << total_bytes_writen_ << "/" << file_info_.filesize << "\n";
return;
}
total_bytes_writen_ += fwrite(buffer_, 1, bytes, fp_);
async_receive_content();
}
static void print_error(const Error& err) { std::cerr << err.message() << "\n"; }
static void print_error(const char* msg) { std::cerr << msg << "\n"; }
clock_t clock_;
TCP::socket socket_;
FILE* fp_;
File_info file_info_;
Size_type total_bytes_writen_;
static const unsigned k_buffer_size = 32 * 1024;
char buffer_[k_buffer_size];
};
class Tcp_server {
public:
Tcp_server(asio::io_service& io, unsigned port)
: acceptor_(io, TCP::endpoint(TCP::v4(), port)) { start_accept(); }
private:
void start_accept() {
Session::Pointer session = Session::create(acceptor_.get_io_service());
acceptor_.async_accept(session->socket(),
boost::bind(&Tcp_server::handle_accept, this, session, asio::placeholders::error));
}
void handle_accept(Session::Pointer session, const Error& err) {
if (!err) session->start();
start_accept();
}
TCP::acceptor acceptor_;
};
# Server
g++ -std=c++11 -O2 -pthread server.cpp -lboost_system -o server
./server
# Client
g++ -std=c++11 -O2 client.cpp -lboost_system -o client
./client 127.0.0.1 bigfile.dat
注意:同步 Client 無超時機制,生產環境建議改非同步並加上 deadline_timer 實作超時控制。