Players unable to rejoin lobbies after disconnecting once Unity Relay & Lobbies - c#

so iv been having trouble trying to reconnect clients to the same lobby after they leave.
Creating a public lobby then joining it using QuickJoinLobbyAsync seems to work just fine, but if a client leaves a lobby they unable to reconnect to that same lobby. when trying to connect again I get this error : "[Lobby]: NoOpenLobbies, (16006). Message: failed to find any open lobbies matching the search criteria"
The following script is attached to a GameObject that is active in the hierarchy.
I have buttons that when pressed activate certain functions such as CreateAMatch()
There are 2 scenes in my project, Multiplayer and MainMenu, This script is used in MultiPlayer
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Netcode;
using Unity.Services.Authentication;
using Unity.Services.Core;
using Unity.Services.Lobbies;
using Unity.Services.Lobbies.Models;
using Unity.Services.Relay;
using Unity.Services.Relay.Models;
using UnityEngine;
using UnityEngine.Events;
using Unity.Netcode.Transports.UTP;
public class GameManagerV2 : MonoBehaviour
{
public static GameManagerV2 _instance;
public static GameManagerV2 Instance => _instance;
public RelayHostData _hostData;
public RelayJoinData _joinData;
public string joinCode;
public string lobbyId;
public string lobbyjoinCode;
public UnityAction<string> UpdateState;
// Notify Match found
public UnityAction MatchFound;
public List<string> lobbyPlayers = new List<string>();
async void Awake()
{
}
async void Start()
{
// UnityServices.InitializeAsync() will initialize all services that are subscribed to Core.
await UnityServices.InitializeAsync();
Debug.Log(UnityServices.State);
SetupEvents();
await SignInAnonymouslyAsync();
NetworkManager.Singleton.OnClientConnectedCallback += ClientConnected;
NetworkManager.Singleton.OnClientDisconnectCallback += ClientDisconnected;
}
// Setup authentication event handlers if desired
void SetupEvents()
{
AuthenticationService.Instance.SignedIn += () => {
// Shows how to get a playerID
Debug.Log($"PlayerID: {AuthenticationService.Instance.PlayerId}");
// Shows how to get an access token
Debug.Log($"Access Token: {AuthenticationService.Instance.AccessToken}");
};
AuthenticationService.Instance.SignInFailed += (err) => {
Debug.LogError(err);
};
AuthenticationService.Instance.SignedOut += () => {
Debug.Log("Player signed out.");
};
AuthenticationService.Instance.Expired += () =>
{
Debug.Log("Player session could not be refreshed and expired.");
};
}
async Task SignInAnonymouslyAsync()
{
try
{
await AuthenticationService.Instance.SignInAnonymouslyAsync();
Debug.Log("Sign in anonymously succeeded!");
// Shows how to get the playerID
Debug.Log($"PlayerID: {AuthenticationService.Instance.PlayerId}");
}
catch (AuthenticationException ex)
{
// Compare error code to AuthenticationErrorCodes
// Notify the player with the proper error message
Debug.LogException(ex);
}
catch (RequestFailedException ex)
{
// Compare error code to CommonErrorCodes
// Notify the player with the proper error message
Debug.LogException(ex);
}
}
private void ClientConnected(ulong id)
{
// Player with id connected to our session
Debug.Log("Connected player with id: " + id);
UpdateState?.Invoke("Player found!");
MatchFound?.Invoke();
}
private async void ClientDisconnected(ulong id)
{
lobbyId.ToString();
string playerId = AuthenticationService.Instance.PlayerId;
Debug.Log("The Player : " + playerId + " Has left from lobby : " + lobbyId);
await LobbyService.Instance.RemovePlayerAsync(lobbyId, playerId);
}
public struct RelayHostData
{
public string JoinCode;
public string IPv4Address;
public ushort Port;
public Guid AllocationID;
public byte[] AllocationIDBytes;
public byte[] ConnectionData;
public byte[] Key;
}
/// <summary>
/// RelayHostData represents the necessary informations
/// for a Host to host a game on a Relay
/// </summary>
public struct RelayJoinData
{
public string JoinCode;
public string IPv4Address;
public ushort Port;
public Guid AllocationID;
public byte[] AllocationIDBytes;
public byte[] ConnectionData;
public byte[] HostConnectionData;
public byte[] Key;
}
public async void CreateAMatch()
{
Debug.Log("Creating a new lobby...");
UpdateState?.Invoke("Creating a new match...");
int maxConnections = 20;
try
{
Allocation allocation = await Relay.Instance.CreateAllocationAsync(maxConnections);
_hostData = new RelayHostData
{
Key = allocation.Key,
Port = (ushort)allocation.RelayServer.Port,
AllocationID = allocation.AllocationId,
AllocationIDBytes = allocation.AllocationIdBytes,
ConnectionData = allocation.ConnectionData,
IPv4Address = allocation.RelayServer.IpV4
};
Debug.Log("Key is " + allocation.Key);
_hostData.JoinCode = await Relay.Instance.GetJoinCodeAsync(allocation.AllocationId);
CreateLobbyOptions options = new CreateLobbyOptions();
string lobbyName = "new_lobby";
int maxPlayers = 20;
options.IsPrivate = false;
options.Data = new Dictionary<string, DataObject>()
{
{
"joinCode", new DataObject(
visibility: DataObject.VisibilityOptions.Public,
value: _hostData.JoinCode)
},
};
Lobby lobby = await LobbyService.Instance.CreateLobbyAsync(lobbyName, maxPlayers, options);
lobbyId = lobby.Id;
Debug.Log("This Is the RAW Lobby Id : " + lobby.Id);
Debug.Log("This Is the RAW Join Code: " + _hostData.JoinCode);
StartCoroutine(HeartbeatLobbyCoroutine(lobby.Id, 15));
NetworkManager.Singleton.GetComponent<UnityTransport>().SetRelayServerData(
_hostData.IPv4Address,
_hostData.Port,
_hostData.AllocationIDBytes,
_hostData.Key,
_hostData.ConnectionData);
NetworkManager.Singleton.StartHost();
UpdateState?.Invoke("Waiting for players...");
Debug.Log("Match Created! ");
}
catch (LobbyServiceException e)
{
Console.WriteLine(e);
throw;
}
}
IEnumerator HeartbeatLobbyCoroutine(string lobbyId, float waitTimeSeconds)
{
var delay = new WaitForSecondsRealtime(waitTimeSeconds);
while (true)
{
Lobbies.Instance.SendHeartbeatPingAsync(lobbyId);
Debug.Log("Lobby Heartbit");
yield return delay;
}
}
public async void JoinACreatedMatch()
{
try
{
QuickJoinLobbyOptions options = new QuickJoinLobbyOptions();
options.Filter = new List<QueryFilter>()
{
new QueryFilter(
field: QueryFilter.FieldOptions.MaxPlayers,
op: QueryFilter.OpOptions.GE,
value: "20")
};
var lobby = await LobbyService.Instance.QuickJoinLobbyAsync(options);
string joinCode = lobby.Data["joinCode"].Value;
lobbyId = lobby.Id;
lobbyjoinCode = lobby.Data["joinCode"].Value;
JoinAllocation allocation = await Relay.Instance.JoinAllocationAsync(joinCode);
_joinData = new RelayJoinData
{
Key = allocation.Key,
Port = (ushort)allocation.RelayServer.Port,
AllocationID = allocation.AllocationId,
AllocationIDBytes = allocation.AllocationIdBytes,
ConnectionData = allocation.ConnectionData,
HostConnectionData = allocation.HostConnectionData,
IPv4Address = allocation.RelayServer.IpV4
};
NetworkManager.Singleton.GetComponent<UnityTransport>().SetRelayServerData(
_joinData.IPv4Address,
_joinData.Port,
_joinData.AllocationIDBytes,
_joinData.Key,
_joinData.ConnectionData,
_joinData.HostConnectionData);
// Finally start the client
NetworkManager.Singleton.StartClient();
}
catch (LobbyServiceException e)
{
Debug.Log(e);
}
}
}
As for the disconnect function:
This script is attached to the player prefab.
inside of void update.
if (IsOwner)
{
if (Input.GetKeyDown(KeyCode.O))
{
NetworkManager.Singleton.Shutdown();
SceneManager.LoadScene("MultiPlayer");
}
}
PS: I'm very new to programing and unity in general
At first i noticed i had no OnClientDisconnectCallback method, and so i promptly added that. later i added a disconnect function to the client so that upon a key press the client would end its connection "NetworkManager.Singleton.Shutdown();" i thought that this would both indicate to the host that a client had left, and the client was no longer connected.

So for me... it turns out I was using a "ConnectionNotificationManager.cs" that I then Destroyed on the client side after connection.
This resulted in the client callback registered to the NetworkManager becoming null. Then I did not get my call back.
Perhaps you are unregistering your callback or destroying the object.

Related

WebView How to run even when app is in background/closed (foreground service active)

I'm building an app which will scrape some data from a website and shows a notification when some criteria are met.
Everything works well without problems when the app is open (because the WebView is being rendered) but when I close the app the WebView is disabled so I cannot use it to scrape data anymore.
The scraping code is inside a class called from a ForegroundService.
I've already looked on the internet but I'm unable to find a solution or a substitute to WebView, do you have any ideas?
I'm sorry if this question looks stupid to you, I've started to develop for mobile just one week ago
Below the JDMonitoring class which is called from the AlarmTask class
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using Xamarin.Forms;
namespace CGSJDSportsNotification {
public class JDMonitoring {
class Ticket {
string owner;
string title;
string store;
string lastUpdated;
string link;
public string ID { get; set; }
public string Owner {
get {
return owner == null ? "Nobody" : owner;
} set {
owner = value.Remove(0, value.IndexOf('(') + 1).Replace(")", "");
}
}
public string Title {
get {
return title;
} set {
if (value.StartsWith("(P"))
title = value.Remove(0, value.IndexOf(')') + 2);
}
}
public string Status { get; set; }
public string Store {
get {
return store;
} set {
store = value.Replace(#"\u003C", "").Replace(">", "");
}
}
public string LastUpdated {
get {
return lastUpdated;
} set {
string v;
int time = Convert.ToInt32(System.Text.RegularExpressions.Regex.Replace(value, #"[^\d]+", ""));
// Convert to minutes
if (value.Contains("hours"))
time *= 60;
v = time.ToString();
if (value.Contains("seconds"))
v = v.Insert(v.Length, " sec. ago");
else
v = v.Insert(v.Length, " min. ago");
lastUpdated = v;
}
}
public string Link {
get {
return link;
} set {
link = "https://support.jdplc.com/" + value;
}
}
}
public JDMonitoring() {
WB.Source = JDQueueMainUrl;
WB.Navigated += new EventHandler<WebNavigatedEventArgs>(OnNavigate);
}
IForegroundService FgService { get { return DependencyService.Get<IForegroundService>(); } }
WebView WB { get; } = MainPage.UI.MonitoringWebView;
string JDQueueMainUrl { get; } = "https://support.jdplc.com/rt4/Search/Results.html?Format=%27%3Cb%3E%3Ca%20href%3D%22__WebPath__%2FTicket%2FDisplay.html%3Fid%3D__id__%22%3E__id__%3C%2Fa%3E%3C%2Fb%3E%2FTITLE%3A%23%27%2C%0A%27%3Cb%3E%3Ca%20href%3D%22__WebPath__%2FTicket%2FDisplay.html%3Fid%3D__id__%22%3E__Subject__%3C%2Fa%3E%3C%2Fb%3E%2FTITLE%3ASubject%27%2C%0AStatus%2C%0AQueueName%2C%0AOwner%2C%0APriority%2C%0A%27__NEWLINE__%27%2C%0A%27__NBSP__%27%2C%0A%27%3Csmall%3E__Requestors__%3C%2Fsmall%3E%27%2C%0A%27%3Csmall%3E__CreatedRelative__%3C%2Fsmall%3E%27%2C%0A%27%3Csmall%3E__ToldRelative__%3C%2Fsmall%3E%27%2C%0A%27%3Csmall%3E__LastUpdatedRelative__%3C%2Fsmall%3E%27%2C%0A%27%3Csmall%3E__TimeLeft__%3C%2Fsmall%3E%27&Order=DESC%7CASC%7CASC%7CASC&OrderBy=LastUpdated%7C%7C%7C&Query=Queue%20%3D%20%27Service%20Desk%20-%20CGS%27%20AND%20(%20%20Status%20%3D%20%27new%27%20OR%20Status%20%3D%20%27open%27%20OR%20Status%20%3D%20%27stalled%27%20OR%20Status%20%3D%20%27deferred%27%20OR%20Status%20%3D%20%27open%20-%20awaiting%20requestor%27%20OR%20Status%20%3D%20%27open%20-%20awaiting%20third%20party%27%20)&RowsPerPage=0&SavedChartSearchId=new&SavedSearchId=new";
bool MonitoringIsInProgress { get; set; } = false;
public bool IsConnectionAvailable {
get {
try {
using (new WebClient().OpenRead("http://google.com/generate_204"))
return true;
} catch {
return false;
}
}
}
async Task<bool> IsOnLoginPage() {
if (await WB.EvaluateJavaScriptAsync("document.getElementsByClassName('left')[0].innerText") != null)
return true;
return false;
}
async Task<bool> Login() {
await WB.EvaluateJavaScriptAsync($"document.getElementsByName('user')[0].value = '{UserSettings.SecureEntries.Get("rtUser")}'");
await WB.EvaluateJavaScriptAsync($"document.getElementsByName('pass')[0].value = '{UserSettings.SecureEntries.Get("rtPass")}'");
await WB.EvaluateJavaScriptAsync("document.getElementsByClassName('button')[0].click()");
await Task.Delay(1000);
// Checks for wrong credentials error
if (await WB.EvaluateJavaScriptAsync("document.getElementsByClassName('action-results')[0].innerText") == null)
return true;
return false;
}
async Task<List<Ticket>> GetTickets() {
List<Ticket> tkts = new List<Ticket>();
// Queue tkts index (multiple of 2)
int index = 2;
// Iterates all the queue
while (await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].innerText") != null) {
Ticket tkt = new Ticket();
tkt.LastUpdated = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index + 1}].getElementsByTagName('td')[4].innerText");
// Gets only the tkts which are not older than the value selected by the user
if (Convert.ToInt32(System.Text.RegularExpressions.Regex.Replace(tkt.LastUpdated, #"[^\d]+", "")) > Convert.ToInt32(UserSettings.Entries.Get("searchTimeframe")))
break;
tkt.ID = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[0].innerText");
tkt.Owner = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[4].innerText");
tkt.Title = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[1].innerText");
tkt.Status = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[2].innerText");
tkt.Store = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index + 1}].getElementsByTagName('td')[1].innerText");
tkt.Link = await WB.EvaluateJavaScriptAsync($"document.getElementsByClassName('ticket-list collection-as-table')[0].getElementsByTagName('tbody')[0].getElementsByTagName('tr')[{index}].getElementsByTagName('td')[1].getElementsByTagName('a')[0].getAttribute('href')");
tkts.Add(tkt);
index += 2;
}
return tkts;
}
//async Task<string> QueueGetTkt
async void OnNavigate(object sender, WebNavigatedEventArgs args) {
if (MonitoringIsInProgress)
return;
if (IsConnectionAvailable) {
if (await IsOnLoginPage()) {
if (await Login() == false) {
// If the log-in failed we can't proceed
MonitoringIsInProgress = false;
FgService.NotificationNewTicket("Log-in failed!", "Please check your credentials");
// Used to avoid an infinite loop of OnNavigate method calls
WB.Source = "about:blank";
return;
}
}
// Main core of the monitoring
List<Ticket> tkts = await GetTickets();
if (tkts.Count > 0) {
foreach(Ticket t in tkts) {
// Looks only after the tkts with the country selected by the user (and if it was selected by the user, also for the tkts without a visible country)
// Firstly we look in the title
if (t.Title.Contains(MainPage.UI.CountryPicker.SelectedItem.ToString())) {
FgService.NotificationNewTicket($"[{t.ID}] {t.LastUpdated}",
$"{t.Title}\r\n\r\n" +
$"Status: {t.Status}\r\n" +
$"Owner: {t.Owner}\r\n" +
$"Last updated: {t.LastUpdated}");
break;
}
}
}
}
MonitoringIsInProgress = false;
}
}
}
AlarmTask class
using Android.App;
using Android.Content;
using Android.Support.V4.App;
namespace CGSJDSportsNotification.Droid {
[BroadcastReceiver(Enabled = true, Exported = true, DirectBootAware = true)]
[IntentFilter(new string[] { Intent.ActionBootCompleted, Intent.ActionLockedBootCompleted, "android.intent.action.QUICKBOOT_POWERON", "com.htc.intent.action.QUICKBOOT_POWERON" }, Priority = (int)IntentFilterPriority.HighPriority)]
public class AlarmTask : BroadcastReceiver {
IAlarm _MainActivity { get { return Xamarin.Forms.DependencyService.Get<IAlarm>(); } }
public override void OnReceive(Context context, Intent intent) {
if (intent.Action != null) {
if (intent.Action.Equals(Intent.ActionBootCompleted)) {
// Starts the app after reboot
var serviceIntent = new Intent(context, typeof(MainActivity));
serviceIntent.AddFlags(ActivityFlags.NewTask);
context.StartActivity(serviceIntent);
Intent main = new Intent(Intent.ActionMain);
main.AddCategory(Intent.CategoryHome);
context.StartActivity(main);
// Does not work, app crashes on boot received
/*if (UserSettings.Entries.Exists("monitoringIsRunning")) {
if ((bool)UserSettings.Entries.Get("monitoringIsRunning"))
FgService.Start();
}*/
}
} else
// Checks for new tkts on a new thread
new JDMonitoring();
// Restarts the alarm
_MainActivity.AlarmStart();
}
// Called from JDMonitoring class
public static void NotificationNewTicket(string title, string message, bool icoUnknownCountry = false) {
new AlarmTask().NotificationShow(title, message, icoUnknownCountry);
}
void NotificationShow(string title, string message, bool icoUnknownCountry) {
int countryFlag = Resource.Drawable.newTktUnknownCountry;
if (icoUnknownCountry == false) {
switch (MainPage.UI.CountryPicker.SelectedItem.ToString()) {
case "Italy":
countryFlag = Resource.Drawable.newTktItaly;
break;
case "Spain":
countryFlag = Resource.Drawable.newTktSpain;
break;
case "Germany":
countryFlag = Resource.Drawable.newTktGermany;
break;
case "Portugal":
countryFlag = Resource.Drawable.newTktPortugal;
break;
}
}
var _intent = new Intent(Application.Context, typeof(MainActivity));
_intent.AddFlags(ActivityFlags.ClearTop);
_intent.PutExtra("jdqueue_notification", "extra");
var pendingIntent = PendingIntent.GetActivity(Application.Context, 0, _intent, PendingIntentFlags.OneShot);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(Application.Context, "newTktNotification_channel")
.SetVisibility((int)NotificationVisibility.Public)
.SetPriority((int)NotificationPriority.High)
.SetDefaults((int)NotificationDefaults.Sound | (int)NotificationDefaults.Vibrate | (int)NotificationDefaults.Lights)
.SetSmallIcon(Resource.Drawable.newTktNotification)
.SetLargeIcon(Android.Graphics.BitmapFactory.DecodeResource(Application.Context.Resources, countryFlag))
.SetSubText("Click to check the queue")
.SetStyle(new NotificationCompat.BigTextStyle()
.SetBigContentTitle("New ticket available!")
.BigText(message))
.SetContentText(title)
.SetAutoCancel(true)
.SetContentIntent(pendingIntent);
NotificationManagerCompat.From(Application.Context).Notify(0, notificationBuilder.Build());
}
}
}
And the ForegroundService class which is responsible to trigger for the first time the alarm
using Android.App;
using Android.Content;
using Android.OS;
namespace CGSJDSportsNotification.Droid {
[Service]
class ForegroundService : Service {
IAlarm _MainActivity { get { return Xamarin.Forms.DependencyService.Get<IAlarm>(); } }
public override IBinder OnBind(Intent intent) { return null; }
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId) {
// Starts the Foreground Service and the notification channel
StartForeground(9869, new ForegroundServiceNotification().ReturnNotif());
Android.Widget.Toast.MakeText(Application.Context, "JD Queue - Monitoring started!", Android.Widget.ToastLength.Long).Show();
_MainActivity.AlarmStart();
return StartCommandResult.Sticky;
}
public override void OnDestroy() {
Android.Widget.Toast.MakeText(Application.Context, "JD Queue - Monitoring stopped!", Android.Widget.ToastLength.Long).Show();
_MainActivity.AlarmStop();
UserSettings.Entries.AddOrEdit("monitoringIsRunning", false);
UserSettings.Entries.AddOrEdit("monitoringStopPending", false, false);
base.OnDestroy();
}
public override bool StopService(Intent name) {
return base.StopService(name);
}
}
}
Thank you!
[BETTER-FINAL-SOLUTION]
After several hours I've discovered Android WebView which does exactly what I need (I'm developing this app only for Android)
I've written this Browser helper class
class Browser {
public Android.Webkit.WebView WB;
static string JSResult;
public class CustomWebViewClient : WebViewClient {
public event EventHandler<bool> OnPageLoaded;
public override void OnPageFinished(Android.Webkit.WebView view, string url) {
OnPageLoaded?.Invoke(this, true);
}
}
public Browser(CustomWebViewClient wc, string url = "") {
WB = new Android.Webkit.WebView(Android.App.Application.Context);
WB.Settings.JavaScriptEnabled = true;
WB.SetWebViewClient(wc);
WB.LoadUrl(url);
}
public string EvalJS(string js) {
JSInterface jsi = new JSInterface();
WB.EvaluateJavascript($"javascript:(function() {{ return {js}; }})()", jsi);
return JSResult;
}
class JSInterface : Java.Lang.Object, IValueCallback {
public void OnReceiveValue(Java.Lang.Object value) {
JSResult = value.ToString();
}
}
}
[EDIT]
Improved the JS returning function with async callbacks (so the JS return value will be always delivered).
Credits to ChristineZuckerman
class Browser {
public Android.Webkit.WebView WB;
public class CustomWebViewClient : WebViewClient {
public event EventHandler<bool> OnPageLoaded;
public override void OnPageFinished(Android.Webkit.WebView view, string url) {
OnPageLoaded?.Invoke(this, true);
}
}
public Browser(CustomWebViewClient wc, string url = "") {
WB = new Android.Webkit.WebView(Android.App.Application.Context);
WB.ClearCache(true);
WB.Settings.JavaScriptEnabled = true;
WB.Settings.CacheMode = CacheModes.NoCache;
WB.Settings.DomStorageEnabled = true;
WB.Settings.SetAppCacheEnabled(false);
WB.Settings.UserAgentString = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.224 Safari/534.10";
WB.LoadUrl(url);
WB.SetWebViewClient(wc);
}
public async Task<string> EvalJS(string js, bool returnNullObjectWhenNull = true) {
string JSResult = "";
ManualResetEvent reset = new ManualResetEvent(false);
Device.BeginInvokeOnMainThread(() => {
WB?.EvaluateJavascript($"javascript:(function() {{ return {js}; }})()", new JSInterface((r) => {
JSResult = r;
reset.Set();
}));
});
await Task.Run(() => { reset.WaitOne(); });
return JSResult == "null" ? returnNullObjectWhenNull ? null : "null" : JSResult;
}
class JSInterface : Java.Lang.Object, IValueCallback {
private Action<string> _callback;
public JSInterface(Action<string> callback) {
_callback = callback;
}
public void OnReceiveValue(Java.Lang.Object value) {
string v = value.ToString();
if (v.StartsWith('"') && v.EndsWith('"'))
v = v.Remove(0, 1).Remove(v.Length - 2, 1);
_callback?.Invoke(v);
}
}
}
Example:
Browser.CustomWebViewClient wc = new Browser.CustomWebViewClient();
wc.OnPageLoaded += BrowserOnPageLoad;
Browser browser = new Browser(wc, "https://www.google.com/");
void BrowserOnPageLoad(object sender, bool e) {
string test = browser.EvalJS("document.getElementsByClassName('Q8LRLc')[0].innerText");
// 'test' will contain the value returned from the JS script
// You can acces the real WebView object by using
// browser.WB
}
// OR WITH THE NEW RETURNING FUNCTION
async void BrowserOnPageLoad(object sender, bool e) {
string test = await browser.EvalJS("document.getElementsByClassName('Q8LRLc')[0].innerText");
// 'test' will contain the value returned from the JS script
// You can acces the real WebView object by using
// browser.WB
}
[FINAL-SOLUTION]
Finally I've found an easy and efficient alternative to WebView.
Now I'm using SimpleBroswer and works great!
[SEMI-SOLUTION]
Alright, I've written a workaround but I don't really like this idea, so please, if you know a better method let me know.
Below my workaround:
In my ForegroundServiceHelper interface I've added a method to check if the MainActivity (where the WebView it's rendered) is visible or not, if isn't visible the MainActivity will be shown and immediately will be hidden back.
And the app will be removed from the last used applications
Method inside my ForegroundServiceHelper Interface
public void InitBackgroundWebView() {
if ((bool)SharedSettings.Entries.Get("MainPage.IsVisible") == false) {
// Shows the activity
Intent serviceIntent = new Intent(context, typeof(MainActivity));
serviceIntent.AddFlags(ActivityFlags.NewTask);
context.StartActivity(serviceIntent);
// And immediately hides it back
Intent main = new Intent(Intent.ActionMain);
main.AddFlags(ActivityFlags.NewTask);
main.AddCategory(Intent.CategoryHome);
context.StartActivity(main);
// Removes from the last app used
ActivityManager am = (new ContextWrapper(Android.App.Application.Context)).GetSystemService(Context.ActivityService).JavaCast<ActivityManager>();
if (am != null) {
System.Collections.Generic.IList<ActivityManager.AppTask> tasks = am.AppTasks;
if (tasks != null && tasks.Count > 0) {
tasks[0].SetExcludeFromRecents(true);
}
}
}
}
The SharedSettings class is an helper class wrapped around the App.Current.Properties Dictionary
And in the OnAppearing and OnDisappearing callbacks I set the shared values to true/false
[EDIT]
This workaround works only if the user is on the homepage, so I need to find an another solution...

Proper way to rise an event from one class and handle it in other class

I am building api for one application. what i want is when I get a new order, i have to insert it in DB and I want to rise an event (without blocking my api call may be async). so that latter in other class i can handle that event and do the other work like send the new order notification to account and send order update to user using pubnub.
public class OrderService : IOrderService
{
public delegate void OrderEventHandler(Order order,bool isNewOrder);
public static event OrderEventHandler OrderEvents = delegate { };
public OrderService()
{
OrderEventListener listener = new OrderEventListener();
OrderEvents += new OrderEventHandler(listener.HandleOrderEvents);
}
#region Methods
public void Test()
{
INPRODataFacade facade = new NPRODataFacade();
var unitOfWork = facade.GetUnitOfWork();
var order = unitOfWork.OrderRepository.Find(o => o.OrderID == 1).FirstOrDefault();
unitOfWork.Commit();
facade.ReturnUnitOfWork();
RiseEvent(order,true);
}
private async System.Threading.Tasks.Task RiseEvent(Order order,bool isNewOrder)
{
if (order == null)
{
return;
}
OrderEvents(order,isNewOrder);
}
}
public class OrderEventListener
{
PubNub.PubNub pubNub = null;
const string ChanelPrifix = "fasttract";
public OrderEventListener()
{
pubNub = new PubNub.PubNub(ConfigurationSettings.PubNub_SubscribeKey, ConfigurationSettings.PubNub_PublishKey, ConfigurationSettings.PubNub_SecretKey, ConfigurationSettings.PubNub_SSlOn);
}
public void HandleOrderEvents(Order order, bool isNewOrder)
{
string chanelName = isNewOrder ? string.Format("{0}_{1}", ChanelPrifix, order.AccountID)
: string.Format("{0}_{1}_{2}", ChanelPrifix, order.AccountID, order.OrderID);
var channel = pubNub.Channel(chanelName);
channel.Publish(new PubNubMessageModel { Message = GetMessage( order, isNewOrder) });
}
public string GetMessage(Order order,bool isNewOrder)
{
string message = string.Empty;
if(isNewOrder)
{
message = "You have recieved a new order #" + order.OrderID;
return message;
}
message = order.Status.ToString();
return message;
}
}
Here have noticed that it is blocking my api call. means I am not getting api response back until event handler finished its execution. is this the right way to handle events ? I think there should be a way that i just add events while getting new order and then in listener i process that events anytime without blocking api call.

Lock analog in Azure

I need to implement some thread consequent logic in Azure web instances.
I have some code like this:
lock (_bookingLock)
{
// Check for a free time
bool isTimeFree = _scheduleService.IsTimeFree(dateTimeGuidId);
//* race condition here
if (isTimeFree)
{
// Make a booking. So this time is busy
newBookingId = _paymentService.CreateBooking(dateTimeGuidId).ToString();
}
}
But I can't use lock in a multi instance environment and I can't omit lock because there is a race condition at *. What is the best approach here?
I decided to use blob leases. I upgraded smarx's code to use Azure Storage client version 2 or 3 and wrote one addtitional method. Here is full code:
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Blob;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.WindowsAzure.Storage.Shared.Protocol;
using Microsoft.WindowsAzure.Storage.Blob.Protocol;
using System.Configuration;
namespace TerminalManager.Domain.Foundation.BlobLeases
{
public class AutoRenewLease : IDisposable
{
public bool HasLease { get { return leaseId != null; } }
AccessCondition _accessCondition;
private CloudBlockBlob blob;
private string leaseId;
private Thread renewalThread;
private bool disposed = false;
public static void DoOnce(CloudBlockBlob blob, Action action) { DoOnce(blob, action, TimeSpan.FromSeconds(5)); }
public static void DoOnce(CloudBlockBlob blob, Action action, TimeSpan pollingFrequency)
{
// blob.Exists has the side effect of calling blob.FetchAttributes, which populates the metadata collection
while (!blob.Exists() || blob.Metadata["progress"] != "done")
{
using (var arl = new AutoRenewLease(blob))
{
if (arl.HasLease)
{
action();
blob.Metadata["progress"] = "done";
AccessCondition ac = new AccessCondition();
ac.LeaseId = arl.leaseId;
blob.SetMetadata(ac);
}
else
{
Thread.Sleep(pollingFrequency);
}
}
}
}
/// <summary>
/// Выполнить последовательно
/// </summary>
/// <param name="lockBlobName">имя блоба - просто буквы</param>
/// <param name="action"></param>
/// <param name="cnStrName">из конфига</param>
/// <param name="containerName">из конфига</param>
/// <param name="pollingFrequency"></param>
public static void DoConsequence(string lockBlobName, Action action,
string cnStrName = "StorageConnectionString",
string containerName = "leasesContainer", TimeSpan? pollingFrequency = null)
{
//http://www.windowsazure.com/en-us/develop/net/how-to-guides/blob-storage/
// Формат пути к блобу
//http://<storage account>.blob.core.windows.net/<container>/<blob>
// Блобовский аккаунт
var account = CloudStorageAccount.Parse(ConfigurationManager.ConnectionStrings[cnStrName].ConnectionString); //CloudStorageAccount.Parse("UseDevelopmentStorage=true"); // Не работает на SDK 2.2 // or your real connection string
var blobs = account.CreateCloudBlobClient();
// Контейнер - типа папки
var container = blobs
.GetContainerReference(ConfigurationManager.AppSettings[containerName]);
container.CreateIfNotExists();
var blob = container.GetBlockBlobReference(lockBlobName);
bool jobDone = false;
while (!jobDone)
{
using (var arl = new AutoRenewLease(blob))
{
if (arl.HasLease)
{
// Some Sync Work here
action();
jobDone = true;
}
else
{
Thread.Sleep(pollingFrequency ?? TimeSpan.FromMilliseconds(300));
}
}
}
}
public static void DoEvery(CloudBlockBlob blob, TimeSpan interval, Action action)
{
while (true)
{
var lastPerformed = DateTimeOffset.MinValue;
using (var arl = new AutoRenewLease(blob))
{
if (arl.HasLease)
{
blob.FetchAttributes();
DateTimeOffset.TryParseExact(blob.Metadata["lastPerformed"], "R", CultureInfo.CurrentCulture, DateTimeStyles.AdjustToUniversal, out lastPerformed);
if (DateTimeOffset.UtcNow >= lastPerformed + interval)
{
action();
lastPerformed = DateTimeOffset.UtcNow;
blob.Metadata["lastPerformed"] = lastPerformed.ToString("R");
AccessCondition ac = new AccessCondition();
ac.LeaseId = arl.leaseId;
blob.SetMetadata(ac);
}
}
}
var timeLeft = (lastPerformed + interval) - DateTimeOffset.UtcNow;
var minimum = TimeSpan.FromSeconds(5); // so we're not polling the leased blob too fast
Thread.Sleep(
timeLeft > minimum
? timeLeft
: minimum);
}
}
public AutoRenewLease(CloudBlockBlob blob)
{
this.blob = blob;
blob.Container.CreateIfNotExists();
try
{
if (!blob.Exists())
{
blob.UploadFromByteArray(new byte[0], 0, 0, AccessCondition.GenerateIfNoneMatchCondition("*"));// new BlobRequestOptions { AccessCondition = AccessCondition.IfNoneMatch("*") });
}
}
catch (StorageException e)
{
if (e.RequestInformation.HttpStatusCode != (int)HttpStatusCode.PreconditionFailed // 412 from trying to modify a blob that's leased
&& e.RequestInformation.ExtendedErrorInformation.ErrorCode != BlobErrorCodeStrings.BlobAlreadyExists
)
{
throw;
}
}
try
{
leaseId = blob.AcquireLease(TimeSpan.FromSeconds(60), null);
_accessCondition = new AccessCondition { LeaseId = leaseId };
}
catch (Exception)
{
Trace.WriteLine("==========> Lease rejected! <==========");
}
if (HasLease)
{
renewalThread = new Thread(() =>
{
while (true)
{
Thread.Sleep(TimeSpan.FromSeconds(40));
var ac = new AccessCondition();
ac.LeaseId = leaseId;
blob.RenewLease(ac);//.RenewLease(leaseId);
}
});
renewalThread.Start();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
if (renewalThread != null)
{
renewalThread.Abort();
blob.ReleaseLease(_accessCondition);
renewalThread = null;
}
}
disposed = true;
}
}
~AutoRenewLease()
{
Dispose(false);
}
}
}
Here is how to use this (don't forget about setup config for blob connection string and catalog name):
// lock analog
AutoRenewLease.DoConsequence("testBlob2", () =>
{
// Some task
if (collection == 0)
{
Thread.Sleep(1000 * 2);
collection++;
}
Trace.WriteLine(tNo + " =====Collection=" + collection);
Trace.WriteLine(tNo + " =====MustBe = 1");
});
I would highly recommend modeling this through a series of messages. You can send a message (command) via Azure Service Bus to create booking. Only one consumer will process the message and you won't need a "lock". In addition, you can scale out to multiple consumers so that you can process multiple commands concurrently. Events can also be used to notify consumers of changes in state (like a booking was created or updated) and do what they need to do.

How to Perform Multiple "Pings" in Parallel using C#

I am trying to calculate the average round-trip time for a collection of servers. In order to speed things up, I would like to perform the pings in parallel. I have written a function called AverageRoundtripTime() and it seems to work, however, since I don't know very much about multi-threading, I am wondering if what I've done is okay. Please take a look at my code and let me know if it's okay or if there's a better way to achieve what I want:
public void Main()
{
// Collection of hosts.
List<String> hosts = new List<String>();
// Add 100 hosts to the collection.
for (Int32 i = 0; i < 100; ++i) hosts.Add("www.google.com");
// Display the average round-trip time for 100 hosts.
Console.WriteLine(AverageRoundtripTime(hosts));
}
public Double AverageRoundtripTime(IEnumerable<String> hosts)
{
// Collection of threads.
List<Thread> threads = new List<Thread>();
// Collection of ping replies.
List<PingReply> pingReplies = new List<PingReply>();
// Loop through all host names.
foreach (var host in hosts)
{
// Create a new thread.
Thread thread = new Thread(() =>
{
// Variable to hold the ping reply.
PingReply reply = null;
// Create a new Ping object and make sure that it's
// disposed after we're finished with it.
using (Ping ping = new Ping())
{
reply = ping.Send(host);
}
// Get exclusive lock on the pingReplies collection.
lock (pingReplies)
{
// Add the ping reply to the collection.
pingReplies.Add(reply);
}
});
// Add the newly created thread to the theads collection.
threads.Add(thread);
// Start the thread.
thread.Start();
}
// Wait for all threads to complete
foreach (Thread thread in threads)
{
thread.Join();
}
// Calculate and return the average round-trip time.
return pingReplies.Average(x => x.RoundtripTime);
}
Update:
Check out a related question that I asked:
Task Parallel Library Code Freezes in a Windows Forms Application - Works fine as a Windows Console Application
The ping class has a method SendAsync. This follows the Event-based Asynchronous Programming (EAP) pattern. Check out this article: http://msdn.microsoft.com/en-us/library/ee622454.aspx.
For a quick example here is a method I have that implements that article in a very basic fashion. You can basically call this as many times as you want and all the pings will be done asychronously.
class Program
{
public static string[] addresses = {"microsoft.com", "yahoo.com", "google.com"};
static void Main(string[] args)
{
List<Task<PingReply>> pingTasks = new List<Task<PingReply>>();
foreach (var address in addresses)
{
pingTasks.Add(PingAsync(address));
}
//Wait for all the tasks to complete
Task.WaitAll(pingTasks.ToArray());
//Now you can iterate over your list of pingTasks
foreach (var pingTask in pingTasks)
{
//pingTask.Result is whatever type T was declared in PingAsync
Console.WriteLine(pingTask.Result.RoundtripTime);
}
Console.ReadLine();
}
static Task<PingReply> PingAsync(string address)
{
var tcs = new TaskCompletionSource<PingReply>();
Ping ping = new Ping();
ping.PingCompleted += (obj, sender) =>
{
tcs.SetResult(sender.Reply);
};
ping.SendAsync(address, new object());
return tcs.Task;
}
}
use the Parallel.For and a ConcurrentBag
static void Main(string[] args)
{
Console.WriteLine(AverageRoundTripTime("www.google.com", 100));
Console.WriteLine(AverageRoundTripTime("www.stackoverflow.com", 100));
Console.ReadKey();
}
static double AverageRoundTripTime(string host, int sampleSize)
{
ConcurrentBag<double> values = new ConcurrentBag<double>();
Parallel.For(1, sampleSize, (x, y) => values.Add(Ping(host)));
return values.Sum(x => x) / sampleSize;
}
static double Ping(string host)
{
var reply = new Ping().Send(host);
if (reply != null)
return reply.RoundtripTime;
throw new Exception("denied");
}
// The solution becomes simpler using LINQ
List<String> hosts = new List<String>();
for (Int32 i = 0; i < 100; ++i) hosts.Add("www.google.com");
var average = hosts.AsParallel().WithDegreeOfParallelism(64).
Select(h => new Ping().Send(h).RoundtripTime).Average();
Console.WriteLine(average)
Maybe using SendPingAsync like this:
using (var ping = new Ping())
{
var replies = await Task.WhenAll(hosts.Select(x => ping.SendPingAsync(x)))
.ConfigureAwait(false);
// false here ^ unless you want to schedule back to sync context
... process replies.
}
A solution:
internal class Utils
{
internal static PingReply Ping (IPAddress address, int timeout = 1000, int ttl = 64)
{
PingReply tpr = null;
var p = new Ping ();
try {
tpr = p.Send (address,
timeout,
Encoding.ASCII.GetBytes ("oooooooooooooooooooooooooooooooo"),
new PingOptions (ttl, true));
} catch (Exception ex) {
tpr = null;
} finally {
if (p != null)
p.Dispose ();
p = null;
}
return tpr;
}
internal static List<PingReply> PingAddresses (List<IPAddress> addresses, int timeout = 1000, int ttl = 64)
{
var ret = addresses
.Select (p => Ping (p, timeout, ttl))
.Where (p => p != null)
.Where (p => p.Status == IPStatus.Success)
.Select (p => p).ToList ();
return ret;
}
internal static Task PingAddressesAsync (List<IPAddress> addresses, Action<Task<List<PingReply>>> endOfPing, int timeout = 1000, int ttl = 64)
{
return Task.Factory.StartNew<List<PingReply>> (() => Utils.PingAddresses (
addresses, timeout, ttl)).ContinueWith (t => endOfPing (t));
}
}
And using:
Console.WriteLine ("start");
Utils.PingAddressesAsync (new List<IPAddress> () {
IPAddress.Parse ("192.168.1.1"),
IPAddress.Parse ("192.168.1.13"),
IPAddress.Parse ("192.168.1.49"),
IPAddress.Parse ("192.168.1.200")
}, delegate(Task<List<PingReply>> tpr) {
var lr = tpr.Result;
Console.WriteLine ("finish with " + lr.Count.ToString () + " machine found");
foreach (var pr in lr) {
Console.WriteLine (pr.Address.ToString ());
}
});
Console.WriteLine ("execute");
Console.ReadLine ();
This is an async worker that can ping multiples endpoints. You can Start() or Stop() the heartbeat worker and suscribe to the following events :
PingUp (edge-triggered when and endpoint gets down)
PingDown (edge-triggered when and endpoint gets up)
PulseStarted
PulseEnded
PingError
-
public class NetworkHeartbeat
{
private static object lockObj = new object();
public bool Running { get; private set; }
public int PingTimeout { get; private set; }
public int HeartbeatDelay { get; private set; }
public IPAddress[] EndPoints { get; private set; }
public int Count => EndPoints.Length;
public PingReply[] PingResults { get; private set; }
private Ping[] Pings { get; set; }
public NetworkHeartbeat(IEnumerable<IPAddress> hosts, int pingTimeout, int heartbeatDelay)
{
PingTimeout = pingTimeout;
HeartbeatDelay = heartbeatDelay;
EndPoints = hosts.ToArray();
PingResults = new PingReply[EndPoints.Length];
Pings = EndPoints.Select(h => new Ping()).ToArray();
}
public async void Start()
{
if (!Running)
{
try
{
Debug.WriteLine("Heartbeat : starting ...");
// set up the tasks
var chrono = new Stopwatch();
var tasks = new Task<PingReply>[Count];
Running = true;
while (Running)
{
// set up and run async ping tasks
OnPulseStarted(DateTime.Now, chrono.Elapsed);
chrono.Restart();
for (int i = 0; i < Count; i++)
{
tasks[i] = PingAndUpdateAsync(Pings[i], EndPoints[i], i);
}
await Task.WhenAll(tasks);
for (int i = 0; i < tasks.Length; i++)
{
var pingResult = tasks[i].Result;
if (pingResult != null)
{
if (PingResults[i] == null)
{
if (pingResult.Status == IPStatus.Success)
OnPingUp(i);
}
else if (pingResult.Status != PingResults[i].Status)
{
if (pingResult.Status == IPStatus.Success)
OnPingUp(i);
else if (PingResults[i].Status == IPStatus.Success)
OnPingDown(i);
}
}
else
{
if (PingResults[i] != null && PingResults[i].Status == IPStatus.Success)
OnPingUp(i);
}
PingResults[i] = tasks[i].Result;
Debug.WriteLine("> Ping [" + PingResults[i].Status.ToString().ToUpper() + "] at " + EndPoints[i] + " in " + PingResults[i].RoundtripTime + " ms");
}
OnPulseEnded(DateTime.Now, chrono.Elapsed);
// heartbeat delay
var delay = Math.Max(0, HeartbeatDelay - (int)chrono.ElapsedMilliseconds);
await Task.Delay(delay);
}
Debug.Write("Heartbeat : stopped");
}
catch (Exception)
{
Debug.Write("Heartbeat : stopped after error");
Running = false;
throw;
}
}
else
{
Debug.WriteLine("Heartbeat : already started ...");
}
}
public void Stop()
{
Debug.WriteLine("Heartbeat : stopping ...");
Running = false;
}
private async Task<PingReply> PingAndUpdateAsync(Ping ping, IPAddress epIP, int epIndex)
{
try
{
return await ping.SendPingAsync(epIP, PingTimeout);
}
catch (Exception ex)
{
Debug.Write("-[" + epIP + "] : error in SendPing()");
OnPingError(epIndex, ex);
return null;
}
}
// Event on ping errors
public event EventHandler<PingErrorEventArgs> PingError;
public class PingErrorEventArgs : EventArgs
{
public int EndPointIndex { get; private set; }
public Exception InnerException { get; private set; }
public PingErrorEventArgs(int epIndex, Exception ex)
{
EndPointIndex = epIndex;
InnerException = ex;
}
}
private void OnPingError(int epIndex, Exception ex) => PingError?.Invoke(this, new PingErrorEventArgs(epIndex, ex));
// Event on ping Down
public event EventHandler<int> PingDown;
private void OnPingDown(int epIndex)
{
Debug.WriteLine("# Ping [DOWN] at " + EndPoints[epIndex]);
PingDown?.Invoke(this, epIndex);
}
// Event on ping Up
public event EventHandler<int> PingUp;
private void OnPingUp(int epIndex)
{
Debug.WriteLine("# Ping [UP] at " + EndPoints[epIndex] );
PingUp?.Invoke(this, epIndex);
}
// Event on pulse started
public event EventHandler<PulseEventArgs> PulseStarted;
public class PulseEventArgs : EventArgs
{
public DateTime TimeStamp { get; private set; }
public TimeSpan Delay { get; private set; }
public PulseEventArgs(DateTime date, TimeSpan delay)
{
TimeStamp = date;
Delay = delay;
}
}
private void OnPulseStarted(DateTime date, TimeSpan delay)
{
Debug.WriteLine("# Heartbeat [PULSE START] after " + (int)delay.TotalMilliseconds + " ms");
PulseStarted?.Invoke(this, new PulseEventArgs(date, delay));
}
// Event on pulse ended
public event EventHandler<PulseEventArgs> PulseEnded;
private void OnPulseEnded(DateTime date, TimeSpan delay)
{
PulseEnded?.Invoke(this, new PulseEventArgs(date, delay));
Debug.WriteLine("# Heartbeat [PULSE END] after " + (int)delay.TotalMilliseconds + " ms");
}
}

RabbitMQ and Serialization weird error

I have two apps, app1.cs and app2.cs (codes below). In addition I also have a dll I extracted from refer.cs(code below). When I compile app1.cs(which sends a measurement object) I get the following exception:
Unhandled Exception: RabbitMQ.Client.Exceptions.OperationInterruptioedException
I can't see how the connection is interrupted. Do you see where the problem is caused at?
Regards,
Demi
//refer.cs from which refer.dll is created
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
namespace refer
{
//start alternate serialization
public static class AltSerialization
{
public static byte[] AltSerialize(Measurement m)
{
using (var ms = new MemoryStream())
{
var bf = new BinaryFormatter();
bf.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
bf.Serialize(ms, m);
return ms.GetBuffer();
}
}
public static Measurement AltDeSerialize(byte[] seriM)
{
using (var stream = new MemoryStream( seriM ))
{
BinaryFormatter bf = new BinaryFormatter();
bf.AssemblyFormat = System.Runtime.Serialization.Formatters.FormatterAssemblyStyle.Simple;
return (Measurement)bf.Deserialize(stream);
}
}
}
//end alternte serialization
[Serializable] //This attribute sets class to be serialized
public class Measurement : ISerializable
{
[NonSerialized] public int id;
public int time; //timestamp
public double value;
public Measurement()
{
id = 1;
time = 12;
value = 0.01;
}
public Measurement(int _id, int _time, double _value)
{
id = _id;
time = _time;
value = _value;
}
//Deserialization constructor
public Measurement(SerializationInfo info, StreamingContext ctxt)
{
//Assign the values from info to the approporiate properties
Console.WriteLine("DeSerialization construtor called.");
time = (int)info.GetValue("MeasurementTime", typeof(int));
value = (double)info.GetValue("MeasurementValue", typeof(double));
}
//Serialization function
public void GetObjectData(SerializationInfo info, StreamingContext ctxt)
{
// Custom name-value pair
// Values must be read with the same name they're written
info.AddValue("MeasurementTime", time);
info.AddValue("MeasurementValue", value);
}
}
}
//MB1.cs
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using UtilityMeasurement;
public interface IMessageBus
{
string MsgSys // Property 1
{
get;
set;
}
void write (Measurement m1);
Measurement read();
void publish(string queue);
void subscribe(string queue);
}
public class Rabbit : IMessageBus
{
// Implementation of methods for Rabbit class go here
private List<string> publishQ = new List<string>();
private List<string> subscribeQ = new List<string>();
public void write ( Measurement m1 )
{
byte[] body = Measurement.AltSerialize( m1 );
IConnection connection = factory.CreateConnection();
IModel channel = connection.CreateModel();
foreach (string queue in publishQ)
{
channel.BasicPublish("", queue, null, body);
Console.WriteLine("\n [x] Sent to queue {0}.", queue);
}
}
public void publish(string queueName)
{
channel.QueueDeclare(queueName, true, false, false, null); //durable=true
publishQ.Add(queueName); //and, add it the list of queue names to publish to
}
public Measurement read()
{
QueueingBasicConsumer consumer = new QueueingBasicConsumer(channel);
foreach (string queue in subscribeQ)
{
channel.BasicConsume(queue, true, consumer);
}
System.Console.WriteLine(" [*] Waiting for messages." +
"To exit press CTRL+C");
BasicDeliverEventArgs ea =
(BasicDeliverEventArgs)consumer.Queue.Dequeue();
return Measurement.AltDeSerialize(ea.Body);
}
public void subscribe(string queueName)
{
channel.QueueDeclare(queueName, true, false, false, null);
subscribeQ.Add(queueName);
}
public static string MsgSysName;
public string MsgSys
{
get
{
return MsgSysName;
}
set
{
MsgSysName = value;
}
}
public Rabbit(string _msgSys) //Constructor
{
ConnectionFactory factory = new ConnectionFactory();
factory.HostName = "localhost";
System.Console.WriteLine("\nMsgSys: RabbitMQ");
MsgSys = _msgSys;
}
}
public class Zmq : IMessageBus
{
public void write ( Measurement m1 )
{
//
}
public Measurement read()
{
//
return null;
}
public void publish(string queue)
{
//
}
public void subscribe(string queue)
{
//
}
public static string MsgSysName;
public string MsgSys
{
get
{
return MsgSysName;
}
set
{
MsgSysName = value;
}
}
// Implementation of methods for Zmq class go here
public Zmq(string _msgSys) //Constructor
{
System.Console.WriteLine("ZMQ");
MsgSys = _msgSys;
}
}
public class MessageBusFactory
{
public static IMessageBus GetMessageBus(string MsgSysName)
{
switch ( MsgSysName )
{
case "Zmq":
return new Zmq(MsgSysName);
case "Rabbit":
return new Rabbit(MsgSysName);
default:
throw new ArgumentException("Messaging type " +
MsgSysName + " not supported." );
}
}
}
public class MainClass
{
public static void Main()
{
//Asks for the message system
System.Console.WriteLine("\nEnter name of messageing system: ");
System.Console.WriteLine("Usage: [Rabbit] [Zmq]");
string MsgSysName = (System.Console.ReadLine()).ToString();
//Create a new Measurement message
Measurement m1 = new Measurement(2, 2345, 23.456);
//Declare an IMessageBus instance:
//Here, an object of the corresponding Message System
// (ex. Rabbit, Zmq, etc) is instantiated
IMessageBus obj1 = MessageBusFactory.GetMessageBus(MsgSysName);
System.Console.WriteLine("\nA {0} object is now created.", MsgSysName);
System.Console.WriteLine("With Test message:\n ID: {0}", m1.id);
System.Console.WriteLine(" Time: {0}", m1.time);
System.Console.WriteLine(" Value: {0}", m1.value);
// Ask queue name and store it
System.Console.WriteLine("Enter a queue name to publish the message to: ");
string QueueName = (System.Console.ReadLine()).ToString();
obj1.publish( QueueName );
System.Console.WriteLine("Enter another queue name: ");
QueueName = (System.Console.ReadLine()).ToString();
obj1.publish( QueueName );
// Write message to the queue
obj1.write( m1 );
}
}
//MB2.cs
using System;
using System.IO;
using System.Collections.Generic;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using UtilityMeasurement;
public interface IMessageBus
{
string MsgSys // Property 1
{
get;
set;
}
void write (Measurement m1);
Measurement read();
void publish(string queue);
void subscribe(string queue);
}
public class Rabbit : IMessageBus
{
// Implementation of methods for Rabbit class go here
private List<string> publishQ = new List<string>();
private List<string> subscribeQ = new List<string>();
public void write ( Measurement m1 )
{
byte[] body = Measurement.AltSerialize( m1 );
IConnection connection = factory.CreateConnection();
IModel channel = connection.CreateModel();
foreach (string queue in publishQ)
{
channel.BasicPublish("", queue, null, body);
Console.WriteLine("\n [x] Sent to queue {0}.", queue);
}
}
public void publish(string queueName)
{
channel.QueueDeclare(queueName, true, false, false, null); //durable=true
publishQ.Add(queueName); //and, add it the list of queue names to publish to
}
public Measurement read()
{
QueueingBasicConsumer consumer = new QueueingBasicConsumer(channel);
foreach (string queue in subscribeQ)
{
channel.BasicConsume(queue, true, consumer);
}
System.Console.WriteLine(" [*] Waiting for messages." +
"To exit press CTRL+C");
BasicDeliverEventArgs ea =
(BasicDeliverEventArgs)consumer.Queue.Dequeue();
return Measurement.AltDeSerialize(ea.Body);
}
public void subscribe(string queueName)
{
channel.QueueDeclare(queueName, true, false, false, null);
subscribeQ.Add(queueName);
}
public static string MsgSysName;
public string MsgSys
{
get
{
return MsgSysName;
}
set
{
MsgSysName = value;
}
}
public Rabbit(string _msgSys) //Constructor
{
ConnectionFactory factory = new ConnectionFactory();
factory.HostName = "localhost";
System.Console.WriteLine("\nMsgSys: RabbitMQ");
MsgSys = _msgSys;
}
}
public class Zmq : IMessageBus
{
public void write ( Measurement m1 )
{
//
}
public Measurement read()
{
//
return null;
}
public void publish(string queue)
{
//
}
public void subscribe(string queue)
{
//
}
public static string MsgSysName;
public string MsgSys
{
get
{
return MsgSysName;
}
set
{
MsgSysName = value;
}
}
// Implementation of methods for Zmq class go here
public Zmq(string _msgSys) //Constructor
{
System.Console.WriteLine("ZMQ");
MsgSys = _msgSys;
}
}
public class MessageBusFactory
{
public static IMessageBus GetMessageBus(string MsgSysName)
{
switch ( MsgSysName )
{
case "Zmq":
return new Zmq(MsgSysName);
case "Rabbit":
return new Rabbit(MsgSysName);
default:
throw new ArgumentException("Messaging type " +
MsgSysName + " not supported." );
}
}
}
public class MainClass
{
public static void Main()
{
//Asks for the message system
System.Console.WriteLine("\nEnter name of messageing system: ");
System.Console.WriteLine("Usage: [Rabbit] [Zmq]");
string MsgSysName = (System.Console.ReadLine()).ToString();
//Declare an IMessageBus instance:
//Here, an object of the corresponding Message System
// (ex. Rabbit, Zmq, etc) is instantiated
IMessageBus obj1 = MessageBusFactory.GetMessageBus(MsgSysName);
System.Console.WriteLine("\nA {0} object is now created.", MsgSysName);
System.Console.WriteLine("Enter a queue to subscribe to: ");
string QueueName = (System.Console.ReadLine()).ToString();
obj1.subscribe( QueueName );
//Create a new Measurement object m2
Measurement m2 = new Measurement();
//Read message into m2
m2 = obj1.read();
m2.id = 11;
System.Console.WriteLine("\nMessage received from queue {0}:\n ID: {1}",QueueName, m2.id);
System.Console.WriteLine(" Time: {0}", m2.time);
System.Console.WriteLine(" Value: {0}", m2.value);
}
}
I just created a vanilla C# VS2010 Console application project with the Refer.cs and App1.cs in the same project.
I made the following changes:
Added RabbitMQ.Client.dll
Removed the AssemblyVersion attributes
Added string[] args to the Main method in App1.cs
Also, I changed:
factory.HostName = "localhost";
To this:
factory.HostName = "192.168.56.101";
Which is the ip address to my VirtualBox Ubuntu VM running rabbitmq-server. There was no exception thrown, and the message successfully was received on the server.
All signs point to server configuration with what is given. My guess is either your rabbitmq-server is not running at all, it's not running on localhost, or there is some kind of connectivity issue with port 5672.

Categories

Resources