码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2016-04-02 14:49:10      阅读:240      评论:0      收藏:0      [点我收藏+]

标签:

在使用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;

}


现在再来测测吧。

技术分享

? ?

再看看服务器端。

技术分享

? ?

技术分享

? ?

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

技术分享

? ?

再看看服务器端。

技术分享

? ?

技术分享

? ?

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

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

标签:

原文地址:http://www.cnblogs.com/qq260250932/p/5347548.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!