MQTTnet 2.8 及 3.0.16 的使用

来源:https://www.cnblogs.com/chenwolong/archive/2023/03/21/17239760.html
-Advertisement-
Play Games

十年河東,十年河西,莫欺少年窮 學無止境,精益求精 netcore3.1控制台應用程式,引入MQTTnet 2.8版本 訂閱端: using RabbitMQ.Client; using RabbitMQ.Client.Events; using System; using System.Text; ...


十年河東,十年河西,莫欺少年窮

學無止境,精益求精

netcore3.1控制台應用程式,引入MQTTnet 2.8版本

訂閱端:

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;
using MQTTnet;
using MQTTnet.Server; 
using MQTTnet.Client;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using MQTTnet.Protocol;

namespace swapConsole
{
    class Program
    {
        private static MqttClient mqttClient = null;
        private static  string topic = "test123ABC";
        private static IMqttClientOptions Options
        {
            get
            {
                MqttClientOptionsBuilder builder = new MqttClientOptionsBuilder(); 
                builder.WithCleanSession(false);
                //用戶名 密碼
                builder.WithCredentials("", "");
                var id = Guid.NewGuid().ToString();
                builder.WithClientId(id);
                builder.WithTcpServer("1270.0.0.0", 1883);
                return builder.Build();
            }
        }
        static async Task Main(string[] args)
        {
            MqttFactory factory = new MqttFactory();
            if (mqttClient == null)
            {
                mqttClient = (MqttClient)factory.CreateMqttClient();
                mqttClient.ApplicationMessageReceived += MqttClient_ApplicationMessageReceived;
                mqttClient.Connected += MqttClient_Connected;
                mqttClient.Disconnected += async (s, e) =>
                 {
                     Console.WriteLine("嘗試重連!" + Environment.NewLine);
                     await ConnectToServer();
                 };
            }
            await ConnectToServer(); 

            Console.ReadLine();
        }
        /// <summary>
        /// 連接MQTT伺服器
        /// </summary>
        private   static async Task ConnectToServer()
        {
            try
            {
                var res =await  mqttClient.ConnectAsync(Options);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"連接到MQTT伺服器失敗!" + Environment.NewLine + ex.Message + Environment.NewLine);
            }
        }
        /// <summary>
        /// 連接MQTT伺服器觸發
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MqttClient_Connected(object sender, EventArgs e)
        {
            Console.WriteLine("已連接到MQTT伺服器!" + Environment.NewLine);
            SubscribeInfo();
        }

        /// <summary>
        /// 接收消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MqttClient_ApplicationMessageReceived(object sender, MqttApplicationMessageReceivedEventArgs e)
        {
            Console.WriteLine($">> {Encoding.UTF8.GetString(e.ApplicationMessage.Payload)}{Environment.NewLine}");
        }

        /// <summary>
        /// 訂閱消息
        /// </summary>
        public static void SubscribeInfo()
        {
            if (string.IsNullOrEmpty(topic))
            {
                Console.WriteLine("訂閱主題不能為空!");
                return;
            }

            if (!mqttClient.IsConnected)
            {
                Console.WriteLine("MQTT客戶端尚未連接!");

                return;
            }
            mqttClient.SubscribeAsync(new List<TopicFilter> {
                new  TopicFilter(topic, MqttQualityOfServiceLevel.ExactlyOnce)
            });

            Console.WriteLine($"已訂閱[{topic}]主題" + Environment.NewLine);
        }

        /// <summary>
        /// 退訂消息
        /// </summary>
        public static void UnSubscribeInfo()
        { 

            if (string.IsNullOrEmpty(topic))
            {
                Console.WriteLine("退訂主題不能為空!");
                return;
            }
            if (!mqttClient.IsConnected)
            {
                Console.WriteLine("MQTT客戶端尚未連接!");
                return;
            }
            mqttClient.UnsubscribeAsync(topic);
            Console.WriteLine($"已退訂[{topic}]主題" + Environment.NewLine);
        }

    }
}
View Code

發佈端:

using MQTTnet;
using MQTTnet.Client;
using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace swapPublish
{
    class Program
    {
        private static MqttClient mqttClient = null;
        private static string topic = "test123ABC";
        private static IMqttClientOptions Options
        {
            get
            {
                MqttClientOptionsBuilder builder = new MqttClientOptionsBuilder();
                builder.WithCleanSession(false);
                //用戶名 密碼
                builder.WithCredentials("", "");
                var id = Guid.NewGuid().ToString();
                builder.WithClientId(id);
                builder.WithTcpServer("127.0.0.1", 1883);
                return builder.Build();
            }
        }
        static async Task  Main(string[] args)
        {
            MqttFactory factory = new MqttFactory();
            if (mqttClient == null)
            {
                mqttClient = (MqttClient)factory.CreateMqttClient(); 
                mqttClient.Connected += MqttClient_Connected;
                mqttClient.Disconnected += async(s, e) =>
                {
                    Console.WriteLine("嘗試重連!" + Environment.NewLine);
                    await ConnectToServer();
                };
            }
           await  ConnectToServer();
            Console.WriteLine("已斷開MQTT連接!" + Environment.NewLine);

            Console.ReadLine();
        }
        /// <summary>
        /// 連接MQTT伺服器
        /// </summary>
        private static async Task ConnectToServer()
        {
            try
            {
                var res = await mqttClient.ConnectAsync(Options);
            }
            catch (Exception ex)
            {
                Console.WriteLine($"連接到MQTT伺服器失敗!" + Environment.NewLine + ex.Message + Environment.NewLine);
            }
        }
        /// <summary>
        /// 連接MQTT伺服器觸發
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void MqttClient_Connected(object sender, EventArgs e)
        {
            Console.WriteLine("已連接到MQTT伺服器!" + Environment.NewLine);
            for(int i = 0; i < 10; i++)
            {
                var tak = PublishInfo(); 
                Thread.Sleep(2000);
            }
          
        }

        private static async  Task PublishInfo( )
        { 

            if (string.IsNullOrEmpty(topic))
            {
               Console.WriteLine("發佈主題不能為空!");
                return;
            }

            string inputString = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
            MqttApplicationMessageBuilder builder = new MqttApplicationMessageBuilder(); 
            builder.WithPayload(Encoding.UTF8.GetBytes(inputString));
            builder.WithTopic(topic);
            builder.WithRetainFlag(false);
            builder.WithExactlyOnceQoS();
            await mqttClient.PublishAsync(builder.Build());
        }
    }
}
View Code

 如何只允許一個客戶端消費同一個消息,暫時未解決!

大家有解決方法,請貼出評論。謝謝

MQTTnet  3.0.16 版本的使用

客戶端:

using MQTTnet;
using MQTTnet.Adapter;
using MQTTnet.Client;
using MQTTnet.Client.Connecting;
using MQTTnet.Client.Disconnecting;
using MQTTnet.Client.Options;
using MQTTnet.Client.Receiving;
using MQTTnet.Protocol;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;

namespace mqttsub
{
    class Program
    {
        static async Task Main(string[] args)
        {
            MqttClient mqtt = new MqttClient();
            await mqtt.StartAsync();
            Console.ReadKey();
        }
    }

    public class MqttClient
    {
        private IMqttClient client; 
        private IMqttClientOptions options;
        MqttClientDto model =null;
        public MqttClient()
        {
            model = new MqttClientDto
            {
                Account = "",
                PassWord = "",
                ClientId = Guid.NewGuid().ToString(),
                IP = "",
                Port = 1883,
                Topic="test/+/ABC" //通配符模式 該模式匹配 test/123/ABC  testABC  test/DDDDD/ABC 等
            };
        }
        public async Task StartAsync()
        {
            try
            {
                client = new MqttFactory().CreateMqttClient();
                var build = new MqttClientOptionsBuilder()
                //配置客戶端Id
                .WithClientId(Guid.NewGuid().ToString())
                //配置登錄賬號
                .WithCredentials(model.Account,model.PassWord)
                //配置伺服器IP埠 這裡得埠號是可空的
                .WithTcpServer(model.IP, 1883)
                .WithCleanSession();

                options = build.Build();
                //收到伺服器發來消息
                client.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(MessageReceivedHandler);
                //client.UseApplicationMessageReceivedHandler(args=> {
                //    Console.WriteLine("===================================================");
                //    Console.WriteLine("收到消息:");
                //    Console.WriteLine($"主題:{args.ApplicationMessage.Topic}");
                //    Console.WriteLine($"消息:{Encoding.UTF8.GetString(args.ApplicationMessage.Payload)}");
                //    Console.WriteLine("+++++++++++++++++++++++++++++++++++++++++++++++++++");
                //    Console.WriteLine();
                //});
                //連接成功 
                client.ConnectedHandler = new MqttClientConnectedHandlerDelegate(ConnectedHandler);
                //client.UseConnectedHandler(args=> {
                //    Console.WriteLine("本客戶端已連接成功");
                //    Console.WriteLine($"地址:{model.IP}");
                //    Console.WriteLine($"埠:{model.Port}");
                //    Console.WriteLine($"客戶端:{model.ClientId}");
                //    Console.WriteLine($"賬號:{model.Account}");
                //    Console.WriteLine();
                //    //第1種訂閱方式
                //    client.SubscribeAsync("主題名稱").GetAwaiter().GetResult();

                //    //第2種訂閱方式
                //    List<MqttTopicFilter> Topics = new List<MqttTopicFilter>();
                //    Topics.Add(new MqttTopicFilter() { Topic = "主題名稱A", QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce });
                //    Topics.Add(new MqttTopicFilter() { Topic = "主題名稱B" });
                //    Topics.Add(new MqttTopicFilter() { Topic = "主題名稱C" });
                //    client.SubscribeAsync(Topics.ToArray()).GetAwaiter().GetResult();

                //    //第3種訂閱方式
                //    MqttClientSubscribeOptionsBuilder builder = new MqttClientSubscribeOptionsBuilder();
                //    builder.WithTopicFilter("AAA");
                //    client.SubscribeAsync(builder.Build()).GetAwaiter().GetResult();
                //});
                //斷開連接 重連就寫在此處
                client.DisconnectedHandler = new MqttClientDisconnectedHandlerDelegate(DisconnectedHandler);
                //client.UseDisconnectedHandler(args =>
                //{
                //    Console.WriteLine("本客戶端已經斷開連接");
                //    Console.WriteLine();
                //    try
                //    {
                //        client.ConnectAsync(options).GetAwaiter().GetResult();
                //    }
                //    catch (Exception ex)
                //    {
                //        Console.WriteLine("重連失敗");
                //    }
                //});
                //客戶端發送消息
                //await client.PublishAsync("你想要的主題", "你需要發送的東西");
                //await client.PublishAsync("你想要的主題", Encoding.UTF8.GetBytes("你需要發送的東西").ToList());
                //連接
                await client.ConnectAsync(options);
            }
            catch (MqttConnectingFailedException)
            {
                Console.WriteLine("身份校驗失敗");
            }
            catch (Exception ex)
            {
                Console.WriteLine("出現異常");
                Console.WriteLine(ex.Message);
            }
        }


        /// <summary>
        /// 客戶端斷開連接後,如果需要重連在此處實現
        /// </summary>
        /// <param name="obj"></param>
        private async void DisconnectedHandler(MqttClientDisconnectedEventArgs obj)
        {
            Console.WriteLine("本客戶端已經斷開連接");
            Console.WriteLine();
            try
            {
                await client.ConnectAsync(options);
            }
            catch (Exception)
            {
                Console.WriteLine("重連失敗");
            }
        }

        /// <summary>
        /// 連接成功 在此處做訂閱主題(Topic)操作
        /// </summary>
        /// <param name="obj"></param>
        private async void ConnectedHandler(MqttClientConnectedEventArgs obj)
        {
            Console.WriteLine("本客戶端已連接成功");
            Console.WriteLine($"地址:{model.IP}");
            Console.WriteLine($"埠:{model.Port}");
            Console.WriteLine($"客戶端:{model.ClientId}");
            Console.WriteLine($"賬號:{model.Account}");
            Console.WriteLine();
            //第1種訂閱方式
            // client.SubscribeAsync("主題名稱").GetAwaiter().GetResult();

            //第2種訂閱方式
            List<MqttTopicFilter> Topics = new List<MqttTopicFilter>();
            Topics.Add(new MqttTopicFilter() { Topic = model.Topic, QualityOfServiceLevel = MqttQualityOfServiceLevel.ExactlyOnce});
            //Topics.Add(new MqttTopicFilter() { Topic = "主題名稱B" });
            //Topics.Add(new MqttTopicFilter() { Topic = "主題名稱C" });
            await client.SubscribeAsync(Topics.ToArray());

            //第3種訂閱方式
            //MqttClientSubscribeOptionsBuilder builder = new MqttClientSubscribeOptionsBuilder();
            //builder.WithTopicFilter("AAA");
            //client.SubscribeAsync(builder.Build()).GetAwaiter().GetResult();
        }

        /// <summary>
        /// 收到消息
        /// </summary>
        /// <param name="obj"></param>
        private void MessageReceivedHandler(MqttApplicationMessageReceivedEventArgs obj)
        {
            Console.WriteLine("===================================================");
            Console.WriteLine("收到消息:");
            Console.WriteLine($"主題:{obj.ApplicationMessage.Topic}");
            Console.WriteLine($"消息:{Encoding.UTF8.GetString(obj.ApplicationMessage.Payload)}");
            Console.WriteLine("+++++++++++++++++++++++++++++++++++++++++++++++++++");
            Console.WriteLine();
        }
    }

    public class MqttClientDto
    {
        /// <summary>
        /// 連接地址
        /// </summary>
        public string IP { get; set; }
        /// <summary>
        /// 賬號
        /// </summary>
        public string Account { get; set; }
        /// <summary>
        /// 密碼
        /// </summary>
        public string PassWord { get; set; }
        /// <summary>
        /// 客戶端Id
        /// </summary>
        public string ClientId { get; set; }

        public int Port { get; set; }

        public string Topic { get; set; }
    }
}
View Code

服務端:

using MQTTnet;
using MQTTnet.Client.Receiving;
using MQTTnet.Protocol;
using MQTTnet.Server;
using System;
using System.Net;
using System.Text;
using System.Threading.Tasks;

namespace MqttPub
{
    class Program
    {
        static async Task Main(string[] args)
        {
            await new ServerDome(). StartAsync();
            Console.Read();
        }
    }

    public class ServerDome  
    {
        private IMqttServer server;
        MqttClientDto model = null;
        public ServerDome()
        {
            model = new MqttClientDto
            {
                Account = "",
                PassWord = "",
                ClientId = Guid.NewGuid().ToString(),
                IP = "",
                Port = 1883,
                Topic = "test"
            };
        }

        public async Task StartAsync()
        {
            if (server == null || !server.IsStarted)
            {

                server = new MqttFactory().CreateMqttServer();
                MqttServerOptionsBuilder serverOptions = new MqttServerOptionsBuilder();
                //、預設監聽埠 
                serverOptions.WithDefaultEndpointPort(model.Port);
                //校驗客戶端信息
                serverOptions.WithConnectionValidator(client => {
                    string Account = client.Username;
                    string PassWord = client.Password;
                    string clientid = client.ClientId;
                    if (Account == "" && PassWord == "")
                    {
                        client.ReasonCode = MqttConnectReasonCode.Success;
                        Console.WriteLine("校驗成功");
                    }
                    else
                    {
                        client.ReasonCode = MqttConnectReasonCode.BadUserNameOrPassword;
                        Console.WriteLine("校驗失敗");
                    }
                });

                //客戶端發送消息監聽
                server.ApplicationMessageReceivedHandler = new MqttApplicationMessageReceivedHandlerDelegate(MessageReceivedHandler);
                //server.UseApplicationMessageReceivedHandler(args=>{
                //    Console.WriteLine("===================================================");
                //    Console.WriteLine("收到消息:");
                //    Console.WriteLine($"客戶端:{args.ClientId}");
                //    Console.WriteLine($"主題:{args.ApplicationMessage.Topic}");
                //    Console.WriteLine($"消息:{Encoding.UTF8.GetString(args.ApplicationMessage.Payload)}");
                //    Console.WriteLine("+++++++++++++++++++++++++++++++++++++++++++++++++++");
                //    Console.WriteLine();
                //});
                //客戶端連接事件
                server.ClientConnectedHandler = new MqttServerClientConnectedHandlerDelegate(ClientConnectedHandler);
                //server.UseClientConnectedHandler(args =>
                //{
                //    Console.WriteLine($"{args.ClientId}此客戶端已經連接到伺服器");
                //});
                //客戶端斷開連接事件
                server.ClientDisconnectedHandler = new MqttServerClientDisconnectedHandlerDelegate(ClientDisconnectedHandler);
                //server.UseClientDisconnectedHandler(args => {
                //    Console.WriteLine($"斷開連接的客戶端:{args.ClientId}");
                //    Console.WriteLine($"斷開連接類型:{args.DisconnectType.ToString()}");
                //});

                //客戶端訂閱主題事件
                server.ClientSubscribedTopicHandler = new MqttServerClientSubscribedHandlerDelegate(ClientSubscribedTopicHandler);
                //客戶端取消訂閱主題事件
                server.ClientUnsubscribedTopicHandler = new MqttServerClientUnsubscribedTopicHandlerDelegate(ClientUnsubscribedTopicHandler);
                //伺服器啟動事件
                server.StartedHandler = new MqttServerStartedHandlerDelegate(StartedHandler);
                //伺服器停止事件
                server.StoppedHandler = new MqttServerStoppedHandlerDelegate(StoppedHandler);
                //服務端發送數據
                //await  server.PublishAsync("你想要的主題","你需要發送的東西");
                //var mqttApplicationMessage = new MqttApplicationMessage();
                //mqttApplicationMessage.Topic = "你想要的主題";
                //mqttApplicationMessage.Payload = Encoding.ASCII.GetBytes("你需要發送的東西");
                //await server.PublishAsync(mqttApplicationMessage);
                //啟動伺服器
                await server.StartAsync(serverOptions.Build());
            }
        }

        public async Task StopAsync()
        {
            if (server != null)
            {
                if (server.IsStarted)
                {
                    	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 資源調度器是 YARN 中最核心的組件之一,它是 ResourceManager 中的一個插拔式服務組件,負責整個集群資源的管理和分配。 Yarn 預設提供了三種可用資源調度器,分別是FIFO (First In First Out )、 Yahoo! 的 Capacity Scheduler 和 ... ...
  • 安全配置Security Defenses 通過對Security Defenses的配置 ,可以對http頭添加相應的安全配置 ,如csp, X-Frame-Options, X-Content-Type-Option等 1 X-Frame-Options 你的網站添加了X-Frame-Optio ...
  • 說明 使用 VLD 記憶體泄漏檢測工具輔助開發時整理的學習筆記。 1. 使用前的準備 參考本人另一篇博客 安裝 Visual Leak Detector 下載 vld-2.5.1-setup.exe 並按步驟安裝 VLD。這一種使用方式的缺點是,當把項目拷貝到別的電腦上編譯運行時,需要按以下流程重新配 ...
  • 由於 Blazor-WebAssembly 是在瀏覽器中運行的,通常不需要執行伺服器代碼,只要有個“窩”能托管並提供相關文件的下載即可。所以,當你有一個現成的 Blazor wasm 項目,沒必要用其他語言重寫,或者你不想用 ASP.NET Core 來托管(有些大材小用了),就可以試試用 node ...
  • 1. Grid佈局 ,(Table 佈局) 兩行兩列佈局, Border 0 行 0 列預設開始 <Window x:Class="WpfApp.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" ...
  • 1 枚舉 enum E_MonsterType//定義了一個枚舉的變數類型 { normal1,//0 boss = 5,//5 normal2,//6,前一個自動加1 } //枚舉和switch語句天生一對,寫switch時能對枚舉類型自動補全 E_MonsterType monsterType ...
  • 用了很多年的Rapid SCADA v5,現在官網已經推出了v6,就簡單寫一下有關v6的安裝指南吧。 本指南面向Windows用戶,不適用於linux用戶 步驟 從官網下載Rapid SCADA最新的RC版本的v6,然後運行壓縮包內的ScadaSetup.exe程式。 FAQ 提示埠占用 Rapi ...
  • 簡介 本文主要介紹使用 利用 SqlSugar 來實現多資料庫的維護 ,動態建類CRUD,動態建表 ,全局過濾器 ,跨庫查詢等功能 1、創建表 SqlSugar支持了3種模式的建表(無實體建表、實體建表,實體特性建表),非常的靈活 可以多個資料庫 MYSQL MSSQL ORACLE SQLITE ...
一周排行
    -Advertisement-
    Play Games
  • .Net8.0 Blazor Hybird 桌面端 (WPF/Winform) 實測可以完整運行在 win7sp1/win10/win11. 如果用其他工具打包,還可以運行在mac/linux下, 傳送門BlazorHybrid 發佈為無依賴包方式 安裝 WebView2Runtime 1.57 M ...
  • 目錄前言PostgreSql安裝測試額外Nuget安裝Person.cs模擬運行Navicate連postgresql解決方案Garnet為什麼要選擇Garnet而不是RedisRedis不再開源Windows版的Redis是由微軟維護的Windows Redis版本老舊,後續可能不再更新Garne ...
  • C#TMS系統代碼-聯表報表學習 領導被裁了之後很快就有人上任了,幾乎是無縫銜接,很難讓我不想到這早就決定好了。我的職責沒有任何變化。感受下來這個系統封裝程度很高,我只要會調用方法就行。這個系統交付之後不會有太多問題,更多應該是做小需求,有大的開發任務應該也是第二期的事,嗯?怎麼感覺我變成運維了?而 ...
  • 我在隨筆《EAV模型(實體-屬性-值)的設計和低代碼的處理方案(1)》中介紹了一些基本的EAV模型設計知識和基於Winform場景下低代碼(或者說無代碼)的一些實現思路,在本篇隨筆中,我們來分析一下這種針對通用業務,且只需定義就能構建業務模塊存儲和界面的解決方案,其中的數據查詢處理的操作。 ...
  • 對某個遠程伺服器啟用和設置NTP服務(Windows系統) 打開註冊表 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\W32Time\TimeProviders\NtpServer 將 Enabled 的值設置為 1,這將啟用NTP伺服器功 ...
  • title: Django信號與擴展:深入理解與實踐 date: 2024/5/15 22:40:52 updated: 2024/5/15 22:40:52 categories: 後端開發 tags: Django 信號 松耦合 觀察者 擴展 安全 性能 第一部分:Django信號基礎 Djan ...
  • 使用xadmin2遇到的問題&解決 環境配置: 使用的模塊版本: 關聯的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-r ...
  • 今天我打算整點兒不一樣的內容,通過之前學習的TransformerMap和LazyMap鏈,想搞點不一樣的,所以我關註了另外一條鏈DefaultedMap鏈,主要調用鏈為: 調用鏈詳細描述: ObjectInputStream.readObject() DefaultedMap.readObject ...
  • 後端應用級開發者該如何擁抱 AI GC?就是在這樣的一個大的浪潮下,我們的傳統的應用級開發者。我們該如何選擇職業或者是如何去快速轉型,跟上這樣的一個行業的一個浪潮? 0 AI金字塔模型 越往上它的整個難度就是職業機會也好,或者說是整個的這個運作也好,它的難度會越大,然後越往下機會就會越多,所以這是一 ...
  • @Autowired是Spring框架提供的註解,@Resource是Java EE 5規範提供的註解。 @Autowired預設按照類型自動裝配,而@Resource預設按照名稱自動裝配。 @Autowired支持@Qualifier註解來指定裝配哪一個具有相同類型的bean,而@Resourc... ...