Protokół TCP ma to do siebie, że możemy mu zaufać – zawsze mamy gwarancję, że dane wysłane trafią do odbiorcy (a jeśli nie trafią, to będziemy o tym wiedzieli). Dlatego możliwe jest traktowanie przesyłu danych tą drogą podobnie, jak chociażby wymiany danych między pamięcią operacyjną a plikiem na dysku. Z tego też powodu wiele języków programowania pozwala na opakowanie połączeń TCP/IP w strumienie o identycznym interfejsie jak te służące na przykład do manipulowania zawartością pliku.
W praktyce jednak nie da się pominąć zupełnie tego prostego faktu, iż odbierane dane pochodzą z sieci i wysyłane także tam trafiają. Dotyczy to na przykład takiej kwestii jak dzielenie informacji na małe porcje u nadawcy i ich interpretacja po stronie odbiorcy.
Jak bowiem wiadomo, dane przesyłane przez TCP/IP mogą być po drodze dzielone i składane, a zagwarantowana jest jedynie ich kolejność. Nie ma natomiast pewności, że kawałek danych wysłany jednym wywołaniem w rodzaju Send
zostanie odebrany także jednym wywołaniem Receive
. Granice między porcjami danych każdy protokół musi więc ustalać samodzielnie. Można to zrobić na kilka sposobów, jak chociażby:
\0
). Odbieranie danych polega wtedy na odczytywaniu kolejnych bajtów do bufora i interpretacji pakietu dopiero po otrzymaniu końcowego znacznika.<foo>...</foo>
– to koniec takiego elementu będzie jednocześnie wiadomością o końcu pakietu. Można to więc traktować jak nieco bardziej skomplikowany wariant znaczników końca.Jeśli tworzymy nowy protokół dla własnych aplikacji, to który wariant wybrać? Pierwszy wydaje się być dobry dla protokołów binarnych; tych jednak generalnie nie powinno się używać ze względu na liczne problemy z kodowaniem i pakowaniem danych. Druga opcja jest bardzo szeroko stosowana w wielu powszechnie używanych usługach sieciowych i wydaje się sprawdzać całkiem dobrze. Trzecia jest w gruncie rzeczy podobna, ale nieco bardziej złożona i może być kłopotliwa od strony kodu odbierającego dane.
Zasadniczo to systemy uniksowe znane są z intensywnego wykorzystania trybu tekstowego, czyli wiersza poleceń konsoli. W przypadku systemu Windows wydaje się, że jest to bardziej pozostałość po starym (nie)dobrym DOS-ie, która została prawie całkowicie zastąpiona przez interfejs graficzny. Problem jednak w tym, że nie wszystko da się zawsze wygodnie wyklikać – a co więcej, takie czynności niezbyt nadają się do zautomatyzowania (na przykład po to, aby wielokrotnie przeprowadzać zmiany w konfiguracji na wielu maszynach). Mając elastyczny tryb tekstowy, zwykle bowiem wystarczy napisać odpowiedni skrypt; w przypadku trybu graficznego nie ma takiej możliwości.
Ten mankament był w Windows obecny od dawna i różne były sposoby na jego obejście – począwszy od plików wsadowych (.bat) przez eksporty/importy Rejestru (.reg), pliki .inf czy skrypty WSH (Windows Scripting Host). Te ostatnie są na przykład znane z powodu… kilku wirusów, które zdołały się szeroko rozprzestrzenić, wykorzystując tę technologię (np. słynny I.Love.You).
Wszystkie podobne pomysły rozwiązywały jednak cały problem dość średnio. Lecz od jakiegoś czasu istnieje Windows PowerShell, który wydaje się znacznie ciekawszym rozwiązaniem. Jest to tekstowa powłoka poleceń – wraz ze skryptowym językiem programowania – która nie tylko jest o wiele bardziej przydatna niż wcześniejsze wynalazki Microsoftu, ale też wypada wcale nieźle w porównaniu z innymi shellami, jak choćby bash. Ma ona bowiem kilka interesujących cech szczególnych:
W tym momencie zapewne przydałby się wymowny przykład, który może wyglądać choćby następująco:
To polecenie najpierw listuje wszystkie procesy w systemie (ps
), następnie wybiera spośród nich te, które nie odpowiadają, aby w końcu posłać je do Krainy Wiecznych Zwisów :) Operujemy tutaj na obiektach reprezentujących procesy, a jedną z ich właściwości jest, jak widać, Responding
, którą można użyć do filtrowania.
W pełnej wersji komenda wyglądałaby tak naprawdę następująco:
co być może wygląda wymowniej, lecz jest z pewnością bardziej rozwlekłe. Na szczęście PowerShell definiuje fabrycznie kilkanaście aliasów, które mapują się na polecenia odpowiadające z grubsza tym znanym z innych shelli – jak dir
, ls
, cd
, rm
, ps
, man
czy type
. To pozwala w miarę szybko poznać podstawowe komendy i przyspiesza ich wpisywanie.
Na koniec trzeba stwierdzić, że przydatność PowerShella zależy prawdopodobnie przede wszystkim od tego, czy potrafimy wykorzystać całe to ogromne bogactwo klas .NET, COM i WMI, które powłoka ta udostępnia. Jeśli tak, to możemy znaleźć dla niej szerokie pole zastosowań. Polecam w każdym razie przyjrzenie się temu wynalazkowi (dostępnemu na każdą sensowną wersję Windows, tj. od XP SP2 wzwyż). Sam lubię używać go do zabijania procesów znacznie bardziej niż standardowego Menedżera zadań ;-)
Jako język nie posiadający słowa kluczowego finally
, C++ preferuje nieco inną metodę na radzenie sobie z nieprzewidzianymi “wyskokami” z funkcji i związaną z tym możliwością wycieku zasobów (resource leak). Ten inny sposób jest znany skądinąd jako RAII: Resource Acquisition Is Initialization i polega na związaniu z każdym użyciem zasobu jakiegoś obiektu lokalnego, np.:
Proste i całkiem wygodne, jeśli tylko posiadamy już (lub zechcemy napisać) odpowiednią klasę, która w konstruktorze pozyskuje dany zasób – tutaj blokadę muteksa – a w destruktorze go oddaje.
Ale ten nieskomplikowany mechanizm daje możliwości popełnienia błędów, które są na swój interesujące, ale w realnym kodzie na pewno niezbyt przyjemne :) Pierwszy z nich związany jest z faktem, że lokalnych obiektów nie tworzy się znowu aż tak dużo i można popełnić w ich składni drobne, acz wielce znaczące faux pas z nawiasami:
Taki wiersz nie stworzy nam bowiem żadnego obiektu, ale zadeklaruje funkcję lock
, zwracającą obiekt typu ThreadLock
i niebiorącą żadnych argumentów. Zaskakujące? A to tylko prosta konsekwencja faktu, że cokolwiek, co można zinterpretować w C++ jako deklarację funkcji, zostanie tak właśnie zinterpretowane.
Można jednak ripostować, że nic takiego nie zdarzy się, jeśli do konstruktora naszego obiektu-blokady przekażemy chociaż jeden parametr. A zwykle tak właśnie będzie; tutaj np. byłoby nim odwołanie do obiektu typu mutex lub semafora, który chcemy zająć. Jednak nie zmienia to faktu, że w większości przypadków obiekt realizujący RAII wystarcza nam przez samo swoje istnienie, co z kolei sprawia, że w dalszym kodzie w ogóle się do niego nie odwołujemy. To zaś może spowodować, że pominiemy i tak nieużywany składnik jego deklaracji – czyli nazwę:
Takie zagranie również nie powinno wywołać protestów kompilatora, ale prawie na pewno nie jest tym, o co nam chodzi. Tworzony obiekt jest teraz bowiem nie lokalny, ale tymczasowy: jego zasięg ogranicza się do wyrażenia, w którym został wprowadzony. Czyli do… średnika kończącego powyższą instrukcję! Taki też zakres ma opakowana przez ów obiekt blokada międzywątkowa.
Jak zatem widać, jest tu kilka okazji do popełnienia błędów, które mogą być trudne do wykrycia. Powinniśmy więc zwrócić na nie uwagę tym bardziej, że wobec braku w C++ instrukcji finally
technika RAII jest jedynym sensownym wyjściem dla lokalnego pozyskiwania i zwalniania zasobów.
Kiedy piszemy jakieś klasy, wydaje nam się, że dokładnie przemyśleliśmy ich interfejs, który jest intuicyjny, łatwy w użytkowaniu, wydajny, rozszerzalny, i tak dalej. Musi tak być, skoro sami go napisaliśmy, prawda? ;-) Cóż, życie niestety aż nazbyt często weryfikuje to jakże naiwne założenie. A wtedy pozostaje nam przyjrzeć się, co zaprojektowaliśmy nie dość dobrze i próbować to zmienić.
Jest tylko jedno “ale”: do chwili, gdy zdecydujemy się na zmiany w interfejsie, nasza klasa może być już wykorzystywana w innych fragmentach projektu lub zgoła nawet w innych projektach. To może dotyczyć i takich, których nie jesteśmy autorami – jeżeli tylko zdecydowaliśmy się udostępnić naszą twórczość szerszej publiczności. A wtedy sprawa staje się co najmniej problematyczna, bo każda nieprzewidziana modyfikacja może spowodować kaskadę kolejnych zmian, jakie trzeba będzie poczynić w odpowiedzi na nią.
Jeśli naturalnie bardzo tego chcemy możemy je wszystkie przeprowadzić. Wówczas przynajmniej nasz interfejs będzie znów elegancki – przynajmniej do czasu, gdy stwierdzimy, że znów już taki nie jest ;] Jest to jak najbardziej możliwe, ale pewnie nie trzeba wspominać, jak pracochłonna może być taka operacja.
Spójrzmy raczej na sposób, w jaki radzą sobie z tym problemem twórcy szeroko wykorzystywanych bibliotek programistycznych różnego rodzaju. To, co jest ich cechą wspólną w kolejnych wersjach, to zachowywana zawsze kompatybilność wstecz. Jest ona osiągana przez modyfikacje polegające wyłącznie na dodawaniu elementów interfejsu bez zmiany już istniejących. Pewnie najbardziej znanym przejawem takiej praktyki jest istnienie funkcji z końcówką Ex
w Windows API, które robią trochę więcej niż ich “zwykłe” odpowiedniki i mają nieco zmienioną listę parametrów.
To oczywiście nie jedyna droga nieinwazyjnego poprawiania interfejsu. Takich sposobów jest co najmniej kilka, jak chociażby:
Ex
. Zauważmy, że po takiej operacji możemy zmienić implementację starych metod tak, aby wewnętrznie korzystały one z nowych, rozszerzonych wersji. Póki nie zmieni się sposób ich wywoływania, kod pozostanie kompatybilny wstecz.Można się zastanawiać, czy taki “miękki refaktoring” nie jest odkładaniem na później tego, co i tak należy wykonać? Zapewne tak: przecież każdy kod można zawsze napisać lepiej, czyli od nowa :) Trzeba jednak odpowiedzieć sobie na pytanie, czy korzyści z tego będą większe niż włożony wysiłek. Jeżeli nie kodujemy jedynie dla samej przyjemności kodowania, to nie ma cudów: odpowiedź najczęściej będzie negatywna.
Jeśli w praktyce obliczamy wartości funkcji sinus lub cosinus dla danego kąta, to bardzo często zdarza się, że tak naprawdę potrzebujemy ich obu. Jest tak przy obliczaniu punktów okręgu, przy rozkładzie wektorów sił i jeszcze dla wielu innych okoliczności. Zależy nam naturalnie, aby policzyć to wszystko tak szybko, jak tylko się da, dlatego dobrze jest stosować funkcje w rodzaju sincos, które wyznaczają obie wartości jednocześnie.
Niestety nie każdy język programowania taką funkcję posiada. Mają ją języki shaderowe (GLSL, HLSL i asembler GPU) oraz np. Delphi, ale już nasz ulubiony C++ nie. Można by oczywiście uzupełnić ten brak poprzez taką implementację:
ale chyba nie trzeba nikogo przekonywać, że większego sensu ona nie ma :) Nie występuje tu bowiem żaden zysk na wydajności, bo wartości są obliczane oddzielnie.
Co więc można zrobić? Ano wykorzystać to, co drzemie w jednostce zmiennoprzecinkowej procesora, ale nie jest używane przez wszystkie języki wyższego poziomu. Istnieje mianowicie instrukcja FSINCOS, która wykonuje całą potrzebną “magię”. Należy ją tylko opakować:
Jakkolwiek tajemniczo to może wyglądać, funkcja ta po prostu ładuje argument (kąt) na stos FPU, by potem odebrać wyniki – cosinus i sinus. W przypadku operowania na liczbach typu float
nie ma możliwości podania zbyt dużego/małego argumentu, więc nie trzeba sprawdzać rejestru flag.
I tyle – funkcja mała, acz użyteczna. Asembler czasem się przydaje, proszę państwa ;P
Oto stereotyp typowego programisty: przygarbiony osobnik, koniecznie płci męskiej, z zapałem stukający w klawisze celem produkcji niezrozumiałego wyrobu tekstopodobnego i odchodzący od ekranu tylko w stanach wyższej konieczności. Czy to brzmi znajomo? Być może tak. Nie sądzę jednak, żeby ktokolwiek chciał przypisać sobie taką definicję. Jest ona zresztą zupełnie nieuzasadniona!
Koder bowiem istotą społeczną jest i nie ma co do tego żadnych wątpliwości. Aby sie jednak o tym przekonać, należy ów gatunek zaobserwować w jego naturalnym środowisku – czyli w grupie osób o podobnych zainteresowaniach i pasjach. Wówczas okazuje się, że kod jest jak pewna firma produkująca telefony komórkowe – łączy ludzi :)
Taką okazją są oczywiście wszelkie zloty, zjazdy, konwenty, itp. Nie inaczej jest już od pięciu lat z doroczną konferencją IGK w Siedlcach. Trzeba zresztą przyznać, że słówko ‘konferencja’ pasuje do tej imprezy w stopniu wybitnie zmiennym. Zdarzają się edycje, które rzeczywiście przypominają naukowe konferencje lub (uwaga, trudne słowo) sympozja. Jest tak zwykle wtedy, gdy odpowiednie zareklamowanie imprezy powoduje przyciągnięcie większej liczby uczestników oraz sponsorów, co przekłada się na bogatszą agendę. W skrajnym przypadku mogą to być nawet dwie równoległe sesje referatów, co aczkolwiek zdarzyło się dotąd tylko raz.
Jeśli zaś na miejscu nie pojawia się tłum gości, a plan poszczególnych dni nie jest wypełniony do ostatniej minuty, konferencja nabiera bardziej kameralnego charakteru. W tej kategorii modelowym przykładem jest rzecz jasna pionierska edycja nr 1. W tych latach bardziej ujawnia się ten drugi – oprócz merytorycznego – aspekt konferencji: traktowanie jej przede wszystkim jako spotkania ludzi, którzy na czymś dobrze się znają i oprócz wymiany doświadczeń chcą też dobrze się bawić.
A oto nietrudno, a dzięki szybkiemu rozwojowi techniki jest właściwie coraz prościej :) Weźmy na przykład grupę koderów i umieśćmy ją w sali, której jedynym znaczącym elementem jest duży stół i krzesła. Za parę minut zobaczymy tam skleconą naprędce, lecz działająca bez problemów sieć opartą o switche, crossowane kable, WiFi i co tylko się da. Jakimś magicznym sposobem zawsze przecież znajdzie się jedno gniazdko, jedno miejsce i jeden kawałek skrętki więcej ;-)
Istnieją też bardziej wyrafinowane i zorganizowane formy rozrywki niż zwyczajowe rozgrywki w gry sieciowe. Można na przykład zaaranżować tematyczny quiz, w którym uczestnicy odpowiadają na pytania z dziedziny – a jakże – kodowania oczywiście. Ten pomysł nie jest nowy ani oryginalny – to już prawie tradycja na GDC (Game Developers Conference). Jak się okazało, przeniesienie go na polski grunt okazało się całkiem niezłym posunięciem. Zwłaszcza, że ta zupełnie oddolna inicjatywa została w końcu wsparta przez organizatorów IGK i zrealizowana przy ich pomocy.
I być może za rok zostanie przynajmniej powtórzona, a może nawet nie będzie jedynym tego typu ‘nadobowiązkowym’ wydarzeniem. Bo przecież kodowanie kodowaniem, ale – nie ma co ukrywać – grać też trzeba, prawda? ;]
Jednym z zabawnych aspektów konferencji takich jak IGK jest możliwość spotkania ludzi, których dotąd znaliśmy jedynie za pośrednictwem internetowych form kontaktu, takich jak fora czy komunikatory. Przez ten czas zdążyliśmy zapewne wyrobić sobie jakieś wyobrażenia o tym, kto kryje się pod ciągiem znaków składających się na dany nick. Zależnie od tego, czego te przypuszczenia dotyczą, ich trafność może wahać się od całkiem wysokiej do niemal zerowej.
Zawsze jednak jest to ciekawe przeżycie, a ich mnogość to jeden z argumentów przemawiających za uczestniczeniem w konferencjach w rodzaju IGK :) Przybycie po raz pierwszy na tą imprezę jest często oznaką, że dana osoba zabawi na Warsztacie nieco dłużej, nie zniknie bez śladu i nie straci zainteresowania tematyką programowania gier. Od reguły są oczywiście wyjątki, które, jak wiemy, głównie ją potwierdzają.
Nie da się jednak ukryć, że konferencja – mająca od początku swoje korzenie w pomyśle na “zjazd Warsztatu” – stała się najważniejszym, corocznym wydarzeniem tego community. I fakt, że co roku widzimy na niej nowe – warsztatowe – twarze oznacza, że jako społeczność trzymamy się mimo wszystko całkiem dobrze :)