Pokazywanie postów oznaczonych etykietą message. Pokaż wszystkie posty
Pokazywanie postów oznaczonych etykietą message. Pokaż wszystkie posty

piątek, 22 lutego 2013

[HTML|JS|CSS] HTML5: WebSockets

Poprzedni post wprowadził do tematyki WebSocketów oraz pokazał, jak prosto zaimplementować je po stronie serwerowej. Tym razem czas na stronę kliencką, gdzie dzięki API HTML5 możemy w prosty sposób wysyłać i odbierać wiadomości. Kluczową funkcją konstruktora jest WebSocket, gdzie jako parametr podaje się URI serwera z gniazdem. Do dyspozycji mamy cztery zdarzenia, do których należy dopisać callbacki. Są to:

  • open
  • message
  • error
  • close
Do komunikacji z serwerem mamy funkcje send i close.

var close = document.getElementById("close");
var send = document.getElementById("send");

socket = new WebSocket("ws://localhost:9001", "echo-protocol");

socket.addEventListener("open", function(event) {
  status.textContent = "Connected";
});

socket.addEventListener("message", function(event) {
  var element = document.createElement('li');
  element.innerHTML = event.data;
  ul.appendChild(element); 
});

socket.addEventListener("error", function(event) {
  alert('Error');
});

socket.addEventListener("close", function(event) {
  status.textContent = "Not Connected";
});


close.addEventListener("click", function(event) {
  message.textContent = "";
  socket.close();
});

send.addEventListener("click", function(event) {
  socket.send(text);
  text.value = "";
});

[HTML|JS|CSS] Node.js: WebSockets

WebSockety to ciekawa technologia wykorzystywana w modelu klient - serwer. Zasada działania jest bardzo prosta. Pomiędzy przeglądarką a serwerem otwierany jest dwukierunkowy kanał komunikacji. Obie strony mogą wysyłać wiadomości bez konieczności odpytywania się nawzajem, a także przetwarzać otrzymane wiadomości na zasadzie obsługi zdarzeń. Zgodnie ze specyfikacją, adresy uri dla websocketów zaczynają się od ws lub wss (dla połączeń szyfrowanych).

Implementując WebSockety w Node.js, najwygodniej jest skorzystać z modułu ws. Obsługa połączenia a także wiadomości tradycyjnie wykonywana jest w callbackach, przy czym należy pamiętać że callback dla wiadomości zagnieżdża się w callbacku obsługi połączenia.

var ws = require('ws');
var WebSocketServer = require('ws').Server;
var wss = new WebSocketServer({port: 9002});

wss.on('connection', function(ws) {
 ws.on('message', function(message) {  
  var date = new Date();
  var str = date.toLocaleDateString() + ' ' +
   date.toLocaleTimeString();

  var reversed = "";
  for (var i = message.length - 1; i >= 0; i--) {
   reversed += message[i];
  }
   ws.send(reversed);
  });

 var date = new Date();
 var str = date.toLocaleDateString() + ' ' +
  date.toLocaleTimeString();
 ws.send('WebSocket connected at: ' + str);
});

wtorek, 10 lipca 2012

[Wzorce projektowe] Event Aggregator

Analogia z życia: 


Nowe wydania gazet pojawiają się przeważnie każdego dnia, co tydzień lub np. co miesiąc. Kiedy tylko wydawnictwo przygotuje całe wydanie, wysyła je do kiosku. Czytelnik zainteresowany swoimi ulubionymi gazetami udaje się do kiosku by nabyć nowe egzemplarze. Informacje na temat tego, czy nowe wydanie już jest dostępne uzyskuje właśnie od kioskarza.



Zastosowanie:

Event Aggregator upraszcza zarządzanie zdarzeniami przez wprowadzenie jednego scentralizowanego obiektu zarządzającego nimi. Dzięki temu powiązania między nadawcami i odbiorcami eventów są zredukowane do minimum. Zatem wzorca tego należy użyć wszędzie tam, gdzie mamy do czynienia z wieloma modułami, które muszą się ze sobą komunikować, a także w każdym innym przypadku, gdzie relacja między nadawcami a odbiorcami zdarzeń przedstawia się jako wiele do wielu. Dodatkowo zmniejszone zostaje ryzyko wycieków pamięci związanych z eventami, a wprowadzenie nowego zdarzenia nie zaburza struktury całości.

Zasada działania:

W omawianym wzorcu występują trzy rodzaje obiektów : nadawcy, odbiorcy i agregator. Zarówno nadawcy, jak i odbiorcy przechowują referencję do agregatora. Gdy zajdzie taka potrzeba nadawcy wywołują odpowiednie metody z agregatora notyfikując odbiorców. Odbiorcy wołają metody subskrypcji by odbierać wiadomości, które ich interesują. Odbiorca może na przykład implementować generyczny interfejs, mówiący o tym, które eventy chce subskrybować. Event Aggregator przechowuje jedynie słabe referencje (takie, które nie powstrzymają GC przed sprzątnięciem obiektu) do subskrybentów, co zapobiega wyciekom pamięci.

Przykład implementacyjny:

 public interface IEventAggregator
    {
        void Subscribe(object subscriber);
        void Publish<TEvent>(TEvent eventToSend);
    }

    public class SimpleEventAggregator : IEventAggregator
    {
        private readonly Dictionary<Type, List<WeakReference>> _subscribersList = 
            new Dictionary<Type, List<WeakReference>>();

        private readonly object _syncObject = new object();

        public void Subscribe(object subscriber)
        {
            lock (_syncObject)
            {
                var subscriberTypes = subscriber.GetType().GetInterfaces().Where(i => i.IsGenericType
                                                                                      &&
                                                                                      i.GetGenericTypeDefinition() ==
                                                                                      typeof (ISubscriber<>));
                var weakReference = new WeakReference(subscriber);
                foreach (Type subscriberType in subscriberTypes)
                {
                    var subscribers = GetSubscribers(subscriberType);
                    subscribers.Add(weakReference);
                }
            }
        }

        private List<WeakReference> GetSubscribers(Type subscriberType)
        {
            List<WeakReference> subscribers;
            lock (_syncObject)
            {
                var found = _subscribersList.TryGetValue(subscriberType, out subscribers);
                if(!found)
                {
                    subscribers = new List<WeakReference>();
                    _subscribersList.Add(subscriberType, subscribers);
                }
            }
            return subscribers;
        }

        public void Publish<TEvent>(TEvent eventToSend)
        {
            var subscriberType = typeof (ISubscriber<>).MakeGenericType(typeof (TEvent));
            var subscribers = GetSubscribers(subscriberType);
            List<WeakReference> toRemove = new List<WeakReference>();

            foreach (WeakReference weakSubscriber in subscribers)
            {
                if(weakSubscriber.IsAlive)
                {
                    var subscriber = (ISubscriber<TEvent>) weakSubscriber.Target;
                    var syncContext = SynchronizationContext.Current;
                    if(syncContext == null)
                        syncContext = new SynchronizationContext();
                    syncContext.Post(s => subscriber.OnEvent(eventToSend), null);
                }
                else
                {
                    toRemove.Add(weakSubscriber);
                }
            }

            if(toRemove.Any())
            {
                lock (_syncObject)
                {
                    foreach (WeakReference weakReference in toRemove)
                    {
                        subscribers.Remove(weakReference);
                    }
                }

            }
        }
    }
 public interface ISubscriber<in T>
    {
        void OnEvent(T message);
    }

    public class StatsCounter : ISubscriber<ScoreMessage>, ISubscriber<AllPlayersFinished>
    {
        public static readonly object _sync = new object();
        private Dictionary<string, int> _players;

        public StatsCounter()
        {
            _players = new Dictionary<string, int>();
        }


        public void OnEvent(ScoreMessage message)
        {
            lock (_sync)
            {
                var name = message.Sender.Name;
                Console.WriteLine(String.Format("{0} scored !", name));
                if(_players.ContainsKey(name))
                {
                    _players[name]++;
                }
                else
                {
                    _players.Add(name,0);
                }
            }
        }

        public void OnEvent(AllPlayersFinished message)
        {
            lock (_sync)
            {
                Console.WriteLine("Results : ");
                foreach (KeyValuePair<string, int> keyValuePair in _players)
                {
                    Console.WriteLine(String.Format("{0}\t scored {1} time(s)"),
                        keyValuePair.Key, keyValuePair.Value);
                }
                Console.ReadKey();
            }
        }
    }

    public class GameManager : ISubscriber<FinishMessage>
    {
        private readonly IEventAggregator _ea;

        private int _counter;

        public GameManager(IEventAggregator eventAggregator)
        {
            _ea = eventAggregator;
        }

        public void OnEvent(FinishMessage message)
        {
            _counter++;
            lock (StatsCounter._sync)
            {
                Console.WriteLine(String.Format("{0} Finished ! <-----",message.Sender.Name));
                Console.ReadKey();
            }
            if(_counter == 3)
                _ea.Publish(new AllPlayersFinished());
        }
    }
public interface IPlayer
    {
        string Name { get; set; }
    }

    public class Player : IPlayer
    {
        private IEventAggregator _ea;

        public int Sum { get; set; }
        public string Name { get; set; }

        public Player(IEventAggregator eventAggregator, string name)
        {
            _ea = eventAggregator;
            Name = name;
        }

        public void Play()
        {
            Random r = new Random();
            while (Sum < 1000)
            {
                int num = (r.Next() % 20);
                Sum += num;
                if(Sum % 10 == 0)
                    _ea.Publish(new ScoreMessage{Sender = this});
                Thread.Sleep(10*num);
            }
            _ea.Publish(new FinishMessage() {Sender = this});
        }
    }
public class ScoreMessage
    {
        public IPlayer Sender { get; set; }
    }

    public class FinishMessage
    {
        public IPlayer Sender { get; set; }
    }

    public class AllPlayersFinished
    {
        
    }
class Program
    {
        static void Main(string[] args)
        {
            IEventAggregator eventAggregator = new SimpleEventAggregator();
            var player1 = new Player(eventAggregator, "Player 1 [POL]");
            var player2 = new Player(eventAggregator, "Player 2 [USA]");
            var player3 = new Player(eventAggregator, "Player 3 [RUS]");
            StatsCounter counter = new StatsCounter();
            eventAggregator.Subscribe(counter);
            GameManager manager = new GameManager(eventAggregator);
            eventAggregator.Subscribe(manager);

            Thread t1 = new Thread(player1.Play);
            Thread t2 = new Thread(player2.Play);
            Thread t3 = new Thread(player3.Play);
            
            Console.WriteLine("The game has started !");

            t1.Start();
            t2.Start();
            t3.Start();
        }
    }

niedziela, 8 lipca 2012

[Wzorce projektowe] Chain of Responsibility

Analogia z życia:

Często zdarza się, że w dużej korporacji szeregowy pracownik zgłasza zapotrzebowanie zakupu jakiegoś drogiego urządzenia, np. tokarki CNC za cenę ponad 100 000 zł. Zgłoszenie to przekazuje swojemu kierownikowi. Kierownik rozważa pewne za i przeciw i może takie zgłoszenie odrzucić, lub przekazać do dyrektora działu. Dyrektor działu konsultuje pomysł ze specjalistami i gdy uzna, że takie urządzenia będzie przydatne, zgłasza zapotrzebowanie do prezesa firmy, a ten kontaktuje się z pracownikiem aby omówić szczegóły zakupu. Jednak w przypadku, gdy pracownik zgłosi zapotrzebowanie na coś tańszego, zgłoszenie zostanie zaakceptowane na niższym poziomie, np. zakup monitora może zatwierdzać kierownik, bez wiedzy przełożonych.



Zastosowanie:

Łańcucha odpowiedzialności używa się wszędzie tam, gdzie mamy do czynienia z wiadomością przekazywaną od nadawcy, do hierarchii odbiorców. Zależnie od treści wiadomości, pierwszy odbiorca może ją przekazać do następnego odbiorcy lub samemu przetworzyć i wysłać odpowiedź, nie informując o tym następnych odbiorców. Nadawca jest zawsze świadomy tylko pierwszego odbiorcy, natomiast każdy odbiorca wie tylko o następnym nadawcy. Nadawca nie wie, kto konkretnie przetworzył wiadomość, interesuje go tylko odpowiedź. Warto także pamiętać o tym, że kolejność jest ważna i należy także obsłużyć przypadek, gdy żaden odbiorca nie przetworzy wiadomości. Zastosowanie wzorca umożliwia dynamiczne zarządzanie MessageHandlerami a także ich hierarchizację.

Zasada działania:

W pierwszej kolejności należy utworzyć interfejs reprezentujący wiadomość, którą należy przetworzyć, a także zastanowić się nad tym, w jakiej postaci ma być przekazywana odpowiedź np. enum. Drugi interfejs będą implementowali odbiorcy wiadomości, którzy będą musieli przeładować metodę zwracają odpowiedź (np. enum) a przyjmującą obiekt typu wiadomości. Odbiorca musi także przechowywać referencję do następnego odbiorcy, który jest wywoływany, gdy odpowiedź nie może zostać przetworzona. Należy pamiętać o zaimplementowaniu w jakiś sposób ostatniego ogniwa z łańcucha.

Przykład implementacyjny:


public interface IPurchaseRequest
    {
        decimal Cost { get; set; }
    }

    public class PurchaseRequest : IPurchaseRequest
    {
        public string Name { get; set; }

        public PurchaseRequest(decimal cost)
        {
            Cost = cost;
        }

        public decimal Cost
        {
            get; set; 
        }
    }

    public interface IPurchaseApprover
    {
        Response ConsiderRequest(IPurchaseRequest request);
        void SetNextSupervisor(IPurchaseApprover approver);
    }

    public enum Response
    {
        Granted,
        Denied,
        NotAllowedToDecide
    }
public class Supervisor : IPurchaseApprover
    {
        public decimal ApprovalLimit { get; set; }
        public IPurchaseApprover Next { get; set; }
        public string Name { get; set; }

        public Supervisor(string n, decimal l)
        {
            ApprovalLimit = l;
            Name = n;
            Next = EndOfChain.Instance;
        }

        public void SetNextSupervisor(IPurchaseApprover approver)
        {
            Next = approver;
        }

        public Response ConsiderRequest(IPurchaseRequest request)
        {
            if (request.Cost < ApprovalLimit)
            {
                return Response.Granted;
            }
            return Next.ConsiderRequest(request);
        }
    }
 public static class EndOfChain
    {
        public static IPurchaseApprover Instance
        {
            get { return new Denier(); }
        }
    }

    public class Denier : IPurchaseApprover
    {
        public Response ConsiderRequest(IPurchaseRequest request)
        {
            return Response.Denied;
        }

        public void SetNextSupervisor(IPurchaseApprover approver)
        {
            throw new NotImplementedException();
        }
    }
class Program
    {
        static void Main(string[] args)
        {
            var cncRequest = new PurchaseRequest(120000);
            cncRequest.Name = "CNC";

            IPurchaseApprover chief = new Supervisor("Chief Tom", 1000);
            IPurchaseApprover cto = new Supervisor("CTO Mark", 50000);
            IPurchaseApprover owner = new Supervisor("Owner Mike", 500000);
            
            chief.SetNextSupervisor(cto);
            cto.SetNextSupervisor(owner);

            Console.WriteLine("We need to buy CNC");
            var resp = chief.ConsiderRequest(cncRequest);
            if(resp == Response.Granted)
                Console.WriteLine("OK, Let's do it");
            else
            {
                Console.WriteLine("We do not need it");
            }
            Console.ReadKey();

        }
    }