Poradniki – Nastoletni Programiści Thu, 15 Nov 2018 14:35:06 +0000 pl-PL hourly 1 https://wordpress.org/?v=4.9.8 115968029 Wstęp do Machine Learning /wstep-do-machine-learning/ /wstep-do-machine-learning/#respond Sun, 20 May 2018 16:35:14 +0000 /?p=1766 Wstęp do Machine Learning

Machine Learning (pol. uczenie maszynowe, samouczenie się maszyn, systemy uczące się) jest jedną z aktualnie najlepiej prosperujących dziedzin w świecie IT.

W 1959 Alan Turing i Arthur Samuel amerykańscy pionierzy w dziedzinie gier komputerowych i sztucznej inteligencji ukształtowali współczesny termin machine learning .

The field of study that gives computers the ability to learn without being explicitly programmed.

~ Arthur Samuel

Oznacza to możliwość nauki programu komputerowego na podstawie wcześniej przygotowanych danych w celu rozwiązania konkretnego problemu bez znania sposobu na osiągnięcie go, czyli wcześniejszej listy kroków którą ma wykonać program. Oparte jest to o zestaw gotowych algorytmów z podanych dziedzin matematyki:

  • algebra liniowa
  • rachunek prawdopodobieństwa
  • statystyka

Sieć neuronowa

Machine Learning działa w oparciu o sieci neuronowe wzorowane na ludzkich. Powyższy schemat sieci neuronowej pokazuje jej uproszczony sposób działania. Składa się ona z neuronów . Każdy neuron jest wprowadzany przez dendryty od innych neuronów przenosząc sygnały otrzymywane z innych neuronów przez synapsy . Na grafice powyżej czerwone neurony stanowią warstwę wejściową, niebieskie – warstwę ukrytą, a zielone – warstwę wyjściową. Ilość ukrytych warstw jest zależna od tzw. głębokości , im głębsza tym bardziej skomplikowane są połączenia między nimi.

Podczas trenowania własnej sieci neuronowej dane są przekazywane od neuronu do neuronu, każdy kolejny może zapamiętywać (Recurrent Neural Network) wynik poprzedniego i przekazywać coraz to lepsze dane. Im więcej razy będziemy trenować taką sieć tym mniej błędów i lepszy wynik.

Rodzaje sieci neuronowych w jednym zdaniu

Feedforward Neural Network

Ta sieć bardzo dobrze klasyfikuje proste rzeczy, ale nie pamięta o poprzednich czynnościach/zmianach oraz ma nieskończone warianty wyników.

Recurrent Neural Network

W tym przypadku sieć pamięta poprzednie zmiany i ma skończony wariant wyników.

Sposoby uczenia

Uczenie nadzorowane (supervised learning)

Ten sposób uczenia polega na stworzeniu modelu danych, wejściowych czyli próbek (samples) wraz z modelem wyjściowym na przykład etykiet. Gdy wytrenujemy naszą sieć będziemy mogli prawidłowo przypisać wyjście dla obiektu którego dotychczas nie było na wejściu. Nasza sieć uczy się w oparciu o nasz model tzw. dataset .

Uczenie nienadzorowane (unsupervised learning)

Tym razem sieć nie otrzymuje danych wyjściowych (etykiet), więc sama musi znaleźć odpowiedni sposób, aby uzyskać dane wejściowe.

Uczenie przez wzmacnianie (reinforcement learning)

W uczenie przez wzmacnianie sieć działa bez określonych danych wejściowych i wyjściowych. Jedyne informacje jakie otrzymuje to tzw sygnał wzmocnienia, który może być pozytywny (nagroda) w przypadku podejmowania trafnych decyzji lub negatywny w przypadku mylenia się (kara). Jest to całkiem niezły sposób na naukę naszej AI grania w proste gry bez pomocy człowieka czy nawet zostania mistrzem świata w Go i tworzenia własnych skomplikowanych strategii. Jest to niestety najbardziej wymagający czasu jak i mocy obliczeniowej sposób nauki.

Rodzaje problemów

Dwa najpopularniejsze rodzaje problemów to klasyfikacja oraz regresja.

Klasyfikacja (classification)

Nasze wejściowe dane łączone są w model na podstawie którego jest określane czy spełnione są odpowiednie warunki czy też nie. Wynik z największym prawdopodobieństwem zostanie nam zwrócony. Na przykład rozpoznawanie ręcznie narysowanych cyfr, w tym przypadku mamy dataset w którym są próbki z zdjęciami narysowanych cyfr oraz etykiety dla każdego z nich. Podajemy nasze dane (zdjęcie) oraz trenujemy sieć używając naszego datasetu i otrzymujemy wynik.

Regresja (regression)

Działa to tak jak wyżej, lecz z jedną różnicą, a mianowicie tutaj otrzymujemy ciąg wyników/tablice z wynikami, a nie tylko jeden wynik. Przykładem problemu regresji może być przewidywanie ceny bitcoina w zależności od jego aktualnego kursu oraz w oparciu o dane z poprzednich miesięcy.

Podsumowując, Machine Learning ma wielki potencjał niemal w każdej dziedzinie, myślę, że ten wstęp powinien przybliżyć Ci jak mniej więcej to działa.

Źródła:

  • https://en.wikipedia.org/wiki/Artificial_neural_network
  • http://scikit-learn.org/
  • http://itcraftsman.pl/wstep-do-machine-learning/
]]>
/wstep-do-machine-learning/feed/ 0 1766
Sztuczna inteligencja w grach #01 – wstęp, zmysł wzroku oraz prosty debug-mode. /sztuczna-inteligencja-w-grach-01-wstep-zmysl-wzroku-oraz-prosty-debug-mode/ /sztuczna-inteligencja-w-grach-01-wstep-zmysl-wzroku-oraz-prosty-debug-mode/#comments Fri, 14 Apr 2017 12:27:34 +0000 /?p=1452 Sztuczna inteligencja w grach jest nieodłącznym elementem od dawien dawna. Jest ona mózgiem każdego wroga i NPC-ta. Kiedy programujemy A.I. mamy ogromne pole do popisu. Rozbudowywanie jej nie jest trudne i daje świetne efekty. Jest to element, który łatwo dostrzec i dzięki temu ludzie doceniają Twoją pracę jeszcze bardziej. W różnych grach można spotkać przeróżny poziom jakości zachowań postaci sterowanych komputerem. To jak dobre A.I. będą mieli wrogowie, często ma największy wpływ na końcową ocenę gry.

Niektóre z tytułów wręcz są popularne ze swojej prymitywnej S.I.:

Policjanci w GTA.

Źródło: http://www.memecenter.com/fun/509528/gta-sa-logic

Wbrew popularnej opinii sztuczna inteligencja policjantów w San Andreas nie jest aż taka prosta, jednak można by było ją trochę doszlifować. W tym artykule rozpoczniemy tworzenie podstawowego zmysłu dla przeciwników – zmysłu wzroku i wprowadzimy sobie prosty interfejs do debuggingu.

Zaczniemy od kodu finalnego z mojego poprzedniego artykułu: Jak programować gry? Najważniejsze elementy w grach 2D. Kod źródłowy z tamtego artykułu znajdziecie tutaj: GitHub

1. Przygotowanie sterowania i postaci pod grę.

Rozpoczniemy od stworzenia klasy gracza oraz klasy przeciwnika, które nazwiemy odpowiednio CPlayer oraz CEnemy . Następnie stworzymy prosty kontroler dla obydwu, a dodatkowo w kontrolerze gracza dodamy możliwość poruszania się.

Podobny schemat realizowałem już w filmie o tworzeniu bombermana, jednak przewiduję, że prawdopodobnie go nie obejrzałeś (bo komu chciałoby się oglądać w całości film o tworzeniu gry trwający 2 godziny). Z tego względu jeszcze raz wytłumaczę, co zrobiliśmy:

I) Utworzyliśmy klasę gracza i jego kontrolera:

  • CPlayer – klasa gracza
  • CPlayerController – klasa kontrolera gracza zawierająca sterowanie

Opisałem kod tak, by było dokładnie widać co krok po kroku trzeba wykonać.

Plik nagłówkowy Player.hpp :

Plik źródłowy Player.cpp :

II) Utworzyliśmy klasę wroga i jego kontrolera. Póki co nasze A.I. zostawimy w spokoju. Zajmiemy się nim w następnej części tego artykułu.

  • CEnemy – klasa wroga
  • CEnemyAIController – klasa kontrolera S.I. wroga

Plik nagłówkowy Enemy.hpp :

Plik źródłowy Enemy.cpp :

III) Wczytaliśmy potrzebne tekstury przy uruchamianiu gry:

IV) Stworzyliśmy gracza i jednego wroga, a następnie dodaliśmy ich do poziomu:

2. Utworzenie zmysłu wzroku.

Sztuczna inteligencja w grach jest oparta o różne zmysły np.: zmysł wzroku, zmysł słuchu czy nawet czasem zmysł węchu. Najprostszym do zaimplementowania jest zmysł wzroku, który jak zapewne łatwo jest się domyślić, będzie odpowiadał za widzenie. Będzie on działał w ten sposób, że będzie sprawdzał, czy jest coś interesującego w zasięgu wzroku oraz w obszarze widoku (kąt widzenia zrobimy łatwy do dostosowania) i jeśli coś wykryje, to zwróci to w wyniku.

Na początku, jako że wszystkie zmysły będą miały kilka wspólnych metod i własności, utworzymy sobie klasę bazową. Nazwałem ją IAISense (w swoich projektach przedrostek „I” dodaje do klas bazowych, dostarczających swego rodzaju interfejs do późniejszego rozbudowywania, jednak takich, które nie mogą być bezpośrednio używane). Do implementacji stworzymy sobie osobne pliki – AISense.hpp i AISense.cpp .

Teraz, zanim przejdziemy do kodu, zastanówmy się, co będzie musiał mieć każdy zmysł. Po chwili rozmyślań doszedłem do wniosku, że:

  • zmysł musi wiedzieć, kto jest jego właścicielem, gdyż każdy z nich musi znać np. pozycje właściciela;
  • zmysł musi posiadać jakąś listę postrzeganych aktorów;
  • zmysł musi dostarczać możliwość uaktualnienia postrzeganych aktorów (np. gdy ktoś wejdzie do pola widzenia albo wyjdzie z niego).

Będąc świadomym tego, do czego dążymy, utworzyłem następującą klasę bazową:

Warto bardzo dokładnie przeanalizować komentarze, szczególnie ten przy metodzie QueryActors, bo jest to kluczowa metoda tej klasy. Teraz do zaimplementowania został nam tak naprawdę tylko konstruktor (zwróć uwagę, że QueryActors jest metodą czysto wirtualną, GetSensedActors jest zdefiniowana jako inline ).

Kod konstruktora jest bardzo prosty:

Pokusiłem się tutaj o referencje na klasę IPawn (lub bazową) właściciela. Takie rozwiązanie wymusza podanie go (gdybyśmy użyli wskaźników, ktoś mógłby podać np. pusty wskaźnik), przez co reszta kodu, która się do niego odnosi, zadziała poprawnie.

Następnym etapem będzie stworzenie zmysłu wzroku. Aby mieć jakiś wgląd w sposób jego działania, spójrz na poniższy obraz:

Zatem mamy do stworzenia kolejną klasę, która będzie zawierała kąt i zasięg widzenia oraz implementacje metody QueryActors , która sprawdzi, czy aktorzy są polu widzenia. Zdecydowałem, że umieszczę tę klasę również w plikach AISense.hpp i AISense.cpp by nasz projekt nie miał za chwile 40 plików.

Tak oto wygląda ta klasa:

Tym razem do zaimplementowania mamy konstruktor oraz trzy metody. Może zajmijmy się najpierw konstruktorem:

Nie wrzuciłem ustawienia m_sightDistance i m_sightAngle do listy inicjalizacyjnej, ze względu na to, że mamy od tego odpowiednie funkcje, które zapobiegają wprowadzeniu nieodpowiednich danych.

Teraz spójrzmy dalej do metod SetSightDistance i SetSightAngle . Odpowiednie środki bezpieczeństwa są tutaj wymagane. Nie chcemy przecież mieć ujemnego zasięgu lub kąta widzenia > 180 stopni. Warto również zauważyć, że podane funkcje zwracają true , jeśli poprawnie ustawiono wartość a false , jeśli wartość była niepoprawna. Nie będziemy póki co z tego korzystać, ale warto mieć coś takiego zaimplementowanego – może niedługo się przyda 🙂

Przyszedł czas na kluczowy moment. Teraz zajmiemy się całą logiką zmysłu. Pomyślmy, co powinien on krok po kroku zrobić:

  • pobrać listę wszystkich aktorów ze sceny;
  • każdy aktor powinien być sprawdzony pod niżej wypisanymi kryteriami. Jeśli wszystkie z nich zostaną spełnione, dodajemy go do wynikowej listy aktorów;
    • aktor nie jest właścicielem tego zmysłu;
    • aktor znajduje się w odległości mniejszej niż zasięg wzroku;
    • kąt, pod którym znajduje się aktor względem właściciela, jest mniejszy niż kąt widzenia;
    • funkcja filtrująca pozwoliła aktorowi na dodanie do wyników.
  • zwrócić końcową listę aktorów.

Problemem jednak jest to, że w poprzednim artykule nie stworzyliśmy sobie metody, dzięki której zyskamy dostęp do listy aktorów ze sceny. Dlatego właśnie potrzebna jest nam poniższa funkcja, którą dodałem do klasy CLevel :

Na tych założeniach zbudowałem taką funkcję. Polecam przeanalizować po kolei każdy etap jej działania, gdyż właśnie jesteśmy w punkcie kulminacyjnym tego artykułu.

Uff… już najtrudniejsze za nami. Teraz możemy dodać już zmysł wzroku do wroga.
CSightSense m_sightSense; // Zmysl wzroku wroga
Należy również pamiętać o prawidłowym wywołaniu konstruktora zmysłu z poziomu listy inicjalizacyjnej. Następnie, aby nasz zmysł „działał” musimy go ciągle uaktualniać. Do tego posłuży nam metoda IActor::Update , którą sobie przeładujemy. Musimy jednak pamiętać, że metoda ta również ma swoją implementację w klasie bazowej IPawn , więc musimy umieścić też odwołanie do implementacji bazowej:

Co nam jednak z tego, że tak się narobiliśmy, a wciąż nie widzimy efektów? Właśnie dlatego teraz zaimplementujemy…

3. Widok informacji dla debuggingu

Wyszukiwanie błędów w grach jest bardzo uciążliwe, jeśli przed sobą mamy same cyferki i nic konkretnego. Dlatego właśnie wiele gier dodaje sobie prosty panel debugowania „in-game”. Tym właśnie się teraz zajmiemy.

Debugowanie w Gothic 2 NK

Na samym początku już wiemy, że potrzebna będzie nam funkcja generująca kształt wycinka koła. Jako że SFML sam jej nie dostarcza, postanowiłem napisać ją sam. Stworzyłem więc plik z deklaracją SFMLShapes.hpp oraz plik źródłowy SFMLShapes.cpp . Możliwe, że w przyszłości potrzebne będzie nam więcej własnych kształtów i wtedy umieścimy ich generowanie również w tych plikach.

Nazwałem ten kształt Pie (odnosi się do angielskiej nazwy ciasta, ponieważ nasz kształt to jakby wycinek ciasta 😉 ).

Powyższa funkcja nie jest w pełni funkcjonalna, jednak wystarcza do podstawowych zastosowań. Jej mankamentem jest to, że używa sf::ConvexShape (convex – wypukły). Może to spowodować niechciany efekt przy wyświetlaniu wycinka o kącie > 90 stopni. Jeśli ktoś ma na tyle ochoty, żeby się z tym bawić, to polecam do tego użyć sf::VertexArray .

Teraz już możemy wykorzystać ten generator. Pamiętamy jeszcze klasę IAISense ? Każdy zmysł będzie mógł się popisać jakimś fajnym symbolem przy debuggingu, dlatego też utworzymy metodę dla tej klasy, która będzie rysowała takie symbole na ekranie.

Metoda ta, nie jest metodą czysto wirtualną, bo zmysł ma mieć możliwość wyświetlenia debug info, ale nie jest do tego zmuszany.

Teraz czym do diabła jest to static bool DebugMode; ?

Dobrym pomysłem jest posiadanie jakiegoś przełącznika, którym będziemy sterowali, by albo włączyć tryb testowy, albo go wyłączyć. Statyczna zmienna DebugMode jest właśnie takim przełącznikiem. Oczywiście pamiętamy o tym, że taką zmienną statyczną trzeba też zainicjalizować. Najprościej będzie zrobić to na początku pliku źródłowego:

bool IAISense::DebugMode = true;

Mamy już bazę, to teraz trzeba zaimplementować wyświetlanie informacji trybu testowego dla zmysłu wzroku. Zrobiłem to tak:

Weźmy głęboki oddech… w tym miejscu artykuł ten możemy zakończyć jedną linijką, którą dodamy, by nasz debug info mógł się w ogóle wyświetlić. Jak zapewne się już domyśliłeś, umieścimy ją w metodzie wyświetlającej wroga na ekranie.

m_sightSense.DrawDebug();

4. Podsumowanie

W tym artykule zbudowaliśmy sobie podstawę pod dalsze rozwijanie modułu sztucznej inteligencji. Dodaliśmy również możliwość wyświetlania podstawowych informacji debuggingu. Kod z tego artykułu znajdziesz tutaj: GitHub . Teksturki można pobrać tutaj: Mega .

]]>
/sztuczna-inteligencja-w-grach-01-wstep-zmysl-wzroku-oraz-prosty-debug-mode/feed/ 2 1452
Zabezpieczenie antypirackie – aktywacja online z algorytmem RSA i BCrypt /zabezpieczenie-antypirackie-aktywacja-online-z-algorytmem-rsa-i-bcrypt/ /zabezpieczenie-antypirackie-aktywacja-online-z-algorytmem-rsa-i-bcrypt/#comments Mon, 27 Feb 2017 18:19:32 +0000 /?p=1307 Tworzycie aplikacje, które chcecie potem sprzedać? Za pewne chociaż przez chwilę pomyślałeś: „a co z piratami?”. W tym artykule chciałbym pokazać przykład zabezpieczenia aplikacji z aktywacją online, a także pokazać, jak szyfruje się algorytmem RSA i hashuje BCrypt’em.

Uwaga, aby artykuł nie miał setek stron, pominąłem dogłębne tłumaczenie kodu Javy/PHP/SQL. Co nie znaczy, że jeśli jesteś zaciekawionym tematem to nic nie zrozumiesz. Postarał się w miarę możliwości dobrze okomentować kod. W każdym razie czujcie się ostrzeżeni!

Z nielegalnym oprogramowaniem spotykamy się na co dzień – w Polsce jest ono dosyć popularne . Tworząc aplikację, którą chcemy sprzedać komuś, chcielibyśmy zadbać o to, żeby inni nie mieli do niej dostępu. Dlatego programiści starają się wymyślać coraz to nowsze zabezpieczenia przeciw piratom. Chciałbym pokazać jedną z prostych metod zabezpieczenia aplikacji.

Typowe zabezpieczenia:

  1. Aktywacja offline – tzn. wpisujemy klucz i wszystko działa
    • Zalety:
      • Brak konieczności połączenia z internetem
      • Najmniejszy problem dla użytkownika
    • Wady:
      • Najgorsze zabezpieczenie, możliwość wpisania tego samego klucza na wielu urządzeniach
  2. Aktywacja online z kontem – wpisujemy klucz i zostaje on przypisany do konta
    • Zalety:
      • Klucz nie jest przypisany do urządzenia, lecz do konta (możliwość przeniesienia licencji na inne urządzenia)
      • Po pierwszej aktywacji, można korzystać z programu offline
    • Wady:
      • Konieczność jednorazowego połączenia z internetem
      • Każdy użytkownik musi zrobić konto
      • Serwer aktywacji musi stale być dostępny, lecz tylko na czas aktywacji
  3. Aktywacja urządzenia online – wpisujemy klucz i urządzenie jest zapisywane do bazy danych
    • Zalety:
      • Brak możliwości skopiowania pliku weryfikującego licencję
      • Po pierwszej aktywacji, można korzystać z programu offline
    • Wady:
      • Konieczność jednorazowego połączenia z internetem
      • Serwer aktywacji musi stale być dostępny, lecz tylko na czas aktywacji
      • Brak możliwości aktywacji na kolejnym urządzeniu (co jeśli komputer się zepsuje?)
  4. Stały wymóg online
    • Zalety:
      • Najlepsze zabezpieczenie, bez zmian w kodzie programu trudne do obejścia
      • Możliwość instalacji programu na różnych urządzeniach (z tym że jednocześnie można włączyć tylko jeden)
    • Wady:
      • Konieczność stałego połączenia z internetem.
      • Serwer aktywacji musi stale być dostępny! Inaczej użytkownik nie włączy programu.

Do każdego z tych punktów można dopisać jeszcze jedną wadę:

Żadne zabezpieczenie nie jest całkiem bezpieczne

Żadne z tych zabezpieczeń nie odeprze jednego: zmiany kodu programu. Dlatego warto jest korzystać z różnych zabezpieczeń, które modyfikują nam nazwy zmiennych/funkcji w kodzie, aby potem kod był trudny do rozczytania po dekompilacji.

Aktywacja systemu Windows

Przed zastosowaniem zabezpieczenia warto przemyśleć, które warto wybrać. Jak każdy wie, stały wymóg połączenia internetowego może być denerwujący dla użytkownika. Jeżeli nie robicie aplikacji, która operuje na połączeniu internetowym, to nie zawsze warto wybierać to zabezpieczenie. Na dzień dzisiejszy najlepszym sposobem wydawać się może jednorazowa aktywacja – ma ją np. Windows, Steam. W tym artykule chciałbym zaprezentować mój sposób na system aktywacji, który przedstawiłem jako nr. 3 w liście wyżej; czyli – aktywacja urządzenia online.

Dlaczego akurat to wybrałem? Gdy myślałem o zastosowaniu mojej aplikacji, stwierdziłem, że zależy mi, aby nie była ona wykorzystywana przez jedną osobę na kilku urządzeniach. Dlatego aktywacja urządzenia wydaje się być rozsądnym wyborem.

Jak to wygląda?

Identyfikator sprzętowy

Przy aktywacji urządzenia pierwszą rzeczą, jaka jest nam potrzebna, to pobranie jakiegoś identyfikatora urządzenia. I to jest drobny problem – co można wziąć? W większości przypadków znajdziecie na internecie wykorzystanie adresu MAC karty sieciowej, ale to rozwiązanie jest dosyć słabe – ponieważ w przypadku laptopów jest możliwość, iż aplikacja była instalowana gdy interfejs sieciowy WLAN był włączony, a potem wyłączony. Adres MAC w aplikacji zostanie zwrócony inny i aplikacja przestanie działać . Dlatego polecam np. wykorzystać identyfikator BIOS’u. W Windowsie pobiera się go wpisując komendę w konsoli: wmic bios get serialnumber , w przypadku Linuxa sprawa jest nieco trudniejsza, ale i tak są pewne rozwiązania.

Schemat aktywacji

Okej – mamy już identyfikator sprzętowy – teraz przydałoby się go jakoś wysłać do serwera i odebrać plik weryfikacyjny. Tak to będzie wyglądać:

  1. Urządzenie klienta wysyła do serwera informację z identyfikatorem urządzenia + kluczem produktu do serwera.
  2. Serwer sprawdza czy klucz jest w bazie danych, jeśli jest to zwiększa jego licznik użycia i dodaje identyfikator urządzenia do tabeli.
  3. Następnie szyfruje algorytmem RSA przy użyciu klucza prywatnego sam identyfikator, wpisuje go do bazy danych i
  4. odsyła klientowi.
  5. Gdy klient odbierze plik, odszyfrowuje go za pomocą klucza publicznego i sprawdza czy jest to ten sam identyfikator sprzętowy.
  6. Następnie zapisuje zaszyfrowaną wiadomość w pliku *.key, który znajduje się w lokalizacji instalacji programu.

Ogólny schemat pierwszej aktywacji prezentuje się tak:

Schemat będzie się nieco różnić w przypadku, gdy klient aktywuje drugi raz aplikację na tym samym urządzeniu. Wtedy gdy serwer wykryje że klucz jest już użyty (czyli used_num>=1 ) to będzie sprawdzać tabelę activations i będzie próbował znaleźć hardware_id taki, jaki wysłał klient. Jeśli znajdzie takowy w tabeli, to oznacza, że klucz jest poprawny i odsyła klientowi klucz weryfikacyjny z kolumny response_key .

Przejdźmy do implementacji. Potrzebny nam serwer + baza danych na tym serwerze. Ja wybrałem bazę danych MySQL. Stworzyłem bazę danych o nazwie Activation , a w środku dwie tabele:

  • activation_keys – w tej tabeli zawarte są wszystkie zaszyfrowane klucze programu i ich liczba użyć
    CREATE TABLE activation_keys (id int PRIMARY KEY NOT NULL AUTO_INCREMENT, activation_key varchar(72) NOT NULL, used_num int);
  • activations – w tej tabeli zwarte są wszystkie aktywacje, identyfikator sprzętowy i identyfikator klucza z tabeli activation_keys
    CREATE TABLE activations (id int PRIMARY KEY NOT NULL AUTO_INCREMENT, id_key int NOT NULL, hardware_id varchar(64), response_key TEXT);

Identyfikator urządzenia będziemy pobierać, korzystając z wyżej wymienionego kodu + hashowania SHA-256.

Klucze będziemy szyfrować algorytmem BCrypt , który generuje maksymalnie 72-znakowy kod. Na obrazku obok jest pokazany schemat BCrypt. Jest to bardzo zalecane , gdyż mając atak hakerów na bazę danych, będą oni w stanie sprawdzić wszystkie dostępne klucze . Ogólnie, BCrypt zamienia klucz do formy zhashowanej. Ten proces trudno odwrócić, tzn.posiadając hash, trudno odnaleźć taki klucz, który po zhashowaniu będzie mieć taką samą formę jak zhashowany. BCrypt często wykorzystuje się do zapisywania haseł użytkowników na wielu stronach internetowych. Klient wysyła do serwera hasło w formie zahashowanej , a następnie jest sprawdzana jego poprawność. W przypadku ataku na bazę danych z hasłami, haker będzie mieć tylko tabelę z hasłami zahashowanymi oraz będzie mieć problem z odwróceniem procesu hashowania. Biblioteki BCrypt znajdziecie w Google.

Uwaga! Np. SHA-1, SHA-256 przestał być bezpiecznym algorytmem i dziś z dobrym komputerem nie ma większych problemów z inwersją. Jeżeli zależy wam na zabezpieczeniu przeciw dobrym hakerom, to polecam poszukać w Google nowych, skuteczniejszych zabezpieczeń. Jednak jeśli chcecie się obronić przed „Script kiddie”, to wystarczy nawet MD5. (nie warto też wrzucać tysięcy kluczy do tabeli, lecz dodawać nowe przy zakupie przez daną osobę)

Do tworzenia kluczy polecam stworzyć program. Ja uznałem że moje klucze będą 16 znakowe. Tak prezentuje się kod w Javie do wygenerowania losowego klucza i zhashowania go. Potrzebna jest też biblioteka BCrypt dla Javy: link .

BCrypt.gensalt(10) – decyduje o złożoności hasła. Czym większa liczba w argumencie, tym trudniej będzie odszyfrować. Ale też sprawdzanie za pomocą serwera będzie dłuższe. BCrypt.gensalt() generuje optymalną wartość, wybraną przez twórcę biblioteki. Warto zauważyć, że duża wartość może być też problemem dla serwera przy sprawdzaniu poprawności klucza.

String AB – w tym łańcuchu znaków znajdują się wszystkie znaki, które będą mogły znaleźć się w kluczu niezaszyfrowanym.

funkcja randomString – zwraca losowy łańcuch znaków o długości len , z wykorzystaniem znaków z łańcucha znaków String AB .

Test generacji klucza

Dodajmy od razu testowy klucz 1234567890123456. Aby taki zrobić wystarczy w kodzie zamiast String key = randomString(16); dać String key = "1234567890123456"; Klucze w takiej formie będą przechowywane w tabeli do której nikt nie ma prawa mieć dostępu. Możecie zrobić bazę danych albo po prostu wklejać je do arkusza kalkulacyjnego. Otrzymujemy zaszyfrowany klucz – $2a$10$SjT/4zh1LwNrsiKCvhUlzec4bAilAsvhw3xb2vpJkRK6eTacCTJFy. Teraz wpisujemy go do tabeli na serwerze korzystając z komendy SQL INSERT INTO .

INSERT INTO `activation_keys`(`activation_key`,`used_num`) VALUES (" $2a$10$SjT/4zh1LwNrsiKCvhUlzec4bAilAsvhw3xb2vpJkRK6eTacCTJFy ",0);

Czyli zhashowane klucze dodajemy do tabeli SQL, a klucz niezhashowany wysyłamy klientowi. Aplikacja będzie hashować klucz wpisany przez klienta, następnie wysyła go do serwera, który będzie porównywać wszystkie zhashowane klucze.

Okej, więc mamy bazę danych z kluczami gotową. Przejdźmy teraz do pliku weryfikującego licencję. Aby program zadziałał, w swojej lokalizacji musi mieć plik activation.key , który po odszyfrowaniu kluczem publicznym RSA wskaże SHA-256 identyfikatora sprzętowego urządzenia, na którym jest odpalany właśnie ten program. Ten plik activation.key będzie tworzony podczas pierwszej aktywacji (serwer wyśle odpowiedni kod dla klienta, który następnie zostanie zapisany w owym pliku). Trudno stworzyć taki klucz, żeby po odszyfrowaniu kluczem publicznych, dał SHA-256 identyfikatora, dlatego to zabezpieczenie jest w miarę bezpieczne. Serwer, posiadając klucz prywatny RSA może z łatwością stworzyć klucz weryfikacyjny. Rysunek pomoże dobrze zinterpretować to co napisałem:

Tworzenie klucza RSA

Dla przypomnienia – schemat szyfrowania RSA . Wszystko pokazane na obrazku wyżej: kluczem publicznym szyfrujemy wiadomość; zaszyfrowanej wiadomości nie można rozszyfrować kluczem publicznym , potrzebny jest do tego klucz prywatny . Szyfrowanie to może też działać w drugą stronę – można szyfrować kluczem prywatnym, a odszyfrowywać publicznym, co dziś wykorzystamy. W naszym przypadku klucz weryfikacyjny będzie szyfrowany kluczem prywatnym na serwerze , a potem klucz będzie odszyfrowywany w aplikacji kluczem publicznym.

Jak stworzyć klucz RSA ? Do jego stworzenia wykorzystam narzędzie OpenSSL , które można pobrać tu dla Windowsa: http://gnuwins32.sourceforge.net/packages/openssl.htm
Program ten odpala się w trybie konsolowym. Potrzebne nam będzie parę komend:

  • openssl genrsa -out private_key.pem 2048 – tworzy 2048-bitowy klucz prywatny, o rozszerzeniu *.pem, które dobrze odczyta PHP. Jeśli chcecie lepszy poziom zabezpieczeń, to możecie użyć 3072 bitów.
  • openssl pkcs8 -topk8 -inform PEM -outform DER -in private_key.pem -out private_key.der -nocrypt – tworzy plik private_key.der w formacie PKCS#8, który lepiej jest odczytywany w Javie (bez kombinowania)
  • openssl rsa -in private_key.pem -pubout -outform DER -out public_key.der – tworzy plik public_key.der, który łatwiej odczytać w Javie (bez tworzenia przeróżnych funkcji które zmieniają format klucza)

Są klucze, więc przyszła część na PHP, do którego będzie się zwracał klient. PHP będzie wypisywać proste komunikaty typu „ERROR”, „OK”, „WRONGKEY”, które następnie program wczyta i wskaże odpowiedni komunikat.

Klucz i identyfikator urządzenia będą wysyłane metodą POST. Wynik będzie wypisany funkcją echo, a oto lista możliwych wyników:

  • OK;klucz_weryfikacyjny; – aktywacja przebiegła pomyślnie, pomiędzy średnikami znajduje się klucz weryfikacyjny (zaszyfrowany kluczem prywatnym)
  • OK_USED;klucz_weryfikacyjny; – aktywacja przebiegła pomyślnie, nie jest to pierwsza aktywacja na tym urządzeniu
  • WRONGKEY – klucz nie znajduje się w bazie
  • WRONGHARDWARE – identyfikator urządzenia jest pusty
  • USED – klucz jest już aktywowany dla innego urządzenia
  • ERROR – Błąd przy łączeniu z bazą lub inny

Po co te średniki w odpowiedziach? Są one po to aby wydzielić granice klucza weryfikacyjnego. Głębsze wyjaśnienie tego znajdziecie w dalszej części artykułu.

Program desktopowy będzie sprawdzać, jaki kod został otrzymany i wyświetli odpowiedni komunikat.

Część serwerowa – plik PHP

Na początku stworzyłem sobie dwie funkcje, które kończą wykonywanie pliku PHP i zwracają błąd:

Jeśli jakimś cudem nie ma parametrów HTTP POST lub identyfikator sprzętowy jest pusty to należy zakończyć działanie programu:

Klucz prywatny (private_key.pem) wrzucamy do poprzedniego katalogu niż jest plik PHP. Upewnijmy się, że prawa do pliku są tylko dla serwera i że nie można tego pliku otworzyć bezpośrednio z innego urządzenia.

Teraz należy pobrać klucz prywatny RSA z poprzedniego katalogu, który nie jest dostępny dla nikogo:

Połączenie się z bazą danych i ewentualne zwrócenie błędu:

Pobieramy rekordy z tabeli activation_keys i sprawdzamy, czy klucz jest dobry.

password_verify() – sprawdza czy klucz w formie niezaszyfrowanej, jest odpowiednikiem zaszyfrowanego podanego w drugim argumencie (BCrypt)

trim() – usuwa spacje na początku i końcu klucza. (zabezpieczenie, jeśli do bazy danych daliśmy zaszyfrowany klucz z niepotrzebną spacją – mi się to zdarzyło)

Jeżeli klucz nie został jeszcze użyty – czyli $used jest równe 0, to przygotujemy klucz weryfikacyjny dla klienta metodą openssl_private_encrypt($informacja_do_zaszyfrowania, $zmienna_gdzie_zapisze_sie_zaszyfrowany_tekst, $klucz_prywatny_RSA); Następnie należy powiększyć licznik użycia klucza i dodać identyfikator sprzętowy do tabeli activations . Na sam koniec wystarczy zwrócić klientowi klucz weryfikacyjny funkcją echo , który następnie klient zapisze do pliku.

Użyte jest to proste zabezpieczenie przed SQL Injection, więcej o tym możecie poczytać w innym artykule na tej stronie: Zabezpieczenia baz danych

Jeżeli klucz jest użyty to należy sprawdzić, czy urządzenie o identyfikatorze wysłanym w HTTP POST występuje w tabeli activations . Jeśli występuje to należy klucz weryfikujący wysłać klientowi.

Jeśli po zakończeniu pętli nie odnaleziono klucza, to należy zwrócić błąd – zły klucz.

Cały plik prezentuje się tak:

Możecie stworzyć też prosty plik HTML i porobić parę testów:

Aplikacja desktopowa

W tym fragmencie artykułu będę opisywał stworzenie aplikacji desktopowej Javie, co nie zmienia faktu, że tak samo da się zrobić w innych językach programowania.

Zacznijmy od stworzenia projektu i dodania biblioteki BCrypt. Link do biblioteki: http://www.mindrot.org/projects/jBCrypt/

Stwórzmy najpierw klasę pomocniczą Activation.java . Będzie ona pomagać w tworzeniu klucza identyfikatora urządzenia i do odszyfrowania klucza weryfikacyjnego:

Jak pewnie zauważyliście, do pobrania identyfikatora sprzętowego potrzebna jest magiczna klasa WindowsID. Wyżej napisałem, jak w przypadku systemu Windows pobierać identyfikator. Oto ta klasa:

W przypadku innych systemów też znajdziecie różne rozwiązania na pobranie identyfikatora sprzętowego.

Przejdźmy teraz do GUI aplikacji. Na początku można zrobić prosty layout w JavaFX (Java Scene Builder):

A tutaj przegląd obiektów, dla których przypisałem jakieś id. Jest to potrzebne po to, aby już w kodzie Javy móc pobrać te obiekty:

Do katalogu resources (jeśli nie wiecie jak dodawać to zapraszam tutaj: link ) wrzucamy plik public_key.der . Startowy plik Javy wygląda tak:

OnActivationListener zawiera funkcję, która zostanie aktywowana, jeśli aktywacja się powiedzie oraz funkcję pobrania klucza publicznego.

Teraz część kontrolera Aktywacji. W JavaFX, korzystając z kontrolera scen, można łatwo pobierać obiekty GUI dla których przypisaliśmy jakiś identyfikator. Wystarczy podać adnotację @FXML a następnie klasę obiektu i jego identyfikator. Bardzo ułatwia to pracę.

Po kliknięciu przycisku submitButton jest on wyłączany (na czas próby połączenia z serwerem) i są wykonywane czynności łączenia. Do funkcji connectToServer dodałem 3 argumenty:

  • keyInput.getText() – klucz produktu
  • id – identyfikator urządzenia
  • interfejs ActivationListener – informuje o rezultacie połączenia / zwraca wynik wykonania PHP

Interfejs ActivationListener wygląda tak:

W odpowiedzi serwera musimy sprawdzić wszystkie przypadki, które wymieniłem wcześniej, czyli: OK , OK_USED , WRONGKEY , WRONGHARDWARE , USED , ERROR i jeszcze przypadek, jak coś innego zostało zwrócone (nieznany błąd). Gdy wystąpi błąd połączenia z serwerm (brak internetu lub serwer jest wyłączony) to zostanie wywołana funkcja z interfejsu ActivationListener void error(); Błąd połączania wyskoczy jako wyjątek IOException , który łatwo wyłapać.

A tak wygląda sama funkcja łączenia z serwerem:

Warto zaznaczyć, że koniecznie trzeba tu użyć wątków. Bez nich aplikacja zacięłaby się na czas łączenia z serwerem. Tak nie może być! Dlatego też skorzystałem z prostego wątku Thread.

Wracamy do interfejsu ActivationListener . Co jeśli jest błąd połączenia? Należy wyświetlić odpowiedni komunikat i ponownie włączyć przycisk submitButton (bo użytkownik ma prawo ponowić próbę).

Warto zauważyć, iż aktualizując elementy interfejsu z innego wątku, należy skorzystać z funkcji Platform.runLater , która aktualizuje GUI z wątku interfejsu graficznego. Bez tego nie da się aktualizować interfejsu.

Przejdźmy teraz do drugiej funkcji interfejsu ActivationListener. Serwer zwraca pewien ciąg bajtów, które można zamienić na łańcuch znaków: String responseString = new String (response); Problem polega na tym, że PHP zwraca niepotrzebne spacje i nowe linie na końcu pliku, dlatego też użyłem średników do wyznaczania początku i końca klucza weryfikacyjnego. Do tego klucz weryfikacyjny jest zapisany w formie bitów i zawiera różne znaki, które po przekształceniu do String’a, zmienią swój format. Dlatego, w przypadku zwróconego klucza weryfikacyjnego przez serwer, trzeba na nim operować jako na tablicy bajtów . Funkcja trimKey obcina tablicę bajtów do pierwszej znalezionej liczby podanej w argumencie 2 oraz obcina tablicę od końca to ostatniej znalezionej liczby podanej w argumencie 2. Średnik to w kodzie ASCII numer 59, dlatego też taki wpisałem do funkcji trimKey . Dobry klucz jest pobierany do zmiennej goodKey , a do zmiennej keyFromServerDecrypted przypisywany jest odszyfrowany klucz weryfikacyjny. Jeśli oba te zmienne mają takie same wartości ( contentEquals ), to należy zapisać klucz zaszyfrowany w pliku activation.key . Starałem się okomentować kod, aby był w miarę możliwości zrozumiały:

A funkcja trimKey prezentuje się tak:

Na sam koniec wystarczy jeszcze dopieszczyć plik startowy, aby program nie wyświetlał okna z aktywacją przy każdym uruchomieniu. Klucz będzie sprawdzany tak samo jak w przypadku aktywacji, więc nie tłumaczę już kodu. Poniżej fragment kodu z funkcji startowej JavaFX:

Wygląd okienka aktywacyjnego

Nareszcie koniec ! Cieszę się, jeśli dotrwaliście do tego momentu. Od teraz możecie powiedzieć, że algorytm RSA nie taki straszny, a nasza aplikacja stała się w miarę bezpieczna! Jakbyście czegoś nie rozumieli to zapraszam do komentowania !

Podsumowanie

Tworząc aplikacje, które dotrą do wielkiej liczby użytkowników, nie jesteśmy w stanie całkiem przeciwdziałać ich niecnym zamiarom okradania nas. Jednakże systemy antypirackie takie jak aktywacja online pozwalają pozbyć się tego problemu nawet w większości przypadków. Warto łączyć różne systemy aktywacji, tworzyć własne, aby były trudniejsze do ogarnięcia .

W tym artykule przekazałem także informacje na temat szyfrowania – jest ono używane wszędzie: na stronach banku , sklepach, mediach społecznościowych. Dlatego kryptografia (utajnianie wiadomości) jest taka ważna. Korzystamy z niej na co dzień! Tworząc stronę internetową warto zadbać o to, aby hasła użytkowników nie były narażone na proste ataki hakerów. Zabezpieczenie haseł nie jest trudnym zajęciem, jednakże nadal sporo osób omija to szerokim łukiem lub używa przestarzałych algorytmów.

]]>
/zabezpieczenie-antypirackie-aktywacja-online-z-algorytmem-rsa-i-bcrypt/feed/ 3 1307
Kontenery w C++ – std::array, czyli ładniejsza tablica. /kontenery-w-c-stdarray-czyli-ladniejsza-tablica/ /kontenery-w-c-stdarray-czyli-ladniejsza-tablica/#comments Sun, 19 Feb 2017 13:33:02 +0000 http://nastoletni.pl/?p=1280 W standardowych i popularnych kursach języka C++ zazwyczaj nikt nie rusza tematów poświęconych szczegółom takim jak kontenery, czy też inne pierdoły z referencji języka – mogą one się wydać rozpraszające lub po prostu trudne dla początkującego, lub komuś po prostu się nie chce. Dlatego postanowiłem napisać serię poradników poświęconych właśnie takim szczegółom, które potrafią bardzo ułatwić życie a o których rzadko kiedy się pisze. Zakładam oczywiście, że czytelnik posiada podstawową wiedzę na temat C++’a, bo bez tego może być mu ciężko ugryźć ten poradnik.

Zacznę od podstawowego kontenera, jakim jest std::array .

Zakładam, że wiesz co to tablica, prawda? Ot, zbiór elementów jednego typu poukładanych w pamięci jeden za drugim. Nic strasznego, nic trudnego. Jest to bardzo prosta rzecz, można by rzec nawet, że aż za prosta – standardowy C-style array nie posiada w sobie nic ciekawego, żadnych iteratorów, żadnych fajnych metod dostępu do niego, wszystko trzeba robić „ręcznie”.

I tutaj na scenie pojawia się std::array z biblioteki array , dodany w standardzie C++11

Ważne – (zazwyczaj) domyślnie każdy kompilator korzysta ze standardu C++03. Żeby skorzystać z nowszych, należy to sprecyzować przy użyciu flagi -std=c++11/14/17 (zależnie od standardu), albo w opcjach kompilatora – ale to zostawiam już do samodzielnego ogarnięcia).

Jest to bardzo przyjemna alternatywa, posiadająca dużą wartość użytkową – po prostu ułatwia programiście życie. No dobra, ale co w tym takiego fajnego? Dlaczego miałbym się przestawić nagle na std::array , zamiast używać starego dobrego c-style arraya?

But… why?

Używanie std::array jest prawie takie samo jak używanie zwykłego c-style arraya – losowy dostęp do elementów umożliwia standardowy operator [] , ale oprócz tego ten kontener posiada kilka bajerów:

Po pierwsze – std::array posiada tak zwany iterator . Iterator to obiekt wskazujący na jakiś element z zakresu elementów, który potrafi iterować (czyli, potocznie mówiąc, „przeskakiwać” ) między elementami przy pomocy na przykład operatorów inkrementacji czy dekrementacji (ale częściej iteratorów używa się ich w range-based for’ach na przykład, o czym za chwilę). Jest to duże ułatwienie w sytuacjach gdzie musimy na przykład przelecieć całą tablicę od początku do końca (lub też od końca do początku, bo std::array posiada także reverse iterator!)

Po drugie – posiada przeładowane operatory porównania == , != , < , <= , > , >= . Czyli możemy w łatwy sposób porównywać dwa std::array’e. Dodatkowo, ma przeładowania dla std::swap i std::get , gdybyśmy potrzebowali w fikuśny sposób dostać się do danego elementu tablicy lub zamienić jej zawartość z zawartością innej.

Po trzecie – posiada kilka użytkowych metod, takich jak fill (wypełnienie tablicy pewną wartością), front / back (dostęp do pierwszego/ostatniego elementu), empty (sprawdzenie czy tablica jest pusta), size / max_size (sprawdzenie aktualnej i maksymalnej ilości elementów). Jest to też typ agregowalny, czyli przykładowo można go inicjalizować poprzez klamry {} , tak jak zwykłą tablicę.

Podsumowując – std::array jest prawie taki sam w użytkowaniu jak zwykły c-style array, tylko fajniejszy oraz często wygodniejszy.

No fajnie, tylko jak tego użyć?

Jak już wcześniej mówiłem, można używać std::array tak samo jak zwykłej tablicy. Inaczej wygląda deklaracja, ponieważ jest to zwyczajna struktura z szablonowymi typami w konstruktorze:

template<class T, std::size_t N> struct array;

Gdzie T to typ danych przechowywanych w naszym kontenerze, a N to jego początkowa wielkość. std::size_t to ogólnie mówiąc typ danych dla liczb całkowitych bez znaku.

Warto o tym pamiętać, polecam przy odnoszeniu się do wielkości kontenerów używać std::size_t zamiast int’a czy jakiegokolwiek innego typu danych, oszczędzi nam to ostrzeżeń kompilatora mówiących o porównywaniu typów ze znakiem i bez niego.

Przykładowe deklaracje takich kontenerów wyglądają następująco

No i fajnie, mamy jakieśtam tablice. Co teraz z nimi zrobić? Wykorzystajmy dla przykładu iteratory.

Ten kod chyba wymaga tłumaczenia, szczególnie jeśli ktoś wcześniej nie miał kontaktu z nowszymi standardami C++.

Więc tak – pierwszy przykład wyświetla nam zawartość std::array przy użyciu range-based for’a. const auto &v to stała referencja do kolejnych wartości kontenera, iterator automatycznie inkrementuje się co iterację pętli. Dlaczego odnoszę się do elementów w ten sposób? Mógłbym to zrobić klasycznie, poprzez int v , ale w ten sposób każdy z elementów byłby kopiowany do tymczasowej zmiennej v , co w przypadku większych kontenerów stwarzało by duży problem wydajnościowy. Odniesienie się poprzez referencję pozwala na kopiowanie jedynie adresu do elementu, unikając kopiowania jego samego, a const uniemożliwia zmianę danego elementu (bo nie chcemy go przecież zmieniać). W tym wypadku jest to na tyle mała skala że w praktyce nie zauważymy żadnych wzrostów wydajności, ale warto wyrobić sobie praktykę używania referencji. Jeśli chodzi o auto , to jest to tylko kwestia wygody – ten keyword i tak zostanie zamieniony przez kompilator na odpowiedni typ danych.

W następnym kawałku kodu wypełniamy sobie tablicę bezpośrednio odnosząc się do iteratorów – b.begin() wskazuje na początek, b.end() na koniec naszego kontenera (analogicznie, b.rbegin() wskazuje na koniec, a b.rend() na początek). Jak wcześniej mówiłem, iteratory mają przeładowane operatory – inkrementacja, dekrementacja i tego typu rzeczy, które są bardzo wygodne.

Dalej, modyfikacja elementów przy użyciu range-based for’a, tutaj używam referencji bez const ponieważ modyfikuję elementy (i w tym wypadku nie mogę odnieść się bez referencji, ponieważ bez niej operował bym na kopiach elementów tablicy)

I na koniec smaczek, przelatuję sobie tablicę od końca co drugi element.

Ale jak by to wyglądało na normalnej tablicy?

Oczywiście to tylko kilka przykładowych porównań, ale widzimy, że std::array jest o wiele bardziej czytelniejszy niż standardowa tablica.

Podsumowanie – plusy i minusy

Przyszedł czas na krótkie podsumowanie naszego kontenera

  • + Czytelny – posiada wiele funkcji które pozwalają na pisanie czytelnego kodu z wykorzystaniem naszego arraya
  • + Wygodny – Iteratory, przeładowania operatorów, metody pozwalające na sprawdzenie wielkości i dostęp do krańcowych elementów oraz wiele więcej metod i bajerów ułatwiających życie
  • + Kompatybilny – Można go używać w ten sam sposób, jak normalnej tablicy. Ale nie trzeba.
  • – Stała wielkość – Jeśli potrzebujemy kontenera o zmiennej wielkości, należy użyć na przykład std::vector

I w sumie tyle. Jeśli masz jakieś wątpliwości, nadal czegoś nie rozumiesz, zapomniałem o czymś, masz jakąś sugestię czy cokolwiek – pisz śmiało w komentarzu.

A po pełną referencję i więcej przykładów dla tego kontenera zapraszam tutaj: http://en.cppreference.com/w/cpp/container/array .

Następny rozdział będzie poświęcony kontenerowi std::vector.

]]>
/kontenery-w-c-stdarray-czyli-ladniejsza-tablica/feed/ 5 1280
Zarabianie na zleceniach /zarabianie-na-zleceniach/ /zarabianie-na-zleceniach/#comments Sun, 12 Feb 2017 21:50:37 +0000 http://nastoletni.pl/?p=1166 Umiecie programować i chcielibyście zdobyć więcej kieszonkowego? A może nie macie motywacji by cokolwiek robić samemu, co wydawać się może niepotrzebne? Z pomocą przychodzą wam płatne zlecenia, dzięki którym doszlifujecie umiejętności w programowaniu i możecie coś zarobić!


Jako nastoletni programiści nieraz mamy taki moment, że nie wiemy co mamy robić. Kolejna strona/program? Brak pomysłów ! Więc czemu by nie sprawdzić siebie w zleceniach? W tym artykule chciałbym omówić tę kwestię.

Zlecenia to dobry sposób na nabycie doświadczenia.

W Polsce najpopularniejszym serwisem od zleceń dla programistów z pewnością jest Oferia.pl . Na tej stronie znajdziecie pełno zleceń – od budownictwa, grafik, transportu, aż po właśnie programowanie. Aktualnie jest 1300+ zleceń, które możemy wykonywać. Myślę, że każdy może tu znaleźć coś dla siebie – najwięcej zleceń jest z PHP, C++, C#, HTML i Javy.

Przykładowe oferty – jak widać jest duża różnorodność

Rejestracja

– jest dwuetapowa. Najpierw należy zrobić konto, podając adres email i hasło, a następnie po aktywacji przystąpić do konfiguracji konta. Co ważne – nie musicie mieć 18 lat ! W całym regulaminie Oferii nie ma wzmianki o wieku. Co więcej, nie musicie go nawet podawać! Z rejestracją jest tylko jeden mały problem – nasze dane osobowe będą widoczne w Google . Niektórym będzie to przeszkadzać, ale cóż, z tym nie da się nic zrobić. Podczas rejestracji należy podać imię, nazwisko, telefon i adres zamieszkania.

Legalność / oszustwa

Jak to z legalnością? – tutaj w większości zależy od zleceniodawcy. Zdecydowana większość zleceniodawców woli nie mieć żadnych umów . Większość tutaj kontaktuje się przez waszego maila, całkowicie omijając potem serwis Oferia – co najwyżej potem mogą wystawić wam ocenę, jeśli poprosicie. Zrobiłem już parę zleceń i ani razu nie miałem dalszego kontaktu przez ten serwis, nawet ocen nie dostałem.

„Wielu spotkałem takich ludzi, którzy chcieliby oszukiwać, ale takiego, który by chciał być oszukiwany, nie spotkałem.” – Aureliusz Augustyn z Hippony

Skoro bez umowy, to łatwo nas okraść? – tak, to niestety jest prawda . Oczywiście, możesz wymagać takowej umowy, ale szczerze mówiąc nie trafiłem jeszcze na kogoś, kto by mnie oszukał. A nawet jakby ktoś oszukał, to chciałoby wam się „gonić” za tą osobą? Oczywiście to zależy od ceny zlecenia, lecz na sam początek proponuję nie brać tych droższych – lepiej oswoić się z zleceniami, robiąc coś prostego.

Trafiają się też osoby, które najpierw wymagały mniej, a potem okazało się, że chcą jeszcze „to i tamto”. Wtedy musicie zadecydować – albo robicie w tej cenie, albo cena wzrośnie.

Wracając trochę do wstępu – jak nie macie co robić i robicie w miarę tanie zlecenia, to nawet jeśli dalibyście się zrobić w konia (dość rzadko się to zdarza), to zawsze macie kolejny projekt do portfolio ! Nie każdy ma motywację, żeby robić sobie samemu takie projekty do portfolio, a dzięki takim zleceniom macie ciekawe pomysły + uczycie się nowych technologii! Jest wiele prostych aplikacji, których nie chciałoby wam się robić, bo wydają się wam niepotrzebne, a jest wiele osób, które potrzebują takie aplikacje. Np. studenci często potrzebują prostych zadań – wyświetlanie figur 3D, prosty odtwarzacz muzyki, liczenie kredytów – to dobra okazja, aby nauczyć się jakiegoś API!

Pewnie teraz zapytacie – czyli jak składam ofertę, to muszę umieć perfekcyjnie to, co jest w zleceniu ? – odpowiedź brzmi: nie ! Jeżeli w ofercie jest wymagane używanie jakiegoś frameworka, to przejrzyj go z grubsza, oceń czy dasz radę i po prostu napisz ofertę. Zleceń nie trzeba wykonywać w 1 dzień – nieraz firmy robią dłużej projekty niż osoby prywatne. Skoro się jednocześnie uczysz i robisz zlecenia, to możesz dodać konkurencyjną cenę!

Przykład, że mimo pakietu MAX, ogłoszenia są mało atrakcyjne.

Portfolio i opis zgłoszenia

– to jest najważniejsze. Po przeczytaniu waszej oferty zleceniodawcy często patrzą na portfolio. Na Oferii
macie możliwość tworzenia portfolio, jednakże ja polecam własną stronę – bo to po prostu ładniej wygląda. Kolejne projekty będziecie tam wrzucać i będzie to wszystko ładnie rosło. Ja jeszcze nie mam, ale musicie uwierzyć mi na słowo.

Bez portfolio trudno dostać dobrą ofertę. Wyobraźcie siebie na miejscu zleceniodawcy – chcecie mieć dobry program/stronkę, a nie wykonaną przez gimnazjalistę w Scratchu. W opisie oferty zawsze odpowiadaj dokładnie na to, co prosi zleceniodawca – tzn. jeśli chce konkretną kwotę, to musisz ją podać! Uwzględnij swoje kompetencje, np. jeśli robiłeś coś podobnego w przeszłości, to wspomnij o tym. Możesz powiedzieć coś o sobie, przytoczyć inne projekty (portfolio). Dodaj, że bardzo chętnie podejmiesz się tego zlecenia i na sam koniec koniecznie podaj adres e-mail – bo, jak mówiłem, większość zleceniodawców woli kontakt przez e-mail niż przez prywatne wiadomości serwisu Oferia. Ogólnie jest dużo osób, które nawet nie czytają zlecenia, kopiują swoją ofertę i wklejają do wielu ofert (z ceną „do negocjacji”) – Ty już jesteś od nich lepszy !

Jak pewnie zauważysz, niektóre oferty są podświetlone – jest to pakiet MAX na Oferii. To już do was należy przemyśleć, czy chcecie taki kupić – z doświadczenia powiem, że bez tego z pewnością znajdziecie zlecenia. Opis i tak jest ważniejszy!

Bez niesamowitych pakietów można stworzyć lepszą ofertę!

Konkurencja

Gdy już znajdziesz odpowiednie zlecenie, to często inni wykonawcy będą podbijać i edytować swoje oferty. Możesz tutaj troszkę oszukać system i napisać coś więcej – ale wtedy licz się z tym, że zleceniodawca będzie wymagał od Ciebie profesjonalizmu. Pamiętaj też, że najniższa cena niczego nie gwarantuje. Jeśli nie masz portfolio, a ofertę masz kiepską, to najniższa cena nic nie pomoże.

Wysyłajcie wiele ofert – z doświadczenia wiem, że niektórzy zleceniodawcy rezygnują z projektu i do nikogo nie piszą. Jeśli jest jakiekolwiek nowe zlecenie, które Ci się podoba – pisz od razu . Jeśli nawet trafi Ci się potem tak, że napisze do Ciebie paru gości na maila, to po prostu powiesz im, że aktualnie nie dasz rady tego wykonać.

Podczas wykonywania zleceń możecie trafić na osobę, która w przyszłości będzie wam dostarczać zlecenia. Wasze dane osobowe są teraz publiczne, więc może się zdarzyć, że zleceniodawcy do was zadzwonią: ja na przykład otrzymałem ofertę stworzenia systemu dla biblioteki (spis książek, aplikacja webowa i Androidowa). Co więcej – nawet dzwoniła do mnie firma, która szuka programistę Java. Niestety jako iż jeszcze się uczę, a praca miała być na cały etat, to musiałem odmówić.

Inne strony

Inne strony ( żeby nie było, że to reklama Oferii ) – Z innych stron korzystałem też z freelancer.com . Tam jest baaaardzo dużo ofert (w końcu nie tylko polskie), jednak jakoś nie miałem tam powodzenia. Jest tam też dużo większy wybór pakietów (i dużo droższych) + większy limit ofert na miesiąc. Ale spróbować na pewno warto .

Podsumowanie

Przywołując słowa Alberta Einsteina:

„Każda pra­ca jest dob­ra, o ile jest dob­rze wykonywana.”

starajmy się, aby każde nasze zlecenie było dobrze wykonywane. Czym więcej zadowolonych klientów, to tym więcej doświadczenia i szansa na zauważenie takiej osoby przez znane firmy. Wykonując dobrą robotę, dostaniecie dobrą pracę, więc jeśli jeszcze nie wykonywałeś żadnej aplikacji na zlecenie – spróbuj !

]]>
/zarabianie-na-zleceniach/feed/ 2 1166
Battle City Clone – Godot /battle-city-clone-godot/ /battle-city-clone-godot/#comments Fri, 27 Jan 2017 00:02:03 +0000 http://nastoletni.pl/?p=1024 Prosto i bez zbędnego rozwijania. Klon znanej raczej każdemu gry na kultowego kiedyś pegasusa  – Battle City –  lub także pod nazwą – Tank 1990. Wykonany on zostanie w open source’owym oraz darmowym silniku jakim jest Godot Engine.

Całość znajduje się na repo /Vunnysher/GodotBattleCity – ( 01_Movement )

Sterowanie czołgiem

Postaram się odwzorować sterowanie czołgiem używając do tego godotowej fizyki.

Zaczynając, oczywiście tworzę czysty projekt.

Oraz skorzystam z oryginalnych assetów z Battle City. Poniżej źródła:

  • Tekstury – Click
  • Dźwięki – Click

Z tekstur usunę niepotrzebne tło w gimpie i zostawię przeźroczyste. Dodaję nową warstwę przeźroczystą, ustawiam ją na samym dole. Kopiuję całą zawartość na przeźroczystą warstwę, a starą usuwam. Następnie zaznaczam Różdżką wybrany kolor (w tym przypadku turkusowy), który chcę usunąć i naciskam Delete.

Tworzę hierarchię KinematicBody2D – Sprite2D

Wchodząc w Inspector Sprite’a, wybieram dla niego teksturę, a następnie używając strzałeczki na prawo od tekstury przechodzę do jej właściwości. Odznaczam wszystkie flagi aby piksele się nie rozmywały.

Wracam do Inspectora Sprite’a i zaznaczam flagę Region . Umożliwia ona wycięcie z tekstury wybranego fragmentu. Niżej w Region Rect ustawiam koordynaty czołgu oraz jego szerokość, która wynosi 13 x 13px. I oto widać pojazd:

Pora zrobić sterowanie. W tym celu najzwyczajniej klikam prawym na KInematicBody2D oraz wybierając z menu Add Script. Tworzę nowy plik i wpisuję podany niżej kod.

set_fixed_process(true)

Uruchamia funkcję _fixed_process , która powtarza się 60 razy na sekundę.

if Input.is_action_pressed("akcja"):

Zwraca true lub false w zależności od tego czy dany klawisz akcji jest wciśnięty.

self.move(Vector(x,y))

Jest to funkcja przeznaczona dla klasy KinematicBody ( do siebie odnosimy się poprzez self ), przesuwa ona ciało obiektu o daną ilość pikseli na klatkę.

Teraz tylko odrobinę powiększę Sprite z czołgiem, ustawiając mu w Inspector Scale na 4,4.

Jeździć jeździ. Ale jakoś tak… bokiem. Zmienię trochę skrypt, alby wycięty był inny obszar tekstury w zależności od kierunku.

onready var

to zmienna której przypisujemy wartość jeszcze przed rozpoczęciem programu.

get_node("scieżka")

pobiera Node do zmiennej.

set_region_rect(Rect2(x,y,width,height))

to funkcja dla klasy Sprite, która zmienia położenie wycinka z tekstury.

Teraz wygląda to tak:

Przydało by się zanimować poruszanie. Zmieniam więc teksturę na tą drugą ponieważ zawiera ona także klatki poruszania się gąsienic. Zaraz po tym aktualizuję pozycje dla Region Rect w kodzie, oraz dodaję do skryptu kilka nowych rzeczy.

Na sam początek zmienna frameTime będzie przechowywała czas dla klatek.
Jeżeli jest mniejszy niż 0.1s to jest jedna klatka, jeżeli większy to druga. Gdy frameTime osiągnie 0, nadaję mu wartość 0.2s aby go „zresetować” (ostatnie 2 linijki).

set_rot(rad)

to funkcja dla klasy Node2D która ustawia obiekt pod podanym kątem w radianach. Dlatego tez użyłem funkcji

deg2rad(deg)

która zamienia podane stopnie (degrees) na radiany.

set_flip_h(boolean)

odwraca sprite względem osi poziomej ( horizontal ) – użyłem aby odwrócić czołg odpowiednią stroną naświetlenia.

I dzięki tej aktualizacji kodu używamy tak naprawdę tylko dwóch klatek z tekstury.

Tak to teraz wygląda:

Na razie tyle, możliwe że niedługo będzie dalsza część tworzenia klona Battle City.

]]>
/battle-city-clone-godot/feed/ 1 1024
Minecraft a programowanie? Architektura klient – serwer. /minecraft-a-programowanie-architektura-klient-serwer/ /minecraft-a-programowanie-architektura-klient-serwer/#comments Thu, 29 Dec 2016 12:33:57 +0000 http://nastoletni.pl/?p=605 Minecraft jest grą, która mocno przyczyniła się do rozwoju gatunku „gier piaskownic” (ang. sandbox ). Jej ogromna popularność spowodowała powstanie dużej strefy moderskiej, skupionej na tworzeniu kolejnych elementów, już i tak bardzo rozbudowanego świata.

Moimi ulubionymi modyfikacjami okazały się dodatki przemysłowe, dodające kolejne stopnie przerabiania surowców oraz maszyny ułatwiające ich przetwarzanie. Każdy, kto chociaż trochę zainteresuje się uprzemysłowieniem produkcji zawsze dobrnie do momentu, w którym ręczne operowanie całą fabryką straci sens, ponieważ nie jest to już takie efektywne jak to, czego się oczekiwało. W takim przypadku z pomocą przychodzą nam komputery.

W tym poradniku przedstawię rozwiązanie architektury klient – serwer na jednej z dwóch najpopularniejszych modyfikacji dodającej maszyny liczące. Będzie to ComputerCraft . W kolejnych poradnikach skupię się na drugiej modyfikacji tego typu – OpenComputers . Ten poradnik skierowany jest do osób, które miały już styczność z językiem Lua oraz tą modyfikacją.

Architektura w teorii

Poradnik rozpocznę od cytatu z portalu Wikipedia.com:

Klient-serwer (ang. client/server, client-server model ) – architektura systemu komputerowego, w szczególności oprogramowania, umożliwiająca podział zadań (ról). Polega to na ustaleniu, że serwer zapewnia usługi dla klientów , zgłaszających do serwera żądania obsługi (ang. service request ).

Najlepszym (moim zdaniem) przykładem takiej architektury jest relacja przeglądarka – serwer WWW . Na jakiej zasadzie działa takie połączenie? Przeglądarka wysyła do serwera żądanie (ang. request ) i oczekuje od serwera odpowiedzi (ang. response ). Kilka lat temu natknąłem się na określenie połączenia typu ping-pong , czyli połączenie polegające na przekazaniu pakietu danych razem z wirtualną „piłeczką”. Określenie osobliwe, jednakże nie odbiega znacznie od głównej idei a może ułatwić początkującym zrozumienie tego typu relacji.

Na obecnym etapie ujawniają się kolejne właściwości tego typu połączenia. Jedną z głównych jest zależność, z której wynika, że jeden serwer może przyjmować żądania od wielu klientów . Co jednak w momencie, w którym serwer otrzyma w tym samym momencie kilka żądań? Jest na to kilka rozwiązań, nie tylko sprzętowych ale również programowych. Czasem jest to dodatkowy serwer kolejki (ang. queue server ), który zostaje umieszczony pomiędzy klientem a serwerem i wysyła żądania serwerowi głównemu w kolejce, w której je otrzymał, bez jego przeciążania, a czasem program uruchomiony w tle , który przetrzymuje żądania w kolejce, a program serwera po kolei może je z niego wybierać. W tym przykładzie pominę to zagadnienie i skupię się głównie na samym połączeniu, bez rozwiązywania problemów z możliwymi przeciążeniami.

Projekt protokołu

Połączenie obu komputerów musi być w jakiś sposób ujednolicone , dlatego utworzyłem protokół (w ogromnym uproszczeniu), który korzysta z API Modemu ( link ). Sama modyfikacja udostępnia jeszcze API nazwane „Rednet” , jednakże pozwala ono tylko na połączenie po modemie bezprzewodowym, a sporządzając swój własny protokół można korzystać nie tylko z bezprzewodowych, ale i przewodowych modemów.

Identyfikacja komputerów w sieci w tej modyfikacji znacznie odbiega od właściwości protokołu TCP/IP . Każdy komputer posiada swój unikalny identyfikator i jest on niezmienny, niezależny od sieci, do której jesteśmy połączeni.

Sam projekt rozpocznę od ramki , czyli „piłeczki”, którą będziemy „rzucali” między komputerami. Obiekt ten musi zawierać wszystkie potrzebne informacje do działania protokołu jak i dane, które będziemy chcieli przekazać dalej. Oto lista:

  • ID adresata , czyli identyfikator komputera, z którego wysyłamy ramkę
  • ID odbiorcy , czyli identyfikator komputera, do którego docelowo ma dotrzeć ramka
  • Typ wiadomości , czyli ciąg znakowy identyfikujący typ zawartości ramki (np. obiekt, ciąg znakowy, liczba, typ logiczny…)
  • Zawartość , czyli faktyczna wiadomość wysyłana w ramce

API Modemu pozwala na operowanie na kanałach – odpowiednikach portów w protokole TCP/IP. Chcąc uzyskać podobieństwo do protokołu HTTP , skorzystam z kanału 80 .

Oto konstrukcja obiektu ramki, którym będą wymieniały się komputery:

Cały protokół oparty jest na 2 funkcjach – odbierającej i wysyłającej. Funkcja odbiorcza przyjmuje 3 argumenty. Pierwszy, wymagany argument to kanał, na którym program ma nasłuchiwać. Pozostałe dwa argumenty są opcjonalne i nie trzeba ich wpisywać. Pierwszy z opcjonalnych to czas, przez który program ma nasłuchiwać do czasu zwrócenia błędu o braku odpowiedzi (ang. timeout ). Domyślną wartością w tym przypadku będzie 10 sekund , a maksymalną możliwą do wpisania będzie 120 sekund . W argumencie tym można wpisać również wartość logiczną false , która zablokuje wywołanie timera, a co za tym idzie, nie pozwoli na zwrócenie błędu o braku odpowiedzi. Ta właściwość przyda się podczas pisania programu serwera. Drugi zaś to typ wiadomości, którego program oczekuje. Jeżeli ciąg znakowy wpisany w tym parametrze nie będzie zgodny z ciągiem podanym w ramce, program przystąpi do dalszego nasłuchu. Funkcje te są przystosowane do wykrycia modemu, tak więc w celu uniknięcia błędów/niepożądanego działania lepiej nie podłączać do komputera większej ich ilości.

Teraz można wywołać funkcję odbiorczą wpisująć receive(kanał[, czas, typ]) . Funkcja waitForMessage() jest funkcją wewnętrzną wykorzystywaną przez funkcję receive() .

Funkcja nadawcza przyjmuje 2 argumenty. Oba są wymagane do jej działania. Pierwszy argument to obiekt ramki, który program chce przesłać. Drugi argument to kanał, po którym ramka ma zostać przesłana.

Aby wysłać wiadomość, wystarczy sporządzić odpowiedni obiekt żądania według wzoru ramki i wywołać funkcję send(żądanie, kanał) .

Obie funkcje można umieścić w jednym pliku i wywoływać je w innym (np. w programie serwera) poprzez załączenie ich jako API. Aby to zrobić, trzeba zapisać je w pliku o nazwie np. protocol i załadować je do programu jak w poniższym przykładzie. Potem będzie można odwoływać się do nich jak do metod obiektu.

Modyfikacja udostępnia API serwisu Pastebin.com do pobierania skryptów ze strony na komputery w grze. Aby umożliwić jednak korzystanie z tej funkcji, trzeba odnaleźć plik ComputerCraft.cfg i zmienić wartość linii B:enableAPI_http= z false na true . Wtedy będzie można skorzystać z tego udogodnienia. W celu pobrania protokołu wystarczy wpisać pastebin get DfjzLK6R protocol .

Schemat algorytmu

Poradnik przewiduje 2 programy. Pierwszy program ( klient ) będzie przyjmował od użytkownika komendę, po czym nastąpi jej przekazanie do serwera i oczekiwanie na odpowiedź. W przypadku otrzymania odpowiedzi nastąpi jej wyświetlenie, a w przypadku braku odpowiedzi w wyznaczonym czasie zostanie wyświetlony błąd o braku odpowiedzi z serwera. Po wyświetleniu błędu program zada pytanie użytkownikowi, czy ten życzy sobie ponownego wysłania żądania do serwera. Odpowiedź twierdząca powtórzy procedurę wysłania żądania, a przecząca zakończy program. Drugi program ( serwer ) będzie działał w wiecznej pętli, podczas której będzie oczekiwał zdarzenia (ang. event ) otrzymania żądania od klienta, po której nastąpi przetworzenie ów żądania i wystosowanie odpowiedniej odpowiedzi. Po wysłaniu wiadomości zwrotnej program przystąpi od początku do nasłuchiwania.

Algorytm programu klienta i serwera

Program klienta

Klient będzie prostym programem konsolowym, który będzie wysyłał komendy wprowadzone przez użytkownika do serwera i zwracał użytkownikowi odpowiedź serwera. W tym przypadku będą to komendy włączenia/wyłączenia lampki oraz zwrócenia stanu lampki (czy włączona). Do programu przyda się funkcja wysyłająca żądanie i zwracająca odpowiedź. Oto ona:

W funkcji tej utworzyłem obiekt żądania i wypełniłem go potrzebnymi danymi. Adres serwera oraz komendę wpisuje użytkownik podczas działania programu. Następuje teraz bezpieczne wywołanie funkcji wysłania żądania funkcją pcall() . W przypadku wystąpienia błędu, program nie zatrzyma się, a przekaże błąd do wyniku działania funkcji. Wtedy warunek w linii 10 wykryje takowy, jeżeli tylko się pojawi i wyświetli stosowny komunikat. Analogiczna sytuacja w przypadku funkcji odbierania odpowiedzi. Gdy wszystko będzie w porządku, funkcja zwróci wiadomość w postaci obiektu odpowiedzi.

Zgodnie z algorytmem, w pierwszej kolejności użytkownik musi podać wymagane informacje dotyczące identyfikatora serwera oraz komendy, którą zamierza przesłać. Pętlę rozwiązałem otaczając cały blok wprowadzania argumentu pętlą repeat ... until . Jest to pętla będąca odpowiednikiem do ... while w językach C-podobnych. W każdym bloku sprawdzam również zgodność typu danych oraz czy nie zwróciło fałszu lub pustej wartości ( nil ). Oto moje rozwiązanie:

Skoro jest już rozwiązane wprowadzanie i wysył/odbiór danych to pozostało oprogramować całość do końca. Trzeba zaimplementować mechanizm wyświetlania odpowiedzi i ponowienia wysłania ramki. Tutaj również użyłem pętli repeat ... until . Gdy program wykryje brak odpowiedzi, zaproponuje użytkownikowi ponowienie operacji. W tym przypadku użytkownik ma do wyboru naciśnięcie dwóch przycisków. Naciśnięcie przycisku T na klawiaturze spowoduje powtórzenie pętli, zaś naciśnięcie N zakończy działanie programu. Otrzymanie odpowiedzi spowoduje jej wyświetlenie i również zakończenie dalszych działań.

Teraz pozostało dołączyć API naszego protokołu i połączyć całą resztę ze sobą. Oto efekt:

Program klienta możemy pobrać na komputer w grze wpisując komendę pastebin get 04ckzKNf client

Program serwera

Zadaniem serwera będzie kontrola lampki, która poleceniem klienta będzie mogła zostać zapalona lub zgaszona. Klient będzie mógł również poprosić serwer o podanie stanu lampki (czyli czy jest zapalona czy zgaszona). Program serwera rozpocznę od napisania paru funkcji do sterowania lampką:

Jak widać są to dwie funkcje – jedna do włączania lampki i druga do jej wyłączania. Każda z nich przyjmuje argument w postaci ciągu znakowego oznaczającego stronę komputera, po której został umieszczony przewód prowadzący do lampki. Obie funkcje mają ten sam mechanizm działania. W przypadku, gdy wykonywana przez nie operacja powiedzie się, zwracają logiczną prawdę. Każdy inny wynik operacji spowoduje zwrócenie logicznego fałszu. Tak więc jeżeli będziemy chcieli zapalić lampkę a ta będzie już zapalona, otrzymamy false .

Ciągi znakowe oznaczające strony to: top, bottom, left, right, front, back . Tłumacząc na język polski w kolejności: góra, dół, lewo, prawo, przód, tył .

Postępując zgodnie ze schematem algorytmu do napisania pozostały jeszcze 2 funkcje – przetwarzania żądania i odsyłania odpowiedzi.

Aby użyć funkcji odbiorczej w programie serwera wystarczy wpisać w argumencie funkcji odpowiadającym za czas oczekiwania wartość false . Nastąpi wtedy wyłączenie timer’a, który odpowiada za wywołanie błędu braku odpowiedzi. Pozwoli to programowi na działanie w wiecznej pętli nasłuchu bez konieczności napisania nowej funkcji.

Oto kod pętli programowej serwera. Tak jak napisałem wcześniej – wieczna pętla, w której program nasłuchuje wiadomości i odpowiada na żądania. Ale jak wygląda odpowiadanie i przetwarzanie tych żądań? callback() to prosta funkcja przyjmująca jako jedyny argument obiekt odpowiedzi serwera i wysyłający go zgodnie z danymi w nim zawartymi. Zadaniem funkcji process() jest przetwarzanie żądania, które otrzymał serwer, wykonywanie polecenia w nim zawartego i wystosowanie obiektu odpowiedzi.

Ułatwiłem sobie trochę zadanie poprzez utworzenie funkcji prepareResponse() , która zwraca mi kompletną konstrukcję obiektu, który potem zostanie użyty w funkcji callback() . Kod wygląda w ten sposób znacznie schludniej. Pokusiłem się również o dodanie prostej informacji wstępnej z ID komputera oraz stroną z kablem prowadzącym do lampki. Oczywiście stronę można sobie ustawić samemu edytując zmienną side .

Tak samo jak w przypadku kodu klienta, kod serwera również można pobrać w grze komendą pastebin get q88vs6TJ server

Uruchomienie

Do działania potrzeba oczywiście dwóch komputerów. Każdy z nich powinien być wyposażony w modem. W przypadku modemów bezprzewodowych trzeba pamiętać o ograniczeniu odległości. Moja platforma testowa wygląda tak:

Komputer po lewej ma zainstalowany program klienta, ten po prawej zaś działa już jako serwer, z lampką podłączoną do tylnej ścianki. Oba łączą się za pomocą modemów bezprzewodowych. Najlepiej zacząć od wgrania protokołu oraz programu na komputer, który ma pełnić rolę serwera. Po uruchomieniu serwera trzeba zwrócić uwagę na dwie rzeczy:

Pierwsza rzecz to ID , czyli identyfikator serwera, który potem będzie trzeba wpisać w programie klienta. Druga rzecz to informacja po której stronie powinien znajdować się kabel prowadzący do lampki . Specjalnie dla początkujących spolszczyłem nazwy w programie. Jeżeli ta wartość jest inna niż zamierzona, trzeba zmienić ją w programie.

Podczas instalacji programu klienta jak i serwera należy pamiętać o równoczesnej instalacji protokołu . Bez tego program nie będzie w stanie zadziałać.

Teraz skupię się na programie klienta. Po wgraniu programu razem z protokołem można go uruchomić.

W tym momencie program wymaga wprowadzenia komendy. Jeżeli nie zostanie rozpoznana przez serwer, prześle on odpowiedź zwrotną z informacją o nierozpoznanej komendzie. Dla testu wprowadzę komendę is_on , która powinna zwrócić stan lampki.

Tutaj należy wpisać ID komputera, na którym zainstalowany jest program serwera. W moim przypadku ID wynosi 0. Po tym kroku nastąpi wysłanie informacji na serwer i oczekiwanie odpowiedzi. Kolejne okno pojawi się, gdy klient otrzyma wiadomość lub minie czas oczekiwania na wiadomość. W moim przypadku czas ten w protokole ustawiłem na 3 sekundy .

Po lewej stronie klient odebrał wiadomość od serwera, po stronie prawej natomiast tej odpowiedzi nie otrzymał i dał nam wybór na powtórzenie wysłania wiadomości.

Podsumowanie

Do czego można użyć takiej konfiguracji? Sterowanie jedną maszyną/fabryką z wielu miejsc, system wewnętrznych wiadomości, system kontroli dostępu… Możliwości jest naprawdę wiele. Sam system można naprawdę mocno rozbudować, na pewno w wielu miejscach również poprawić lub napisać na nowo – lepiej, wydajniej. Mam nadzieję, że ten poradnik będzie dla wielu przydatny i że chociaż odrobinę przybliżyłem piękno leżące w komunikacji sieciowej i jej możliwościach. Odpowiem na wszelkie pytania w komentarzach w ramach mojej najlepszej wiedzy. Nie bójcie się pytać. Każdy kiedyś zaczynał, a przykład takiej gry jak Minecraft , gdzie zastosowanie znajdują ogromne ilości nie tylko informatycznych zagadnień, tylko potwierdza regułę.

]]>
/minecraft-a-programowanie-architektura-klient-serwer/feed/ 3 605
Język Go – czy warto? Wprowadzenie. /jezyk-go-czy-warto-wprowadzenie/ /jezyk-go-czy-warto-wprowadzenie/#comments Mon, 12 Dec 2016 19:26:37 +0000 http://nastoletni.pl/?p=513 Witam! Nigdy jeszcze nie pisałem żadnego bloga, więc proszę o wyrozumiałość.

Zajmę się tematem języka Go. Czym on jest, dlaczego warto się go nauczyć, jak stworzyć w nim prosty program „Hello World”, jak stworzyć sobie środowisko do pracy z nim i zamieszczę parę skryptów do zautomatyzowania sobie pracy.

Pragnę jeszcze tylko nadmienić, że ten wpis jest skierowany do użytkowników, którzy już potrafią programować w jakimś języku, ale jeśli nie potrafisz, to nie martw się. Do wszystkiego zrobiłem odnośniki, więc nawet jeżeli nie zrozumiesz tego wpisu, to i tak coś stąd wyniesiesz.

Czym jest Go i co potrafi?

Go (często nazywany Golang’iem dla odróżnienia od słowa „go”) jest językiem programowania opracowanym przez programistów z Google: Roberta Griesemera, Roba Pikea oraz Kena Thompsona.

Łączy w sobie łatwość pisania aplikacji charakterystyczną dla języków dynamicznych (np. Pythona, Lispa, C#’a oraz JavaScriptu) z wydajnością języków kompilowanych (np. C, C++).

Kompilacja w Go po pierwszym razie jest bardzo szybka w porównaniu do np. kompilacji w C++. Język ten posiada również wbudowany Garbage Collector , który bardzo ułatwia zarządzanie zasobami. Składnia Golang’a i budowa kodu nad wyraz przypomina to co znamy z JavaScriptu. Nie ma tu klas, ponieważ byty, które przyjdzie nam zadeklarować, to instancje struktur z przypisanymi do ich typów metodami – czyli deklarujemy typ jakiejś struktury, a następnie przypisujemy do jej typu różne metody. Przypomina to trochę tworzenie klasy ze zmiennymi, ale bez metod w C#, a następnie dodanie do tej klasy metod rozszerzeń . W Go można także tworzyć literały struktur , które bardzo przypominają literały obiektów z JavaScriptu. Konstruktorów i destruktorów tu nie ma, ale możliwa jest ich sztuczna implementacja poprzez funkcje czy też, jak już wspomniałem, metody przypisane do typów struktur z wykorzystaniem instrukcji defer . Obiektowość w Golangu realizowana jest za pomocą interfejsów.

Go do bólu ułatwia również zrównoleglanie wywołań. Nie trzeba zajmować czasu na wątki, rezerwacje zasobów, mutexy i resztę tego tałatajstwa. Zamiast tego Golang oferuje nam tzw. „goroutines” (które pozwalają na asynchroniczne uruchomienie danej funkcji poprzez dodanie przed jej wywołaniem instrukcji go ) oraz daje nam kanały, które w bardzo dużym uproszczeniu działają jak wskaźniki. Możesz coś do nich wrzucić w jednym procesie i odczytać w innym. Go wspiera UTF-8, więc nie ma żadnych problemów z polskimi znakami.

Jak zainstalować Golanga?

Jeżeli doszedłeś/aś do tego etapu, to prawdopodobnie przekonał Cię mój wstęp. Bardzo mnie to cieszy. Teraz pokażę Ci jak zainstalować Golanga. Zrealizuję to w formie instrukcji.

  1. Wejdź na główną stronę projektu i przejdź do strony „Downloads”.
  2. Przejdź do „Stable versions” i pobierz najnowszą, stabilną wersję .ZIP na twój komputer.
  3. Przerzuć folder „go” z paczki ZIP do folderu C:\. U mnie był już folder ze starą wersją SDK do Golanga, więc ja tylko sobie zaktualizowałem.
  4. Teraz musisz dodać ścieżkę do C:\go\bin do zmiennych środowiskowych. Jeżeli wiesz jak to zrobić, możesz pominąć do punku 10.
  5. Wejdź we właściwości Komputera.
  6. Otwórz „Zaawansowane ustawienia systemu”.
  7. Otwórz „Zmienne środowiskowe”.
  8. Jeżeli nie masz zmiennej „Path”, utwórz ją za pomocą opcji „Nowa…”. Ja jedynie edytuje.
  9. Wciśnij przycisk „Nowy” i wpisz C:\go\bin. (Jeżeli jesteś na win 7/8, to wystarczy, że wkleisz tą ścieżkę i dodasz za nią znak ‚;’ do oddzielania ścieżek.
  10. Otwórz konsolę systemową (skrót WIN+R, wpisz cmd i wciśnij enter) i wpisz go version . Jeżeli pokazuje Ci się podobna informacja do mojej, oznacza to, że wszystko jest okej.

Jak skonfigurować sobie środowisko do pracy?

Wiesz już co to Go i potrafisz go poprawnie zainstalować. Czas napisać pierwszy program!

Pamiętasz jak napisałem, że zamieszczę parę skryptów do zautomatyzowania sobie pracy? Oto one:

goCreate.bat nazwaProjektu – służy do tworzenia nowych projektów

goPackage.bat nazwaPaczki – tworzy nowe paczki

goHere.bat – kiedy jesteś w folderze projektu i uruchomisz ten skrypt, projekt będzie gotowy do pracy, a kompilator będzie widział wszystkie twoje pliki.

createGoFile.bat – goPackage i goCreate wykorzystują go do tworzenia potrzebnych plików.

Wrzuć te pliki do folderu z twoimi skryptami (jeżeli takiego nie masz, stwórz go), a następnie dodaj ścieżkę do tego folderu do zmiennych środowiskowych („PATH”).

Dlaczego daje Ci te skrypty?

Projekty w Golangu należy zapisywać projekty w specjalnej strukturze z folderów (więcej tutaj ). Ręczne tworzenie owej struktury za każdym razem jest niewygodne i może sprawiać problemy początkującym w Go.

Twój pierwszy program w Go.

Aby stworzyć projekt otwórz konsolę i wpisz goCreate "tutaj nazwa projektu" (możesz to zrobić poprzez przytrzymanie shiftu i wciśnięcie prawego klawisza myszy, a następnie wybranie opcji „Otwórz okno polecenia tutaj”). Gdy to zrobisz, wpisz go install i voilà, napisałeś Hello World w golang. Czy napewno? No nie do końca. Zrobiły to za Ciebie moje skrypty, jednak zapewne widzisz, że w folderze /src/ powstał folder główny projektu wraz z plikiem main.go. Tak jak w innych językach programowania ten plik steruje całym programem. Zawarta jest w nim funkcja main, która wywoływana jest jako pierwsza.

Struktura pliku main.

Przyjrzyjmy się strukturze naszego pliku. Na początku pliku zawsze opisane jest, do jakiego pakietu należy ten plik. Pakietów może być wiele. To coś jak przestrzenie nazw w C++ i C#. Deklaruje je się po prostu pisząc nazwę pakietu. Należy zapamiętać jedynie by zawsze trzymać pliki z danego pakietu w folderze takim jaka jest nazwa jego pakietu. Czyli, gdy dla pliku X.go zadeklarowany jest pakiet Y, to należy umieścić pliki, które mają zadeklarowany ten pakiet w folderze o nazwie Y. W przeciwnym wypadku można napotkać na problemy.

W środku zapisane są importy. W naszym pliku jest tylko jeden import – import fmt . Import to instrukcja preprocesora , która wkleja nam po prostu kod z danego pakietu (tak samo jak słówko „include” w C++ albo „using” w C#). Pakiet „fmt” to główny pakiet Input/Output języka Go. Coś jak iostream .

Na końcu jest już tylko sama funkcja main. Wygląda na bardzo prostą. Nie ma zapisanego typu, który zwraca. Między klamrami jest jedynie instrukcja fmt.Println("Hello World") . Ma ona za zadanie wypisać tekst na ekranie. Zawsze, gdy chcemy wywołać funkcje z danego pakietu, to nazwę funkcji należy poprzedzić nazwą pakietu i operatorem wyłuskania (kropki). Po skompilowaniu i uruchomieniu program wypisuje na ekranie „Hello World”.

Podsumowanie

Jak widać Golang to bardzo prosty i intuicyjny język. Nie miałem zamiaru nauczyć Cię go tu całego, a jedynie pokazać Ci namiastkę tego jak on wygląda w praktyce i co on potrafi. Moim zdaniem jest to świetny język nawet na start i warto się go nauczyć. Nie jest trudny do opanowania i do użytkowania. Warto też się go nauczyć chociażby dlatego, że na każdej platformie ten sam kod Golanga kompiluje się i działa tak samo, a tych platform jest sporo. Posiada on ogrom własnych bibliotek, a w dodatku społeczność, która go rozwija tworzy cały czas nowe.

Jeżeli chciałbyś/chciałabyś dowiedzieć się więcej lub nauczyć się tegoż wspaniałego języka programowania, to polecam Ci tą stronę.

]]>
/jezyk-go-czy-warto-wprowadzenie/feed/ 5 513
Qt – Pierwszy projekt – Fake blue screen /qt-pierwszy-projekt-fake-blue-screen/ /qt-pierwszy-projekt-fake-blue-screen/#comments Sun, 25 Sep 2016 11:43:04 +0000 http://nastoletni.pl/?p=415 Okej, wstęp już za nami, wypadałoby zrobić jakiś mały programik. W tym poradniku pokażę, jak napisać program, który bardzo przypomina blue screena . Zastosowanie? Można wkręcać znajomych/nauczycieli, u mnie w szkole 4/4 informatyków się nabrało, w tym poradniku wykorzystamy część wiedzy z poprzedniego poradnika, wykorzystamy Qt Quick Controls 2, Qt 5.7.

Dobra, jak się za to zabrać? Zróbmy niebieski ekran, w tym celu do ApplicationWindow podpinamy właściwość, jaką jest kolor i ustawiamy na „blue”, teraz trzeba zrobić, aby nasza aplikacja nie wyglądała jak aplikacja, w tym celu dodajemy właściwość flags i wartość Qt.FramelessWindowHint , kod powinien wyglądać tak:

Teraz wypadałoby zmusić aplikację, aby była na pełnym ekranie. Jest parę sposobów, moim zdaniem najprościej będzie zaimportować moduł od okien import QtQuick.Window 2.0 , następnie ustalić width okna na Screen.width i analogicznie height .

Teraz potrzebny nam jakiś tekst, w tym celu do ApplicationWindow „podłączamy” komponent Text, ustalamy kolor, wielkość, font i inne ficzery.

Całość powinna wyglądać tak:

Nasz program właściwie jest już gotowy, można jeszcze dostosować font, wielkość itp. Co do tzw. deploymentu , czyli doprowadzania aplikacji do stanu, w którym można ją uruchomić na innym komputerze – opiszę ten proces w kolejnym poradniku, jest to głębszy temat.

]]>
/qt-pierwszy-projekt-fake-blue-screen/feed/ 1 415
Synchronizacja wątków w C# i C++ (Spinlock) /synchronizacja-watkow-w-c-i-c-spinlock/ /synchronizacja-watkow-w-c-i-c-spinlock/#comments Thu, 15 Sep 2016 13:18:45 +0000 http://nastoletni.pl/?p=309 Wielowątkowość, synchronizacja… Ale po co to komu?

Przytoczę pewne bardzo obrazowe porównanie. Wyobraź sobie, że jesteś w restauracji. Podnosisz menu, wybierasz potrawę i czekasz na kelnera. Widzisz, że tymczasowo obsługuje innego klienta. Mija kilka, kilkanaście minut, a kelner wciąż stoi przy stoliku innego klienta, obsługuje tylko jego. Dopiero, gdy klient płaci i wychodzi z restauracji, kelner podchodzi do Ciebie i zaczyna cię obsługiwać. W tym czasie przychodzi inny klient, ale kelner jest zajęty tylko tobą. Mniej więcej tak działa aplikacja jednowątkowa (nasz kelner). W oczywisty sposób jest to nieefektywne.

Wtedy niczym superbohater, przybywa wielowątkowość.

Super! A więc zyskujemy trochę wydajności. Jednak rozważmy ten kod w języku C# i przeanalizujmy go.

Co tu się dzieje? Program sprawdza czy plik examplefile.txt istnieje, jeżeli nie, tworzy go. Teraz przejdźmy do tego, dlaczego kod ten nie jest bezpieczny. Jako, że dziś mamy do czynienia z wieloma procesorami (rdzeniami), to w tle mogą działać równocześnie różne programy. Nie ma więc żadnej gwarancji, że jeden z nich nie utworzy pliku o nazwie examplefile.txt właśnie w tym katalogu. Rozchodzi się więc o moment pomiędzy otrzymaniem informacji, że plik nie istnieje, a utworzeniem go. Klasa tego błędu to tzw. Race Condition . Aby uniknąć takich sytuacji, wymagana jest synchronizacja.

Najprostszym używanym niskopoziomowym mechanizmem do synchronizacji są tzw. Spinlocki (w luźnym tłumaczeniu – kręcące się blokady). Jest on w zasadzie aktywną pętlą, która czeka, aż dany obiekt (blokada) zostanie zwolniony. Dziś w zasadzie się z nich nie korzysta, gdyż nowoczesne języki takie jak C# mają wbudowane mechanizmy synchronizujące, takie jak lock . Lock działa w ten sposób:

i jest równoważny temu:

gdyż lock jest w zasadzie aliasem tego drugiego. Teraz, zanim przejdę do spinlocków, wytłumaczę z pomocą Wikipedii czym jest owa sekcja krytyczna.

Sekcja krytyczna – fragment kodu programu, w którym korzysta się z zasobu dzielonego, a co za tym idzie w danej chwili może być wykorzystywany przez co najwyżej jeden wątek.

Myślę, że ta definicja spokojnie wystarczy. Przejdźmy teraz do Spinlocków.

W C++ można podejść do tego na prawdę prosto, jest to zwyczajnie (jak już pisałem) aktywna pętla. Wygląda to mniej więcej tak (korzystając z biblioteki <atomic> służącej właśnie do synchronizacji, i jak sama nazwa mówi do operacji atomowych):

Dzięki Spinlockom możemy także zsynchronizować uruchomienie wątków, aby wystartowały w mniej więcej tym samym czasie, co można zaimplementować bardzo prosto:

Wówczas nasza blokada może blokować(?) wykonanie wątku tak długo, jak programista sobie zażyczy. Słówka volatile użyłem, gdyż nie chce, by kompilator potraktował tę pętle tak:
if (slock != THREAD_COUNT) while(1); a tego oczywiście nie chcemy.

C# również udostępnia nam mechanizm spinlocków, ba, w zasadzie całą strukturę! Napisałem tu krótki program implementujący Spinlock:

Przeanalizujmy, co się w nim dzieje.


Zacznijmy tuż przed punktem wejścia do programu, tj. static void Main(string[] args) . Tworzymy w nim instancję struktury SpinLock, w bardzo standardowy sposób, oraz prywatne pole int o nazwie _count , którego zadaniem będzie przechowywanie pewnej wartości. Wewnątrz funkcji głównej tworzę prostą pętlę, w niej tworze natomiast nowy obiekt Task , który będzie asynchronicznie wykonywał naszą funkcję vInc() . Tam widzimy standardowy blok try finally ,  w którym to rozpoczynamy spin SpinLocka. Zauważ, że zanim wszedłem w aktywną pętlę, flagę lockTaken ustawiłem na false i jest przekazywana przez referencję, nie wartość.

To by było na tyle, jeżeli czegoś wystarczająco nie rozwinąłem, albo macie jakieś pytania, zachęcam do komentowania!


Źródła

  • https://msdn.microsoft.com/en-us/library/windows/desktop/ms683614(v=vs.85).aspx
  • http://en.cppreference.com/w/cpp/atomic/atomic_flag
  • http://www.c-sharpcorner.com/UploadFile/1d42da/spinlock-class-in-threading-C-Sharp/
  • https://camedcomputing.wordpress.com/2016/03/31/wielowatkowosc-w-c-threading-locki-spinlocki /

]]>
/synchronizacja-watkow-w-c-i-c-spinlock/feed/ 4 309