示例基于wpf技术 是networkcomms2.3.1自带的示例
通讯框架 英国的networkcomms2.3.1C#通信框架
using System; using System.Collections.Generic; using System.Linq; using System.Text; using NetworkCommsDotNet; using System.ComponentModel; using System.IO; namespace ExamplesWPFFileTransfer { /// <summary> /// A local class which can be used to populate the WPF list box /// </summary> class ReceivedFile : INotifyPropertyChanged { /// <summary> /// The name of the file /// </summary> public string Filename { get; private set; } /// <summary> /// The connectionInfo corresponding with the source /// </summary> public ConnectionInfo SourceInfo { get; private set; } /// <summary> /// The total size in bytes of the file /// </summary> public long SizeBytes { get; private set; } /// <summary> /// The total number of bytes received so far /// </summary> public long ReceivedBytes { get; private set; } /// <summary> /// Getter which returns the completion of this file, between 0 and 1 /// </summary> public double CompletedPercent { get { return (double)ReceivedBytes / SizeBytes; } set { throw new Exception("An attempt to modify readonly value."); } } /// <summary> /// A formatted string of the SourceInfo /// </summary> public string SourceInfoStr { get { return "[" + SourceInfo.RemoteEndPoint.Address + ":" + SourceInfo.RemoteEndPoint.Port + "]"; } } /// <summary> /// Returns true if the completed percent equals 1 /// </summary> public bool IsCompleted { get { return ReceivedBytes == SizeBytes; } } /// <summary> /// Private object used to ensure thread safety /// </summary> object SyncRoot = new object(); /// <summary> /// A memorystream used to build the file /// </summary> Stream data; /// <summary> ///Event subscribed to by GUI for updates /// </summary> public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// Create a new ReceivedFile /// </summary> /// <param name="filename">Filename associated with this file</param> /// <param name="sourceInfo">ConnectionInfo corresponding with the file source</param> /// <param name="sizeBytes">The total size in bytes of this file</param> public ReceivedFile(string filename, ConnectionInfo sourceInfo, long sizeBytes) { this.Filename = filename; this.SourceInfo = sourceInfo; this.SizeBytes = sizeBytes; //We create a file on disk so that we can receive large files data = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 8 * 1024, FileOptions.DeleteOnClose); } /// <summary> /// Add data to file /// </summary> /// <param name="dataStart">Where to start writing this data to the interal memoryStream</param> /// <param name="bufferStart">Where to start copying data from buffer</param> /// <param name="bufferLength">The number of bytes to copy from buffer</param> /// <param name="buffer">Buffer containing data to add</param> public void AddData(long dataStart, int bufferStart, int bufferLength, byte[] buffer) { lock (SyncRoot) { data.Seek(dataStart, SeekOrigin.Begin); data.Write(buffer, (int)bufferStart, (int)bufferLength); ReceivedBytes += (int)(bufferLength - bufferStart); } NotifyPropertyChanged("CompletedPercent"); NotifyPropertyChanged("IsCompleted"); } /// <summary> /// Saves the completed file to the provided saveLocation /// </summary> /// <param name="saveLocation">Location to save file</param> public void SaveFileToDisk(string saveLocation) { if (ReceivedBytes != SizeBytes) throw new Exception("Attempted to save out file before data is complete."); if (!File.Exists(Filename)) throw new Exception("The transfered file should have been created within the local application directory. Where has it gone?"); File.Delete(saveLocation); File.Copy(Filename, saveLocation); } /// <summary> /// Closes and releases any resources maintained by this file /// </summary> public void Close() { try { data.Dispose(); } catch (Exception) { } try { data.Close(); } catch (Exception) { } } /// <summary> /// Triggers a GUI update on a property change /// </summary> /// <param name="propertyName"></param> private void NotifyPropertyChanged(string propertyName = "") { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
ReceivedFile
using System; using System.Collections.Generic; using System.Linq; using System.Text; using ProtoBuf; namespace ExamplesWPFFileTransfer { /// <summary> /// Information class used to associate incoming data with the correct ReceivedFile /// </summary> [ProtoContract] class SendInfo { /// <summary> /// Corresponding filename /// </summary> [ProtoMember(1)] public string Filename { get; private set; } /// <summary> /// The starting point for the associated data /// </summary> [ProtoMember(2)] public long BytesStart { get; private set; } /// <summary> /// The total number of bytes expected for the whole ReceivedFile /// </summary> [ProtoMember(3)] public long TotalBytes { get; private set; } /// <summary> /// The packet sequence number corresponding to the associated data /// </summary> [ProtoMember(4)] public long PacketSequenceNumber { get; private set; } /// <summary> /// Private constructor required for deserialisation /// </summary> private SendInfo() { } /// <summary> /// Create a new instance of SendInfo /// </summary> /// <param name="filename">Filename corresponding to data</param> /// <param name="totalBytes">Total bytes of the whole ReceivedFile</param> /// <param name="bytesStart">The starting point for the associated data</param> /// <param name="packetSequenceNumber">Packet sequence number corresponding to the associated data</param> public SendInfo(string filename, long totalBytes, long bytesStart, long packetSequenceNumber) { this.Filename = filename; this.TotalBytes = totalBytes; this.BytesStart = bytesStart; this.PacketSequenceNumber = packetSequenceNumber; } } }
SendInfo
using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using Microsoft.Win32; using DPSBase; using NetworkCommsDotNet; using SevenZipLZMACompressor; namespace ExamplesWPFFileTransfer { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { #region Private Fields /// <summary> /// Datacontext for the GUI list box /// </summary> ObservableCollection<ReceivedFile> receivedFiles = new ObservableCollection<ReceivedFile>(); /// <summary> /// References to recieved files by remote ConnectionInfo /// </summary> Dictionary<ConnectionInfo, Dictionary<string, ReceivedFile>> receivedFilesDict = new Dictionary<ConnectionInfo, Dictionary<string, ReceivedFile>>(); /// <summary> /// Incoming partial data cache. Keys are ConnectionInfo, PacketSequenceNumber. Value is partial packet data. /// </summary> Dictionary<ConnectionInfo, Dictionary<long, byte[]>> incomingDataCache = new Dictionary<ConnectionInfo, Dictionary<long, byte[]>>(); /// <summary> /// Incoming sendInfo cache. Keys are ConnectionInfo, PacketSequenceNumber. Value is sendInfo. /// </summary> Dictionary<ConnectionInfo, Dictionary<long, SendInfo>> incomingDataInfoCache = new Dictionary<ConnectionInfo, Dictionary<long, SendInfo>>(); /// <summary> /// Custom sendReceiveOptions used for sending files. Can be changed via GUI. /// </summary> SendReceiveOptions customOptions = new SendReceiveOptions<ProtobufSerializer>(); /// <summary> /// Object used for ensuring thread safety. /// </summary> object syncRoot = new object(); /// <summary> /// Boolean used for surpressing errors during GUI close /// </summary> static volatile bool windowClosing = false; #endregion public MainWindow() { InitializeComponent(); //Set the listbox datacontext lbReceivedFiles.DataContext = receivedFiles; //Start listening for new TCP connections StartListening(); } #region GUI Updates /// <summary> /// Adds a line to the GUI log window /// </summary> /// <param name="logLine"></param> private void AddLineToLog(string logLine) { //Use dispatcher incase method is not called from GUI thread logBox.Dispatcher.BeginInvoke(new Action(() => { logBox.Text += DateTime.Now.ToShortTimeString() + " - " + logLine + "\n"; //Update the scroller so that we are always at the bottom scroller.ScrollToBottom(); })); } /// <summary> /// Updates the send file progress bar /// </summary> /// <param name="percentComplete"></param> private void UpdateSendProgress(double percentComplete) { //Use dispatcher incase method is not called from GUI thread sendProgress.Dispatcher.BeginInvoke(new Action(() => { sendProgress.Value = percentComplete; })); } /// <summary> /// Adds a new ReceivedFile to the list box data context /// </summary> /// <param name="file"></param> private void AddNewReceivedItem(ReceivedFile file) { //Use dispatcher incase method is not called from GUI thread lbReceivedFiles.Dispatcher.BeginInvoke(new Action(() => { receivedFiles.Add(file); })); } #endregion #region GUI Events /// <summary> /// Delete the selected ReceivedFile from the application /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void DeleteFile_Clicked(object sender, RoutedEventArgs e) { Button cmd = (Button)sender; if (cmd.DataContext is ReceivedFile) { ReceivedFile fileToDelete = (ReceivedFile)cmd.DataContext; lock (syncRoot) { //Delete the ReceivedFile from the listbox data context receivedFiles.Remove(fileToDelete); //Delete the ReceivedFile from the internal cache if (receivedFilesDict.ContainsKey(fileToDelete.SourceInfo)) receivedFilesDict[fileToDelete.SourceInfo].Remove(fileToDelete.Filename); fileToDelete.Close(); } AddLineToLog("Deleted file ‘" + fileToDelete.Filename + "‘ from ‘" + fileToDelete.SourceInfoStr + "‘"); } } /// <summary> /// Save the selected file to disk /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SaveFile_Clicked(object sender, RoutedEventArgs e) { Button cmd = (Button)sender; if (cmd.DataContext is ReceivedFile) { //Use a SaveFileDialog to request the save location ReceivedFile fileToSave = (ReceivedFile)cmd.DataContext; SaveFileDialog saveDialog = new SaveFileDialog(); saveDialog.FileName = fileToSave.Filename; //If the user selected to save the file we write it to disk if (saveDialog.ShowDialog() == true) { fileToSave.SaveFileToDisk(saveDialog.FileName); AddLineToLog("Saved file ‘" + fileToSave.Filename + "‘ from ‘" + fileToSave.SourceInfoStr + "‘"); } } } /// <summary> /// Toggles the use of compression for sending files /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void UseCompression_Changed(object sender, RoutedEventArgs e) { if (this.UseCompression.IsChecked == true) { //Set the customOptions to use ProtobufSerializer as a serialiser and LZMACompressor as the only data processor customOptions = new SendReceiveOptions<ProtobufSerializer, LZMACompressor>(); AddLineToLog("Enabled compression."); } else if (this.UseCompression.IsChecked == false) { //Set the customOptions to use ProtobufSerializer as a serialiser without any data processors customOptions = new SendReceiveOptions<ProtobufSerializer>(); AddLineToLog("Disabled compression."); } } /// <summary> /// Correctly shutdown NetworkComms.Net if the application is closed /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { //Close all files lock (syncRoot) { foreach (ReceivedFile file in receivedFiles) file.Close(); } windowClosing = true; NetworkComms.Shutdown(); } #endregion #region Comms /// <summary> /// Start listening for new TCP connections /// </summary> private void StartListening() { //Trigger IncomingPartialFileData method if we receive a packet of type ‘PartialFileData‘ NetworkComms.AppendGlobalIncomingPacketHandler<byte[]>("PartialFileData", IncomingPartialFileData); //Trigger IncomingPartialFileDataInfo method if we receive a packet of type ‘PartialFileDataInfo‘ NetworkComms.AppendGlobalIncomingPacketHandler<SendInfo>("PartialFileDataInfo", IncomingPartialFileDataInfo); //Trigger the method OnConnectionClose so that we can do some cleanup NetworkComms.AppendGlobalConnectionCloseHandler(OnConnectionClose); //Start listening for TCP connections TCPConnection.StartListening(true); //Write out some usefull debugging information the log window AddLineToLog("Initialised WPF file transfer example. Accepting TCP connections on:"); foreach (var listenEndPoint in TCPConnection.ExistingLocalListenEndPoints()) AddLineToLog(listenEndPoint.Address + ":" + listenEndPoint.Port); } /// <summary> /// Handles an incoming packet of type ‘PartialFileData‘ /// </summary> /// <param name="header">Header associated with incoming packet</param> /// <param name="connection">The connection associated with incoming packet</param> /// <param name="data">The incoming data</param> private void IncomingPartialFileData(PacketHeader header, Connection connection, byte[] data) { try { SendInfo info = null; ReceivedFile file = null; //Perform this in a thread safe way lock (syncRoot) { //Extract the packet sequence number from the header //The header can also user defined parameters long sequenceNumber = header.GetOption(PacketHeaderLongItems.PacketSequenceNumber); if (incomingDataInfoCache.ContainsKey(connection.ConnectionInfo) && incomingDataInfoCache[connection.ConnectionInfo].ContainsKey(sequenceNumber)) { //We have the associated SendInfo so we can add this data directly to the file info = incomingDataInfoCache[connection.ConnectionInfo][sequenceNumber]; incomingDataInfoCache[connection.ConnectionInfo].Remove(sequenceNumber); //Check to see if we have already recieved any files from this location if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo)) receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>()); //Check to see if we have already initialised this file if (!receivedFilesDict[connection.ConnectionInfo].ContainsKey(info.Filename)) { receivedFilesDict[connection.ConnectionInfo].Add(info.Filename, new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes)); AddNewReceivedItem(receivedFilesDict[connection.ConnectionInfo][info.Filename]); } file = receivedFilesDict[connection.ConnectionInfo][info.Filename]; } else { //We do not yet have the associated SendInfo so we just add the data to the cache if (!incomingDataCache.ContainsKey(connection.ConnectionInfo)) incomingDataCache.Add(connection.ConnectionInfo, new Dictionary<long, byte[]>()); incomingDataCache[connection.ConnectionInfo].Add(sequenceNumber, data); } } //If we have everything we need we can add data to the ReceivedFile if (info != null && file != null && !file.IsCompleted) { file.AddData(info.BytesStart, 0, data.Length, data); //Perform a little cleanup file = null; data = null; GC.Collect(); } else if (info == null ^ file == null) throw new Exception("Either both are null or both are set. Info is " + (info == null ? "null." : "set.") + " File is " + (file == null ? "null." : "set.") + " File is " + (file.IsCompleted ? "completed." : "not completed.")); } catch (Exception ex) { //If an exception occurs we write to the log window and also create an error file AddLineToLog("Exception - " + ex.ToString()); NetworkComms.LogError(ex, "IncomingPartialFileDataError"); } } /// <summary> /// Handles an incoming packet of type ‘PartialFileDataInfo‘ /// </summary> /// <param name="header">Header associated with incoming packet</param> /// <param name="connection">The connection associated with incoming packet</param> /// <param name="data">The incoming data automatically converted to a SendInfo object</param> private void IncomingPartialFileDataInfo(PacketHeader header, Connection connection, SendInfo info) { try { byte[] data = null; ReceivedFile file = null; //Perform this in a thread safe way lock (syncRoot) { //Extract the packet sequence number from the header //The header can also user defined parameters long sequenceNumber = info.PacketSequenceNumber; if (incomingDataCache.ContainsKey(connection.ConnectionInfo) && incomingDataCache[connection.ConnectionInfo].ContainsKey(sequenceNumber)) { //We already have the associated data in the cache data = incomingDataCache[connection.ConnectionInfo][sequenceNumber]; incomingDataCache[connection.ConnectionInfo].Remove(sequenceNumber); //Check to see if we have already recieved any files from this location if (!receivedFilesDict.ContainsKey(connection.ConnectionInfo)) receivedFilesDict.Add(connection.ConnectionInfo, new Dictionary<string, ReceivedFile>()); //Check to see if we have already initialised this file if (!receivedFilesDict[connection.ConnectionInfo].ContainsKey(info.Filename)) { receivedFilesDict[connection.ConnectionInfo].Add(info.Filename, new ReceivedFile(info.Filename, connection.ConnectionInfo, info.TotalBytes)); AddNewReceivedItem(receivedFilesDict[connection.ConnectionInfo][info.Filename]); } file = receivedFilesDict[connection.ConnectionInfo][info.Filename]; } else { //We do not yet have the necessary data corresponding with this SendInfo so we add the //info to the cache if (!incomingDataInfoCache.ContainsKey(connection.ConnectionInfo)) incomingDataInfoCache.Add(connection.ConnectionInfo, new Dictionary<long, SendInfo>()); incomingDataInfoCache[connection.ConnectionInfo].Add(sequenceNumber, info); } } //If we have everything we need we can add data to the ReceivedFile if (data != null && file != null && !file.IsCompleted) { file.AddData(info.BytesStart, 0, data.Length, data); //Perform a little cleanup file = null; data = null; GC.Collect(); } else if (data == null ^ file == null) throw new Exception("Either both are null or both are set. Data is " + (data == null ? "null." : "set.") + " File is " + (file == null ? "null." : "set.") + " File is " + (file.IsCompleted ? "completed." : "not completed.")); } catch (Exception ex) { //If an exception occurs we write to the log window and also create an error file AddLineToLog("Exception - " + ex.ToString()); NetworkComms.LogError(ex, "IncomingPartialFileDataInfo"); } } /// <summary> /// If a connection is closed we cleanup any incomplete ReceivedFiles /// </summary> /// <param name="conn">The closed connection</param> private void OnConnectionClose(Connection conn) { ReceivedFile[] filesToRemove = null; lock (syncRoot) { //Remove any associated data from the caches incomingDataCache.Remove(conn.ConnectionInfo); incomingDataInfoCache.Remove(conn.ConnectionInfo); //Remove any non completed files if (receivedFilesDict.ContainsKey(conn.ConnectionInfo)) { filesToRemove = (from current in receivedFilesDict[conn.ConnectionInfo] where !current.Value.IsCompleted select current.Value).ToArray(); receivedFilesDict[conn.ConnectionInfo] = (from current in receivedFilesDict[conn.ConnectionInfo] where current.Value.IsCompleted select current).ToDictionary(entry => entry.Key, entry => entry.Value); } } //Update the GUI lbReceivedFiles.Dispatcher.BeginInvoke(new Action(() => { lock (syncRoot) { if (filesToRemove != null) { foreach (ReceivedFile file in filesToRemove) { receivedFiles.Remove(file); file.Close(); } } } })); //Write some usefull information the log window AddLineToLog("Connection closed with " + conn.ConnectionInfo.ToString()); } /// <summary> /// Sends requested file to the remoteIP and port set in GUI /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void SendFileButton_Click(object sender, RoutedEventArgs e) { //Create an OpenFileDialog so that we can request the file to send OpenFileDialog openDialog = new OpenFileDialog(); openDialog.Multiselect = false; //If a file was selected if (openDialog.ShowDialog() == true) { //Disable the send and compression buttons sendFileButton.IsEnabled = false; UseCompression.IsEnabled = false; //Parse the neccessary remote information string filename = openDialog.FileName; string remoteIP = this.remoteIP.Text; string remotePort = this.remotePort.Text; //Set the send progress bar to 0 UpdateSendProgress(0); //Perform the send in a task so that we don‘t lock the GUI Task.Factory.StartNew(() => { try { //Create a fileStream from the selected file FileStream stream = new FileStream(filename, FileMode.Open, FileAccess.Read); //Wrap the fileStream in a threadSafeStream so that future operations are thread safe ThreadSafeStream safeStream = new ThreadSafeStream(stream); //Get the filename without the associated path information string shortFileName = System.IO.Path.GetFileName(filename); //Parse the remote connectionInfo //We have this in a seperate try catch so that we can write a clear message to the log window //if there are problems ConnectionInfo remoteInfo; try { remoteInfo = new ConnectionInfo(remoteIP, int.Parse(remotePort)); } catch (Exception) { throw new InvalidDataException("Failed to parse remote IP and port. Check and try again."); } //Get a connection to the remote side Connection connection = TCPConnection.GetConnection(remoteInfo); //Break the send into 20 segments. The less segments the less overhead //but we still want the progress bar to update in sensible steps long sendChunkSizeBytes = (long)(stream.Length / 20.0) + 1; //Limit send chunk size to 500MB long maxChunkSizeBytes = 500L*1024L*1024L; if (sendChunkSizeBytes > maxChunkSizeBytes) sendChunkSizeBytes = maxChunkSizeBytes; long totalBytesSent = 0; do { //Check the number of bytes to send as the last one may be smaller long bytesToSend = (totalBytesSent + sendChunkSizeBytes < stream.Length ? sendChunkSizeBytes : stream.Length - totalBytesSent); //Wrap the threadSafeStream in a StreamSendWrapper so that we can get NetworkComms.Net //to only send part of the stream. StreamSendWrapper streamWrapper = new StreamSendWrapper(safeStream, totalBytesSent, bytesToSend); //We want to record the packetSequenceNumber long packetSequenceNumber; //Send the select data connection.SendObject("PartialFileData", streamWrapper, customOptions, out packetSequenceNumber); //Send the associated SendInfo for this send so that the remote can correctly rebuild the data connection.SendObject("PartialFileDataInfo", new SendInfo(shortFileName, stream.Length, totalBytesSent, packetSequenceNumber), customOptions); totalBytesSent += bytesToSend; //Update the GUI with our send progress UpdateSendProgress((double)totalBytesSent / stream.Length); } while (totalBytesSent < stream.Length); //Clean up any unused memory GC.Collect(); AddLineToLog("Completed file send to ‘" + connection.ConnectionInfo.ToString() + "‘."); } catch (CommunicationException) { //If there is a communication exception then we just write a connection //closed message to the log window AddLineToLog("Failed to complete send as connection was closed."); } catch (Exception ex) { //If we get any other exception which is not an InvalidDataException //we log the error if (!windowClosing && ex.GetType() != typeof(InvalidDataException)) { AddLineToLog(ex.Message.ToString()); NetworkComms.LogError(ex, "SendFileError"); } } //Once the send is finished reset the send progress bar UpdateSendProgress(0); //Once complete enable the send button again sendFileButton.Dispatcher.BeginInvoke(new Action(() => { sendFileButton.IsEnabled = true; UseCompression.IsEnabled = true; })); }); } } #endregion } }
MainWindow
www.networkcomms.cn编辑
时间: 2024-10-06 05:10:25