Code Bye

c#源码:安卓通过TCP通信与Windows服务器进行文件传输

 

APK文件  (对应的windows服务器端已经架设好,可以直接下载进行测试)

 源码       数据库文件

在前面一篇文章:【源码】c#编写的安卓客户端与Windows服务器程序进行网络通信 中我们探讨了,如何通过xamarin技术,完成安卓客户端与Windows服务器的通信,这篇文章,我们探讨一下使用场景非常多的文件传输.

先谈一下为什么使用xamarin.android技术吧,之前有开发过一个公文系统,c#语言开发,服务器部署在Windows Server 2003上,客户端采用Winform技术(.net2.0),使用了一段时间后,客户提出希望系统能够支持安卓移动端。首先想到了用java语言进行开发,用java写安卓程序应该是最好不过了,但是难点出现了,就是如何让java编写的安卓客户端与现有的Windows服务器上的程序通信,探索多日无果,于是想起了xamarin.adnroid技术,使用此技术,可以集成原有的C#通信框架,TCP通信这一块就解决了.这样做还有一个好处,即能够与原有的服务器端程序无缝集成,服务器端程序同时支持Windows客户端与安卓客户端。

学习Xamarin.Android的时间不长,水平有限,希望本文能够抛砖引玉,对xamarin开发有经验的朋友请多多指点,不足之处敬请批评指正。

本Demo效果图如下

当用户点击“从服务器获取文件”按钮后,服务器端会收到相应的请求,并开始通过TCP连接发送数据,本例中,服务器发送一张图片(大小为20k),客户端收到后,新建一个名称为”msdc”的文件夹,并把文件存储在此文件夹中。`

我们来看一下开发过程:

第一步:在Main.axml文件中,增加一个按钮

 <Button
        android:id="@+id/btnGetFile"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="从服务器获取文件" />

第二步:

客户端的MainActivity.cs文件中,编写该按钮相对应的方法

  Button buttonGetFile = FindViewById<Button>(Resource.Id.btnGetFile);

    buttonGetFile.Click += new EventHandler(buttonGetFile_Click);
void buttonGetFile_Click(object sender, EventArgs e)
        {
            GetFileFromServer();
        }
public void GetFileFromServer()
        { 
              //传递的参数为本地保存的路径
              string filePath = GetFileSavePath(this);

              //发送一个请求给服务器,服务器收到该请求后,开始发送文件
               newTcpConnection.SendObject ("GetFileFromServer", filePath);

                
        }
private String GetFileSavePath(Context context)
        {
            String filePath;
            if (checkSDCard())
            {
                filePath = Android.OS.Environment.GetExternalStoragePublicDirectory("").ToString() + @"/MSDC/";//File.Separator
            }
            else
            {
                filePath = context.CacheDir.AbsolutePath + @"/MSDC/";
            }
            Java.IO.File file = new Java.IO.File(filePath);
            if (!file.Exists())
            {
                Boolean b = file.Mkdirs();


            }
            else
            {

            }
            return filePath;
        }

GetFileSavePath
//检测是否存在SD卡
        private Boolean checkSDCard()
        {

            if (Android.OS.Environment.ExternalStorageState.Equals(Android.OS.Environment.MediaMounted))
            {
                return true;
            }
            else
            {
                return false;
            }

        }

checkSDCard 检查是否存在SD卡

第三步:看一下服务器端的处理程序

private void IncomingReqMobileUpFile(PacketHeader header, Connection connection, string filePath)
        {  
               //在此Demo中,我们直接指定一个文件,进行发送
                string filename = AppDomain.CurrentDomain.BaseDirectory + "Files\" + "msdc.jpg";

                string fileID = FileIDCreator.GetNextFileID(NetworkComms.NetworkIdentifier.ToString());
                  
                SendFile sendFile = new SendFile(fileID, filename, filePath, connection, customOptions  );
                 
                sendFile.NowSendFile();
             
        }
using System;
using System.Collections.Generic;

using System.Text;

using NetworkCommsDotNet;
using System.ComponentModel;
using System.IO;

using NetworkCommsDotNet;
using DPSBase;
using Mobile.Entity ;
 using System.Threading ;

namespace MobileServer
{
    public class SendFile  
    {

      

        //取消文件的发送
        private volatile bool canceled = false;
        private FileTransFailReason fleTransFailReason = FileTransFailReason.Error ;
        /// <summary>
        /// The name of the file
        /// 文件名
        /// </summary>
        public string Filename { get; private set; }

        /// <summary>
        /// The connectionInfo corresponding with the source
        /// 连接信息
        /// </summary>
      

        /// <summary>
        /// 收发参数
        /// </summary>
        private SendReceiveOptions sendReceiveOptions;
        public SendReceiveOptions SendReceiveOptions
        {
            get { return sendReceiveOptions; }
            set { sendReceiveOptions = value; }
        }



        private Connection connection;

        public Connection Connection
        {
            get { return connection; }
            set { connection = value; }
        }

      

        //文件ID  用于管理文件 和文件的发送 取消发送相关

        private string fileID;

        public string FileID
        {
            get { return fileID; }
            set { fileID = value; }
        }
        //文件传输后存储的路径  客户端传过来的路径  再传回去 
        private string filePath;

        public string Filepath
        {
            get { return filePath; }
            set { filePath = value; }
        }



        /// <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 SentBytes { get; private set; }

        /// <summary>
        /// Getter which returns the completion of this file, between 0 and 1
        ///已经完成的百分比
        /// </summary>
        public double CompletedPercent
        {
            get { return (double)SentBytes / SizeBytes; }

            //This set is required for the application to work
            set { throw new Exception("An attempt to modify read-only value."); }
        }

        /// <summary>
        /// A formatted string of the SourceInfo
        /// 源信息
        /// </summary>
      

        /// <summary>
        /// Returns true if the completed percent equals 1
        /// 是否完成
        /// </summary>
        public bool IsCompleted
        {
            get { return SentBytes == SizeBytes; }
        }

        /// <summary>
        /// Private object used to ensure thread safety
        /// </summary>
        object SyncRoot = new object();


        /// <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 SendFile(string fileID, string filename, string filePath,  Connection connection, SendReceiveOptions sendReceiveOptions )
        {
            //文件ID
            this.fileID = fileID;
            this.Filename = filename;
            this.filePath = filePath;
           

            this.connection = connection;
            this.sendReceiveOptions = sendReceiveOptions;
           

        }

        public void NowSendFile()
        {
            new Action(this.StartSendFile).BeginInvoke(null, null);
        }


        public void StartSendFile()
        {
            try
            {
                //Create a fileStream from the selected file
                //根据选择的文件创建一个文件流
                FileStream stream = new FileStream(this.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);

               
                long sendChunkSizeBytes = 4096;


                this.SizeBytes = stream.Length;

                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, sendReceiveOptions, out packetSequenceNumber);


                    //Send the associated SendInfo for this send so that the remote can correctly rebuild the data
                    //把包的顺序号记录在 SendInfo类中。
                    connection.SendObject("PartialFileDataInfo", new SendInfo(fileID, shortFileName, filePath, stream.Length, totalBytesSent, packetSequenceNumber), sendReceiveOptions);

                    totalBytesSent += bytesToSend;

                    //更新已经发送的字节的属性
                    SentBytes += bytesToSend;

                    ////Update the GUI with our send progress
                    //UpdateSendProgress((double)totalBytesSent * 100 / stream.Length);
                     
                
                    if (! this.canceled)
                    {
                        Thread.Sleep(30);
                    }

                } while ((totalBytesSent < stream.Length) && !this.canceled);

                 

                //AddLineToLog("Completed file send to "" + connection.ConnectionInfo.ToString() + "".");
            }
            catch (CommunicationException)
            {
                

            }
            catch (Exception ex)
            {
                
            }
        }


         

    }
}

SendFile方法
APK文件 

 源码       数据库文件

第四步:客户端接收服务器发来的文件
//处理文件数据 <2>
            NetworkComms.AppendGlobalIncomingPacketHandler<byte[]>("PartialFileData", IncomingPartialFileData);
            //处理文件信息 <3>
            NetworkComms.AppendGlobalIncomingPacketHandler<SendInfo>("PartialFileDataInfo", IncomingPartialFileDataInfo);
private void IncomingPartialFileData(PacketHeader header, Connection connection, byte[] data)
        {
            try
            {
                SendInfo info = null;
                ReceivedFile file = null;

                //Perform this in a thread safe way
                lock (syncLocker)
                {
                    //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
                        //根据顺序号,获取相关SendInfo记录
                        info = incomingDataInfoCache[connection.ConnectionInfo][sequenceNumber];
                        //从信息记录字典中删除相关记录
                        incomingDataInfoCache[connection.ConnectionInfo].Remove(sequenceNumber);
                        //Check to see if we have already initialised this file
                        //检查相关连接上的文件是否存在,如果不存在,则添加相关文件{ReceivedFile}
                        if (!receivedFiles.ContainsKey(info.FileID))
                        {
                            ReceivedFile receivedFile = new ReceivedFile(info.FileID, info.Filename, info.FilePath, connection.ConnectionInfo, info.TotalBytes);



                            receivedFile.FileTransCompleted += new Action<string>(this.receivedFile_FileTransCompleted);

                            receivedFiles.Add(info.FileID, receivedFile);
                          
                        }

                        file = receivedFiles[info.FileID];
                    }
                    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 clean-up
                    file = null;
                    data = null;
                  
                }
                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

                
            }
        }

IncomingPartialFileData
private void IncomingPartialFileDataInfo(PacketHeader header, Connection connection, SendInfo info)
        {
            try
            {
                byte[] data = null;
                ReceivedFile file = null;

                //Perform this in a thread safe way
                lock (syncLocker)
                {
                    //Extract the packet sequence number from the header
                    //The header can also user defined parameters
                    //从 SendInfo类中获取相应数据类的信息号 以便可以对应。
                    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 initialised this file
                        if (!receivedFiles.ContainsKey(info.FileID))
                        {
                            ReceivedFile receivedFile = new ReceivedFile(info.FileID, info.Filename, info.FilePath, connection.ConnectionInfo, info.TotalBytes);




                            receivedFile.FileTransCompleted += new Action<string>(this.receivedFile_FileTransCompleted);
                            receivedFiles.Add(info.FileID, receivedFile);

                        }

                        file = receivedFiles[info.FileID];
                    }
                    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 clean-up
                    file = null;
                    data = null;
                  
                }
                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)
            {
                 
               
            }
        }

 IncomingPartialFileDataInfo

MainActivity.cs中添加相应的字典变量

//接收文件字典

        Dictionary<string, ReceivedFile> receivedFiles = new 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>>();
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using System.IO;
using NetworkCommsDotNet;
using DPSBase;
using Mobile.Entity;

namespace Mobile.Client
{
    public class ReceivedFile
    {


        //////传输过程
        ////public event Action<string, long, long> FileTransProgress;
        //////传输完成
        public event Action<string> FileTransCompleted;
        //////传输中断
        ////public event Action<string, FileTransDisrupttedType> FileTransDisruptted;
         

        /// <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; }

        //文件ID  用于管理文件 和文件的发送 取消发送相关

        private string fileID;

        public string FileID
        {
            get { return fileID; }
            set { fileID = value; }
        }


        /// <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; }

            //This set is required for the application to work
            set { throw new Exception("An attempt to modify read-only value."); }
        }

        /// <summary>
        /// A formatted string of the SourceInfo
        /// 源信息
        /// </summary>
        public string SourceInfoStr
        {
            get { return "[" + SourceInfo.RemoteEndPoint.ToString() + "]"; }
        }

        /// <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 memory stream used to build the file
        /// 用来创建文件的数据流
        /// </summary>
        Stream data;

        /// <summary>
        ///Event subscribed to by GUI for updates
        /// </summary>
    
        //临时文件流存储的位置
        public string TempFilePath = "";
        //文件最后的保存路径
        public string SaveFilePath = "";

        /// <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 fileID, string filename, string filePath, ConnectionInfo sourceInfo, long sizeBytes)
        {
            string tempSizeBytes = sizeBytes.ToString();

            this.fileID = fileID;
            this.Filename = filename;
            this.SourceInfo = sourceInfo;
            this.SizeBytes = sizeBytes;

            //如果临时文件已经存在,则添加.data后缀

            this.TempFilePath = filePath + filename + ".data";
            while (File.Exists(this.TempFilePath))
            {

                this.TempFilePath = this.TempFilePath + ".data";
            }
            this.SaveFilePath = filePath + filename;

            //We create a file on disk so that we can receive large files
            //我们在硬盘上创建一个文件,使得我们可以接收大的文件
            data = new FileStream(TempFilePath, 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 internal 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)
            {
                if (!this.canceled && (this.data != null))
                {
                    try
                    {
                        data.Seek(dataStart, SeekOrigin.Begin);

                        data.Write(buffer, (int)bufferStart, (int)bufferLength);

                        ReceivedBytes += (int)(bufferLength - bufferStart);



                        ////EventsHelper.Fire<string, long, long>(this.FileTransProgress, FileID, SizeBytes, ReceivedBytes);

                        if (ReceivedBytes == SizeBytes)
                        {
                            data.Flush();
                            SaveFileToDisk(SaveFilePath);
                            data.Close();
                            EventsHelper.Fire<string>(this.FileTransCompleted, FileID);
                        }
                    }
                    catch (Exception exception)
                    {
                        //触发文件传输中断事件
                        //this.FileTransDisruptted(Filename, FileTransDisrupttedType.InnerError);

                        ////EventsHelper.Fire<string, FileTransDisrupttedType>(this.FileTransDisruptted, FileID, FileTransDisrupttedType.InnerError);

                    }
                }
            }

          
        }

        private volatile bool canceled;

        public void Cancel(FileTransFailReason disrupttedType, bool deleteTempFile)
        {
            try
            {
                this.canceled = true;
                this.data.Flush();
                this.data.Close();
                this.data = null;
                if (deleteTempFile)
                {
                    File.Delete(this.TempFilePath);
                }
            }
            catch (Exception)
            {
            }
            //通知 Receiver取消,并且触发文件传输中断事件
            ////EventsHelper.Fire<string, FileTransDisrupttedType>(this.FileTransDisruptted, FileID, FileTransDisrupttedType.InnerError);
        }

        /// <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(TempFilePath))
                throw new Exception("The transferred file should have been created within the local application directory. Where has it gone?");

            //File.Delete(saveLocation);

            //覆盖文件
            File.Copy(TempFilePath, saveLocation, true);


        }

        /// <summary>
        /// Closes and releases any resources maintained by this file
        /// </summary>
        public void Close()
        {
            try
            {
                data.Dispose();
            }
            catch (Exception) { }

            try
            {
                data.Close();
            }
            catch (Exception) { }
        }

       
    }
}

ReceivedFile

CodeBye 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权 , 转载请注明c#源码:安卓通过TCP通信与Windows服务器进行文件传输