File Transfer 文件传输助手
2026-07-20 19:06:35
发布于:广东
FileTransfer
概述
这是一个在暑期集训营中,因没有U盘而写出的,基于WinSock2编写的局域网内文件传输实用工具QwQ
本代码构建产物可直接作为客户端/服务端两用,使用命令行参数指定(详见后文 使用方式 )。
构建要求
至少需要
- Windows SDK 10.0.19045
- Visual Studio 2026 / MSVC
- c++20标准编译
构建方式
在 Visual Studio 2026 中新建 C++控制台应用程序 模板项目,在原有的 main.cpp 内复制以下源代码,启动构建
也可在具有 Windows 10 SDK 的环境下直接使用 g++ 编译(?暂时没试过)
使用方式
在启动参数中指定 client 或 server 以指定以客户端或服务端模式运行
服务端 (fileTransfer server)
- 输入具有ipv4格式的监听ip地址
- 输入范围在 [1, 65535] 的监听端口号
- 选择需要传输的文件
- 等待客户端连接
- 按下
Ctrl-C以结束服务端运行
客户端 (fileTransfer client)
- 输入具有ipv4格式的服务端监听地址
- 输入范围在 [1, 65535] 的服务端监听端口号
- 选择文件保存位置
- 等待传输完成,自动断连,结束运行
源代码
以下是用于局域网内文件传输的源代码,在 Windows10 19045 上运行正常
![IMPORTANT] 含有ai辅助代码
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <iphlpapi.h>
#include <combaseapi.h>
#include <ShlObj.h>
#include <iostream>
#include <string>
#include <filesystem>
#include <fstream>
#include <cstring>
#include <thread>
#include <vector>
#include <utility>
#define RETURN_WHEN_FAILED(code, msg, oper) if(int __errc = code != 0){cerr << msg << __errc; {oper} return __errc;}
#pragma comment(lib, "Ws2_32.lib")
using namespace std;
constexpr size_t kMaxPacketSize = 4096;
struct FileObject {
uintmax_t size;
wchar_t path[256];
};
struct FileDataPacket {
int id;
size_t size;
char data[kMaxPacketSize];
};
constexpr FileDataPacket kEofPacket = { .id = -1, .size = static_cast<size_t>(-1), .data = {-1}};
enum RequestType {
kGetFileInfo, kRecvFileData, kShutdown
};
// 安全读取:先读长度,再校验长度再读对象
template<typename T>
T ReadObject(SOCKET socket)
{
static_assert(std::is_trivially_copyable_v<T>, "T must be POD trivial type");
size_t data_size = 0;
// 1. 读取长度头,确保完整读到sizeof(size_t)字节
int ret = recv(socket, reinterpret_cast<char*>(&data_size), sizeof(size_t), 0);
if (ret != sizeof(size_t))
{
throw std::runtime_error("recv read size header failed, connection broken");
}
// 关键校验:防止栈溢出
if (data_size > sizeof(T))
{
throw std::runtime_error("received data size larger than target object size, stack overflow risk");
}
T object{}; // 零初始化替代memset
ret = recv(socket, reinterpret_cast<char*>(&object), data_size, 0);
if (ret != static_cast<int>(data_size))
{
throw std::runtime_error("recv object data incomplete");
}
return object;
}
// 发送对象:修复长度、memcpy、智能指针自动释放
template<typename T>
void SendObject(SOCKET socket, const T& object)
{
static_assert(std::is_trivially_copyable_v<T>, "T must be POD trivial type");
// 修复1:取对象T的字节大小,不是socket句柄
const size_t data_size = sizeof(T);
// 发送长度头部
int ret = send(socket, reinterpret_cast<const char*>(&data_size), sizeof(size_t), 0);
if (ret != sizeof(size_t))
{
throw std::runtime_error("send size header failed");
}
// 修复2:unique_ptr自动释放,避免内存泄漏
std::unique_ptr<char[]> buf = std::make_unique<char[]>(data_size);
// 修复3:memcpy dst src顺序正确
std::memcpy(buf.get(), &object, data_size);
// 发送二进制数据
ret = send(socket, buf.get(), static_cast<int>(data_size), 0);
if (ret != static_cast<int>(data_size))
{
throw std::runtime_error("send object data incomplete");
}
}
// 重载支持右值,内部转发const引用避免重复逻辑
template<typename T>
void SendObject_R(SOCKET socket, T&& object)
{
T cop = object;
SendObject(socket, const_cast<const T&>(cop));
}
template<typename TDialogImpl>
int PickFile(LPCWSTR title, const string& create_fail_message, wstring& out_file_path, LPCWSTR filename = L"") {
IFileDialog* dialog = nullptr;
if (auto hr = CoCreateInstance(__uuidof(TDialogImpl), nullptr, CLSCTX_ALL, IID_PPV_ARGS(&dialog)); FAILED(hr)) {
cerr << create_fail_message << endl;
WSACleanup();
return hr;
}
if (filename != L"") {
dialog->SetFileName(filename);
}
dialog->SetTitle(title);
HWND console_hwnd = GetConsoleWindow();
while (dialog->Show(console_hwnd) == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {} // loop until user picked a file
IShellItem* file_item;
dialog->GetResult(&file_item);
LPWSTR filepath;
file_item->GetDisplayName(SIGDN_FILESYSPATH, &filepath);
wstring filepath_wstr{ filepath };
file_item->Release();
dialog->Release();
out_file_path = std::move(filepath_wstr);
return 0;
}
size_t RecvFile(const wstring& path, SOCKET socket) {
ofstream file_stream{ path , ios::binary };
size_t total = 0;
while (true) {
auto packet = ReadObject<FileDataPacket>(socket);
if (packet.id == kEofPacket.id)
break; // Eof
cout << "recv id:" << packet.id << endl;
file_stream.write(packet.data, packet.size);
total += packet.size;
}
file_stream.flush();
return total;
}
int ClientProc() {
addrinfo *server_address, parse_hints;
ZeroMemory(&parse_hints, sizeof(parse_hints));
parse_hints.ai_family = AF_INET;
parse_hints.ai_socktype = SOCK_STREAM;
parse_hints.ai_protocol = IPPROTO_TCP;
string ip, port;
cout << "请输入服务器ip:";
cin >> ip;
cout << "\n请输入服务器端口号:";
cin >> port;
RETURN_WHEN_FAILED(getaddrinfo(ip.c_str(), port.c_str(), &parse_hints, &server_address), "getaddrinfo failed: ", {
WSACleanup();
CoUninitialize();
});
SOCKET connect_socket = INVALID_SOCKET;
connect_socket = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol);
if (connect_socket == INVALID_SOCKET) {
auto error = WSAGetLastError();
printf("Error at socket(): %ld\n", error);
freeaddrinfo(server_address);
WSACleanup();
CoUninitialize();
return error;
}
if (auto code = connect(connect_socket, server_address->ai_addr, server_address->ai_addrlen); code == SOCKET_ERROR) {
connect_socket = INVALID_SOCKET;
}
freeaddrinfo(server_address);
if (connect_socket == INVALID_SOCKET) {
cerr << "Unable to connect to server!";
WSACleanup();
CoUninitialize();
return WSAGetLastError();
}
cout << "连接成功" << endl;
SendObject_R(connect_socket, RequestType::kGetFileInfo);
FileObject fileobj = ReadObject<FileObject>(connect_socket);
wcout << "文件对象信息:" << endl;
wcout << "路径: " << fileobj.path << endl;
wcout << "大小: " << fileobj.size << " Bytes" << endl;
auto filename = filesystem::path{ fileobj.path }.filename().wstring();
wstring save_path;
RETURN_WHEN_FAILED(PickFile<FileSaveDialog>(TEXT("选择保存文件位置"), "创建保存文件对话框失败", save_path, filename.c_str()), "", {
CoUninitialize();
WSACleanup();
});
SendObject_R(connect_socket, RequestType::kRecvFileData);
cout << "开始接收文件" << endl;
auto recv_size = RecvFile(save_path, connect_socket);
cout << "共接收 " << recv_size << " Bytes" << endl;
cout << "接收完成!请求关闭链接中" << endl;
SendObject_R(connect_socket, RequestType::kShutdown);
cout << "请求完毕,关闭链接" << endl;
shutdown(connect_socket, SD_BOTH);
closesocket(connect_socket);
CoUninitialize();
WSACleanup();
return 0;
}
void SendDataPacket(int id, ifstream& file_stream, size_t size, SOCKET socket) {
FileDataPacket packet{};
file_stream.read(packet.data, size);
std::streamsize read_bytes = file_stream.gcount();
if (read_bytes <= 0)
return;
// 赋值实际长度,发送真实读到的数据
packet.size = static_cast<size_t>(read_bytes);
packet.id = id;
cout << "send id: " << id << endl;
SendObject(socket, packet);
}
streamsize SendFile(const FileObject& file_obj, SOCKET socket) {
streamsize total = 0;
int packet_id = 1;
if (ifstream file_stream{ filesystem::path{file_obj.path}, ios::binary | ios::in }) {
while (!file_stream.eof()) {
auto remains = file_obj.size - file_stream.tellg();
SendDataPacket(packet_id, file_stream, remains >= kMaxPacketSize ? kMaxPacketSize : remains, socket);
std::streamsize curr = file_stream.gcount();
packet_id++;
if (curr <= 0)
break; // 无数据可读,退出
total += curr;
}
SendObject(socket, kEofPacket);
}
else {
throw runtime_error("File stream Initialize failed");
}
return total;
}
int ServerProc() {
addrinfo* server_address, parse_hints;
ZeroMemory(&parse_hints, sizeof(parse_hints));
parse_hints.ai_family = AF_INET;
parse_hints.ai_socktype = SOCK_STREAM;
parse_hints.ai_protocol = IPPROTO_TCP;
string ip, port;
cout << "请输入监听ip:";
cin >> ip;
cout << "请输入监听端口号:";
cin >> port;
RETURN_WHEN_FAILED(getaddrinfo(ip.c_str(), port.c_str(), &parse_hints, &server_address), "getaddrinfo failed: ", {
WSACleanup();
CoUninitialize();
});
SOCKET bind_socket = INVALID_SOCKET;
bind_socket = socket(server_address->ai_family, server_address->ai_socktype, server_address->ai_protocol);
if (bind_socket == INVALID_SOCKET) {
auto error = WSAGetLastError();
cerr << "Error at socket(): %ld" << error;
freeaddrinfo(server_address);
WSACleanup();
CoUninitialize();
return error;
}
cout << "当前监听ip:" << ip << endl;
cout << "当前监听端口号:" << port << endl;
if (bind(bind_socket, server_address->ai_addr, (int)server_address->ai_addrlen) == SOCKET_ERROR) {
cerr << "bind failed with error: " << WSAGetLastError();
freeaddrinfo(server_address);
closesocket(bind_socket);
WSACleanup();
CoUninitialize();
return WSAGetLastError();
}
if (listen(bind_socket, SOMAXCONN) == SOCKET_ERROR) {
cerr << "Listen failed with error: %ld" << WSAGetLastError();
freeaddrinfo(server_address);
closesocket(bind_socket);
WSACleanup();
CoUninitialize();
return WSAGetLastError();
}
freeaddrinfo(server_address);
wstring send_file_path;
RETURN_WHEN_FAILED(PickFile<FileOpenDialog>(TEXT("选择需要传输的文件"), "创建打开文件对话框失败", send_file_path), "", {
freeaddrinfo(server_address);
closesocket(bind_socket);
CoUninitialize();
WSACleanup();
});
FileObject fileobj{ 0 };
fileobj.size = filesystem::file_size(send_file_path);
send_file_path.copy(fileobj.path, send_file_path.length() + 1);
wcout << "文件对象信息:" << endl;
wcout << "路径: " << fileobj.path << endl;
wcout << "大小: " << fileobj.size << " Bytes" << endl;
cout << "开始接受连接..." << endl;
while (true) {
SOCKET client_socket = INVALID_SOCKET;
client_socket = accept(bind_socket, NULL, NULL);
if (client_socket == INVALID_SOCKET) {
cerr << WSAGetLastError();
continue;
}
cout << "已连接到客户端" << endl;
bool is_closed = false;
while (!is_closed) {
RequestType type = ReadObject<RequestType>(client_socket);
streamsize send_size = 0;
switch (type) {
case RequestType::kGetFileInfo:
cout << "客户端尝试获取文件信息" << endl;
SendObject(client_socket, fileobj);
break;
case RequestType::kRecvFileData:
cout << "客户端开始接收文件" << endl;
send_size = SendFile(fileobj, client_socket);
cout << "共发送" << send_size << "字节" << endl;
break;
case RequestType::kShutdown:
cout << "客户端请求关闭链接" << endl;
shutdown(client_socket, SD_BOTH);
is_closed = true;
break;
}
}
closesocket(client_socket);
cout << "关闭链接" << endl;
}
CoUninitialize();
WSACleanup();
return 0;
}
int main(int argc, char** argv) {
if (auto hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); FAILED(hr)) {
cerr << "ComInitialize failed: " << hr;
return hr;
}
WSADATA wsa_data;
RETURN_WHEN_FAILED(WSAStartup(MAKEWORD(2, 2), &wsa_data), "WSAStartup failed: ", {});
if (strcmp(argv[1], "client") == 0) {
return ClientProc();
}
else if (strcmp(argv[1], "server") == 0) {
return ServerProc();
}
}
全部评论 1
我是托,这很好用
2026-07-20 来自 广东
0



















有帮助,赞一个