# 實作通訊&定位app ###### tags: `自主學習` ## 概念 & 規劃 :::success - 採用**公鑰加密 私鑰解密** - 每個使用者有一個專屬自己的頻道 ::: ![](https://i.imgur.com/CtRyRVD.png) ## 程式碼 ### Main ```csharp= public MainPage() { // 初始化頁面 InitializeComponent(); // 建立 user list userList = new List<string>(); // 創建使用者的 client Client(); } ``` ### 建立 MQTT Client ```csharp= public async void Client() { Console.WriteLine("Into"); try { //客戶端初始值設定 var configuration = new MqttConfiguration { BufferSize = 128 * 1024, Port = 1883, KeepAliveSecs = 60000, WaitTimeoutSecs = 2, MaximumQualityOfService = MqttQualityOfService.AtMostOnce, AllowWildcardsInTopicFilters = true }; Console.WriteLine("Basic Infom"); configuration.MaximumQualityOfService = MqttQualityOfService.ExactlyOnce; //? Console.WriteLine("Config"); //創建客戶端 client = await MqttClient.CreateAsync("163.22.17.72", configuration); //主機: 163.22.17.72 Console.WriteLine("Create"); //Client端連線 await client.ConnectAsync(new MqttClientCredentials(clientId: username)); Console.WriteLine("User"); //Subscribe訂閱主題 await client.SubscribeAsync("key", MqttQualityOfService.AtMostOnce); Console.WriteLine("Subscribe"); //收到來自 "個人頻道" 的消息(訊息內容) //接收訊息後,drawPin(msg) client.MessageStream .Where(msg => msg.Topic == username) .Subscribe(msg => drawPin(msg)); // 收到來自 "key頻道" 的消息(public key) //接收訊息後,GetPublicKey(msg) client.MessageStream .Where(msg => msg.Topic == "key") .Subscribe(msg => GetPublicKey(msg)); } catch (Exception ex) { Console.WriteLine("error" + ex); } } ``` ### 產生 & 傳送鑰匙 ```csharp= public async void produce() { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); privatekey = rsa.ToXmlString(true); //私鑰 string publickey = rsa.ToXmlString(false); //公鑰 User me = new User { name = username, publicKey = publickey }; //序列化 (object to string) string jsonString = JsonSerializer.Serialize(me); //傳送公鑰 var key = new MqttApplicationMessage("key", Encoding.UTF8.GetBytes(jsonString)); await client.PublishAsync(key, MqttQualityOfService.AtMostOnce, false); Console.WriteLine("send the key"); } public void SendKey(object sender, EventArgs e) { produce(); } ``` ### 接收鑰匙 ```csharp= public async void GetPublicKey(MqttApplicationMessage msg) { string oherKey = Encoding.UTF8.GetString(msg.Payload); //轉成物件(User) var options = new JsonSerializerOptions { IncludeFields = true, }; //反序列化 User getKey = JsonSerializer.Deserialize<User>(oherKey, options); //檢查有沒有重複的使用者 for (int i = 0; i < allkey.Count; i++) { //使用者之前有傳過->覆蓋 if (allkey[i].name == getKey.name) { allkey[i].publicKey = getKey.publicKey; return; } } //沒有紀錄過就新增在list allkey.Add(getKey); //訂閱他的頻道 await client.SubscribeAsync(getKey.name, MqttQualityOfService.AtMostOnce); } ``` ### 加密 ```csharp= public static byte[] Encode(string publicKey, string content) { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(publicKey); byte[] encryptString = rsa.Encrypt(Encoding.UTF8.GetBytes(content), false); return encryptString; } ``` ### 解密 ```csharp= public static string Decode(string privateKey, byte[] encryptedContent) { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(privateKey); string decryptString = Encoding.UTF8.GetString(rsa.Decrypt(encryptedContent, false)); return decryptString; } ``` ### 發訊息 ```csharp= private async void Publish(object sender, EventArgs e) { // 把位置資訊存成 string: "名字 緯度 經度"(用空格隔開) var location = await Geolocation.GetLastKnownLocationAsync(); string msg = username + " " + Math.Round(location.Latitude,3) + " " + Math.Round(location.Longitude,3) + " " + DateTime.Now.ToLongTimeString() + " " + PublishText.Text; //加密 for (int i = 0; i < allkey.Count; i++) { byte[] encryptString = Encode(allkey[i].publicKey, msg); //發送訊息到頻道 var message = new MqttApplicationMessage(allkey[i].name, encryptString); await client.PublishAsync(message, MqttQualityOfService.AtMostOnce, false); Console.WriteLine("Send Message"); } PublishText.Text = ""; } ``` ### 收訊息 & 在地圖上更新Pin ```csharp= public void drawPin(MqttApplicationMessage msg) { // 轉換收到的訊息型態(string) byte[] msgByte = msg.Payload; Console.Write("~~received: "); Console.WriteLine(msgByte); //解密 string encryptString = Decode(privatekey, msgByte); // 從 msgSting 切出資料: 名字, 經度, 緯度 string[] data = encryptString.Split(' '); string name = data[0]; // 看清單上是否已經有該使用者的資料 // 如果沒有,新增到 userList 並畫 Pin if (userList.IndexOf(name) == -1) { userList.Add(name); Pin newLocation = AddPin(data); map.Pins.Add(newLocation); } // 如果有,覆蓋原本的 Pin else { Pin newLocation = AddPin(data); map.Pins[userList.IndexOf(name)] = newLocation; } // 移動地圖至最新接收到訊息的位置 Device.InvokeOnMainThreadAsync(() => { map.MoveToRegion(MapSpan.FromCenterAndRadius(map.Pins[userList.IndexOf(name)].Position, Distance.FromMiles(10))); }); } ``` ``` csharp= // AddPin(): 產生一個 Pin public Pin AddPin(string[] data) { string name = data[0]; float latitude = float.Parse(data[1]); float longitude = float.Parse(data[2]); string time = data[3] + data[4]; string messenge = data[5]; Pin newLocation = new Pin { Label = name + ":" + messenge, Address = "(" + data[1] + ", " + data[2] + "), " + time, Type = PinType.Generic, Position = new Position(latitude, longitude) }; return newLocation; } ``` :::spoiler 完整程式碼 ```csharp= using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; using Xamarin.Forms; using Xamarin.Forms.Maps; using Xamarin.Essentials; using System.Timers; using System.Net.Mqtt; using System.Reactive.Linq; using System.Text.Json; using System.Security.Cryptography;//加密 namespace Maps2 { public partial class MainPage : ContentPage { public IMqttClient client; // public System.Timers.Timer timer; // 計時器 public List<string> userList; // 紀錄所有使用者名稱 public int count = 0; // 秒數 // 使用者名稱 public string username = "Amber"; string privatekey; //自己的私鑰 List<User> allkey = new List<User>();//記錄所有人的公key //使用者的公鑰匙 public class User { public string name { get; set; } //名字 public string publicKey { get; set; } //公鑰 } // AddPin(): 產生一個 Pin public Pin AddPin(string[] data) { // 加 Pin string name = data[0]; float latitude = float.Parse(data[1]); float longitude = float.Parse(data[2]); string time = data[3] + data[4]; string messenge = data[5]; Pin newLocation = new Pin { Label = name + ":" + messenge, Address = "(" + data[1] + ", " + data[2] + "), " + time, Type = PinType.Generic, Position = new Position(latitude, longitude) }; return newLocation; } // drawPin(): 接收訊息&畫 Pin // msg: 收到的訊息 public void drawPin(MqttApplicationMessage msg) { // 轉換收到的訊息型態(string) byte[] msgByte = msg.Payload; Console.Write("~~received: "); Console.WriteLine(msgByte); //解密 string encryptString = Decode(privatekey, msgByte); // 從 msgSting 切出資料: 名字, 經度, 緯度 string[] data = encryptString.Split(' '); string name = data[0]; // 看清單上是否已經有該使用者的資料 // 如果沒有,新增到 userList 並畫 Pin if (userList.IndexOf(name) == -1) { userList.Add(name); Pin newLocation = AddPin(data); map.Pins.Add(newLocation); } // 如果有,覆蓋原本的 Pin else { Pin newLocation = AddPin(data); map.Pins[userList.IndexOf(name)] = newLocation; } Device.InvokeOnMainThreadAsync(() => { 移動地圖 map.MoveToRegion(MapSpan.FromCenterAndRadius(map.Pins[userList.IndexOf(name)].Position, Distance.FromMiles(10))); }); } // Publish(): 發布訊息 // userLocation: 位置資訊 private async void Publish(object sender, EventArgs e) { // 把位置資訊存成 string: "名字 緯度 經度"(用空格隔開) var location = await Geolocation.GetLastKnownLocationAsync(); string msg = username + " " + Math.Round(location.Latitude,3) + " " + Math.Round(location.Longitude,3) + " " + DateTime.Now.ToLongTimeString() + " " + PublishText.Text; //加密 for (int i = 0; i < allkey.Count; i++) { byte[] encryptString = Encode(allkey[i].publicKey, msg); //發送訊息到頻道 var message = new MqttApplicationMessage(allkey[i].name, encryptString); await client.PublishAsync(message, MqttQualityOfService.AtMostOnce, false); Console.WriteLine("Send Message"); } PublishText.Text = ""; } //加密 public static byte[] Encode(string publicKey, string content) { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(publicKey); byte[] encryptString = rsa.Encrypt(Encoding.UTF8.GetBytes(content), false); return encryptString; } //解密(公鑰) public static string Decode(string privateKey, byte[] encryptedContent) { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); rsa.FromXmlString(privateKey); string decryptString = Encoding.UTF8.GetString(rsa.Decrypt(encryptedContent, false)); return decryptString; } // 接收鑰匙 public async void GetPublicKey(MqttApplicationMessage msg) { string oherKey = Encoding.UTF8.GetString(msg.Payload); //轉成物件(User) var options = new JsonSerializerOptions { IncludeFields = true, }; //反序列化 User getKey = JsonSerializer.Deserialize<User>(oherKey, options); //檢查有沒有重複的使用者 for (int i = 0; i < allkey.Count; i++) { //使用者之前有傳過->覆蓋 if (allkey[i].name == getKey.name) { allkey[i].publicKey = getKey.publicKey; return; } } //沒有紀錄過就新增在list allkey.Add(getKey); //訂閱他的頻道 await client.SubscribeAsync(getKey.name, MqttQualityOfService.AtMostOnce); } //產生鑰匙 public async void produce() { RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(); privatekey = rsa.ToXmlString(true); //私鑰 string publickey = rsa.ToXmlString(false); //公鑰 User me = new User { name = username, publicKey = publickey }; //序列化 (object to string) string jsonString = JsonSerializer.Serialize(me); //傳送公鑰 var key = new MqttApplicationMessage("key", Encoding.UTF8.GetBytes(jsonString)); await client.PublishAsync(key, MqttQualityOfService.AtMostOnce, false); Console.WriteLine("send the key"); } public void SendKey(object sender, EventArgs e) { produce(); } // Client(): 創建客戶、連線、訂閱 // userName: 使用者名 public async void Client() { Console.WriteLine("Into"); try { //客戶端初始值設定 var configuration = new MqttConfiguration { BufferSize = 128 * 1024, Port = 1883, KeepAliveSecs = 60000, WaitTimeoutSecs = 2, MaximumQualityOfService = MqttQualityOfService.AtMostOnce, AllowWildcardsInTopicFilters = true }; Console.WriteLine("Basic Infom"); configuration.MaximumQualityOfService = MqttQualityOfService.ExactlyOnce; //? Console.WriteLine("Config"); //創建客戶端 client = await MqttClient.CreateAsync("163.22.17.72", configuration); //主機: 163.22.17.72 Console.WriteLine("Create"); //Client端連線 await client.ConnectAsync(new MqttClientCredentials(clientId: username)); Console.WriteLine("User"); //Subscribe訂閱主題: maps2 await client.SubscribeAsync("maps2", MqttQualityOfService.AtMostOnce); await client.SubscribeAsync("key", MqttQualityOfService.AtMostOnce); Console.WriteLine("Subscribe"); //收到來自訂閱主題的消息 //接收訊息,畫ping client.MessageStream .Where(msg => msg.Topic == username) .Subscribe(msg => drawPin(msg)); // 接收public key client.MessageStream .Where(msg => msg.Topic == "key") .Subscribe(msg => GetPublicKey(msg)); } catch (Exception ex) { Console.WriteLine("error" + ex); } } public MainPage() { InitializeComponent(); // 把使用者加到 usersList userList = new List<string>(); // 創建使用者的 client Client(); } } } ``` ::: ### 顯示結果 ![](https://i.imgur.com/skJZZmz.png)