WCF开山篇__图片传输
一. 简介
二. 实例
今天要讲解的是一个WCF实现的小功能——图片传输,实现服务器端向客户端发送图片,客户端显示接收到的图片....闲话少说...直接切入正题......
WCF编程模式基于两个实体:WCF服务端、WCF客户端,但是这两者在通信的时候是基于某种契约,只有同时满足契约的条件的双方才能通信,这时就有了WCF服务....所以我们首先从契约入手.....
契约是基于一个定义服务与客户端之间协定的接口,它是用ServiceContractAttribute 属性来进行标记的;服务简单说,就是继承并实现了接口中定义的方法的一个类
首先贴上整个框架结构,如图:
【模块介绍】
Holyknihgt.PicPass.Client_SendPic:发送图片(客户端)
Holyknihgt.PicPass.Contract:定义契约(接口)
Holyknihgt.PicPass.ReceivePic:接受图片(客户端)
首先从契约入手,贴上代码:

2 using System.IO;
3
4 namespace HolyKnight.PicPass.Contract
5 {
6 [ServiceContract]
7 public interface IContracts
8 {
9 //获取图片
10 [OperationContract]
11 Stream GetPic();
12
13 //发送图片
14 [OperationContract]
15 void SendPic(Stream picPass);
16
17 }
18 }
契约中主要定义了两个方法:获取图片方法(GetPic) 和 发送图片方法(SendPic),并添加了对应的ServiceContractAttribute特性
接下去就是对这个接口的实现,即服务,贴上代码:

2
3 namespace HolyKnight.PicPass.Service
4 {
5 public class MyService:IContracts
6 {
7 //定义一个静态内存流 用于存储图片
8 public static Stream picStream = new MemoryStream();
9
10 //获取图片
11 public Stream GetPic()
12 {
13 //实例化一个内存流对象
14 MemoryStream ms = new MemoryStream();
15
16 //设置静态内存流的位置为0 为了后面进行拷贝操作时可以从头开始拷贝内容
17 //【这里的Position不一样了,用了多态的思想,子类已经重写了父类的Position属性】
18 picStream.Position = 0;
19
20 //把内存流中的内容拷贝到当前内存流中
21 picStream.CopyTo(ms);
22
23 //返回内存流对象
24 return ms;
25 }
26
27 //发送图片
28 public void SendPic(Stream picPass)
29 {
30 //【出错】:设置位置为0 -- 因为stream类的position属性是抽象属性的 不能直接复制使用
31 //picPass.Position = 0;
32
33 //【再次报错】--考虑不全:下面这句代码没指定,所以发送的时候没有把Position设为0 而接受的时候一直是从0开始接受 所以一直接受不到后面发送的图片
34 picStream.Position = 0;
35
36 //拷贝到静态内存流中 保存
37 picPass.CopyTo(picStream);
38 }
39
这块对我来说是"重灾区",因为我一开始在这个地方出了两次同样的错误,,悲哀,,错误已经在注释中写明了,,,就是Position的问题,一开始在发送的方法中加上了
接下来贴上服务端的代码:

2 {
3 static void Main(string[] args)
4 {
5 //设置 绑定方式为Tcp
6 NetTcpBinding tcpBind = new NetTcpBinding();
7 //设置 用于存储消息的缓冲区的 最大大小
8 tcpBind.MaxBufferSize = 217736174;
9 //设置服务器 使用流式处理模式 传输消息
10 tcpBind.TransferMode = TransferMode.Streamed;
11 //设置绑定可以处理的最大接受消息大小
12 tcpBind.MaxReceivedMessageSize = 217736174;
13
14 tcpBind.Security.Mode = SecurityMode.None;
15
16 //BasicHttpBinding httpBind = new BasicHttpBinding();
17 //httpBind.MaxBufferSize = 1234567;
18 //httpBind.TransferMode = TransferMode.Streamed;
19 //httpBind.MaxReceivedMessageSize = 1234567;
20
21 //发布
22 //新建服务主机
23 using (ServiceHost host = new ServiceHost(typeof(MyService)))
24 {
25 //为主机添加服务终结点 配置ABC A:address B:bind C:contract 并且基址为net.tcp地址
26 host.AddServiceEndpoint(typeof(IContracts), tcpBind, "net.tcp://192.168.29.223:8000/MyService");
27
28 //控制服务行为
29 ServiceMetadataBehavior behavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>();
30 //如果为空 则实例化一个对象 并设置对应属性
31 if (behavior == null)
32 {
33 behavior = new ServiceMetadataBehavior();
34 behavior.HttpGetEnabled = true;
35
36 //这里的地址为http地址 且端口号不能和上面定义的一样 否则会端口占用
37 behavior.HttpGetUrl = new Uri("http://192.168.29.223:9999/MyService/Medata");
38
39 //将该行为添加到主机
40 host.Description.Behaviors.Add(behavior);
41 }
42
43 host.Opened += delegate
44 {
45 Console.WriteLine("图片传输服务已启动,按任意键关闭服务.....");
46 };
47
48 //打开通信
49 host.Open();
50
51 Console.ReadKey();
52 }
53 }
服务端很简单,大致过程为先添加一个服务主机,参数为服务的类型;然后为该服务主机添加一个服务终结点,服务终结点要配置三个信息即A,B,C,其中A就是Address,表示基址地址,由于这里是通过tcp传输,所以这里的地址为tcp地址,并设定端口号;B就是Binding,表示绑定的类型和方式;C就是Contract,表示终结点定义的契约,这三点配置好了终结点算配置好了,然后添加一个服务行为behavior,首先查询主机中是否含有了服务行为,如果没有则添加一个行为,并设置该行为的HttpGetEnabled属性为true,表示可以通过Http来检索发布的元数据,并设置元数据发布的地址HttpGetUrl,注意这里的地址是Http地址,并设定端口,这里的端口不能和上面的基址的端口相同,否则会端口占用,然后将该行为添加到主机就完成了配置了,然后利用Opened事件,用匿名函数来打印一段信息,然后打开通信,这样服务器端就配置完成了,代码上的语句都做了相应的注释....
当然服务端我们也可以不用手写代码的方式,我们可以选择使用配置文件,下面讲讲使用配置文件来搭建主机
首先我们为主机模块添加一个应用程序配置文件app.config,然后利用vs工具中的wcf配置工具打开该配置文件,如图:

然后进去配置界面,首先新建一个服务
然后进入服务配置界面,选择对应的服务的dll文件,点击添加
然后选择该服务即可,然后点击下一步,会自动为你匹配好对应的契约,直接下一步,到基址配置,如图:
输入正确的基址地址,服务就算建好了.....然后添加服务行为,如图:
然后选择添加,然后选择对应的服务元数据项
然后双击该元数据项,进行配置,配置如图:
最后再在主机中添加该配置好的行为就可以了,,如图:
这样,我们的配置文件算是配好了,,贴上配置文件结果:
服务端就告一段落了,接下来搭建我们的客户端,首先搭建发送端,贴上代码:

2 {
3 public partial class Send : Form
4 {
5 public Send()
6 {
7 InitializeComponent();
8 }
9
10 //选择图片事件
11 private void btn_selectPic_Click(object sender, EventArgs e)
12 {
13 string fileName = "";
14 OpenFileDialog open = new OpenFileDialog();
15 if (open.ShowDialog() == DialogResult.OK)
16 {
17 //文本框显示图片路径
18 fileName = open.FileName;
19 //图片框显示图片
20 pb_Pic.Load(fileName);
21 }
22 else
23 {
24 return;
25 }
26
27 //新建一个文件流 读取图片信息
28 FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
29 //新建一个内存流 用于存放图片
30 Stream strPic = new MemoryStream();
31 //设置位置为0 从头开始拷贝
32 fs.Position = 0;
33 //拷贝
34 fs.CopyTo(strPic);
35
36 //网络地址
37 EndpointAddress address = new EndpointAddress("net.tcp://192.168.29.223:8000/MyService");
38
39 #region TCP
40
41 //设置 绑定方式为Tcp
42 NetTcpBinding tcpBind = new NetTcpBinding();
43 //设置 用于存储消息的缓冲区的 最大大小
44 tcpBind.MaxBufferSize = 217736174;
45 //设置服务器 使用流式处理模式 传输消息
46 tcpBind.TransferMode = TransferMode.Streamed;
47 //设置绑定可以处理的最大接受消息大小
48 tcpBind.MaxReceivedMessageSize = 217736174;
49
50 tcpBind.Security.Mode = SecurityMode.None;
51
52 #endregion
53
54 //BasicHttpBinding httpBind = new BasicHttpBinding();
55 //httpBind.MaxBufferSize = 1234567;
56 //httpBind.TransferMode = TransferMode.Streamed;
57 //httpBind.MaxReceivedMessageSize = 1234567;
58
59
60 //创建通信通道
61 IContracts client = ChannelFactory<IContracts>.CreateChannel(tcpBind, address);
62 //设置位置为0
63 strPic.Position = 0;
64 //调用SendPic方法 发送图片
65 client.SendPic(strPic);
66
67 }
68 }
69 }
代码语句都有对应注释,就不加赘述了,思路就是先获取到本地图片,然后转换成内存流,再连接到主机,创建代理类,通过代理类调用服务中的SendPic方法,实现发送图片
再贴上接收端的代码,思路同发送端雷同,调用服务的GetPic方法:

2 {
3 public partial class Receive : Form
4 {
5 public Receive()
6 {
7 InitializeComponent();
8 }
9
10 public void ShowPic()
11 {
12 //网络地址
13 EndpointAddress address = new EndpointAddress("net.tcp://192.168.29.223:8000/MyService");
14
15 #region TCP
16
17 //设置 绑定方式为Tcp
18 NetTcpBinding tcpBind = new NetTcpBinding();
19
20 //设置 用于存储消息的缓冲区的 最大大小
21 tcpBind.MaxBufferSize = 217736174;
22
23 //设置服务器 使用流式处理模式 传输消息
24 tcpBind.TransferMode = TransferMode.Streamed;
25
26 //设置绑定可以处理的最大接受消息大小
27 tcpBind.MaxReceivedMessageSize = 217736174;
28
29 //设置无安全性检验
30 tcpBind.Security.Mode = SecurityMode.None;
31
32 #endregion
33
34 #region HTTP
35
36 //BasicHttpBinding httpBind = new BasicHttpBinding();
37 //httpBind.MaxBufferSize = 1234567;
38 //httpBind.TransferMode = TransferMode.Streamed;
39 //httpBind.MaxReceivedMessageSize = 1234567;
40
41 #endregion
42
43 //获取服务代理类
44 IContracts prox = ChannelFactory<IContracts>.CreateChannel(tcpBind, address);
45
46 //用死循环持续监听
47 while (true)
48 {
49 //通过调用GetPic()方法获取图片
50 Stream strPic = prox.GetPic();
51
52 //新建一个内存流
53 MemoryStream ms = new MemoryStream();
54
55 //将获取到得图片拷贝到内存流
56 strPic.CopyTo(ms);
57 if (ms.Length == 0)
58 {
59 //线程阻塞300毫秒
60 System.Threading.Thread.Sleep(300);
61 continue;
62 }
63 //将图片信息加载到位图
64 Bitmap bm = new Bitmap(ms);
65
66 //显示图片
67 pb_showPic.Image = bm;
68
69 //线程阻塞
70 System.Threading.Thread.Sleep(300);
71
72 }
73
74
75 }
76
77 private void Form1_Load(object sender, EventArgs e)
78 {
79 //利用线程监听GetPic方法
80 Thread threadPic = new Thread(ShowPic);
81 threadPic.IsBackground = true;
82 threadPic.Start();
83 }
84 }
85 }
这样,我们服务端,客户端的配置都完成了,接下来看看演示效果......直接上图:
首先打开服务:
然后再开启两个客户端,首先贴上客户端页面:
发送端:
接收端:
然后开始发送图片:
测试:我们在客户端更换图片
OK。。。运行,测试都没有问题.....那么这个例子就算完成了..............
发发感慨:再过个十来天,本人也即将步入社会,投出人生中的第一份简历了,经历过多少次的迷茫,努力,迷茫,努力之后,终于有一种去社会跃跃欲试的冲动了,看着周围的人都找到了自己心仪的工作,渐渐的,这种冲动也越来越强烈了,,即使社会有很多的未知性,但相信社会始终是块验金石,会对你究竟的能力来个很好的鉴定,,或许你坚定的相信本身就是块金子,只是一开始进入社会被埋没了,未被发掘出来,就好比千里马没有伯乐的相识的无奈,所以只能沉默,沉默,,,但不要紧,相信自己,坚定自己的信念,如果人生是艘船,那么自己才是唯一的船长,奔着自己的信念,扬帆...起航....,毕竟,是金子,总会发光..........
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述