基于TCP協(xié)議Socket編程,使用WPF實現(xiàn)文件上傳和保存文件完整示例
需求分析
假設(shè)我們需要實現(xiàn)一個基于網(wǎng)絡(luò)的文件上傳系統(tǒng),用戶可以通過客戶端將本地文件上傳到服務(wù)端。這種情況經(jīng)常出現(xiàn)在文件存儲和共享、云存儲等應(yīng)用場景中。使用Socket編程可以實現(xiàn)高效可靠的文件傳輸。
1、客戶端需求:
- 用戶可以選擇本地文件進行上傳。
- 用戶需要輸入服務(wù)端的IP地址和端口號。
- 客戶端需要將選擇的文件發(fā)送給服務(wù)端進行保存。
2、服務(wù)端需求:
- 服務(wù)端需要監(jiān)聽指定的端口,等待客戶端連接請求。
- 接收到客戶端連接后,服務(wù)端需要接收文件數(shù)據(jù)。
- 服務(wù)端需要將接收到的文件保存到指定位置。
3、文件傳輸需求:
- 傳輸協(xié)議:使用TCP協(xié)議確??煽康臄?shù)據(jù)傳輸。
- 文件分片:為了減小內(nèi)存開銷和網(wǎng)絡(luò)負載,將大文件分成多個較小的數(shù)據(jù)包進行傳輸。
- 文件校驗:傳輸過程中需要對文件進行校驗,確保數(shù)據(jù)的完整性。
4、錯誤處理需求:
- 連接錯誤:需要處理連接失敗、超時等錯誤情況。
- 文件錯誤:需要處理文件讀取失敗、傳輸中斷等錯誤情況。
- 異常處理:需要處理網(wǎng)絡(luò)異常、IO異常等情況,并提供相應(yīng)的錯誤提示和處理機制。
基于以上需求,我們可以使用C#的Socket編程實現(xiàn)一個文件上傳系統(tǒng),包括客戶端和服務(wù)端的程序。在程序中使用Socket進行網(wǎng)絡(luò)連接和數(shù)據(jù)傳輸,同時對連接錯誤和文件錯誤進行適當處理和異常捕獲。
客戶端代碼和實現(xiàn)過程
dotnet new wpf -n "FileUploaderClient"
MainWindow.xaml:
<Window x:Class="FileUploaderClient.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="文件上傳客戶端" Height="350" Width="500">
<Grid>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="選擇要上傳的文件:" Margin="0 0 0 10" />
<Button Content="瀏覽..." Click="BrowseButton_Click" Width="80" Height="30" Margin="0 0 0 10" />
<TextBox x:Name="FilePathTextBox" Text="{Binding FilePath}" Width="300" Height="30" Margin="0 0 0 10" />
<TextBlock Text="輸入服務(wù)端IP地址:" Margin="0 0 0 10" />
<TextBox x:Name="ServerIPTextBox" Text="{Binding ServerIP}" Width="200" Height="30" Margin="0 0 0 10" />
<TextBlock Text="輸入服務(wù)端端口號:" Margin="0 0 0 10" />
<TextBox x:Name="ServerPortTextBox" Text="{Binding ServerPort}" Width="100" Height="30" Margin="0 0 0 10" />
<Button Content="上傳" Click="UploadButton_Click" Width="80" Height="30" Margin="0 0 0 10" />
<TextBlock x:Name="ResultTextBlock" Text="{Binding ResultMessage}" Margin="0 10" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.ComponentModel;
using System.IO;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Windows;
namespace FileUploaderClient
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string filePath;
public string FilePath
{
get { return filePath; }
set
{
filePath = value;
OnPropertyChanged("FilePath");
}
}
private string serverIP;
public string ServerIP
{
get { return serverIP; }
set
{
serverIP = value;
OnPropertyChanged("ServerIP");
}
}
private int serverPort;
public int ServerPort
{
get { return serverPort; }
set
{
serverPort = value;
OnPropertyChanged("ServerPort");
}
}
private string resultMessage;
public string ResultMessage
{
get { return resultMessage; }
set
{
resultMessage = value;
OnPropertyChanged("ResultMessage");
}
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void BrowseButton_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
if (openFileDialog.ShowDialog() == true)
{
FilePath = openFileDialog.FileName;
}
}
private void UploadButton_Click(object sender, RoutedEventArgs e)
{
try
{
// 讀取本地文件
byte[] fileData = File.ReadAllBytes(FilePath);
// 連接服務(wù)端并發(fā)送文件
using (TcpClient client = new TcpClient(ServerIP, ServerPort))
{
using (NetworkStream stream = client.GetStream())
{
// 發(fā)送文件名和文件長度
string fileName = Path.GetFileName(FilePath);
byte[] fileNameBytes = Encoding.UTF8.GetBytes(fileName);
byte[] fileNameLengthBytes = BitConverter.GetBytes(fileNameBytes.Length);
byte[] fileLengthBytes = BitConverter.GetBytes(fileData.Length);
stream.Write(fileNameLengthBytes, 0, 4);
stream.Write(fileNameBytes, 0, fileNameBytes.Length);
stream.Write(fileLengthBytes, 0, 4);
// 發(fā)送文件內(nèi)容
int bufferSize = 1024;
int bytesSent = 0;
while (bytesSent < fileData.Length)
{
int remainingBytes = fileData.Length - bytesSent;
int bytesToSend = Math.Min(bufferSize, remainingBytes);
stream.Write(fileData, bytesSent, bytesToSend);
bytesSent += bytesToSend;
}
ResultMessage = "文件上傳成功!";
}
}
}
catch (Exception ex)
{
ResultMessage = "文件上傳失?。? + ex.Message;
}
}
}
}
使用該客戶端程序,用戶可以選擇本地文件進行上傳,并輸入服務(wù)端的IP地址和端口號??蛻舳藭⑦x擇的文件發(fā)送給服務(wù)端進行保存。
這個示例實現(xiàn)了基于TCP協(xié)議的文件上傳功能,使用TcpClient和NetworkStream進行連接和數(shù)據(jù)傳輸。文件被分成較小的數(shù)據(jù)包進行傳輸,發(fā)送前會計算文件名和文件長度,并通過4字節(jié)的長度前綴指示接收方應(yīng)該接收多少數(shù)據(jù)。
服務(wù)端代碼和實現(xiàn)過程
dotnet new wpf -n "FileUploaderServer"
MainWindow.xaml:
<Window x:Class="FileUploaderServer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="文件上傳服務(wù)端" Height="350" Width="500">
<Grid>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="輸入要監(jiān)聽的端口號:" Margin="0 0 0 10" />
<TextBox x:Name="PortTextBox" Text="{Binding Port}" Width="100" Height="30" Margin="0 0 0 10" />
<Button Content="啟動服務(wù)" Click="StartButton_Click" Width="80" Height="30" Margin="0 0 0 10" />
<TextBlock x:Name="ResultTextBlock" Text="{Binding ResultMessage}" Margin="0 10" TextWrapping="Wrap" />
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs:
using System;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Windows;
namespace FileUploaderServer
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private int port;
public int Port
{
get { return port; }
set
{
port = value;
OnPropertyChanged("Port");
}
}
private string resultMessage;
public string ResultMessage
{
get { return resultMessage; }
set
{
resultMessage = value;
OnPropertyChanged("ResultMessage");
}
}
private TcpListener serverListener;
private Thread serverThread;
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private void StartButton_Click(object sender, RoutedEventArgs e)
{
try
{
// 啟動服務(wù)端監(jiān)聽
IPAddress ipAddress = IPAddress.Any;
serverListener = new TcpListener(ipAddress, Port);
serverListener.Start();
// 啟動服務(wù)端線程
serverThread = new Thread(new ThreadStart(ServerThreadProc));
serverThread.IsBackground = true;
serverThread.Start();
ResultMessage = "服務(wù)啟動成功!";
}
catch (Exception ex)
{
ResultMessage = "服務(wù)啟動失敗:" + ex.Message;
}
}
private void ServerThreadProc()
{
while (true)
{
try
{
// 接受客戶端連接請求
TcpClient client = serverListener.AcceptTcpClient();
// 處理客戶端連接請求
Thread clientThread = new Thread(new ParameterizedThreadStart(ClientThreadProc));
clientThread.IsBackground = true;
clientThread.Start(client);
}
catch (Exception)
{
break;
}
}
}
private void ClientThreadProc(object parameter)
{
TcpClient client = (TcpClient)parameter;
try
{
using (client)
{
using (NetworkStream stream = client.GetStream())
{
// 讀取文件名和文件長度
byte[] fileNameLengthBytes = new byte[4];
stream.Read(fileNameLengthBytes, 0, 4);
int fileNameLength = BitConverter.ToInt32(fileNameLengthBytes, 0);
byte[] fileNameBytes = new byte[fileNameLength];
stream.Read(fileNameBytes, 0, fileNameLength);
string fileName = Encoding.UTF8.GetString(fileNameBytes);
byte[] fileLengthBytes = new byte[4];
stream.Read(fileLengthBytes, 0, 4);
int fileLength = BitConverter.ToInt32(fileLengthBytes, 0);
// 接收文件內(nèi)容
int bufferSize = 1024;
byte[] buffer = new byte[bufferSize];
int bytesRead = 0;
int totalBytesRead = 0;
byte[] fileData = new byte[fileLength];
while (totalBytesRead < fileLength && (bytesRead = stream.Read(buffer, 0, Math.Min(bufferSize, fileLength - totalBytesRead))) > 0)
{
Buffer.BlockCopy(buffer, 0, fileData, totalBytesRead, bytesRead);
totalBytesRead += bytesRead;
}
// 保存文件到本地
string savePath = Path.Combine(Environment.CurrentDirectory, "Uploads", fileName);
if (File.Exists(savePath))
{
savePath = Path.Combine(Environment.CurrentDirectory, "Uploads", Path.GetFileNameWithoutExtension(fileName) + "_" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + Path.GetExtension(fileName));
}
using (FileStream fileStream = new FileStream(savePath, FileMode.CreateNew))
{
fileStream.Write(fileData, 0, fileLength);
}
ResultMessage = "文件保存成功:" + savePath;
}
}
}
catch (Exception ex)
{
ResultMessage = "文件保存失?。? + ex.Message;
}
}
}
}
使用該服務(wù)端程序,用戶可以輸入要監(jiān)聽的端口號,并啟動服務(wù)端監(jiān)聽。當有客戶端連接時,服務(wù)端會接收文件數(shù)據(jù),并保存到指定位置。
這個示例實現(xiàn)了基于TCP協(xié)議的文件接收和保存功能,使用TcpListener和TcpClient進行監(jiān)聽和連接,使用NetworkStream進行數(shù)據(jù)傳輸。文件被分成較小的數(shù)據(jù)包進行傳輸,發(fā)送前會計算文件名和文件長度,并通過4字節(jié)的長度前綴指示接收方應(yīng)該接收多少數(shù)據(jù)。
運行結(jié)果
啟動服務(wù)端,開啟端口12345。
啟動客戶端程序,配置服務(wù)端地址。