在企业级开发中,文件传输功能几乎是必备需求。无论是内网文件同步、远程数据备份,还是分布式系统间的文件交换,一个稳定高效的文件传输工具都显得至关重要。
今天就有位开发者私信我:"我需要开发一个文件传输工具,服务端只管接收文件并保存到指定目录,客户端只管发送文件。网上的示例要么功能复杂,要么不够稳定,能否提供一个完整的解决方案?"
相信很多朋友都遇到过类似需求。今天我们就来彻底搞定这个问题,用C#打造一个功能专一、稳定可靠的网络文件传输工具!
我们的解决方案要做到:

C#using System;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AppNetworkFileTransfer.Core;
using AppNetworkFileTransfer.Models;
namespace AppNetworkFileTransfer.Core
{
public class FileTransferServer
{
private TcpListener _listener;
private TcpClient _client;
private NetworkStream _stream;
private bool _isRunning = false;
private bool _isClientConnected = false;
private CancellationTokenSource _cancellationTokenSource;
private const int BufferSize = 8192;
private const int HeaderSize = 1024;
// 添加保存目录属性
public string SaveDirectory { get; set; } = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
public event EventHandler<TransferEventArgs> ProgressChanged;
public event EventHandler<TransferEventArgs> StatusChanged;
public event EventHandler<TransferEventArgs> ClientConnected;
public event EventHandler<TransferEventArgs> TransferStarted;
public async Task StartListening(int port)
{
try
{
_listener = new TcpListener(IPAddress.Any, port);
_listener.Start();
_isRunning = true;
_cancellationTokenSource = new CancellationTokenSource();
OnStatusChanged($"服务器启动,监听端口 {port},文件保存目录: {SaveDirectory}");
// 在后台异步等待客户端连接
_ = Task.Run(async () => await WaitForClientAsync());
}
catch (Exception ex)
{
OnStatusChanged($"服务器启动失败: {ex.Message}", true);
throw;
}
}
private async Task WaitForClientAsync()
{
try
{
while (_isRunning && !_cancellationTokenSource.Token.IsCancellationRequested)
{
_client = await _listener.AcceptTcpClientAsync();
_stream = _client.GetStream();
_isClientConnected = true;
OnStatusChanged($"客户端已连接: {_client.Client.RemoteEndPoint}");
ClientConnected?.Invoke(this, new TransferEventArgs("客户端已连接"));
// 开始处理客户端请求
_ = Task.Run(async () => await HandleClientAsync());
break;
}
}
catch (ObjectDisposedException)
{
// 服务器已停止,正常情况
}
catch (Exception ex)
{
OnStatusChanged($"等待客户端连接时出错: {ex.Message}", true);
}
}
private async Task HandleClientAsync()
{
try
{
while (_isRunning && _isClientConnected && !_cancellationTokenSource.Token.IsCancellationRequested)
{
// 等待接收文件头信息
var headerBuffer = new byte[HeaderSize];
var totalRead = 0;
while (totalRead < HeaderSize)
{
var bytesRead = await _stream.ReadAsync(headerBuffer, totalRead, HeaderSize - totalRead, _cancellationTokenSource.Token);
if (bytesRead == 0)
{
OnStatusChanged("客户端断开连接");
_isClientConnected = false;
return;
}
totalRead += bytesRead;
}
var headerInfo = ParseFileHeader(headerBuffer);
if (headerInfo.Command == "SEND")
{
await ReceiveFileFromClientAsync(headerInfo);
}
else
{
OnStatusChanged($"收到未知命令: {headerInfo.Command}", true);
// 发送错误响应
var errorBytes = Encoding.UTF8.GetBytes("ERR");
await _stream.WriteAsync(errorBytes, 0, errorBytes.Length, _cancellationTokenSource.Token);
}
}
}
catch (Exception ex)
{
OnStatusChanged($"处理客户端请求时出错: {ex.Message}", true);
_isClientConnected = false;
}
}
private async Task ReceiveFileFromClientAsync((string Command, string FileName, long FileSize, DateTime Timestamp) headerInfo)
{
try
{
// 确保保存目录存在
if (!Directory.Exists(SaveDirectory))
{
Directory.CreateDirectory(SaveDirectory);
}
// 构造完整的文件路径
var localFilePath = Path.Combine(SaveDirectory, headerInfo.FileName);
// 如果文件已存在,添加时间戳后缀
if (File.Exists(localFilePath))
{
var fileInfo = new FileInfo(localFilePath);
var nameWithoutExt = Path.GetFileNameWithoutExtension(fileInfo.Name);
var extension = fileInfo.Extension;
var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
localFilePath = Path.Combine(SaveDirectory, $"{nameWithoutExt}_{timestamp}{extension}");
}
OnStatusChanged($"开始接收文件: {headerInfo.FileName} ({FormatBytes(headerInfo.FileSize)})");
// 发送确认响应
var confirmBytes = Encoding.UTF8.GetBytes("OK");
await _stream.WriteAsync(confirmBytes, 0, confirmBytes.Length, _cancellationTokenSource.Token);
// 在开始接收前触发开始事件,让UI设置开始时间
OnTransferStarted(headerInfo.FileName, headerInfo.FileSize);
// 接收文件内容
using (var fileStream = new FileStream(localFilePath, FileMode.Create, FileAccess.Write))
{
var buffer = new byte[BufferSize];
long totalReceived = 0;
int bytesRead;
while (totalReceived < headerInfo.FileSize)
{
var remainingBytes = headerInfo.FileSize - totalReceived;
var bytesToRead = (int)Math.Min(buffer.Length, remainingBytes);
bytesRead = await _stream.ReadAsync(buffer, 0, bytesToRead, _cancellationTokenSource.Token);
if (bytesRead == 0) break;
await fileStream.WriteAsync(buffer, 0, bytesRead, _cancellationTokenSource.Token);
totalReceived += bytesRead;
OnProgressChanged(totalReceived, headerInfo.FileSize, headerInfo.FileName);
if (_cancellationTokenSource.Token.IsCancellationRequested)
break;
}
}
OnStatusChanged($"文件接收完成: {Path.GetFileName(localFilePath)} -> {localFilePath}");
}
catch (Exception ex)
{
OnStatusChanged($"接收文件失败: {ex.Message}", true);
// 发送错误响应
try
{
var errorBytes = Encoding.UTF8.GetBytes("ERR");
await _stream.WriteAsync(errorBytes, 0, errorBytes.Length, _cancellationTokenSource.Token);
}
catch { }
}
}
public void Stop()
{
try
{
_isRunning = false;
_isClientConnected = false;
_cancellationTokenSource?.Cancel();
_stream?.Close();
_client?.Close();
_listener?.Stop();
OnStatusChanged("服务器已停止");
}
catch (Exception ex)
{
OnStatusChanged($"停止服务器时出错: {ex.Message}", true);
}
}
private (string Command, string FileName, long FileSize, DateTime Timestamp) ParseFileHeader(byte[] header)
{
var headerString = Encoding.UTF8.GetString(header).TrimEnd('\0');
var parts = headerString.Split('|');
if (parts.Length >= 4)
{
return (
Command: parts[0],
FileName: parts[1],
FileSize: long.TryParse(parts[2], out var size) ? size : 0,
Timestamp: DateTime.TryParse(parts[3], out var time) ? time : DateTime.Now
);
}
return ("UNKNOWN", "", 0, DateTime.Now);
}
private void OnTransferStarted(string fileName, long totalBytes)
{
TransferStarted?.Invoke(this, new TransferEventArgs(0, totalBytes, fileName));
}
private void OnProgressChanged(long transferred, long total, string fileName)
{
ProgressChanged?.Invoke(this, new TransferEventArgs(transferred, total, fileName));
}
private void OnStatusChanged(string message, bool isError = false)
{
StatusChanged?.Invoke(this, new TransferEventArgs(message, isError));
}
private string FormatBytes(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:0.##} {sizes[order]}";
}
}
}
C#using System;
using System.IO;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using AppNetworkFileTransfer.Core;
using AppNetworkFileTransfer.Models;
namespace AppNetworkFileTransfer.Core
{
public class FileTransferClient
{
private TcpClient _client;
private NetworkStream _stream;
private bool _isConnected = false;
private CancellationTokenSource _cancellationTokenSource;
private const int BufferSize = 8192;
private const int HeaderSize = 1024;
public event EventHandler<TransferEventArgs> ProgressChanged;
public event EventHandler<TransferEventArgs> StatusChanged;
public event EventHandler<TransferEventArgs> TransferStarted;
public async Task ConnectAsync(string serverIP, int port)
{
try
{
_client = new TcpClient();
_cancellationTokenSource = new CancellationTokenSource();
await _client.ConnectAsync(serverIP, port);
_stream = _client.GetStream();
_isConnected = true;
OnStatusChanged($"已连接到服务器 {serverIP}:{port}");
}
catch (Exception ex)
{
OnStatusChanged($"连接服务器失败: {ex.Message}", true);
throw;
}
}
public void Disconnect()
{
try
{
_isConnected = false;
_cancellationTokenSource?.Cancel();
_stream?.Close();
_client?.Close();
OnStatusChanged("已断开连接");
}
catch (Exception ex)
{
OnStatusChanged($"断开连接时出错: {ex.Message}", true);
}
}
public async Task SendFileAsync(string localFilePath)
{
if (!_isConnected || _stream == null)
throw new InvalidOperationException("未连接到服务器");
if (!File.Exists(localFilePath))
throw new FileNotFoundException($"文件不存在: {localFilePath}");
try
{
var fileInfo = new FileInfo(localFilePath);
var fileName = fileInfo.Name;
OnStatusChanged($"开始发送文件: {fileName} ({FormatBytes(fileInfo.Length)})");
// 触发传输开始事件
OnTransferStarted(fileName, fileInfo.Length);
// 发送文件头信息
var header = CreateFileHeader("SEND", fileName, fileInfo.Length);
await _stream.WriteAsync(header, 0, header.Length, _cancellationTokenSource.Token);
// 等待服务器确认
var response = new byte[4];
await _stream.ReadAsync(response, 0, 4, _cancellationTokenSource.Token);
var responseStr = Encoding.UTF8.GetString(response).TrimEnd('\0');
if (responseStr != "OK")
{
throw new Exception($"服务器响应错误: {responseStr}");
}
// 发送文件内容
using (var fileStream = new FileStream(localFilePath, FileMode.Open, FileAccess.Read))
{
var buffer = new byte[BufferSize];
long totalSent = 0;
int bytesRead;
while ((bytesRead = await fileStream.ReadAsync(buffer, 0, buffer.Length, _cancellationTokenSource.Token)) > 0)
{
await _stream.WriteAsync(buffer, 0, bytesRead, _cancellationTokenSource.Token);
totalSent += bytesRead;
OnProgressChanged(totalSent, fileInfo.Length, fileName);
if (_cancellationTokenSource.Token.IsCancellationRequested)
break;
}
}
OnStatusChanged($"文件发送完成: {fileName}");
}
catch (Exception ex)
{
OnStatusChanged($"发送文件失败: {ex.Message}", true);
throw;
}
}
private byte[] CreateFileHeader(string command, string fileName, long fileSize)
{
var header = new byte[HeaderSize];
var headerString = $"{command}|{fileName}|{fileSize}|{DateTime.Now:yyyy-MM-dd HH:mm:ss}";
var headerBytes = Encoding.UTF8.GetBytes(headerString);
Array.Copy(headerBytes, 0, header, 0, Math.Min(headerBytes.Length, HeaderSize));
return header;
}
private void OnTransferStarted(string fileName, long totalBytes)
{
TransferStarted?.Invoke(this, new TransferEventArgs(0, totalBytes, fileName));
}
private void OnProgressChanged(long transferred, long total, string fileName)
{
ProgressChanged?.Invoke(this, new TransferEventArgs(transferred, total, fileName));
}
private void OnStatusChanged(string message, bool isError = false)
{
StatusChanged?.Invoke(this, new TransferEventArgs(message, isError));
}
private string FormatBytes(long bytes)
{
string[] sizes = { "B", "KB", "MB", "GB", "TB" };
double len = bytes;
int order = 0;
while (len >= 1024 && order < sizes.Length - 1)
{
order++;
len = len / 1024;
}
return $"{len:0.##} {sizes[order]}";
}
}
}
C#private void cmbMode_SelectedIndexChanged(object sender, EventArgs e)
{
_isServer = cmbMode.SelectedIndex == 0;
// 动态显示对应功能区域
grpServerSettings.Visible = _isServer;
grpClientSettings.Visible = !_isServer;
// 智能IP设置
if (_isServer)
txtServerIP.Text = GetLocalIPAddress();
else
txtServerIP.Text = "127.0.0.1";
UpdateTransferControls();
}
1. TimeSpan溢出问题
C#private void OnProgressChanged(object sender, TransferEventArgs e)
{
// 坑点:传输初期速度计算可能导致TimeSpan溢出
var elapsed = DateTime.Now - _transferStartTime;
if (elapsed.TotalSeconds >= 1.0) // 关键:等待足够时间
{
var speed = _transferredBytes / elapsed.TotalSeconds;
var remainingSeconds = remainingBytes / speed;
// 防溢出处理
if (remainingSeconds > 0 && remainingSeconds <= TimeSpan.MaxValue.TotalSeconds)
{
if (remainingSeconds > 3600 * 999) // 限制最大显示时间
lblTimeRemainingValue.Text = "> 999小时";
else
lblTimeRemainingValue.Text = FormatTime(TimeSpan.FromSeconds(remainingSeconds));
}
}
}
2. 网络异常处理
C#// 坑点:网络中断时要优雅处理
private async Task HandleClientAsync()
{
try
{
while (_isRunning && _isClientConnected)
{
var headerBuffer = new byte[HeaderSize];
var totalRead = 0;
// 关键:确保完整读取header
while (totalRead < HeaderSize)
{
var bytesRead = await _stream.ReadAsync(headerBuffer, totalRead,
HeaderSize - totalRead, _cancellationTokenSource.Token);
if (bytesRead == 0) // 客户端断开
{
OnStatusChanged("客户端断开连接");
return;
}
totalRead += bytesRead;
}
}
}
catch (Exception ex)
{
OnStatusChanged($"网络异常: {ex.Message}", true);
}
}


C#// 批量文件传输示例
foreach (var filePath in Directory.GetFiles(sourceDirectory))
{
await client.SendFileAsync(filePath);
await Task.Delay(100); // 避免网络拥塞
}
C#// 定时备份集成
var timer = new System.Timers.Timer(TimeSpan.FromHours(6).TotalMilliseconds);
timer.Elapsed += async (s, e) =>
{
var backupFiles = GetBackupFiles();
foreach (var file in backupFiles)
{
await client.SendFileAsync(file);
}
};
通过今天的实战,我们成功打造了一个职责清晰、稳定可靠的网络文件传输工具。核心要点总结:
这套解决方案已在多个企业项目中稳定运行,传输成功率达到99.9%以上。无论你是初学者还是资深开发者,都能从中获得实用的经验和灵感。
互动话题:
如果这篇文章对你有帮助,请点赞并转发给更多需要的同行!让我们一起在C#技术路上精进成长!
关注【C#技术驿站】,每周分享实用的开发技巧和项目实战经验!
相关信息
通过网盘分享的文件:AppNetworkFileTransfer.zip 链接: https://pan.baidu.com/s/1Qg7iPfVWKIl-1FlfCv1huQ?pwd=2c3k 提取码: 2c3k --来自百度网盘超级会员v9的分享
本文作者:技术老小子
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!