C# 基于hslcommunication的异步,同步,一对多,webapi等跨程序网络框架实现,适用程序-程序通信

今天介绍一下如何使用hslcommuniation实现方便的网络框架。虽然之前我也写了相关的文章,但是比较零散,没有进行综合的比较。今天的文章将结合一些实际情况来说明。

开始之前先介绍下:hslcommunication 官网:http://www.hslcommunication.cn/

 

在Visual Studio 中的NuGet管理器中可以下载安装,也可以直接在NuGet控制台输入下面的指令安装:

 

1
Install-Package HslCommunication

 

 如果需要教程:Nuget安装教程:http://www.cnblogs.com/dathlin/p/7705014.html

 

组件的完整信息和API介绍参照:http://www.cnblogs.com/dathlin/p/7703805.html   组件的使用限制,更新日志,都在该页面里面。

本库的demo源代码地址:https://github.com/dathlin/HslCommunication

 

情景一:


 我有一个数据服务器,服务器会定时更新这个数据,然后所有需要这个数据的客户端都可以同时获取到数据信息,进行相关的界面更新。我们称这种机制为发布-订阅机制。客户端向服务器订阅自己需要的数据,服务器更新了这个数据就向所有订阅了的客户端发布最新的数据。

这种模式就特别适合用来做一些客户端界面的实时数据的展示。比如我一个客户端的界面,用来显示设备的实时数据,只有服务器才有资格或是条件访问设备。那么就特别合适用发布订阅。

发布-订阅模式最典型的就是MQTT协议,而HSL支持了MQTT协议的3.1.1版本,我们现在来开发一个程序看看。

 

 先新建一个服务器的控制台程序,和客户端的winform程序。接下来是安装nuget组件hslcommunication。打开nuget

 

 我们来点击安装核心的网络通信组件。

 

 我们搜索这个组件,然后勾选安装的项目后,点击进行安装组件。

然后我们在服务器端写点代码,我们现在要推送从0,1,2,3,4,5,6。 我们随便定义一个主题的名字,“A”好了,然后每秒推送一次,程序很短

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HslCommunication;
using HslCommunication.MQTT;

namespace Server
{
	class Program
	{
		static void Main( string[] args )
		{
			MqttServer server = new MqttServer( );
			server.ServerStart( 1883 );
			int i = 0;
			while(true)
			{
				System.Threading.Thread.Sleep( 1000 );
				server.PublishTopicPayload( "A", Encoding.UTF8.GetBytes( i.ToString( ) ) );
				i++;
			}
		}
	}
}

我们然后在客户端的界面上放一个label,用来显示收到的数据信息。

 

我们然后在窗体启动的方法里,进行启动网络。

 

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 HslCommunication;
using HslCommunication.MQTT;

namespace WindowsFormsApp1
{
	public partial class Form1 : Form
	{
		public Form1( )
		{
			InitializeComponent( );
		}

		private void Form1_Load( object sender, EventArgs e )
		{
			client = new MqttClient( new MqttConnectionOptions( )
			{
				IpAddress = "127.0.0.1",
				Port = 1883,
			} );
			// 接收到数据的时候进行触发
			client.OnMqttMessageReceived += Client_OnMqttMessageReceived;
			// 订阅服务器的主题,在连接成功后就去订阅
			client.OnClientConnected += m =>
			{
				m.SubscribeMessage( "A" );
			};
			client.ConnectServer( );


		}

		private void Client_OnMqttMessageReceived( string topic, byte[] payload )
		{
			// 切回主线程进行显示文本
			Invoke( new Action( ( ) =>
			 {
				 label1.Text = Encoding.UTF8.GetString( payload );
			 } ) );
		}

		private MqttClient client;
	}
}

  

然后先启动服务器,我们在启动客户端,就可以看到一个数字在更新。

 

当然了,这可能没有什么,如果你这时候,打开多个客户端,比如打开个8个客户端。

 

 你就会发现所有的数据都是同时更新的。如果是更多的客户端也没事的。这时候可能又有朋友会问了,你这只传递了一个数据啊,我一个界面的数据很多啊,可能几十个,上百个呢。怎么传递

这个问题的核心,如何把你的所有的需要传递的数据给压缩到一个byte[]里面,然后显示的时候,进行解析成你需要的数据,这个过程就是序列化和反序列化。

事实上,这个没有标准的答案,我下面给出2种思路。

第一种思路是,使用json技术,把需要的数据编程变成一个字符串,然后转byte[],我们假设有3个label的数据需要显示。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HslCommunication;
using HslCommunication.MQTT;
using Newtonsoft.Json.Linq;

namespace Server
{
	class Program
	{
		static void Main( string[] args )
		{
			MqttServer server = new MqttServer( );
			server.ServerStart( 1883 );
			int i = 0;
			while(true)
			{
				System.Threading.Thread.Sleep( 1000 );

				JObject json = new JObject( );
				json.Add( "温度1", new JValue( i ) );
				json.Add( "温度2", new JValue( i + 1 ) );
				json.Add( "温度3", new JValue( i + 2 ) );

				server.PublishTopicPayload( "A", Encoding.UTF8.GetBytes( json.ToString( ) ) );
				i++;
			}
		}
	}
}  

我们用了json组件的技术,只要using就可以开始使用了。然后我们在客户端也需要进行修改。

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 HslCommunication;
using HslCommunication.MQTT;
using Newtonsoft.Json.Linq;

namespace WindowsFormsApp1
{
	public partial class Form1 : Form
	{
		public Form1( )
		{
			InitializeComponent( );
		}

		private void Form1_Load( object sender, EventArgs e )
		{
			client = new MqttClient( new MqttConnectionOptions( )
			{
				IpAddress = "127.0.0.1",
				Port = 1883,
			} );
			// 接收到数据的时候进行触发
			client.OnMqttMessageReceived += Client_OnMqttMessageReceived;
			// 订阅服务器的主题,在连接成功后就去订阅
			client.OnClientConnected += m =>
			{
				m.SubscribeMessage( "A" );
			};
			client.ConnectServer( );


		}

		private void Client_OnMqttMessageReceived( string topic, byte[] payload )
		{
			// 切回主线程进行显示文本
			Invoke( new Action( ( ) =>
			 {
				 JObject json = JObject.Parse( Encoding.UTF8.GetString( payload ) );

				 label1.Text = json.Value<int>( "温度1" ).ToString( );
				 label2.Text = json.Value<int>( "温度2" ).ToString( );
				 label3.Text = json.Value<int>( "温度3" ).ToString( );
			 } ) );
		}

		private MqttClient client;
	}
}  

你可以对照一下看看,主要就是修改了显示部分的内容,

 

 

 好了,现在可以同时发多个数据了,当然了,如果是数组,也可以的,无非是json的序列化,和反序列化,你对json组件理解越深刻,你就可以实现更高级的功能了。

我们再来看看另一个xml技术,其实本质上来说,和json是一样的,都是封装数据,解析数据。此处我就贴个关键的代码,实现就靠你们自己了。

服务器端:

 

 客户端的代码:

 

 我们发现,就是压缩和解析的地方不一样而已,思路都是差不多的,性能上来说,也是几乎没有什么差别的。上面举例了复杂数据的传递,我们再来看看另一种经典的情况,应该怎么处理。

 

我们除了form1,还有个子窗体form2,子窗体也需要显示一个主题的信息。

 

当我们点击form1上的按钮时,form2才显示出来。好了,我们现在有两个主题了,“A”和“B”,我们修改下服务器的内容

		static void Main( string[] args )
		{
			MqttServer server = new MqttServer( );
			server.ServerStart( 1883 );
			int i = 0;
			while(true)
			{
				System.Threading.Thread.Sleep( 1000 );

				XElement element = new XElement( "Data" );
				element.SetAttributeValue( "温度1", i );
				element.SetAttributeValue( "温度2", i + 1 );
				element.SetAttributeValue( "温度3", i + 2 );

				server.PublishTopicPayload( "A", Encoding.UTF8.GetBytes( element.ToString( ) ) );

				server.PublishTopicPayload( "B", Encoding.UTF8.GetBytes( i.ToString( ) ) );
				i++;
			}
		}  

 主要就是增加了发送B主题的数据信息。

form1窗体的那个显示form2的按钮的事件如下:

		private void button1_Click( object sender, EventArgs e )
		{
			using(Form2 form = new Form2( ))
			{
				form.ShowDialog( );
			}
		}

 

我们在form2里应该怎么写订阅呢。

  

using HslCommunication.MQTT;
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;

namespace WindowsFormsApp1
{
	public partial class Form2 : Form
	{
		public Form2( )
		{
			InitializeComponent( );
		}

		private void Form2_Load( object sender, EventArgs e )
		{
			client = new MqttClient( new MqttConnectionOptions( )
			{
				IpAddress = "127.0.0.1",
				Port = 1883,
			} );
			// 接收到数据的时候进行触发
			client.OnMqttMessageReceived += Client_OnMqttMessageReceived;
			// 订阅服务器的主题,在连接成功后就去订阅
			client.OnClientConnected += m =>
			{
				m.SubscribeMessage( "B" );
			};
			client.ConnectServer( );

		}

		private void Client_OnMqttMessageReceived( string topic, byte[] payload )
		{
			// 切回主线程进行显示文本
			Invoke( new Action( ( ) =>
			{
				label1.Text = Encoding.UTF8.GetString( payload );
			} ) );
		}

		private MqttClient client;

		private void Form2_FormClosing( object sender, FormClosingEventArgs e )
		{
			client.ConnectClose( );
		}
	}
}  

写法上来说,和form1是一样的,但是有个小细节需要注意,form2的窗体在关闭的时候,需要关闭和服务器的连接,不然后台依然连接的网络,导致刷新界面的时候,界面已经销毁了,然后发送奔溃。

一般来说是需要再实例化一个MqttClient对象的,然后还是会有人咨询,我能不能所有的界面用一个MqttClient对象,不然我界面一多,不就实例化很多了么。

要解决这个问题,确实不太容易。涉及到一个概念,事件的绑定和解绑操作。好的,我们再来看看,如果实现这个需求。我们先修改下form1的代码

		private void Client_OnMqttMessageReceived( string topic, byte[] payload )
		{
			// 切回主线程进行显示文本
			Invoke( new Action( ( ) =>
			 {
				 // 我的form1只对A进行了订阅,按照道理应该只对A进行响应操作。
				 if (topic == "A")
				 {
					 XElement element = XElement.Parse( Encoding.UTF8.GetString( payload ) );

					 label1.Text = element.Attribute( "温度1" ).Value;
					 label2.Text = element.Attribute( "温度2" ).Value;
					 label3.Text = element.Attribute( "温度3" ).Value;
				 }
			 } ) );
		}  

此处修改了一点,在显示数据之前,对topic进行了判断,如果是A的话,再进行显示,其他的不管。因为,我们只使用了一个MqttClient,所以在实例化form2的时候,就需要form1的client传递给form2。

然后form2窗体初始化的时候,就需要订阅B,然后窗体关闭的时候,要取消订阅B,然后绑定的事件进行解绑操作。当然了,显示主题B的数据的时候,也是需要对B主题进行判断的。代码如下:

using HslCommunication.MQTT;
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;

namespace WindowsFormsApp1
{
	public partial class Form2 : Form
	{
		public Form2( MqttClient client )
		{
			InitializeComponent( );
			this.client = client;
		}

		private void Form2_Load( object sender, EventArgs e )
		{
			// 接收到数据的时候进行触发
			client.OnMqttMessageReceived += Client_OnMqttMessageReceived;
			// 订阅服务器的主题,在连接成功后就去订阅
			client.SubscribeMessage( "B" );

		}

		private void Client_OnMqttMessageReceived( string topic, byte[] payload )
		{
			// 切回主线程进行显示文本
			Invoke( new Action( ( ) =>
			{
				if (topic == "B")
				{
					label1.Text = Encoding.UTF8.GetString( payload );
				}
			} ) );
		}

		private MqttClient client;

		private void Form2_FormClosing( object sender, FormClosingEventArgs e )
		{
			client.UnSubscribeMessage( "B" );
			client.OnMqttMessageReceived -= Client_OnMqttMessageReceived;
		}
	}
}

  

关于MQTT服务器更多高级的功能,比如加入ID过滤,用户名和密码的验证,等等其他的内容,也可以下面的博文:

https://www.cnblogs.com/dathlin/p/12312952.html

可以自己实现一些非常高级的功能。此处就不再过多的赘述了。

客户端也可以输入id,用户名和密码,来实现一个高级的操作,还有日志部分的内容。客户端详细的说明参照下面:

https://www.cnblogs.com/dathlin/p/11631894.html

 

 

 

 

情景二:


 

我有一个提供服务的服务器程序,用来给其他的客户端提供服务器,客户端发送数据给服务器,服务器进行相关的处理,然后返回结果给客户端。强调的是一个问答机制,客户端问,服务器答。

在这种情况下,同时客户端也可以用来上传数据,下载数据,当然了,客户端在连接服务器的时候,最好能标记一个客户端的id信息,最好能输入用户名密码进行验证。

 

这一切hslcommunication都已经支持了。而且已经集成在了MQTT的服务器里面。我还是拿上述的项目作为例子来说明。我现在有一个需求,我在客户端里,有两个按钮,用来控制,服务器的MQTT的发布的实时数据的。

这时候,可能有人会问,mqtt客户端发布个数据,mqtt服务器进行拦截,判断就可以实现,是的,确实可以,不过无论是服务器,还是客户端,编程都是麻烦很多,而且客户端需要反馈回结果,是否操作成功,

 

下面就来说说很方便的实现。MQTT服务器,可以接收同步网络的请求,需要判断下session的协议类型即可。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using HslCommunication;
using HslCommunication.MQTT;
using Newtonsoft.Json.Linq;
using System.Xml.Linq;

namespace Server
{
	class Program
	{
		static void Main( string[] args )
		{
			MqttServer server = new MqttServer( );
			server.ServerStart( 1883 );
			bool isPublish = true;               // 是否发布数据的标记。

			server.OnClientApplicationMessageReceive += ( MqttSession session, MqttClientApplicationMessage message ) =>
			{
				if(session.Protocol == "HUSL")
				{
					// 对同步网络进行处理
					if(message.Topic == "STOP")
					{
						isPublish = false;      // 停止发布数据
						server.PublishTopicPayload( session, "SUCCESS", null );   // 返回操作成功的说明
					}
					else if(message.Topic == "CONTINUE")
					{
						isPublish = true;       // 继续发布数据
						server.PublishTopicPayload( session, "SUCCESS", null );   // 返回操作成功的说明
					}
					else
					{
						server.PublishTopicPayload( session, message.Topic, message.Payload );   // 其他的命令不处理,把原数据返回去
					}
				}
			};

			int i = 0;
			while(true)
			{
				System.Threading.Thread.Sleep( 1000 );

				if (isPublish)
				{
					XElement element = new XElement( "Data" );
					element.SetAttributeValue( "温度1", i );
					element.SetAttributeValue( "温度2", i + 1 );
					element.SetAttributeValue( "温度3", i + 2 );

					server.PublishTopicPayload( "A", Encoding.UTF8.GetBytes( element.ToString( ) ) );

					server.PublishTopicPayload( "B", Encoding.UTF8.GetBytes( i.ToString( ) ) );
					i++;
				}
			}
		}

	}
}

  

我们使用 isPublish 来控制数据的发布,这个bool的属性由客户端的按钮来决定的,那么客户端的两个按钮怎么写呢

 

我们用这两个按钮来控制服务器的bool变量。代码比较简单了。

		private void button2_Click( object sender, EventArgs e )
		{
			// 停止发布
			MqttSyncClient syncClient = new MqttSyncClient( "127.0.0.1", 1883 );
			OperateResult<string, string> read = syncClient.ReadString( "STOP", null );
			if (read.IsSuccess)
			{
				MessageBox.Show( "通知关闭成功!" );
			}
			else
			{
				MessageBox.Show( "通知关闭失败:" + read.Message );
			}
		}

		private void button3_Click( object sender, EventArgs e )
		{
			// 继续发布
			MqttSyncClient syncClient = new MqttSyncClient( "127.0.0.1", 1883 );
			OperateResult<string, string> read = syncClient.ReadString( "CONTINUE", null );
			if (read.IsSuccess)
			{
				MessageBox.Show( "通知关闭成功!" );
			}
			else
			{
				MessageBox.Show( "通知关闭失败:" + read.Message );
			}
		}  

我们此处的代码进行了一些验证,接收到字符串为 SUCCESS , 只要判断了IsSuccess代表真的成功!我们来验证一下效果。

 

 点击停止发布之后,服务器就立即停止了发布,点击继续发布,服务器就继续发布了。演示效果非常的满意。

 

可能此处有的小伙伴有疑问了,客户端为啥要判断服务器返回的是否是SUCCESS字符串呢?服务器只要正常反回,不都是这个字符串吗?

一般来说是的,但是此处有个更高级的用法,我们来假设这个场景,这两个按钮的权限等级很高,只有指定的账户才能操作,其他账户不能操作。比如账户名为 "hsl" 的账户

我们就可以来修改服务器的代码:

 

 

 

 

 此时,我们再启动客户端来看看效果。

 

 我们不仅返回了失败的结果,还把失败的原因也返回,方便客户端查看。此处可能会有老铁说,我客户端的软件,如果账户不是hsl,就把按钮给禁了,这也是个思路,不过实际大多数情况,都认为客户端不安全的,服务器都是需要进行安全检查的。

 现在我们的客户端程序修改一下,设置一个用户名及密码之后。

            MqttSyncClient syncClient = new MqttSyncClient(new MqttConnectionOptions( )
            {
                IpAddress = "127.0.0.1",
                Port = 1883,
                Credentials = new MqttCredential( "hsl", "123456" )
            });
            OperateResult<string, string> read = syncClient.ReadString( "STOP", null );
            if (read.IsSuccess)
            {
                MessageBox.Show( "通知关闭成功!" );
            }
            else
            {
                MessageBox.Show( "通知关闭失败:" + read.Message );
            }

  

 

这时候,就有权限进行操作了。如果你需要在服务器端,进行校验用户名和密码,那就修改服务器的验证连接,具体可以参考服务器端的博客文章。

https://www.cnblogs.com/dathlin/p/12312952.html

 

 

这时候,有小伙伴可能会有疑问,你这个同步访问的客户端,我好像没有看到连接的操作啊,实例化之后,就直接读写了啊。

上面的代码机制确实是这样的,原理是短连接,当你读取的时候,进行数据连接,当你读完了,就断开连接。如果我想长连接,确实这个客户端对象,会在很多不同的窗体用到,我只能实例化一个就够了。那么就是这么写代码。

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.Xml.Linq;
using HslCommunication;
using HslCommunication.MQTT;
using Newtonsoft.Json.Linq;

namespace WindowsFormsApp1
{
	public partial class Form1 : Form
	{
		public Form1( )
		{
			InitializeComponent( );
		}

		private void Form1_Load( object sender, EventArgs e )
		{
			client = new MqttClient( new MqttConnectionOptions( )
			{
				IpAddress = "127.0.0.1",
				Port = 1883,
			} );
			// 接收到数据的时候进行触发
			client.OnMqttMessageReceived += Client_OnMqttMessageReceived;
			// 订阅服务器的主题,在连接成功后就去订阅
			client.OnClientConnected += m =>
			{
				m.SubscribeMessage( "A" );
			};
			client.ConnectServer( );

			// 同步网络的实例化
			syncClient = new MqttSyncClient( new MqttConnectionOptions( )
			{
				IpAddress = "127.0.0.1",
				Port = 1883,
				Credentials = new MqttCredential( "hsl", "123456" )
			} );
			syncClient.SetPersistentConnection( ); // 设置为长连接
		}

		private void Client_OnMqttMessageReceived( string topic, byte[] payload )
		{
			// 切回主线程进行显示文本
			Invoke( new Action( ( ) =>
			 {
				 // 我的form1只对A进行了订阅,按照道理应该只对A进行响应操作。
				 if (topic == "A")
				 {
					 XElement element = XElement.Parse( Encoding.UTF8.GetString( payload ) );

					 label1.Text = element.Attribute( "温度1" ).Value;
					 label2.Text = element.Attribute( "温度2" ).Value;
					 label3.Text = element.Attribute( "温度3" ).Value;
				 }
			 } ) );
		}

		private MqttClient client;

		private void button1_Click( object sender, EventArgs e )
		{
			using(Form2 form = new Form2( client ))
			{
				form.ShowDialog( );
			}
		}

		private MqttSyncClient syncClient;

		private void button2_Click( object sender, EventArgs e )
		{
			// 停止发布
			OperateResult<string, string> read = syncClient.ReadString( "STOP", null );
			if (read.IsSuccess)
			{
				MessageBox.Show( "通知关闭成功!" );
			}
			else
			{
				MessageBox.Show( "通知关闭失败:" + read.Message );
			}
		}

		private void button3_Click( object sender, EventArgs e )
		{
			// 继续发布
			OperateResult<string, string> read = syncClient.ReadString( "CONTINUE", null );
			if (read.IsSuccess)
			{
				MessageBox.Show( "通知关闭成功!" );
			}
			else
			{
				MessageBox.Show( "通知关闭失败:" + read.Message );
			}
		}
	}
}  

可以看到,我们只实例化一个客户端的对象,syncClient,实例化之后,设置长连接即可。如果是多个窗体也需要使用syncClient对象的话,把这个对象syncClient传递给其他的窗体即可,用法和form1的代码是一样的。

using HslCommunication;
using HslCommunication.MQTT;
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;

namespace WindowsFormsApp1
{
	public partial class Form2 : Form
	{
		public Form2( MqttClient client , MqttSyncClient syncClient )
		{
			InitializeComponent( );
			this.client = client;
			this.syncClient = syncClient;
		}

		private void Form2_Load( object sender, EventArgs e )
		{
			// 接收到数据的时候进行触发
			client.OnMqttMessageReceived += Client_OnMqttMessageReceived;
			// 订阅服务器的主题,在连接成功后就去订阅
			client.SubscribeMessage( "B" );

		}

		private void Client_OnMqttMessageReceived( string topic, byte[] payload )
		{
			// 切回主线程进行显示文本
			Invoke( new Action( ( ) =>
			{
				if (topic == "B")
				{
					label1.Text = Encoding.UTF8.GetString( payload );
				}
			} ) );
		}

		private MqttClient client;
		private MqttSyncClient syncClient;


		private void Form2_FormClosing( object sender, FormClosingEventArgs e )
		{
			client.UnSubscribeMessage( "B" );
			client.OnMqttMessageReceived -= Client_OnMqttMessageReceived;
		}

		private void button2_Click( object sender, EventArgs e )
		{
			// 停止发布
			OperateResult<string, string> read = syncClient.ReadString( "STOP", null );
			if (read.IsSuccess)
			{
				MessageBox.Show( "通知关闭成功!" );
			}
			else
			{
				MessageBox.Show( "通知关闭失败:" + read.Message );
			}
		}

		private void button3_Click( object sender, EventArgs e )
		{
			// 继续发布
			OperateResult<string, string> read = syncClient.ReadString( "CONTINUE", null );
			if (read.IsSuccess)
			{
				MessageBox.Show( "通知关闭成功!" );
			}
			else
			{
				MessageBox.Show( "通知关闭失败:" + read.Message );
			}
		}
	}
}  

这么写代码之后,我们的form2也具有通知关闭的功能了,在form1里面,我们只需要把客户端的对象传递给form2即可。

 

 

 

 

通过总结上面的代码,我们再仔细的看看服务器代码的核心部分,

 

 

 

 这个Topic的功能类似一个命令码,服务器根据topic的信息来处理不同的数据,实现不同的功能,可能有的网友会问了,上述的命令交互我会了,现在我想通过这个来实现文件的上传下载,能不能实现?

答案也是可以的,我举例实现文件的下载功能,这个功能比较常见,我客户端可能需要向服务器实现请求下载一个文件。比如下载一个word文档。

 

此处在服务器的当前DEBUG目录,我放置了一个word文档。

 

 

我先写服务器侧的代码,这时候,我需要继续新增一个topic判断了,我们设定下载文件的命令吗是 DownloadFile

 

这部分的代码,就是把当前debug目录下的word文档发送给对方,当然你也可以指定绝对路径,此处为了灵活,因为服务器的程序不一定部署在我的电脑上的,还可能是别的电脑的。

 

客户端我们新增一个按钮,下载文件,同样也是保存到客户端当前的目录,然后通知word软件打开它。

 

 点击按钮的代码如下:

 

 此处就是保存在客户端的当前目录,我们来运行一下;

 

 发现文件直接被打开了,我们来看看客户端的目录发生了什么变化?

 

 

确实下载了一个文件。我们再来想想,服务器再读取文件的时候,假设文件被占用了怎么办?读取失败了怎么办?文件不存在怎么办?难道服务器奔溃吗?当然不是了,我们依然可以进返回的Topic进行处理。

我们返回一个带操作是否成功的字符串回去,那么就可以用hsl自带的类。看代码演示。

 

 我们此处就选择,一旦读取失败,就返回错误的界面给客户端。客户端解析部分的代码也需要稍微改一下

 

 然后我们在跑一下服务器,客户端,发现没有问题,可以下载文件,并且打开这个文件。

这时候,我们尝试一下意外的情况,我们删除服务器的debug目录的文件,看看异常的情况会怎么显示。

 

 好了,我们已经删除了,看看客户端,再点击下载的时候,会出现什么情况。

 

 

这时候的客户端可以友好的提醒错误的内容,文件不存在,还是被占用等等。此处还有一个问题需要思考,如果我下载的文件达到了10M大,我的服务器部署在局域网的另一台电脑上的,导致我客户端下载文件的时候,需要下载10秒钟,上面的代码会导致界面假死10分钟,为了不卡UI界面,我们需要使用后台线程来下载,这里举例async....await技术现实的后台下载。

 

 只需要改两个地方,就可以实现后台的异步下载了。如果你的客户端是.net35的,就只能乖乖使用thread类来实现后台线程了。

这时候,可能又有小伙伴说了,我一个文件要下载10秒钟,可能1分钟,可能更长,那我现在下载的进度也想知道,不然用户就不知道现在还要等待多久,体验不好。

没事的,满足你。我们再修改下客户端的代码即可以实现下载进度。

 

 我们再添加一个进度条控件,用来显示下载文件的进度的,我们在下载文件的时候进行更新进度的操作。

 

 我们在下载的方法里传入一个下载进度的委托即可,同理,上传的进度也是一样的。可以仔细查看下

 syncClient.ReadAsync 方法的参数的含义,和相关的用途。

 

这部分的内容最后有个细节,客户端接收数据都是接收到byte[]数组里的,实际上就是内存。如果你的文件很大,一个G,甚至更大,对客户端来说,很占内存,而且服务器管理多文件也不容易实现,如果你需要对文件进行管理,可以参考另一篇博客:

hsl中有专门的文件服务器,来处理高并发,大文件的上传下载的。还支持文件的一些信息管理。

https://www.cnblogs.com/dathlin/p/7746113.html

 

其中,通过网络的请求,支持java的版本,和python的版本,就是说,java和python都可以对C#的服务器进行数据请求。

 

 

 

 

 

 

情景三:


 我有一个提供服务的服务器程序,用来给其他的网页,语言,或是前端的界面。或是我的系统需要对外提供服务。

那么使用webapi是一个很常见的方式,但是通常会怎么去创建一个webapi呢?新建一个asp.net mvc吗?或是grpc?

如果是复杂的webapi,用框架来创建,没有什么问题,但是我就想创建一个简单的webapi,提供几个接口就行,最好直接集成到我的winform,wpf或是控制台程序,不需要再多开软件,比较麻烦。

 

这样也是可以实现的。

 

 

这样就可以启动一个服务器。我们使用postman来测试

 

 同理,GETB也是一样的,此处就不再赘述了,详细的博客,请参照下面的地址:

 

https://www.cnblogs.com/dathlin/p/12335933.html

 

 

 

情景四,我想把数据推送给网页实时更新。


 

参考下面的博客。

 

https://www.cnblogs.com/dathlin/p/12303098.html

 

posted @ 2020-08-02 14:05  dathlin  阅读(8806)  评论(0编辑  收藏  举报