# 實作通訊&定位app
###### tags: `自主學習`
## 概念 & 規劃
:::success
- 採用**公鑰加密 私鑰解密**
- 每個使用者有一個專屬自己的頻道
:::

## 程式碼
### 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();
}
}
}
```
:::
### 顯示結果
