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

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

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

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

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

bool TransferFile(Stream stream, string name);

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

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

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

    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")]
class IService
{
[OperationContract]
bool UpLoadFile(System.IO.Stream streamInput);
}
    class MyService : IService
{
public bool UpLoadFile(System.IO.Stream streamInput)
{
bool isSuccessed = false;
try
{
using (FileStream outputStream = new FileStream("test.mp3", FileMode.OpenOrCreate, FileAccess.Write))
{
// 我们不用对两个流对象进行读写,只要复制流就OK
streamInput.CopyTo(outputStream);
outputStream.Flush();
isSuccessed = true;
Console.WriteLine("在{0}接收到客户端发送的流,已保存到test.map3。", DateTime.Now.ToLongTimeString());
}
}
catch
{
isSuccessed = false;
}
return isSuccessed;
}
}

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

bool UpLoadFile(System.IO.Stream streamInput) 

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

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

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

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

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

    /// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
} private void button1_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog dlg = new OpenFileDialog();
dlg.Filter = "MP3音频文件|*.mp3";
if (dlg.ShowDialog() == true)
{
this.label1.Content = dlg.FileName;
this.label2.Content = "准备就绪。";
}
} private void button2_Click(object sender, RoutedEventArgs e)
{
if (!File.Exists((string)this.label1.Content))
{
return;
}
FileStream fs = new FileStream((string)this.label1.Content, FileMode.Open, FileAccess.Read);
ServiceReference1.ServiceClient cl = new ServiceReference1.ServiceClient();
this.button2.IsEnabled = false;
bool res = cl.UpLoadFile(fs);
this.button2.IsEnabled = true;
if (res == true)
this.label2.Content = "上传完成。";
}
}

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

现在可以运行了。

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

                // 接收消息的最大范围为500M
//binding.MaxReceivedMessageSize = 500 * 1024 * 1024;

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

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

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

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

    [MessageContract]
public class TransferFileMessage
{
[MessageHeader]
public string File_Name; //文件名
[MessageBodyMember]
public Stream File_Stream; //文件流
}

接着操作方法也要改动。

        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秒的时间想一想,为什么上面的代码不能正常运行。

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

        [OperationContract]
bool UpLoadFile(TransferFileMessage tMsg);

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

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

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

void Reconcile(BankingTransaction bt1, BankingTransaction bt2);

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

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

    [MessageContract]
public class ResultMessage
{
[MessageHeader]
public string ErrorMessage;
[MessageBodyMember]
public bool IsSuccessed;
}

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

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

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

        private void button2_Click(object sender, RoutedEventArgs e)
{
if (!File.Exists((string)this.label1.Content))
{
return;
}
FileStream fs = new FileStream((string)this.label1.Content, FileMode.Open, FileAccess.Read);
ServiceReference1.ServiceClient cl = new ServiceReference1.ServiceClient();
this.button2.IsEnabled = false;
bool isSuccessed = false;
var response = cl.UpLoadFile(System.IO.Path.GetFileName((string)this.label1.Content), fs, out isSuccessed);
this.button2.IsEnabled = true;
if (isSuccessed == true)
this.label2.Content = "上传完成。";
else
this.label2.Content = "错误信息:" + response;
}

现在再来测测吧。

再看看服务器端。

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

传说中的WCF(9):流与文件传输的更多相关文章

  1. 重温WCF之流与文件传输(七)

    WCF开启流模式,主要是设置一个叫TransferMode的属性,所以,你看看哪些Binding的派生类有这个属性就可以了. TransferMode其实是一个举枚,看看它的几个有效值: Buffer ...

  2. 我们一起学习WCF 第六篇文件传输

    原文  http://www.cnblogs.com/LipeiNet/p/4653830.html   前言:文件的输出我们并不陌生,但是今天我写的是用wcf模式进行文件传输,我觉得一大好处就是能进 ...

  3. WCF大文件传输

    WCF传输文件的时候可以设置每次文件的传输大小,如果是小文件的时候,可以很方便的将文件传递到服务端,但是如果文件比较大的话,就不可取了 遇到大文件的话可以采取分段传输的方式进行文件传输 思路: 1.客 ...

  4. WCF 用netTcpbinding,basicHttpBinding 传输大文件

    问题:WCF如何传输大文件 方案:主要有几种绑定方式netTcpbinding,basicHttpBinding,wsHttpbinding,设置相关的传输max消息选项,服务端和客户端都要设置,tr ...

  5. 转:wcf大文件传输解决之道(2)

    此篇文章主要是基于http协议应用于大文件传输中的应用,现在我们先解析下wcf中编码器的定义,编码器实现了类的编码,并负责将Message内存中消息转变为网络发送的字节流或者字节缓冲区(对于发送方而言 ...

  6. 转:wcf大文件传输解决之道(1)

    首先声明,文章思路源于MSDN中徐长龙老师的课程整理,加上自己的一些心得体会,先总结如下: 在应对与大文件传输的情况下,因为wcf默认采用的是缓存加载对象,也就是说将文件包一次性接受至缓存中,然后生成 ...

  7. 分享WCF文件传输---WCFFileTransfer

    前几天分享了分享了WCF聊天程序--WCFChat , 本文和大家一起分享利用WCF实现文件的传输.程序运行效果:接收文件端:发送文件端:连接WCF服务,选择要传输的文件文件传输成功:我们会在保存文件 ...

  8. WCF大文件传输【转】

    http://www.cnblogs.com/happygx/archive/2013/10/29/3393973.html WCF大文件传输 WCF传输文件的时候可以设置每次文件的传输大小,如果是小 ...

  9. WCF大文件传输服务

    由于项目需要,自己写一个基于WCF的大文件传输服务雏形.觉得有一定的参考价值,因此放在网上分享. 目前版本为v1.1特点如下: 1.文件传输端口为18650 2.上传和下载文件 3.支持获取文件传输状 ...

随机推荐

  1. .net 高效管理稀缺资源(数据库资源,文件资源等)

    MSDN建议按照下面的模式实现IDisposable接口: public class Foo: IDisposable { public void Dispose() { Dispose(true); ...

  2. SQLite之读取数据库内容

    1.打开已有数据库. //打开数据库 - (BOOL )openDB {// 红色部分修改为自己的数据库路径 return (SQLITE_OK == sqlite3_open([@"/Us ...

  3. HMMPfam的安装使用手记(转载)

    转载至:http://blog.sina.com.cn/s/blog_3f6403290100rb61.html(感谢原文作者) HMMPfam的安装使用手记前言 简要介绍一下 HMMPfam吧.这还 ...

  4. NSNumber、NSValue、NSDate、NSObject

    注:OC中数组和字典只能存储OC对象不能存放基本数据类型. NSNumber NSNumber可以用来把一个基本数据类型包装成一个NSNumber类型的对象. NSNumber *number = [ ...

  5. STL学习三:deque容器

    1.Deque简介 deque是“double-ended queue”的缩写,和vector一样都是STL的容器,deque是双端数组,而vector是单端的. deque在接口上和vector非常 ...

  6. self.a 和 _a 的区别

    在OC中我们可以通过指令@property定义属性. OC对属性封装了许多方法,同时也会自动实现一些方法,相比实例变量,感觉更加面向对象些. 一般定义属性的方法如下,在Class Test中定义属性i ...

  7. java笔试题(1)

    char型变量中能不能存贮一个中文汉字? char型变量是用来存储Unicode编码的字符的,unicode编码字符集中包含了汉字,所以,char型变量中当然可以存储汉字啦.不过,如果某个特殊的汉字没 ...

  8. 小组开发项目--NABC分析

    我们小组--女神经们,开发项目是重力解锁,我认为我们的项目的最大特点就是不使用开锁键唤醒屏幕.下面我将针对这一特点进行NABC分析: N:经调查一部分人群的手机不能使用就是开机键坏了,我们就是针对这一 ...

  9. 我给女朋友讲编程html系列(4) -- html常用简单标签

    今天似乎有点感冒,浑身无力,在操场上躺了半个小时,好了许多.好了,废话不说了,还是写今天的教程吧. 1,html中的换行标签是 br,写法是: <br /> 2,html中有一些特殊的字符 ...

  10. Netsharp快速入门(之17) Netsharp基础功能(参照高级设置)

    5.2     参照高级设置 1. 以往来字段为例,打开平台工具-界面管理-列表管理,找到往来单位的资源节点,记下列表项目中的名称 2.记下往来单位部件工作区的id 3. 打开平台工具-界面管理-参照 ...