Unit Sockets - Free Pascal

0

Może trochę przydługi wstęp będzie ale to mój pierwszy post tutaj i jeszcze nie ogarniam wszystkich zasad tego forum.

Piszę grę dla dwóch osób. Gra turowa i dobrze by było gdyby gracze nie widzieli swoich ruchów więc opcja "hot seats" odpada. Zostaje mi tylko przesyłanie danych przez sieć. Żeby uprościć działanie gry założymy że będą to szachy (oczywiście szachy to nie są, co można wywnioskować chociażby z tego że gracze nie powinni widzieć swoich ruchów).

Mamy mapę podzieloną na pola i pionki na mapie na odpowiednich polach. Pierwszy gracz wykonuje ruch i po zakończeniu ruchu chcę, żeby dane z mapy były przesłane do drugiego gracza, następnie on wykona ruch i uaktualnione dane z mapy były wysłane z powrotem do gracza numer 1.

Wybrałem kompilator FPC i to co chce napisać to jest raczej trolling niż programowanie bo do rysowania używam WinGraph znalezionego na necie a grafika to nic innego jak rysowanie bitmap z 256-bitowego obrazka bmp a nie żadne opengl czy directx. Więc jak już troluje to troluje na całego i tu pojawia się moja prośba: Czy ktoś z forumowiczów był by tak miły i opisał unit Sockets wraz z prostymi krótkimi przykładowymi programami? Jak nawiązać połączenie? Jak przesłać dane? Czy muszę przesyłać tylko stringi czy mogą to być dane typu Byte lub całe tablice? Czy jest limit wielkości przesłanych danych? W internecie tego szukałem ale nie mogłem znaleźć opisu "dla idiotów" z przykładowymi prostymi programami. Bardzo bym był wdzięczy za pomoc.

0

Lepszym pomysłem niż użycie dosyć cienkiego sockets, jest użycie Synapse ( http://www.ararat.cz/synapse/doku.php/start i tu http://wiki.lazarus.freepascal.org/Synapse ). Jest ono wieloplatformowe i wspiera wszystko co do sieci normalnym ludziom potrzeba. Jest od niego masa tutków w necie, więc chyba to najlepszy wybór.

Jeżeli mimo wszystko chcesz Sockets to masz tutaj: http://www.freepascal.org/docs-html/rtl/sockets/index.html - powinny gdzieś być przykłady.

@Patryk27, Synapse<>Sockets

0

Uh chyba porwałem się z motyką na słońce... Zobaczę co to to Synapse skoro tak polecacie. W razie jak bym miał jakieś problemy to są tematy na forum odnośnie Synapse bym nie spamował tutaj tylko sobie poczytał?

0

Zobaczę co to to Synapse skoro tak polecacie. W razie jak bym miał jakieś problemy to są tematy na forum odnośnie Synapse bym nie spamował tutaj tylko sobie poczytał?

Przecież dałem już link do tutoriala angielskiego całkiem dobrego do Synapse. Skoro nie znasz ang. to nie licz na jakikolwiek lepszy tutorial (chociaż pewnie pare gdzieś jest), a jeżeli znasz ang. to wpisanie hasła w google nie powinno być dla ciebie problemem.

Uh chyba porwałem się z motyką na słońce...

Od czegoś trzeba zacząć zabawę z socketami. Jeżeli będziesz mieć jakieś konkretne problemy śmiało pytaj spróbuję rozjaśnić problemy z synapse (chociaż za eksperta w tej sprawie się nie uważam). Tylko oczywiście musisz conieco wiedzieć o programowaniu i wykazać inicjatywę w postaci przeszukania googla i poeksperymentowania.

Pare przydatnych linków o synapse:
http://wiki.lazarus.freepascal.org/Synapse - tutek i instalacja pod Lazarusem (warto ściągnąć Lazarusa i go ogarnąć jak się już troche umie kodzić, bo wygląda i działa o niebo lepiej niż IDE FPC a kompilator jest ten sam).
http://synapse.ararat.cz/doc/help/ - Dokumentacja Synapse, osobiście uważam ją za nie specjalnie przydatną, ale czasami warto zajrzeć.
http://www.ararat.cz/synapse/doku.php/public:howto - Tutki o Synapse, bardzo dobrze uczą jeżeli używa się ich do referencji względem dem dołączonych do Synapse.

0

Dobra trochę poczytałem i spłodziłem coś takiego:

{$MODE DELPHI}
Program Serwer;
Uses Crt,
     blcksock;
Var
  Server : TTCPBlockSocket;
  Buff   : Byte;

Begin
  ClrScr;

  Write ('Czekanie na połaczenie...');
  Server := TTCPBlockSocket.Create;
  Server.Bind('192.168.0.2', '1234');
  If Server.LastError <> 0 Then
  Begin
    Write ('Błąd: ');
    Writeln (Server.LastErrorDesc);
    ReadKey;
    Halt (Server.LastError);
  end;
  Server.Listen;
  Server.Accept;
  Writeln ('Połączono.');
  Writeln;

  Writeln ('Otrzymane dane:');
  Repeat
    Buff := Server.RecvByte (2000);
    Writeln (Buff);
  Until Buff = 0;
  Writeln;
  Writeln ('Koniec');
  Readkey;
end.
{$MODE DELPHI}
Program Klient;
Uses Crt,
     blcksock;
Var
  Client : TTCPBlockSocket;
  Buff   : Byte;

Begin
  ClrScr;
  Randomize;

  Write ('Łączenie...');
  Client := TTCPBlockSocket.Create;
  Client.Connect('192.168.0.2', '1234');
  If Client.LastError <> 0 Then
  Begin
    Write ('Błąd: ');
    Writeln (Client.LastErrorDesc);
    ReadKey;
    Halt (Client.LastError);
  end;
  Writeln ('Połączono');
  Writeln;

  Writeln ('Wysyłanie');
  Repeat
    Buff := Random (256);
    Writeln (Buff);
    Client.SendByte (Buff);
  until buff = 0;
  Writeln ('Koniec Wysyłania');
  Readkey;
end.

I krótki opis jak to powinno działać oraz jak działa:

  1. Jak powinno działać:

Wiadomo serwer czeka na połączenie, klient próbuje się połączyć. Po połączeniu klient losuje liczby z przedziału od 0 do 255 wypisuje na ekran i wysyła do serwera. Serwer Odbiera liczby i również wypisuje na ekran. Gdy klient wylosuje 0 oba programy kończą pracę.

  1. Jak działa:

Po odpaleniu serwera wyświetla się "Czekanie na połączenie...". No to odpalam klienta. Klient łączy się z serwerem i wypisuje wylosowane liczby ostatnia to "0" i potem następna linijka to "Koniec". A w serwerze (po odpaleniu klienta) wypisuje "Czekanie na połączenie...Połączono", linijka przerwy, "Otrzymane dane:" i po dwóch sekundach (taki timeout) "0" linijka przerwy i "Koniec".

Jakaś interakcja między dwoma programami jest ale albo klient nie wysyła danych, albo serwer ich nie odbiera. Żeby nie było problemów to firewalla i antywirusa wyłączyłem całkowicie bo myślałem, że te programy coś blokują ale to nic nie dało. Wersje FPC mam 2.4.0 a synapse 39 (przynajmniej tak była oznaczona na stronie). Ja coś źle zrobiłem czy za stary kompilator na tą wersje synapse lub lipne synapse?

0

Ja coś źle zrobiłem czy za stary kompilator na tą wersje synapse lub lipne synapse?

Coś skopałeś, nawet wiem co.

Server.Accept; - to zwraca socket za pomocą którego komunikujesz się z klientem. Socket servera służy tylko do odbierania połączeń. Do wysyłania/odbierania danych od klientów służy właśnie ten socket który otrzymujesz z accept (jest on już otwarty,po prostu robisz na nim to co chcesz).

Jeszcze taka mała uwaga: Generalnie niewiele robi się tego typu protokołów gdzie każdy pakiet ma jeden bajt, ja bym spróbował nakodzić coś co wysyła między sobą stringi, byłoby bardziej życiowe :) . Ale też trudniejsze, więc jak na pierwszy raz i tak wypadłeś ponadprzeciętnie :P

0

Server.Accept; - to zwraca socket za pomocą którego komunikujesz się z klientem. Socket servera służy tylko do odbierania połączeń. Do wysyłania/odbierania danych od klientów służy właśnie ten socket który otrzymujesz z accept (jest on już otwarty,po prostu robisz na nim to co chcesz).

Ok rozumiem. W dokumentacji doczytałem, że accept to jest funkcja i zwraca dane typu TSocket. Wszystko fajnie tylko co mam zrobić z tą wartością? I co to dokładnie jest za wartość? Bo tak jak przeglądam dokumentacje i googluje o synapse to jedyne co zaobserwowałem to wywołanie accept jak procedury bez przypisania wyniku do czegokolwiek. Na tą chwilę jedyne co wymyśliłem to takie cuś:

 
// program uses itp.
var
  Server : TTCPBlockSocket
  Dane : TTCPBlockSocket

Begin
  // Obsługa socketu Server i ustanowienie połączenia
  Dane := Server.Accept;
  Buff := Dane.RecvByte (2000);
  // Reszta programu
end.

Ewentualnie przed:

 
Buff := Dane.RecvByte (2000);

Można jeszcze wcisnąć:

 
Dane := TTCPBlockSocket.Create;

albo jeszcze wcześniej przed accept.

Jednak dzisiaj już późno więc przetestuje to rozwiązanie jutro i podzielę się wynikiem.

Jeszcze taka mała uwaga: Generalnie niewiele robi się tego typu protokołów gdzie każdy pakiet ma jeden bajt, ja bym spróbował nakodzić coś co wysyła między sobą stringi, byłoby bardziej życiowe :) . Ale też trudniejsze, więc jak na pierwszy raz i tak wypadłeś ponadprzeciętnie :P

To co napisałem to tylko próba ogarnięcia i poznania zasady działania synapse, więc wybrałem najprostszą metodę :) Jak zrozumiem o co chodzi to pobawię się stringami bo z tego co pamiętam to w mojej grze będę musiał przesłać około 700 bajtów danych więc robić z tego 700 pakietów jest bez sensu szczególnie, że pakiet może mieć 1kB więc wszystko mogę zmieścić w jednym pakiecie ewentualnie w dwóch, więc jeśli dobrze zrozumiałem to co czytałem o przesyłaniu danych to przesłanie jednego pakietu jest 700 razy szybsze niż przesłanie 700 pakietów :)

0

Bo tak jak przeglądam dokumentacje i googluje o synapse to jedyne co zaobserwowałem to wywołanie accept jak procedury bez przypisania wyniku do czegokolwiek

O to bardzo ciekawe bo to bez sensu jeżeli chcemy coś zrobić z klientem który się do nas podłączył. Pierwszy kod który dałeś jest poprawny, po akceptacji połączenia odbierze on od niego dane. Najlepiej wtedy zamknąć też Socket czekający na połaczenia o ile nie chcesz więcej połączeń.

Jak zrozumiem o co chodzi to pobawię się stringami bo z tego co pamiętam to w mojej grze będę musiał przesłać około 700 bajtów danych więc robić z tego 700 pakietów jest bez sensu szczególnie, że pakiet może mieć 1kB więc wszystko mogę zmieścić w jednym pakiecie ewentualnie w dwóch, więc jeśli dobrze zrozumiałem to co czytałem o przesyłaniu danych to przesłanie jednego pakietu jest 700 razy szybsze niż przesłanie 700 pakietów

Jak się domyślam, to Synapse nie fluszuje tego Od razu, więc robią się grupki tych bajtów, nie chodziło mi o pakiety w rozumieniu czysto TCP.
Czas przesłania pakietu jest generalnie taki sam póki jest on jednoczęściowy (czyli wysyłany na raz), przy czym może on zostać spowolniony jeżeli w danym momencie nie ma jak go wysłać. Generalnie o ile gra nie wymaga tych pakietów superszybko nie musisz się specjalnie martwić o pakiety itd., bo grunt to żeby to wysłać i odebrać.

0

Ale jestem z siebie dumny :)

 
Server.Socket := Server.Accept;

Załatwiło sprawę i wszystko działa :)

Pierwszy kod który dałeś jest poprawny, po akceptacji połączenia odbierze on od niego dane.

I nie jest poprawny :) poczytałem wymyśliłem i działa :)

Przetestuje to teraz na dwóch oddzielnych komputerach. Jak będzie działać to wysyłanie danych z klienta do serwera ogarnięte :) Zostanie tylko wysyłanie z serwera do klienta. A jak to ogarnę to zajmę się stringami :)

@Edit
Dobra ogarnięte wszystko. Poniżej źródła dwóch programów (serwer,klient). Klient przesyła do serwera to co wpiszemy z klawiatury a serwer to wyświetla.

Serwer:

 
{$MODE DELPHI}
Program Serwer;
Uses Crt,
     blcksock;
Var
  Server : TTCPBlockSocket;
  Buff   : String;

Begin
  ClrScr;

  Write ('Czekanie na połaczenie...');
  Server := TTCPBlockSocket.Create;
  Server.Bind('192.168.0.2', '1234');
  If Server.LastError <> 0 Then
  Begin
    Write ('Błąd: ');
    Writeln (Server.LastErrorDesc);
    ReadKey;
    Halt (Server.LastError);
  end;
  Server.Listen;
  Server.Socket := Server.Accept;
  Writeln ('Połączono.');
  Writeln;

  Writeln ('Otrzymane dane:');
  Repeat
    Buff := Server.RecvString (10000);
    Writeln (Buff);
  Until buff = '';

Klient:

 
{$MODE DELPHI}
Program Klient;
Uses Crt,
     blcksock;
Var
  Client : TTCPBlockSocket;
  Buff   : String;

Begin
  ClrScr;

  Write ('Łączenie...');
  Client := TTCPBlockSocket.Create;
  Client.Connect('192.168.0.2', '1234');
  If Client.LastError <> 0 Then
  Begin
    Write ('Błąd: ');
    Writeln (Client.LastErrorDesc);
    ReadKey;
    Halt (Client.LastError);
  end;
  Writeln ('Połączono');
  Writeln;

  Writeln ('Napisz co wysłać (pusta linia zakończy prace programu):');
  Repeat
    Readln (Buff);
    Buff := Buff + CRLF;
    Client.SendString (buff);
  Until buff = ''+CRLF;
end.

Dzięki za pomoc :) Jak bym miał jeszcze jakieś problemy związane z synapse to zapytam tutaj, a jak skończę grę to się pochwalę :)

0

I nie jest poprawny poczytałem wymyśliłem i działa

No właśnie jest, a twój nie...

Ten kod co dałeś to jest piękny przykład że to że działa nie znaczy że jest dobrze. Twój kod powoduje wyciek pamięci na dodatek powoduje on to że port ciągle będzie zbindowany a socket ciągle będzie oczekiwać połączenia.

Weź jakoś tak to napisz jak to ludzie robią:

var
server:TTCPBlockSocket;
server_cl:TSocket;

[...]
server_cl:=server.Accept;
FreeAndNil(server);
[tutaj sobie odbierasz za pomocą server_cl]
0

Dobra nie rozumiem o co Ci chodzi.
Po wpisaniu

server_cl:TSocket; 

wywala dwa błędy Identifier not found "TSocket" oraz Error in type definition
Zupełnie nie wiem też o jaki wyciek chodzi no i jak wywalę obiekt socketa z pamięci to się zaczną Runtime errory.

1

Dodaj sobie moduły synsock,sysutils, ja je mam i mi widzi TSocket.

Zupełnie nie wiem też o jaki wyciek chodzi

Wyciek pamięci, masz niezwolniony obiekt do którego nie masz żadnej referencji.

no i jak wywalę obiekt socketa z pamięci to się zaczną Runtime errory.

A to z jakiej racji?!

0

Dobra z tego co zrozumiałem to by wysłać lub odebrać dane to wywołuje odpowiednie funkcje obiektu czy to serwer czy client (Serwer.SendCosTam, Client.RecvCosTam) Więc co mi po samym numerku z accept skoro to nie jest obiekt który wysyła/dobiera? Ewentualnie jeśli sam numer jest do czegoś potrzebny to do czego i jak go wykorzystać?

A żeby nie było żadnego wycieku to na końcu programu wystarczy dodać Client.Free, Server.Free?

0

Ah mój błąd nie doczytałem wczoraj co trzeba zrobić z TSocket :) Ale widzę że doszedłeś podobnie tylko że u ciebie gubi się oryginalna referencja.

var
server,server_cl:TTCPBlockSocket;
temp:TSocket;

begin
[blabla oczekiwanie na polaczenie]
temp:=server.Accept;
server_cl:=TTCPBlockSocket.Create();
server_cl.socket:=temp;
FreeAndNil(server);//Już nam oczekiwanie na połączenia nie potrzebne
[teraz sobie gadasz z clientem na server_cl].
FreeAndNil(server_cl);//Zwalniamy grzecznie klasę gdy już nie potrzebna.

A żeby nie było żadnego wycieku to na końcu programu wystarczy dodać Client.Free, Server.Free?

http://4programmers.net/Forum/Delphi_Pascal/97741-Pamiec_-_zwalnianie_i_wycieki -tutaj troche powiedzieli jak się zwalnia klasy.

Generalnie to musisz rozumieć zasadę działania klas i to że definicja a:TKlasa nie tworzy obiektu ale jest jedynie referencją. Potem sobie tworzysz konstruktorem klasę i odnośnik do niej zapisujesz w przykładowo a. Jeżeli zrobisz a:=nil; to nie będzie wiadomo gdzie jest ta klasa więc nie będziesz mógł jej zwolnić, więc zanim zmienisz ten odnośnik na coś innego trzeba zwolnić klasę np. a.Free; albo FreeAndNil(a);. Druga metoda przypisze też do a NIL.
A co się działo w twoim kodzie? Gubiłeś odnośnik do oryginalnego socketa nie zwalniając go w wyniku czego czekał ciągle na połączenie i blokował port. Z tego co teraz widzę nie spowodowałby on wycieku pamięci ale zablokowałby port. Więc najlepiej zwalniać obiekty gdy nie są potrzebne, a już na 100% trzeba zadbać o to żeby nie zgubić do niego referencji bo to by powodowało problemy gdy twój kod zrobi tak milion razy (zapchałbyś pamięć = crash/lag).

0

Po przeczytaniu tego

 
[blabla oczekiwanie na polaczenie]
temp:=server.Accept;
server_cl:=TTCPBlockSocket.Create();
server_cl.socket:=temp;
FreeAndNil(server);

Zatrybiłem o co Ci chodzi :) I nawet zrozumiałem mój błąd :) resztę przeczytałem z Ciekawości i co to konstruktor oraz destruktor to wiem , podstawowe pojęcia znam. Dzięki za pomoc na pewno zamieszczę to w kodzie :)

Mam tylko jedno pytanie. Czy konieczne jest tworzenie zmiennej temp, nie lepiej od razu napisać server_cl.socket := server.Accept?

0

Mam tylko jedno pytanie. Czy konieczne jest tworzenie zmiennej temp, nie lepiej od razu napisać server_cl.socket := server.Accept?

Nie jest konieczne ale ja tak mam w swoich kodach bo po Accept sprawdzam czy server.LastError=0 na wypadek błędu zanim jeszcze to przypisze do socketa. Więc jak nie chcesz tego sprawdzać na wypadek błędu to oczywiście nie trzeba.

Pamiętaj że jeżeli rozwiązałem problem to żeby oznaczyć post który go rozwiązał 'ptaszkiem', taka dziwota tego forum.

0

No to wszystkie twoje posty poptaszkuje :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1