webabcd - 专注于asp.net

ASP.NET
从现在开始 一切都不晚
posts - 149, comments - 4064, trackbacks - 328, articles - 0
  博客园 :: 首页 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理

化零为整WCF(8) - 消息处理(使用流数据传输文件)

Posted on 2008-04-21 08:52 webabcd 阅读(3907) 评论(40)  编辑 收藏 所属分类: WCF
[索引页]
[源码下载]


化零为整WCF(8) - 消息处理(使用流数据传输文件)


作者:webabcd


介绍
WCF(Windows Communication Foundation) - 消息处理:使用流数据传输文件,减少内存开销


示例
1、服务
IStreamed.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.ServiceModel;
using System.IO;

namespace WCF.ServiceLib.Message
{
    
/// <summary>
    
/// 消息契约(定义与 SOAP 消息相对应的强类型类)
    
/// </summary>

    [MessageContract]
    
public class FileWrapper
    
{
        
/// <summary>
        
/// 指定数据成员为 SOAP 消息头
        
/// </summary>

        [MessageHeader]
        
public string FilePath;

        
/// <summary>
        
/// 指定将成员序列化为 SOAP 正文中的元素
        
/// </summary>

        [MessageBodyMember]
        
public Stream FileData;
    }


    
/// <summary>
    
/// IStreamed接口
    
/// </summary>

    [ServiceContract]
    
public interface IStreamed
    
{
        
/// <summary>
        
/// 上传文件
        
/// </summary>
        
/// <remarks>
        
/// 1、支持数据流传输的绑定有:BasicHttpBinding、NetTcpBinding 和 NetNamedPipeBinding
        
/// 2、流数据类型必须是可序列化的 Stream 或 MemoryStream

        // /3、传递时消息体(Message Body)中不能包含其他数据,即参数中只能有一个System.ServiceModel.MessageBodyMember
        /// </remarks>
        
/// <param name="fileWrapper">WCF.ServiceLib.Message.FileWrapper</param>

        [OperationContract]
        
void UploadFile(FileWrapper fileWrapper);
    }

}


Streamed.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.ServiceModel;
using System.IO;

namespace WCF.ServiceLib.Message
{
    
/// <summary>
    
/// IStreamed类
    
/// </summary>

    public class Streamed : IStreamed
    
{
        
/// <summary>
        
/// 上传文件
        
/// </summary>
        
/// <param name="fileWrapper">WCF.ServiceLib.Message.FileWrapper</param>

        public void UploadFile(FileWrapper fileWrapper)
        
{
            var sourceStream 
= fileWrapper.FileData;

            var targetStream 
= new FileStream(fileWrapper.FilePath,
                FileMode.Create,
                FileAccess.Write,
                FileShare.None);

            var buffer 
= new byte[4096];
            var count 
= 0;

            
while ((count = sourceStream.Read(buffer, 0, buffer.Length)) > 0)
            
{
                targetStream.Write(buffer, 
0, count);
            }


            targetStream.Close();
            sourceStream.Close();
        }

    }

}



2、宿主
Streamed.cs
using (ServiceHost host = new ServiceHost(typeof(WCF.ServiceLib.Message.Streamed)))
{
    host.Open();

    Console.WriteLine(
"服务已启动(WCF.ServiceLib.Message.Streamed)");
    Console.WriteLine(
"按<ENTER>停止服务");
    Console.ReadLine();

}

App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<system.serviceModel>
    
<services>
      
<!--name - 提供服务的类名-->
      
<!--behaviorConfiguration - 指定相关的行为配置-->
      
<service name="WCF.ServiceLib.Message.Streamed" behaviorConfiguration="MessageBehavior">
        
<!--address - 服务地址-->
        
<!--binding - 通信方式-->
        
<!--contract - 服务契约-->
        
<!--bindingConfiguration - 指定相关的绑定配置-->
        
<endpoint address="Message/Streamed" binding="netTcpBinding" contract="WCF.ServiceLib.Message.IStreamed" bindingConfiguration="StreamedBindingConfiguration" />
        
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        
<host>
          
<baseAddresses>
            
<add baseAddress="http://localhost:12345/Message/Streamed/"/>
            
<add baseAddress="net.tcp://localhost:54321/"/>
          
</baseAddresses>
        
</host>
      
</service>
    
</services>
    
<behaviors>
      
<serviceBehaviors>
        
<behavior name="MessageBehavior">
          
<!--httpGetEnabled - 使用get方式提供服务-->
          
<serviceMetadata httpGetEnabled="true" />
          
<serviceDebug includeExceptionDetailInFaults="true"/>
        
</behavior>
      
</serviceBehaviors>
    
</behaviors>
    
<bindings>
      
<netTcpBinding>
          
<!--transferMode - 指示通道是使用流处理模式还是缓冲模式来传输请求和响应消息-->
          
<!--maxReceivedMessageSize - 在采用此绑定配置的通道上可接收的最大消息大小(单位:字节)-->
          
<!--receiveTimeout - 在传输引发异常之前可用于完成读取操作的时间间隔-->
          
<binding name="StreamedBindingConfiguration" transferMode="Streamed" maxReceivedMessageSize="1073741824" receiveTimeout="00:10:00" />
      
</netTcpBinding>
    
</bindings>
  
</system.serviceModel>
</configuration>


3、客户端
Streamed.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Windows.Forms;
using System.ServiceModel;
using System.IO;

namespace Client2.Message
{
    
/// <summary>
    
/// 演示Message.Streamed的类
    
/// </summary>

    public class Streamed
    
{
        
/// <summary>
        
/// 流数据上传文件
        
/// </summary>
        
/// <param name="source">源文件地址</param>
        
/// <param name="destination">目标路径</param>

        public void HelloStreamed(string source, string destination)
        
{
            
try
            
{
                var proxy 
= new MessageSvc.Streamed.StreamedClient();

                var sr 
= new System.IO.FileStream(
                    source, System.IO.FileMode.Open);

                proxy.UploadFile(destination 
+ Path.GetFileName(source), sr);

                sr.Close();
                proxy.Close();

                MessageBox.Show(
"上传成功");
            }

            
catch (Exception ex)
            
{
                MessageBox.Show(ex.ToString());
            }

        }

    }

}


App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  
<system.serviceModel>
    
<client>
      
<!--address - 服务地址-->
      
<!--binding - 通信方式-->
      
<!--contract - 服务契约-->
      
<endpoint address="net.tcp://localhost:54321/Message/Streamed" binding="netTcpBinding" contract="MessageSvc.Streamed.IStreamed" bindingConfiguration="StreamedBindingConfiguration" />
    
</client>
    
<bindings>
      
<netTcpBinding>
          
<!--transferMode - 指示通道是使用流处理模式还是缓冲模式来传输请求和响应消息-->
          
<!--sendTimeout - 在传输引发异常之前可用于完成写入操作的时间间隔-->
          
<binding name="StreamedBindingConfiguration" transferMode="Streamed" sendTimeout="00:10:00" />
      
</netTcpBinding>
    
</bindings>
  
</system.serviceModel>
</configuration>


运行结果:
上传文件后提示上传成功


OK
[源码下载]

Feedback

#1楼    回复  引用  查看    

2008-04-21 09:19 by jillzhang      
使用流和使用mtom,有什么异同?各自的适用场合是什么?

#2楼    回复  引用  查看    

2008-04-21 11:11 by 李战      

#3楼 [楼主]   回复  引用  查看    

2008-04-21 12:32 by webabcd      
@jillzhang
:)
使用流数据则不用把数据都加载到内存后才传输,减少内存开销

#4楼 [楼主]   回复  引用  查看    

2008-04-21 12:33 by webabcd      
@李战
:)
啥意思?

#5楼    回复  引用    

2008-04-21 23:01 by 黑白 [未注册用户]
支持
晚了,下下来明天看,对wcf越来越有兴趣了

#6楼 [楼主]   回复  引用  查看    

2008-04-22 07:44 by webabcd      
@黑白
:)
确实,有兴趣的话就会进步很快

#7楼    回复  引用    

2008-04-23 09:23 by Teson [未注册用户]
请问使用流数据下载文件怎么实现?可不可以给个例子?

#8楼 [楼主]   回复  引用  查看    

2008-04-23 12:17 by webabcd      
@Teson
和上传没什么区别的
就是server返回一个Stream
然后client把这个Stream写入硬盘

只要理解了上传,那么下载就很容易实现了

#9楼    回复  引用    

2008-04-23 20:56 by Teson [未注册用户]
@webabcd
上传功能我已经实现了。下载功能会出错。
下面是我的下载服务实现:
public Stream DownloadFile(string pFileName)
{
FileStream fs = null;
try
{
fs = new FileStream(string.Format("FileUploaded\\{0}", pFileName), FileMode.Open);
return fs;
}
catch { }
finally
{
fs.Close();
}
return null;
}
下面是我在客户端的调用:
FileTransferServiceClient fileTransProxy = new FileTransferServiceClient();
this.offlineFileStream = fileTransProxy.DownloadFile(this.fileInfo.FileName);
this.transFileStream = new FileStream(this.sfdTransFile.FileName, FileMode.Create);
while ((recLength = this.offlineFileStream.Read(buffer, 0, buffer.Length)) > 0)
{
try
{
this.transFileStream.Write(buffer, 0, recLength);
writeSize += recLength;
}
catch
{
MessageBox.Show("对方中止文件传输或网络出现异常,文件传输中断。");
break;
}
}
但运行起来会出现以下错误:
The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '00:30:00'.

麻烦看看哪里出问题了,谢谢。

#10楼    回复  引用    

2008-04-23 21:51 by 黑白 [未注册用户]
请教一下,为什么要封装成FileWrapper呢,传两个参数应该更简单吧

#11楼 [楼主]   回复  引用  查看    

2008-04-24 08:41 by webabcd      
@Teson
我在我原来上传文件的代码中改了一下,可以正常流数据下载
没有出现你所述的问题啊

#12楼 [楼主]   回复  引用  查看    

2008-04-24 08:43 by webabcd      
@黑白
因为Message Body不能包含其它数据
所以要把其它数据声明为MessageHeader

#13楼    回复  引用    

2008-04-24 09:12 by Teson [未注册用户]
@webabcd
谢谢您,搞定了。

#14楼 [楼主]   回复  引用  查看    

2008-04-24 09:36 by webabcd      
@Teson
:)
不谢,我也没帮上忙
都是你自己搞定的

#15楼    回复  引用    

2008-04-24 09:37 by Teson [未注册用户]
请问利用上面上传服务上传文件时怎么样获取文件传输速度和进度?

#16楼 [楼主]   回复  引用  查看    

2008-04-24 13:49 by webabcd      
@Teson
流着写文件很容易知道进度的啊
根据writer了多少buffer就能算出来

#17楼    回复  引用    

2008-04-24 15:24 by Teson [未注册用户]
@webabcd
我意思是想在客户端调用上传文件服务时,客户端怎么样获取进度?因为在客户端只是把流作为一个参数传给服务,并没有读取文件到buffer啊。

#18楼 [楼主]   回复  引用  查看    

2008-04-24 17:21 by webabcd      
@Teson
服务端知道就行了,然后利用双向通信的特性把进度返回给客户端就好了

#19楼    回复  引用    

2008-04-24 21:39 by Teson [未注册用户]
@webabcd
我也想过来这样实现的,但是一添加了回调方法就运行出错。是不是使用了流传输的就不能使用双向通信了?

#20楼 [楼主]   回复  引用  查看    

2008-04-25 07:41 by webabcd      
@Teson
没试过,不过应该可以
google一下
有人回答
Streaming is supported under Duplex

#21楼    回复  引用    

2008-05-17 23:27 by 7554753 [未注册用户]
你好,我用你的代码,但是上传的时候出现exception,
"套接字连接已中止。这可能是由于处理消息时出错或远程主机超过接收超时或者潜在的网络资源问题导致的。本地套接字超时是“00:04:59.7396256“

请问是什么原因呢?

#22楼 [楼主]   回复  引用  查看    

2008-05-19 07:59 by webabcd      
@7554753
再次测试我的代码
并没有出现这个问题啊

看看下面这个帖子,希望对你有所帮助
http://forums.microsoft.com/china/ShowPost.aspx?PostID=3083891&SiteID=15

#23楼    回复  引用    

2008-05-20 12:22 by 7554753 [未注册用户]
谢谢,问题已经解决,
我向问下var buffer = new byte[4096];
这里为什么用4096,设置大一些传输速度会不会更快?

#24楼 [楼主]   回复  引用  查看    

2008-05-20 12:50 by webabcd      
@7554753
:)
那就好

缓冲大小,就是在内存中开辟的缓冲
理想条件下设置的大些,会快一些,但是肯定不明显,而且占内存

#25楼    回复  引用    

2008-05-20 13:55 by 7554753 [未注册用户]
@Teson
我用了你下载的代码,结果出现和你一样的问题,你怎么解决的?

#26楼    回复  引用    

2008-05-20 14:29 by 7554753 [未注册用户]
@webabcd
你好,你有下载代码吗?我还是有问题。
另外我是用WindowsService来host服务,这种host方式如何调式服务呢?我在服务代码里设的断点都进不去,另外两种host方式都没问题。

#27楼 [楼主]   回复  引用  查看    

2008-05-20 16:42 by webabcd      
@7554753
没有
这个实现的方法和上传差不多
上传的理解了,下载的就很容易实现了

调试Windows服务
参看
http://support.microsoft.com/kb/824344

#28楼    回复  引用    

2008-05-20 17:28 by 7554753 [未注册用户]
@webabcd
谢谢webabcd,我知道Teson的代码的问题了。
public Stream DownloadFile(string pFileName)
{
FileStream fs = null;
try
{
fs = new FileStream(string.Format("FileUploaded\\{0}", pFileName), FileMode.Open);
return fs;
}
catch { }
finally
{
fs.Close();
}
return null;
}
以上服务代码代码里不应该调用 s.Close();

#29楼 [楼主]   回复  引用  查看    

2008-05-21 08:25 by webabcd      
@7554753
:)
慧眼

#30楼    回复  引用  查看    

2008-06-19 18:09 by 7554753      
@webabcd
有个一问题,WCF服务使用c#写的,而我现在想用c++调用wcf服务该如何做呢。svcutil.exe好像也不能生成c++的代理。

#31楼 [楼主]   回复  引用  查看    

2008-06-20 09:09 by