• 传说中的WCF(9):流与文件传输


    在使用Socket/TCP来传输文件,弄起来不仅会有些复杂,而且较经典的"粘包"问题有时候会让人火冒七丈。如果你不喜欢用Socket来传文件,不妨试试WCF,WCF的流模式传输还是相当强大和相当实用的。

    因为开启流模式是基于绑定的,所以,它会影响到整个终结点的操作协定。如果你不记得或者说不喜欢背书,不想去记住哪些绑定支持流模式,可以通过以下方法:

    因为开启流模式,主要是设置一个叫TransferMode的属性,所以,你看看哪些Binding的派生类有这个属性就可以了。

    TransferMode其实是一个举枚,看看它的几个有效值:

    Buffered:缓冲模式,说白了就是在内存中缓冲,一次调用就把整个消息读/写完,也就是我们最常用的方式,就是普通的操作协定的调用方式;

    StreamedRequest:只是在请求的时候使用流,说简单一点就是在传入方法的参数使用流,如 int MyMethod(System.IO.Stream stream);

    StreamedResponse:就是操作协定方法返回一个流,如 Stream MyMethod(string file_name);

    一般而言,如果使用流作为传入参数,最好不要使用多个参数,如这样:

    bool TransferFile(Stream stream, string name);

    上面的方法就有了两个in参数了,最好别这样,为什么?有空的话,自己试试就知道了。那如果要传入更多的数据,怎么办?呵呵,还记得消息协定吗?

       

    好的,下面我们来弄一个上传MP3文件的实例。实例主要的工作是从客户端上传一个文件到服务器。

    老规矩,一般做这种应用程序,应该先做服务器端。

    [csharp] view plain copy print?

    using System;  

    using System.Collections.Generic;  

    using System.Linq;  

    using System.Text;  

    using System.Threading.Tasks;  

        

    using System.Runtime;  

    using System.Runtime.Serialization;  

    using System.ServiceModel;  

    using System.ServiceModel.Description;  

        

    using System.IO;  

        

    namespace WCFServerTemplate1  

    {  

        class Program  

        {  

            static void Main(string[] args)  

            {  

                // 服务器基址  

                Uri baseAddress = new Uri("http://localhost:1378/services");  

                // 声明服务器主机  

                using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))  

                {  

                    // 添加绑定和终结点  

                    BasicHttpBinding binding = new BasicHttpBinding();  

                    // 启用流模式  

                    binding.TransferMode = TransferMode.StreamedRequest;  

                    binding.MaxBufferSize = 1024;  

                    // 接收消息的最大范围为500M  

                    binding.MaxReceivedMessageSize = 500 * 1024 * 1024;  

                    host.AddServiceEndpoint(typeof(IService), binding, "/test");  

                    // 添加服务描述  

                    host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });  

                    try  

                    {  

                        // 打开服务  

                        host.Open();  

                        Console.WriteLine("服务已启动。");  

                    }  

                    catch (Exception ex)  

                    {  

                        Console.WriteLine(ex.Message);  

                    }  

                    Console.ReadKey();  

                }  

            }  

        }  

        

        [ServiceContract(Namespace = "MyNamespace")]  

        public interface IService  

        {  

            [OperationContract]  

            bool UpLoadFile(System.IO.Stream streamInput);  

        }  

        

        public class MyService : IService  

        {  

        

            public bool UpLoadFile(System.IO.Stream streamInput)  

            {  

                bool isSuccessed = false;  

                try  

                {  

                    using (FileStream outputStream = new FileStream("rec.mp3", FileMode.OpenOrCreate, FileAccess.Write))  

                    {  

                        // 我们不用对两个流对象进行读写,只要复制流就OK  

                        streamInput.CopyTo(outputStream);  

                        outputStream.Flush();  

                        isSuccessed = true;  

                        Console.WriteLine("在{0}接收到客户端发送的流,已保存到rec.map3。",DateTime.Now.ToLongTimeString());  

                    }  

                }  

                catch  

                {  

                    isSuccessed = false;  

                }  

                return isSuccessed;  

            }  

        }  

        

    }  

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.Threading.Tasks;

     

    using System.Runtime;

    using System.Runtime.Serialization;

    using System.ServiceModel;

    using System.ServiceModel.Description;

     

    using System.IO;

     

    namespace WCFServerTemplate1

    {

    class Program

    {

    static void Main(string[] args)

    {

    // 服务器基址

    Uri baseAddress = new Uri("http://localhost:1378/services");

    // 声明服务器主机

    using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))

    {

    // 添加绑定和终结点

    BasicHttpBinding binding = new BasicHttpBinding();

    // 启用流模式

    binding.TransferMode = TransferMode.StreamedRequest;

    binding.MaxBufferSize = 1024;

    // 接收消息的最大范围为500M

    binding.MaxReceivedMessageSize = 500 * 1024 * 1024;

    host.AddServiceEndpoint(typeof(IService), binding, "/test");

    // 添加服务描述

    host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });

    try

    {

    // 打开服务

    host.Open();

    Console.WriteLine("服务已启动。");

    }

    catch (Exception ex)

    {

    Console.WriteLine(ex.Message);

    }

    Console.ReadKey();

    }

    }

    }

     

    [ServiceContract(Namespace = "MyNamespace")]

    public interface IService

    {

    [OperationContract]

    bool UpLoadFile(System.IO.Stream streamInput);

    }

     

    public class MyService : IService

    {

     

    public bool UpLoadFile(System.IO.Stream streamInput)

    {

    bool isSuccessed = false;

    try

    {

    using (FileStream outputStream = new FileStream("rec.mp3", FileMode.OpenOrCreate, FileAccess.Write))

    {

    // 我们不用对两个流对象进行读写,只要复制流就OK

    streamInput.CopyTo(outputStream);

    outputStream.Flush();

    isSuccessed = true;

    Console.WriteLine("在{0}接收到客户端发送的流,已保存到rec.map3。",DateTime.Now.ToLongTimeString());

    }

    }

    catch

    {

    isSuccessed = false;

    }

    return isSuccessed;

    }

    }

     

    }


    从例子我们看到,操作方法是这样定义的:

    [csharp] view plain copy print?

    bool UpLoadFile(System.IO.Stream streamInput)  

    bool UpLoadFile(System.IO.Stream streamInput)

    因为它的返回值是Bool类型,不是流,而只是传入的参数是流,因为在配置绑定时,应用使用StreamedRequest。

    [csharp] view plain copy print?

    BasicHttpBinding binding = new BasicHttpBinding();  

    // 启用流模式  

    binding.TransferMode = TransferMode.StreamedRequest;  

    binding.MaxBufferSize = 1024;  

    // 接收消息的最大范围为500M  

    binding.MaxReceivedMessageSize = 500 * 1024 * 1024;  

    BasicHttpBinding binding = new BasicHttpBinding();

    // 启用流模式

    binding.TransferMode = TransferMode.StreamedRequest;

    binding.MaxBufferSize = 1024;

    // 接收消息的最大范围为500M

    binding.MaxReceivedMessageSize = 500 * 1024 * 1024;


    现在,我们做客户端,因为要选择文件上传,所以使用Windows Forms项目类型。

    在窗口上拖两个按钮,一个用来选择文件,另一个用于启动文件上传,另外两个Label就是用来显示一些文本。

    而窗体的实现代码部分如下:

    [csharp] view plain copy print?

    using System;  

    using System.Collections.Generic;  

    using System.ComponentModel;  

    using System.Data;  

    using System.Drawing;  

    using System.Linq;  

    using System.Text;  

    using System.Threading.Tasks;  

    using System.Windows.Forms;  

    using System.IO;  

        

    namespace wformClient  

    {  

        public partial class Form1 : Form  

        {  

            public Form1()  

            {  

                InitializeComponent();  

            }  

        

            private void btnSelectFile_Click(object sender, EventArgs e)  

            {  

                OpenFileDialog dlg = new OpenFileDialog();  

                dlg.Filter = "MP3音频文件|*.mp3";  

                if (DialogResult.OK.Equals(dlg.ShowDialog()))  

                {  

                    this.lbSelectedFilename.Text = dlg.FileName;  

                    this.lbMessage.Text = "准备就绪。";  

                }  

            }  

        

            private async void btnTransfer_Click(object sender, EventArgs e)  

            {  

                if (!File.Exists(this.lbSelectedFilename.Text))  

                {  

                    return;  

                }  

                FileStream fs = new FileStream(this.lbSelectedFilename.Text, FileMode.Open, FileAccess.Read);  

                WS.ServiceClient cl = new WS.ServiceClient();  

                this.btnTransfer.Enabled = false;  

                bool res = await cl.UpLoadFileAsync(fs);  

                this.btnTransfer.Enabled = true;  

                if (res == true)  

                    this.lbMessage.Text = "上传完成。";  

            }  

        }  

    }  

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Linq;

    using System.Text;

    using System.Threading.Tasks;

    using System.Windows.Forms;

    using System.IO;

     

    namespace wformClient

    {

    public partial class Form1 : Form

    {

    public Form1()

    {

    InitializeComponent();

    }

     

    private void btnSelectFile_Click(object sender, EventArgs e)

    {

    OpenFileDialog dlg = new OpenFileDialog();

    dlg.Filter = "MP3音频文件|*.mp3";

    if (DialogResult.OK.Equals(dlg.ShowDialog()))

    {

    this.lbSelectedFilename.Text = dlg.FileName;

    this.lbMessage.Text = "准备就绪。";

    }

    }

     

    private async void btnTransfer_Click(object sender, EventArgs e)

    {

    if (!File.Exists(this.lbSelectedFilename.Text))

    {

    return;

    }

    FileStream fs = new FileStream(this.lbSelectedFilename.Text, FileMode.Open, FileAccess.Read);

    WS.ServiceClient cl = new WS.ServiceClient();

    this.btnTransfer.Enabled = false;

    bool res = await cl.UpLoadFileAsync(fs);

    this.btnTransfer.Enabled = true;

                if (res == true)

                    this.lbMessage.Text = "上传完成。";

    }

    }

    }


    记住,千万别忘了引用服务!!!!!!!!!!!!!!!!!!!

    现在可以运行了。

       

       

       

    不知道大家注意到没有?在服务器端代码中,我们设置了绑定的MaxReceivedMessageSize为500M,这一般是在消息模式下,为了安全(防止恶意攻击)而设置的限制,那么,如果使用了流模式,这个值还用不用设置。想验证也很简单,把这行代码注释掉,再运行试试。

       

    运行程序,结发现,是不成功的,你看看我下面的截图,只传了40多K,还远着呢。

    因此,MaxReceivedMessageSize还是要设置的,不然,它的默认值太小了,传不了大文件。

       

    现在又希望上面的例子多一个功能,文件上传后,依然按客户端原文件命名,而不是rec.mp3,这就意味着操作方法要传两个参数,前面我提了一下,不要忘了消息协定,而这个我们可以通过消息协定来完成。

    因此,服务器端代码要改一改了,首先,定义一个消息协定。

    [csharp] view plain copy print?

    [MessageContract]  

    public class TransferFileMessage  

    {  

        [MessageHeader]  

        public string File_Name; //文件名  

        [MessageBodyMember]  

        public Stream File_Stream; //文件流  

    }  

    [MessageContract]

    public class TransferFileMessage

    {

    [MessageHeader]

    public string File_Name; //文件名

    [MessageBodyMember]

    public Stream File_Stream; //文件流

    }


    接着操作方法也要改动。

    [csharp] view plain copy print?

    [ServiceContract(Namespace = "MyNamespace")]  

    public interface IService  

    {  

        [OperationContract]  

        bool UpLoadFile(TransferFileMessage tMsg);  

    }  

        

    public class MyService : IService  

    {  

        

        public bool UpLoadFile(TransferFileMessage tMsg)  

        {  

            bool isSuccessed = false;  

            if (tMsg == null || tMsg.File_Stream == null)  

            {  

                return false;  

            }  

            try  

            {  

                using (FileStream outputStream = new FileStream(tMsg.File_Name, FileMode.OpenOrCreate, FileAccess.Write))  

                {  

                    // 我们不用对两个流对象进行读写,只要复制流就OK  

                    tMsg.File_Stream.CopyTo(outputStream);  

                    outputStream.Flush();  

                    isSuccessed = true;  

                    Console.WriteLine("在{0}接收到客户端发送的流,已保存到{1}。",DateTime.Now.ToLongTimeString(), tMsg.File_Name);  

                }  

            }  

            catch  

            {  

                isSuccessed = false;  

            }  

            return isSuccessed;  

        }  

    }  

    [ServiceContract(Namespace = "MyNamespace")]

    public interface IService

    {

    [OperationContract]

    bool UpLoadFile(TransferFileMessage tMsg);

    }

     

    public class MyService : IService

    {

     

    public bool UpLoadFile(TransferFileMessage tMsg)

    {

    bool isSuccessed = false;

    if (tMsg == null || tMsg.File_Stream == null)

    {

    return false;

    }

    try

    {

    using (FileStream outputStream = new FileStream(tMsg.File_Name, FileMode.OpenOrCreate, FileAccess.Write))

    {

    // 我们不用对两个流对象进行读写,只要复制流就OK

    tMsg.File_Stream.CopyTo(outputStream);

    outputStream.Flush();

    isSuccessed = true;

    Console.WriteLine("在{0}接收到客户端发送的流,已保存到{1}。",DateTime.Now.ToLongTimeString(), tMsg.File_Name);

    }

    }

    catch

    {

    isSuccessed = false;

    }

    return isSuccessed;

    }

    }


    在测试服务器端运行成功后,要记得更新客户端的引用。

    可是,遗憾的是,服务没有正常启动。为什么呢?想一想,如果光看错误消息,你可能不太明白。我给你20秒的时间想一想,为什么上面的代码不能正常运行。

    ………………………………………………………………………………………………………………………………………………

    好了,其实,问题就出在操作协定的定义上:

    [csharp] view plain copy print?

    [OperationContract]  

    bool UpLoadFile(TransferFileMessage tMsg);  

    [OperationContract]

    bool UpLoadFile(TransferFileMessage tMsg);


    我们前面说过,什么叫双工,有来有往,是吧?对啊,上面的方法是有传入参数,也有返回值,有来有去啊,是双工啊,为啥不行了呢?

    哈哈,问题就在于我们使用了消息协定,在这种前提下,我们的方法就不能随便定义了,使用消息协定的方法,如果:

    a、消息协定作为传入参数,则只能有一个参数,以下定义是错误的:

    void Reconcile(BankingTransaction bt1, BankingTransaction bt2);

    b、除非你返回值为void,如不是,那你必须返回一个消息协定,bool UpLoadFile(TransferFileMessage tMsg)我们这个定义明显不符合要求。

    那如何解决呢?我们要再定义一个用于返回的消息协定。

    [csharp] view plain copy print?

    [MessageContract]  

    public class ResultMessage  

    {  

        [MessageHeader]  

        public string ErrorMessage;  

        [MessageBodyMember]  

        public bool IsSuccessed;  

    }  

    [MessageContract]

    public class ResultMessage

    {

    [MessageHeader]

    public string ErrorMessage;

    [MessageBodyMember]

    public bool IsSuccessed;

    }


    然后把上面的操作方法也改一下。

    [csharp] view plain copy print?

    public ResultMessage UpLoadFile(TransferFileMessage tMsg)  

    {  

        ResultMessage rMsg = new ResultMessage();  

        if (tMsg == null || tMsg.File_Stream == null)  

        {  

            rMsg.ErrorMessage = "传入的参数无效。";  

            rMsg.IsSuccessed = false;  

            return rMsg;  

        }  

        try  

        {  

            using (FileStream outputStream = new FileStream(tMsg.File_Name, FileMode.OpenOrCreate, FileAccess.Write))  

            {  

                // 我们不用对两个流对象进行读写,只要复制流就OK  

                tMsg.File_Stream.CopyTo(outputStream);  

                outputStream.Flush();  

                rMsg.IsSuccessed = true;  

                Console.WriteLine("在{0}接收到客户端发送的流,已保存到{1}。", DateTime.Now.ToLongTimeString(), tMsg.File_Name);  

            }  

        }  

        catch(Exception ex)  

        {  

            rMsg.IsSuccessed = false;  

            rMsg.ErrorMessage = ex.Message;  

        }  

        return rMsg;  

    }  

    public ResultMessage UpLoadFile(TransferFileMessage tMsg)

    {

    ResultMessage rMsg = new ResultMessage();

    if (tMsg == null || tMsg.File_Stream == null)

    {

    rMsg.ErrorMessage = "传入的参数无效。";

    rMsg.IsSuccessed = false;

    return rMsg;

    }

    try

    {

    using (FileStream outputStream = new FileStream(tMsg.File_Name, FileMode.OpenOrCreate, FileAccess.Write))

    {

    // 我们不用对两个流对象进行读写,只要复制流就OK

    tMsg.File_Stream.CopyTo(outputStream);

    outputStream.Flush();

    rMsg.IsSuccessed = true;

    Console.WriteLine("在{0}接收到客户端发送的流,已保存到{1}。", DateTime.Now.ToLongTimeString(), tMsg.File_Name);

    }

    }

    catch(Exception ex)

    {

    rMsg.IsSuccessed = false;

    rMsg.ErrorMessage = ex.Message;

    }

    return rMsg;

    }


    现在你试试能不能正常运行?好了,客户端记得更新引用,而且,客户端的代码也要修改。

    [csharp] view plain copy print?

    private async void btnTransfer_Click(object sender, EventArgs e)  

    {  

        if (!File.Exists(this.lbSelectedFilename.Text))  

        {  

            return;  

        }  

        FileStream fs = new FileStream(this.lbSelectedFilename.Text, FileMode.Open, FileAccess.Read);  

        WS.ServiceClient cl = new WS.ServiceClient();  

        this.btnTransfer.Enabled = false;  

        var response = await cl.UpLoadFileAsync(Path.GetFileName(this.lbSelectedFilename.Text), fs);  

        this.btnTransfer.Enabled = true;  

        if (response.IsSuccessed == true)  

            this.lbMessage.Text = "上传完成。";  

        else  

            this.lbMessage.Text="错误信息:" + response.ErrorMessage;  

    }  

    private async void btnTransfer_Click(object sender, EventArgs e)

    {

    if (!File.Exists(this.lbSelectedFilename.Text))

    {

    return;

    }

    FileStream fs = new FileStream(this.lbSelectedFilename.Text, FileMode.Open, FileAccess.Read);

    WS.ServiceClient cl = new WS.ServiceClient();

    this.btnTransfer.Enabled = false;

    var response = await cl.UpLoadFileAsync(Path.GetFileName(this.lbSelectedFilename.Text), fs);

    this.btnTransfer.Enabled = true;

    if (response.IsSuccessed == true)

    this.lbMessage.Text = "上传完成。";

    else

    this.lbMessage.Text="错误信息:" + response.ErrorMessage;

    }


    现在再来测测吧。

    在使用Socket/TCP来传输文件,弄起来不仅会有些复杂,而且较经典的"粘包"问题有时候会让人火冒七丈。如果你不喜欢用Socket来传文件,不妨试试WCFWCF的流模式传输还是相当强大和相当实用的。

    因为开启流模式是基于绑定的,所以,它会影响到整个终结点的操作协定。如果你不记得或者说不喜欢背书,不想去记住哪些绑定支持流模式,可以通过以下方法:

    因为开启流模式,主要是设置一个叫TransferMode的属性,所以,你看看哪些Binding的派生类有这个属性就可以了。

    TransferMode其实是一个举枚,看看它的几个有效值:

    • Buffered:缓冲模式,说白了就是在内存中缓冲,一次调用就把整个消息读/写完,也就是我们最常用的方式,就是普通的操作协定的调用方式;
    • StreamedRequest:只是在请求的时候使用流,说简单一点就是在传入方法的参数使用流,如 int MyMethod(System.IO.Stream stream);
    • StreamedResponse:就是操作协定方法返回一个流,如 Stream MyMethod(string file_name);

    一般而言,如果使用流作为传入参数,最好不要使用多个参数,如这样:

    bool TransferFile(Stream stream, string name);

    上面的方法就有了两个in参数了,最好别这样,为什么?有空的话,自己试试就知道了。那如果要传入更多的数据,怎么办?呵呵,还记得消息协定吗?

       

    好的,下面我们来弄一个上传MP3文件的实例。实例主要的工作是从客户端上传一个文件到服务器。

    老规矩,一般做这种应用程序,应该先做服务器端。

    [csharp] view plain copy print?

    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.Linq;  
    4. using System.Text;  
    5. using System.Threading.Tasks;  
    6.     
    7. using System.Runtime;  
    8. using System.Runtime.Serialization;  
    9. using System.ServiceModel;  
    10. using System.ServiceModel.Description;  
    11.     
    12. using System.IO;  
    13.     
    14. namespace WCFServerTemplate1  
    15. {  
    16.     class Program  
    17.     {  
    18.         static void Main(string[] args)  
    19.         {  
    20.             // 服务器基址  
    21.             Uri baseAddress = new Uri("http://localhost:1378/services");  
    22.             // 声明服务器主机  
    23.             using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))  
    24.             {  
    25.                 // 添加绑定和终结点  
    26.                 BasicHttpBinding binding = new BasicHttpBinding();  
    27.                 // 启用流模式  
    28.                 binding.TransferMode = TransferMode.StreamedRequest;  
    29.                 binding.MaxBufferSize = 1024;  
    30.                 // 接收消息的最大范围为500M  
    31.                 binding.MaxReceivedMessageSize = 500 * 1024 * 1024;  
    32.                 host.AddServiceEndpoint(typeof(IService), binding, "/test");  
    33.                 // 添加服务描述  
    34.                 host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });  
    35.                 try  
    36.                 {  
    37.                     // 打开服务  
    38.                     host.Open();  
    39.                     Console.WriteLine("服务已启动。");  
    40.                 }  
    41.                 catch (Exception ex)  
    42.                 {  
    43.                     Console.WriteLine(ex.Message);  
    44.                 }  
    45.                 Console.ReadKey();  
    46.             }  
    47.         }  
    48.     }  
    49.     
    50.     [ServiceContract(Namespace = "MyNamespace")]  
    51.     public interface IService  
    52.     {  
    53.         [OperationContract]  
    54.         bool UpLoadFile(System.IO.Stream streamInput);  
    55.     }  
    56.     
    57.     public class MyService : IService  
    58.     {  
    59.     
    60.         public bool UpLoadFile(System.IO.Stream streamInput)  
    61.         {  
    62.             bool isSuccessed = false;  
    63.             try  
    64.             {  
    65.                 using (FileStream outputStream = new FileStream("rec.mp3", FileMode.OpenOrCreate, FileAccess.Write))  
    66.                 {  
    67.                     // 我们不用对两个流对象进行读写,只要复制流就OK  
    68.                     streamInput.CopyTo(outputStream);  
    69.                     outputStream.Flush();  
    70.                     isSuccessed = true;  
    71.                     Console.WriteLine("{0}接收到客户端发送的流,已保存到rec.map3",DateTime.Now.ToLongTimeString());  
    72.                 }  
    73.             }  
    74.             catch  
    75.             {  
    76.                 isSuccessed = false;  
    77.             }  
    78.             return isSuccessed;  
    79.         }  
    80.     }  
    81.     
    82. }  

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Text;

    using System.Threading.Tasks;

     

    using System.Runtime;

    using System.Runtime.Serialization;

    using System.ServiceModel;

    using System.ServiceModel.Description;

     

    using System.IO;

     

    namespace WCFServerTemplate1

    {

    class Program

    {

    static void Main(string[] args)

    {

    // 服务器基址

    Uri baseAddress = new Uri("http://localhost:1378/services");

    // 声明服务器主机

    using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress))

    {

    // 添加绑定和终结点

    BasicHttpBinding binding = new BasicHttpBinding();

    // 启用流模式

    binding.TransferMode = TransferMode.StreamedRequest;

    binding.MaxBufferSize = 1024;

    // 接收消息的最大范围为500M

    binding.MaxReceivedMessageSize = 500 * 1024 * 1024;

    host.AddServiceEndpoint(typeof(IService), binding, "/test");

    // 添加服务描述

    host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });

    try

    {

    // 打开服务

    host.Open();

    Console.WriteLine("服务已启动。");

    }

    catch (Exception ex)

    {

    Console.WriteLine(ex.Message);

    }

    Console.ReadKey();

    }

    }

    }

     

    [ServiceContract(Namespace = "MyNamespace")]

    public interface IService

    {

    [OperationContract]

    bool UpLoadFile(System.IO.Stream streamInput);

    }

     

    public class MyService : IService

    {

     

    public bool UpLoadFile(System.IO.Stream streamInput)

    {

    bool isSuccessed = false;

    try

    {

    using (FileStream outputStream = new FileStream("rec.mp3", FileMode.OpenOrCreate, FileAccess.Write))

    {

    // 我们不用对两个流对象进行读写,只要复制流就OK

    streamInput.CopyTo(outputStream);

    outputStream.Flush();

    isSuccessed = true;

    Console.WriteLine("在{0}接收到客户端发送的流,已保存到rec.map3。",DateTime.Now.ToLongTimeString());

    }

    }

    catch

    {

    isSuccessed = false;

    }

    return isSuccessed;

    }

    }

     

    }


    从例子我们看到,操作方法是这样定义的:

    [csharp] view plain copy print?

    1. bool UpLoadFile(System.IO.Stream streamInput)  

    bool UpLoadFile(System.IO.Stream streamInput)

    因为它的返回值是Bool类型,不是流,而只是传入的参数是流,因为在配置绑定时,应用使用StreamedRequest

    [csharp] view plain copy print?

    1. BasicHttpBinding binding = new BasicHttpBinding();  
    2. // 启用流模式  
    3. binding.TransferMode = TransferMode.StreamedRequest;  
    4. binding.MaxBufferSize = 1024;  
    5. // 接收消息的最大范围为500M  
    6. binding.MaxReceivedMessageSize = 500 * 1024 * 1024;  

    BasicHttpBinding binding = new BasicHttpBinding();

    // 启用流模式

    binding.TransferMode = TransferMode.StreamedRequest;

    binding.MaxBufferSize = 1024;

    // 接收消息的最大范围为500M

    binding.MaxReceivedMessageSize = 500 * 1024 * 1024;


    现在,我们做客户端,因为要选择文件上传,所以使用Windows Forms项目类型。

    在窗口上拖两个按钮,一个用来选择文件,另一个用于启动文件上传,另外两个Label就是用来显示一些文本。

    而窗体的实现代码部分如下:

    [csharp] view plain copy print?

    1. using System;  
    2. using System.Collections.Generic;  
    3. using System.ComponentModel;  
    4. using System.Data;  
    5. using System.Drawing;  
    6. using System.Linq;  
    7. using System.Text;  
    8. using System.Threading.Tasks;  
    9. using System.Windows.Forms;  
    10. using System.IO;  
    11.     
    12. namespace wformClient  
    13. {  
    14.     public partial class Form1 : Form  
    15.     {  
    16.         public Form1()  
    17.         {  
    18.             InitializeComponent();  
    19.         }  
    20.     
    21.         private void btnSelectFile_Click(object sender, EventArgs e)  
    22.         {  
    23.             OpenFileDialog dlg = new OpenFileDialog();  
    24.             dlg.Filter = "MP3音频文件|*.mp3";  
    25.             if (DialogResult.OK.Equals(dlg.ShowDialog()))  
    26.             {  
    27.                 this.lbSelectedFilename.Text = dlg.FileName;  
    28.                 this.lbMessage.Text = "准备就绪。";  
    29.             }  
    30.         }  
    31.     
    32.         private async void btnTransfer_Click(object sender, EventArgs e)  
    33.         {  
    34.             if (!File.Exists(this.lbSelectedFilename.Text))  
    35.             {  
    36.                 return;  
    37.             }  
    38.             FileStream fs = new FileStream(this.lbSelectedFilename.Text, FileMode.Open, FileAccess.Read);  
    39.             WS.ServiceClient cl = new WS.ServiceClient();  
    40.             this.btnTransfer.Enabled = false;  
    41.             bool res = await cl.UpLoadFileAsync(fs);  
    42.             this.btnTransfer.Enabled = true;  
    43.             if (res == true)  
    44.                 this.lbMessage.Text = "上传完成。";  
    45.         }  
    46.     }  
    47. }  

    using System;

    using System.Collections.Generic;

    using System.ComponentModel;

    using System.Data;

    using System.Drawing;

    using System.Linq;

    using System.Text;

    using System.Threading.Tasks;

    using System.Windows.Forms;

    using System.IO;

     

    namespace wformClient

    {

    public partial class Form1 : Form

    {

    public Form1()

    {

    InitializeComponent();

    }

     

    private void btnSelectFile_Click(object sender, EventArgs e)

    {

    OpenFileDialog dlg = new OpenFileDialog();

    dlg.Filter = "MP3音频文件|*.mp3";

    if (DialogResult.OK.Equals(dlg.ShowDialog()))

    {

    this.lbSelectedFilename.Text = dlg.FileName;

    this.lbMessage.Text = "准备就绪。";

    }

    }

     

    private async void btnTransfer_Click(object sender, EventArgs e)

    {

    if (!File.Exists(this.lbSelectedFilename.Text))

    {

    return;

    }

    FileStream fs = new FileStream(this.lbSelectedFilename.Text, FileMode.Open, FileAccess.Read);

    WS.ServiceClient cl = new WS.ServiceClient();

    this.btnTransfer.Enabled = false;

    bool res = await cl.UpLoadFileAsync(fs);

    this.btnTransfer.Enabled = true;

                if (res == true)

                    this.lbMessage.Text = "上传完成。";

    }

    }

    }


    记住,千万别忘了引用服务!!!!!!!!!!!!!!!!!!!

    现在可以运行了。

       

       

       

    不知道大家注意到没有?在服务器端代码中,我们设置了绑定的MaxReceivedMessageSize500M,这一般是在消息模式下,为了安全(防止恶意攻击)而设置的限制,那么,如果使用了流模式,这个值还用不用设置。想验证也很简单,把这行代码注释掉,再运行试试。

       

    运行程序,结发现,是不成功的,你看看我下面的截图,只传了40K,还远着呢。

    因此,MaxReceivedMessageSize还是要设置的,不然,它的默认值太小了,传不了大文件。

       

    现在又希望上面的例子多一个功能,文件上传后,依然按客户端原文件命名,而不是rec.mp3,这就意味着操作方法要传两个参数,前面我提了一下,不要忘了消息协定,而这个我们可以通过消息协定来完成。

    因此,服务器端代码要改一改了,首先,定义一个消息协定。

    [csharp] view plain copy print?

    1. [MessageContract]  
    2. public class TransferFileMessage  
    3. {  
    4.     [MessageHeader]  
    5.     public string File_Name; //文件名  
    6.     [MessageBodyMember]  
    7.     public Stream File_Stream; //文件流  
    8. }  

    [MessageContract]

    public class TransferFileMessage

    {

    [MessageHeader]

    public string File_Name; //文件名

    [MessageBodyMember]

    public Stream File_Stream; //文件流

    }


    接着操作方法也要改动。

    [csharp] view plain copy print?

    1. [ServiceContract(Namespace = "MyNamespace")]  
    2. public interface IService  
    3. {  
    4.     [OperationContract]  
    5.     bool UpLoadFile(TransferFileMessage tMsg);  
    6. }  
    7.     
    8. public class MyService : IService  
    9. {  
    10.     
    11.     public bool UpLoadFile(TransferFileMessage tMsg)  
    12.     {  
    13.         bool isSuccessed = false;  
    14.         if (tMsg == null || tMsg.File_Stream == null)  
    15.         {  
    16.             return false;  
    17.         }  
    18.         try  
    19.         {  
    20.             using (FileStream outputStream = new FileStream(tMsg.File_Name, FileMode.OpenOrCreate, FileAccess.Write))  
    21.             {  
    22.                 // 我们不用对两个流对象进行读写,只要复制流就OK  
    23.                 tMsg.File_Stream.CopyTo(outputStream);  
    24.                 outputStream.Flush();  
    25.                 isSuccessed = true;  
    26.                 Console.WriteLine("{0}接收到客户端发送的流,已保存到{1}",DateTime.Now.ToLongTimeString(), tMsg.File_Name);  
    27.             }  
    28.         }  
    29.         catch  
    30.         {  
    31.             isSuccessed = false;  
    32.         }  
    33.         return isSuccessed;  
    34.     }  
    35. }  

    [ServiceContract(Namespace = "MyNamespace")]

    public interface IService

    {

    [OperationContract]

    bool UpLoadFile(TransferFileMessage tMsg);

    }

     

    public class MyService : IService

    {

     

    public bool UpLoadFile(TransferFileMessage tMsg)

    {

    bool isSuccessed = false;

    if (tMsg == null || tMsg.File_Stream == null)

    {

    return false;

    }

    try

    {

    using (FileStream outputStream = new FileStream(tMsg.File_Name, FileMode.OpenOrCreate, FileAccess.Write))

    {

    // 我们不用对两个流对象进行读写,只要复制流就OK

    tMsg.File_Stream.CopyTo(outputStream);

    outputStream.Flush();

    isSuccessed = true;

    Console.WriteLine("在{0}接收到客户端发送的流,已保存到{1}。",DateTime.Now.ToLongTimeString(), tMsg.File_Name);

    }

    }

    catch

    {

    isSuccessed = false;

    }

    return isSuccessed;

    }

    }


    在测试服务器端运行成功后,要记得更新客户端的引用。

    可是,遗憾的是,服务没有正常启动。为什么呢?想一想,如果光看错误消息,你可能不太明白。我给你20秒的时间想一想,为什么上面的代码不能正常运行。

    ………………………………………………………………………………………………………………………………………………

    好了,其实,问题就出在操作协定的定义上:

    [csharp] view plain copy print?

    1. [OperationContract]  
    2. bool UpLoadFile(TransferFileMessage tMsg);  

    [OperationContract]

    bool UpLoadFile(TransferFileMessage tMsg);


    我们前面说过,什么叫双工,有来有往,是吧?对啊,上面的方法是有传入参数,也有返回值,有来有去啊,是双工啊,为啥不行了呢?

    哈哈,问题就在于我们使用了消息协定,在这种前提下,我们的方法就不能随便定义了,使用消息协定的方法,如果:

    a、消息协定作为传入参数,则只能有一个参数,以下定义是错误的:

    void Reconcile(BankingTransaction bt1, BankingTransaction bt2);

    b、除非你返回值为void,如不是,那你必须返回一个消息协定,bool UpLoadFile(TransferFileMessage tMsg)我们这个定义明显不符合要求。

    那如何解决呢?我们要再定义一个用于返回的消息协定。

    [csharp] view plain copy print?

    1. [MessageContract]  
    2. public class ResultMessage  
    3. {  
    4.     [MessageHeader]  
    5.     public string ErrorMessage;  
    6.     [MessageBodyMember]  
    7.     public bool IsSuccessed;  
    8. }  

    [MessageContract]

    public class ResultMessage

    {

    [MessageHeader]

    public string ErrorMessage;

    [MessageBodyMember]

    public bool IsSuccessed;

    }


    然后把上面的操作方法也改一下。

    [csharp] view plain copy print?

    1. public ResultMessage UpLoadFile(TransferFileMessage tMsg)  
    2. {  
    3.     ResultMessage rMsg = new ResultMessage();  
    4.     if (tMsg == null || tMsg.File_Stream == null)  
    5.     {  
    6.         rMsg.ErrorMessage = "传入的参数无效。";  
    7.         rMsg.IsSuccessed = false;  
    8.         return rMsg;  
    9.     }  
    10.     try  
    11.     {  
    12.         using (FileStream outputStream = new FileStream(tMsg.File_Name, FileMode.OpenOrCreate, FileAccess.Write))  
    13.         {  
    14.             // 我们不用对两个流对象进行读写,只要复制流就OK  
    15.             tMsg.File_Stream.CopyTo(outputStream);  
    16.             outputStream.Flush();  
    17.             rMsg.IsSuccessed = true;  
    18.             Console.WriteLine("{0}接收到客户端发送的流,已保存到{1}", DateTime.Now.ToLongTimeString(), tMsg.File_Name);  
    19.         }  
    20.     }  
    21.     catch(Exception ex)  
    22.     {  
    23.         rMsg.IsSuccessed = false;  
    24.         rMsg.ErrorMessage = ex.Message;  
    25.     }  
    26.     return rMsg;  
    27. }  

    public ResultMessage UpLoadFile(TransferFileMessage tMsg)

    {

    ResultMessage rMsg = new ResultMessage();

    if (tMsg == null || tMsg.File_Stream == null)

    {

    rMsg.ErrorMessage = "传入的参数无效。";

    rMsg.IsSuccessed = false;

    return rMsg;

    }

    try

    {

    using (FileStream outputStream = new FileStream(tMsg.File_Name, FileMode.OpenOrCreate, FileAccess.Write))

    {

    // 我们不用对两个流对象进行读写,只要复制流就OK

    tMsg.File_Stream.CopyTo(outputStream);

    outputStream.Flush();

    rMsg.IsSuccessed = true;

    Console.WriteLine("在{0}接收到客户端发送的流,已保存到{1}。", DateTime.Now.ToLongTimeString(), tMsg.File_Name);

    }

    }

    catch(Exception ex)

    {

    rMsg.IsSuccessed = false;

    rMsg.ErrorMessage = ex.Message;

    }

    return rMsg;

    }


    现在你试试能不能正常运行?好了,客户端记得更新引用,而且,客户端的代码也要修改。

    [csharp] view plain copy print?

    1. private async void btnTransfer_Click(object sender, EventArgs e)  
    2. {  
    3.     if (!File.Exists(this.lbSelectedFilename.Text))  
    4.     {  
    5.         return;  
    6.     }  
    7.     FileStream fs = new FileStream(this.lbSelectedFilename.Text, FileMode.Open, FileAccess.Read);  
    8.     WS.ServiceClient cl = new WS.ServiceClient();  
    9.     this.btnTransfer.Enabled = false;  
    10.     var response = await cl.UpLoadFileAsync(Path.GetFileName(this.lbSelectedFilename.Text), fs);  
    11.     this.btnTransfer.Enabled = true;  
    12.     if (response.IsSuccessed == true)  
    13.         this.lbMessage.Text = "上传完成。";  
    14.     else  
    15.         this.lbMessage.Text="错误信息:" + response.ErrorMessage;  
    16. }  

    private async void btnTransfer_Click(object sender, EventArgs e)

    {

    if (!File.Exists(this.lbSelectedFilename.Text))

    {

    return;

    }

    FileStream fs = new FileStream(this.lbSelectedFilename.Text, FileMode.Open, FileAccess.Read);

    WS.ServiceClient cl = new WS.ServiceClient();

    this.btnTransfer.Enabled = false;

    var response = await cl.UpLoadFileAsync(Path.GetFileName(this.lbSelectedFilename.Text), fs);

    this.btnTransfer.Enabled = true;

    if (response.IsSuccessed == true)

    this.lbMessage.Text = "上传完成。";

    else

    this.lbMessage.Text="错误信息:" + response.ErrorMessage;

    }


    现在再来测测吧。

       

    再看看服务器端。

       

       

    哈哈,现在就完美解决了。

       

    再看看服务器端。

       

       

    哈哈,现在就完美解决了。

  • 相关阅读:
    sequence.c
     Link 
    转:MFC中屏蔽ESC和回车关闭对话框
    转:CWebBrowser2去除边框、滚动条、右键菜单
    VC:res协议——从模块中获取资源
    20131213
    20131212
    20131211
    20131205
    20131128
  • 原文地址:https://www.cnblogs.com/qq260250932/p/5347548.html
Copyright © 2020-2023  润新知