Skocz do zawartości

[Delphi] Szybka komunikacja programów-klientów przez internet (TCP-IP)


Operator amator Delphi

Polecane posty

Witam.

 

Chcę napisać pewną grę sieciową w której dane do innych graczy będą musiały być wysyłane bardzo często (na poziomie milisekund) do innych graczy. Dane do wysłania będą bardzo małe (około 5 bajtów)

 

Bardzo zależy mi na prędkości oraz dokładności (UDP więc chyba odpada) w wysłaniu danych.

 

Aby bardziej rozjaśnić sprawę wytłumaczę, że w grze tej jest wirtualne pianino. Chcę aby gracze mogli sobie przez takie pianino wspólnie przez neta pograć. Próbowałem to pisać na standardowych Client i Server Socket które są w Delphi ale to jest stanowczo zbyt wolne. Jeden życzliwy człowiek napisał mi multichat na komponentach synapse, ale działo to bardzo, podobnie do moich prób na socketach. Jak ktoś ma coś sensownego do powiedzenia to będę wdzięczny za pomoc.

 

Problem wydaję mi się dość ciekawy, bo jest na przecież mnóstwo gier jak np. Unreal gdzie jest wysłane w net na 100% dużo więcej danych niż moje 5 bajtów i działa to szybciej niż mrugnięcie oka (chyba) :) A więc jak to jest zrobione?

 

Z góry dziękuje za pomoc.

Link do komentarza
Udostępnij na innych stronach

OK, masz rajce Toster. A więc okroiłem program do minimum (żeby kod był czytelniejszy) tylko dla potrzeb zobrazowania mojego problemu na tym forum

Link tutaj:

http://www.speedyshare.com/file/KrFa4/przyklad-na-forum.zip

 

Jak zauważycie w programie Klient wysyła do serwera mały rekordzik w którym jest tylko przekazywany stan przycisku grającego dźwięk (klawisz dol, klawisz gora). Jeśli to zapuścicie sobie przez jakąkolwiek sieć (localhost z wiadomych przyczyn odpada) to zobaczycie, że przy szybszym graniu serwer zwyczajnie nie nadąża z wygrywaniem dźwięku. To jest wierzchołek góry lodowej bo przecież serwer ma jeszcze w pętelce odbijać rekord do wszystkich aktywnych klientów / graczy więc to się jeszcze bardziej opóźni.

 

Ma ktoś jakiś pomysł jak to ulepszyć, żeby było szybciej? Skoro w Couter Strike o zdobyciu punktu decydują ułamki sekund to ja nie wierze, że wysłanie paru bajtów w sieć nie dało by rady przyspieszyć. :) Pytanie jak tylko jak? Proszę o pomoc bo w programowaniu sieciowym jestem troszkę zielony.

Link do komentarza
Udostępnij na innych stronach

jeszcze nie zerkalem do kodu bo chwilowo nie mam czasu ale jak wysylasz duzo malych pakietow to podejrzewam ze nie masz na sockecie ustawionej opcji NO_DELAY. Poszukaj info o tym jak ustawic opcje wysylania bez zbierania danych do pelnej ramki.

Drogi Tosterze mnie się nie spieszy aż tak bardzo. Ważne, żeby rozwiązać ten problem (bym był bardzo wdzięczny)

 

Ta opcja NO_DELAY jest dostępna na w kontrolkach TClientSocket lub TServerSocket? W pliku WinSock.pas znalazłem coś takiego:

{$EXTERNALSYM TCP_NODELAY} TCP_NODELAY = $0001; {$EXTERNALSYM TCP_BSDURGENT} TCP_BSDURGENT = $7000;

 

Czyli rozumiem, że musiał bym to pisać w czystym WinSock-u? Trochę grzebałem na googlach, ale nic nie znalazłem.

 

A co do programu to to co nas interesuje wygląda tak:

 

po stronie serwera:

 

type
 TRec = record
Str: String[10];
 end;
var
 Form1: TForm1;
 Rec: TRec;
implementation
{$R *.dfm}
procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton;
 Shift: TShiftState; X, Y: Integer);
begin
 Rec.Str := 'dol';
 Self.ClientSocket1.Socket.SendBuf(Rec,SizeOf(Rec));
end;
procedure TForm1.Button1MouseUp(Sender: TObject; Button: TMouseButton;
 Shift: TShiftState; X, Y: Integer);
begin
 Rec.Str := 'gora';
 Self.ClientSocket1.Socket.SendBuf(Rec,SizeOf(Rec));
end;

 

po stronie klienta:

 

type
 TRec = record
Str: String[10];
 end;
var
 Rec: TRec;
 MainForm: TMainForm;
 P: TPatch;
 KeyKeyboard: Array [1..60] of Byte;
procedure TMainForm.ServerSocket1ClientRead(Sender: TObject;
 Socket: TCustomWinSocket);
const
 PrzykladowyKlawisz = 41;
begin
 Socket.ReceiveBuf(Rec, SizeOf(Rec));
 if Rec.Str = 'dol' then KlawiszDown(PrzykladowyKlawisz, MainForm.ComboBox1.ItemIndex, GetOktawa, MainForm.CheckBoxPerkusja.Checked, True);
 if Rec.Str = 'gora' then KlawiszUp(PrzykladowyKlawisz, GetOktawa, True);
//odbierz wiadomosc i przesylaj do wszystkich klientów
end;

Link do komentarza
Udostępnij na innych stronach

Sproboj takiego czegos zaraz po polaczeniu

procedure TForm1.Button1Click(Sender: TObject);
var
  r: integer;
  flag: char;
begin
  TcpClient1.Active := true;
  flag := #1;
  r := setsockopt( TcpClient1.Handle, IPPROTO_TCP, TCP_NODELAY, @flag, sizeof(Integer));
  caption := IntToStr(r);
end;

 

do uses dodaj WinSock.

Wynik r = 0 znaczy ze sie udalo, jak jest cos innego to opis jest tutaj:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms740476(v=vs.85).aspx

 

sa spore szanse ze to zalatwi sprawe, jak nie bedzie poprawy to pokminmy dalej

Always Dark<br />u1_tt_logo.png banner-1.pngexFabula-banner.pngson_banner_ubersmall.jpg

Link do komentarza
Udostępnij na innych stronach

  • 2 weeks later...

Sproboj takiego czegos zaraz po polaczeniu

procedure TForm1.Button1Click(Sender: TObject);
var
  r: integer;
  flag: char;
begin
  TcpClient1.Active := true;
  flag := #1;
  r := setsockopt( TcpClient1.Handle, IPPROTO_TCP, TCP_NODELAY, @flag, sizeof(Integer));
  caption := IntToStr(r);
end;

 

do uses dodaj WinSock.

Wynik r = 0 znaczy ze sie udalo, jak jest cos innego to opis jest tutaj:

http://msdn.microsof...s740476(v=vs.85).aspx

 

sa spore szanse ze to zalatwi sprawe, jak nie bedzie poprawy to pokminmy dalej

dzięki za zainteresowanie, ale to niestety nie działa :(

r = 0 ale to nie załatwiło sprawę. Jest wszystko tak jak było z prędkością.

Link do komentarza
Udostępnij na innych stronach

Zrobilem szybki test, na localhost mam opoznienie

Zrob nowy projekt, wrzuca na forme 1button, 1 listbox, TCPClient i TCPServer + kod ponizej:

 

unit Unit1;
interface
uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls, Sockets, mmSystem;
type
 TForm1 = class(TForm)
TcpServer1: TTcpServer;
TcpClient1: TTcpClient;
ListBox1: TListBox;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormMouseDown(Sender: TObject; Button: TMouseButton;
 	Shift: TShiftState; X, Y: Integer);
procedure TcpServer1Accept(Sender: TObject; ClientSocket: TCustomIpClient);
procedure FormMouseUp(Sender: TObject; Button: TMouseButton;
 	Shift: TShiftState; X, Y: Integer);
 private
{ Private declarations }
 public
{ Public declarations }
 end;
 TRecord = record
  time: integer;
  action: integer;
 end;
var
 Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
  TcpServer1.Active := true;
  TcpClient1.Active := true;
end;
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
 Shift: TShiftState; X, Y: Integer);
var
  r: TRecord;
begin
  r.time := timeGetTime;
  r.action := 1;
  TcpClient1.SendBuf(r, sizeOf(r));
end;
procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton;
 Shift: TShiftState; X, Y: Integer);
var
  r: TRecord;
begin
  r.time := timeGetTime;
  r.action := 2;
  TcpClient1.SendBuf(r, sizeOf(r));
end;
procedure TForm1.TcpServer1Accept(Sender: TObject;
 ClientSocket: TCustomIpClient);
var
  r: TRecord;
begin
  ListBox1.Items.Add('Enter');
  while (ClientSocket.Connected = true) do begin
 	ClientSocket.ReceiveBuf(r, sizeOf(r));
 	ListBox1.Items.Add('Time ' + IntToStr( timeGetTime - r.time) +
    	' action:'+IntToStr(r.action));
  end;
  ListBox1.Items.Add('Leave');
end;
end.

Always Dark<br />u1_tt_logo.png banner-1.pngexFabula-banner.pngson_banner_ubersmall.jpg

Link do komentarza
Udostępnij na innych stronach

Na wstępie bardzo dziękuje za Twoją pomoc Toster, ale chyba jednak sockety w Delphi nie dadzą takich prędkości jak w grach FPS. Tam chyba muszą być jakieś bardziej skomplikowane algorytmy.

Przez sieć lokalną zauważalne są odstępy między pakietami. Tak czy siak dzięki za pomoc.

Link do komentarza
Udostępnij na innych stronach

Tak jak pisalem u mnie delaye sa rzedu 1ms co daje ci teoretyczna predkosc 100 FPS'ow. Zauwaz ze zwykly ping do servera np onet.pl zajmuje ~100 ms. Moje pingi podczas gier online to okolo 55-100 ms i daje to calkiem plynna gre.

Delphi nie ma swoich socketow, jak popatrzysz w liby to defacto jest to wraping na winSock. Mozesz siegnac do funkcji niskopoziomowych z api ale nie sadze aby tutaj byl problem. Jakiej wielkosci wyskakuja ci czasy w tym programie ktory ci podalem powyzej? (pierwsza liczba w listboxie)

Always Dark<br />u1_tt_logo.png banner-1.pngexFabula-banner.pngson_banner_ubersmall.jpg

Link do komentarza
Udostępnij na innych stronach

Więc zamieniłem ListBox na Memo, żeby można bylo skopiować.

Wygląda to tak:

Memo1

Enter

Time -773560527 action:1

Time -773560394 action:2

Time -773560530 action:1

Time -773560448 action:2

Time -773560531 action:1

Time -773560409 action:2

Time -773560531 action:1

Time -773560400 action:2

Time -773560529 action:1

Time -773560406 action:2

 

Jak domyślam się czas obliczany jest na podstawie zegara windowsowego. Jesli tak to mija się to z celem ponieważ nawet jesli oba kompy zsynchronizuje z serwer ntp np. time.windows.com to i tak sekundy zawsze nie będą się zgadzały, a co dopiero milisekundy. Na localhoscie to z wiadomych przyczyn wszystko śmiga pięknie.

 

Mi ping do onetu czy wp.pl zajumje około 20-30 ms, ale to nie ważne. :)

 

Próbowałem wysyłać dane z jednego kompa do drugiego co 20 milisekund (program zbierał dane do pamięci i co 20 ms wysyłał). Miało to przyspieszyć transmisje, ponieważ nagłówek ramki TCP ma jakiś tam rozmiar wiec lepiej się to opłacało niby cyklicznie. Na localhoście oczywiście wszystko działo pięknie, ale przez sieć nic się nie zmieniło. Jedyny plus był taki, że miałem okazje sprawdzić, (na localhost oczywiście), że potrzebuje komunikacji na poziomie poniżej 90-80 milisekund. Powyżej zaczyna już lagować. Z ciekawości spróbuje jeszcze się z UDP pobawić chociaż domyślam się, że ten protokół z definicji się do tego nie nada.

Link do komentarza
Udostępnij na innych stronach

czas w przykladzie ktory ci podalem jest w zalozeniu uzywania localhosta, zrobilem to op to abys mogl zmierzyc sobie czas pomiedzy nadaniem a odebraniem. Aby sprawdzic to w sieci musialbys zrobic tak ze:

klient -> Server

Server -> klient (odbija to co dostal w kroku powyzej)

 

wtedy klient moze policzyc roznice miedzy obecnym czasem a czasem zapisanym w wiadomosci. Tak wiec wiesz ze podroz wiadomosci do servera i jej powrot zajelo tyle to a tyle ms.

UDP da ci lepsze transfery pod warunkiem ze mozesz pozwolic sobie na utrate pakietow.

Always Dark<br />u1_tt_logo.png banner-1.pngexFabula-banner.pngson_banner_ubersmall.jpg

Link do komentarza
Udostępnij na innych stronach

W grach FPS stosuje sie protokół UDP, te gry są dynamiczne więć klienci wysyłają bardzo dużo informacji ciągle o swoim stanie, jeżeli jakiś pakiet nie dojdzie, to nieszkodzi bo zaraz przyjdzie nastepny pakiet o nowym stanie. Coś takiego gdzieś kiedyś wyczytałem bodajże w perełkach.

Link do komentarza
Udostępnij na innych stronach

wtedy klient moze policzyc roznice miedzy obecnym czasem a czasem zapisanym w wiadomosci. Tak wiec wiesz ze podroz wiadomosci do servera i jej powrot zajelo tyle to a tyle ms.

Właśnie czegoś takiego (chyba) się nie da zrobić ponieważ nie zsynchronizujesz idealnie zegarów na dwóch kompach. Chyba że czas by pobierać z neta, ale to tez przecież trwa i będzie wtedy przekłamanie w czasie.

 

Muszę sprawdzić jak w UDP ile by było strat w pakietach. Wiem też że tam nie ma kolejkowania wiec to drugi potencjalny problem. Cóż muszę to po testować.

 

@Blind Co to są te perełki? Jakaś strona na necie? Jesteś pewien co do tego, że UDP stosuję się właśnie w tego typu grach? Słyszałem od różnych "fachofcuw", że np. w Couter Strike jest używany TCP. Trochę to prawda mi się dziwne wydawało bo u mnie na routerze przy tym jak wybieram z listy aplikacji Couter Strike do przekierowania portów to wyraźnie jest pokazane, że są również porty UDP są do otwarcia. Podobno TCP jest używany do gry a UDP do czegoś tam innego. Trochę to dziwne, żeby protokół UDP który z jest nastawiony na szybkość zamiast dokładności był używany do czegoś zupełnie odwrotnego.

 

Screen:

przechwytywanieft.jpg

Link do komentarza
Udostępnij na innych stronach

"Perełki programowania gier" są to książki z artykułami, w Polsce wydaje je Helion. Ja nie mówie, ze UDP dla FPS-ow to jedyne sluszne rozwiazanie, mowie ze nie musisz sie martwic utrata danych w takim przypadku. Pozatym w twoim przypadku i tak to jest bez znaczenia czego użyjesz bo do tych celow wszystko bedzie wystarczajaco szybkie. Jak TCP_NODELAY ci nic nie pomoglo to sprawdz jaka jest domyslna wielkosc pakietu(zgaduje ze bedzie to 4kB) i wysylaj takie pakiety.

Link do komentarza
Udostępnij na innych stronach

Zastosowałem komponenty ICS (Internet Component Suite). W demach tego komponentu znalazłem przykład dwóch programów które łączą się ze sobą po TCP tj:

Client5

TcpSrv

 

Trochę poprzerabiałem te programy. Po włączeniu w inspektorze obiektów wsoTcpNoDelay na True pakiety dochodzą bardzo szybko, ale tylko do tego klienta który jest podłączony na localhost do serwera. Czyli mam:

komputer1 - włączony serwer, oraz klient podłączony do serwera localhost.

komputer2 - włączony klient

 

gdy pakiety wędrują od komputer2 na do klienta na komputer1 (po przez serwer) to połączenie jest super szybkie. Natomiast w odwrotną stronę są już przestoje. Jestem więc już bardzo blisko rozwiązania problemu. Wydaje mi się, że po prostu serwer gdy ma wysłać wiadomość gdzieś dalej niż localhost to po prostu sobie nie radzi.

 

Macie jakieś sugestie?

 

OK poradziłem już sobie z tym problemem. Bardzo dziękuje wszystkim udzielającym się w temacie bo bez tego TCP NO DELAY bym sobie nie dał rady. Jak by ktoś chciał znać rozwiązanie to służę pomocą na PW.

 

Pozdrawiam i jeszcze raz dzięki.

Link do komentarza
Udostępnij na innych stronach

Zarchiwizowany

Ten temat jest archiwizowany i nie można dodawać nowych odpowiedzi.

×
×
  • Utwórz nowe...