• 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)
    			{
    				if(read.Content1 == "SUCCESS")
    				{
    					MessageBox.Show( "通知关闭成功!" );
    				}
    				else
    				{
    					MessageBox.Show( "通知关闭失败:" + read.Content1 );
    				}
    			}
    			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)
    			{
    				if (read.Content1 == "SUCCESS")
    				{
    					MessageBox.Show( "通知关闭成功!" );
    				}
    				else
    				{
    					MessageBox.Show( "通知关闭失败:" + read.Content1 );
    				}
    			}
    			else
    			{
    				MessageBox.Show( "通知关闭失败:" + read.Message );
    			}
    		}  

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

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

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

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

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

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

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

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

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

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

  • 相关阅读:
    spring security 学习资料
    设计模式,学习资料
    知名博主
    shiro 学习资料
    nuxt 中使用 koa-session
    koa-session 知识点
    MySQL 中的默认数据库介绍
    JUnit 学习资料
    027_git添加多账号设置
    023_supervisorctl管理服务注意事项
  • 原文地址:https://www.cnblogs.com/dathlin/p/13416030.html
Copyright © 2020-2023  润新知