V minulém dílu seriálu jsme si krok za krokem vysvětlili a ukázali, jakým způsobem vytvořit v Delphi jednoduchý TCP/IP server. Serverová aplikace nám sice fungovala, nicméně nemohla předvést, co se v ní skrývá, protože dosud neexistují žádní klienti, kteří by se k ní připojili. Tento drobný nedostatek dnes napravíme, neboť náplň článku bude spočívat v naprogramování jednoduchého klienta, který dokáže komunikovat s naším serverem z předchozího dílu.
K tomu, abyste si mohli dnešní článek jaksepatří vychutnat, je tedy nezbytné, abyste měli naprogramovanou aplikaci z minulého dílu. Server byl velice jednoduchý a jsem si jist, že všichni čtenáři Živě jeho naprogramování zvládli.
Na samotný úvod zopakuji ještě jedno důležité upozornění: postup popsaný v dnešním článku si opět nebudou moci prakticky zkusit majitelé Delphi 7. Základní komponenty použité v předchozí i dnešní aplikaci (ServerSocket a ClientSocket ze záložky Internet), byly totiž v Delphi 7 odebrány a nahrazeny novými, sice výkonnějšími, ale zato komplikovanějšími. Ale i k těm se dostaneme, majitelé Delphi 7 se proto nemusejí obávat, že jim zůstanou taje socketů skryté.
Ještě než se dáme do práce, zopakujeme si, co vlastně server z minulého dílu umí. Tato informace je při vytváření klientů poměrně zásadní :)
Server je velmi jednoduchý. Po spuštění pouze čeká na připojení klientů, přičemž je poslouchá na svém portu číslo 5050. Z toho pro klienty plyne jeden zásadní důsledek: aby klient mohl přistoupit k tomuto serveru, musí kromě adresy serveru uvést také číslo portu 5050.
Po připojení klienta vypíše server informační hlášení říkající, že došlo k připojení. Podobně se zachová po odpojení kteréhokoliv klienta. Do seznamu ListBox vypisuje server textová data, která jí zasílají připojení klienti. Toť vše, nic víc a nic míň náš chytrý server neumí.
Vzhůru do klientů
Úvodní proslov máme úspěšně za sebou a můžeme se směle pustit do programování klientských aplikací. Co bude náš klient umět?
Nutno poznamenat, že nic moc. Aplikace bude sloužit jako jednoduchý TCP/IP klient, takže bude umožňovat zadání IP adresy serveru a připojení k tomuto serveru. Další podporovanou operací bude zaslání dat (textové zprávy) připojenému serveru a odpojení od serveru. Tím jsou schopnosti našeho klienta vyčerpány.
Ač se to zdá nemožné, vytvoření aplikace bude ještě nepatrně jednodušší než vytvoření serveru:
1. Vytvořte v Delphi novou aplikaci, a na formulář umístěte následující komponenty: 2x editační pole (Edit), 2x nápis (Label), 3x tlačítko (Button) a komponentu ClientSocket ze záložky Internet. Znovu připomínám, že tuto komponentu budou marně hledat majitelé Delphi 7. Možný vzhled aplikace je na následujícím obrázku:
2. Nejprve ošetříme událost OnCreate hlavního formuláře. V její obsluze nastavíme potřebné titulky komponent, ale především nastavíme typ klienta na neblokující a dále nastavíme číslo portu, k němuž se hodláme připojovat, na 5050 (připomeňme, že server – viz minulý článek – poslouchá na tomto čísle portu). O tom, co znamená neblokující klient, si budeme vyprávět v některém z příštích článků, prozatím jen velmi stručně: blokující klient by se choval tak, že po kterékoliv operaci (odeslání dat na server) by byl zablokován až do okamžiku, kdy by byla operace dokončena, zatímco neblokující klient běží ihned vesele dál.
procedure TForm1.FormCreate(Sender: TObject);
begin
Caption := `Klientská aplikace pøes sockety - odpojeno`;
Button1.Caption := `&Pøipoj`;
Button2.Caption := `&Odpoj`;
Button3.Caption := `&Pošli data`;
Label1.Caption := `Zadejte adresu serveru`;
Label2.Caption := `Zadejte data k odeslani`;
Button2.Enabled := False;
Button3.Enabled := False;
Edit1.Text := `127.0.0.1`;
Edit2.Text := ``;
// nastaveni klienta
ClientSocket1.ClientType := ctNonBlocking; // neblokujici spojeni
ClientSocket1.Port := 5050; // pripojujeme se k portu 5050
end;
3. Následně ošetříme událost OnClick tlačítka Button1. Tlačítko Button1 bude sloužit k připojení k serveru.
procedure TForm1.Button1Click(Sender: TObject);
begin
if Edit1.Text <> `` then
ClientSocket1.Address := Edit1.Text
else
ClientSocket1.Address := `127.0.0.1`; // nezadano->lokalni pocitac
ClientSocket1.Open; // navazeme spojeni
end;
Je patrné, že IP adresa vzdáleného serveru se zadává do vlastnosti Address komponenty ClientSocket. Pro navázáni spojeni (pro připojení k serveru) použijeme metodu ClientSocket.Open. Připomeňme, že pro správně fungující komunikaci je nutné zadat ještě číslo portu, to jsme provedli v rámci obsluhy události OnCreate.
4. Dále ošetříme událost OnClick tlačítka Button2. Tlačítko Button2 slouží k odpojení klienta od serveru (ke zrušení spojení). Odpojení od serveru není vůbec obtížné:
// Button2 - odpojeni od serveru
procedure TForm1.Button2Click(Sender: TObject);
begin
ClientSocket1.Close; // zrusime spojeni
end;
5. Posledním nutným krokem je ošetření události OnClick tlačítka Button3. Toto tlačítko bude sloužit k odeslání dat na server. Také odeslání dat je nesmírně jednoduché: postačí nám zavolat metodu ClientSocket.Socket.SendText, do jejíhož parametru předáme požadovanou zprávu pro server:
// Button3 - zaslani dat
procedure TForm1.Button3Click(Sender: TObject);
begin
ClientSocket1.Socket.SendText(Edit2.Text); // posleme text
end;
6. Nyní ošetříme dvě události komponenty ClientSocket, a to OnConnect a OnDisconnect. První z nich vznikne bezprostředně po úspěšném připojení k serveru (a její obsluhu využijeme k nastavení titulku okna a ke správnému nastavení dostupnosti tlačítek), druhá pak naopak po ukončení spojení:
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Caption := `Klientská aplikace pøes sockety - pripojeno`;
Button1.Enabled := False;
Button2.Enabled := True;
Button3.Enabled := True;
end;
procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Caption := `Klientská aplikace pøes sockety - odpojeno`;
Button1.Enabled := True;
Button2.Enabled := False;
Button3.Enabled := False;
end;
6. Poslední maličkost spočívá v ošetření události OnClose formuláře. Pro případ, že by nezbedný uživatel před ukončením práce neodpojil klienta od serveru, provedeme odpojení za něho:
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ClientSocket1.Close;
end;
Tím jsme prakticky hotovi. Ještě než se slavnostně pokusíme poslat sami sobě náš první TCP/IP pozdrav, dodejme k aplikaci několik maličkostí:
- Po klepnutí na Button1 dojde k připojení k serveru (voláním ClientSocket1.Open, stejně tak by bylo ale možné nastavit ClientSocket1.Active := True). Server je tedy identifikován IP adresou (buď adresa v Edit1 nebo 127.0.0.1) a číslem portu (5050).
- Odpojení od serveru se provede zavoláním Close (případně nastavením Active na False).
Zkoušíme komunikaci
Nyní můžeme vyzkoušet, zda všechno funguje. Nejprve přeložíme a spustíme výsledek dnešní práce – klienta (viz následující obrázek):
Klient běží, ale je prozatím odpojen. Když klepneme na tlačítko Připojit, dojde k ohlášení výjimky ESocketError s hláškou Asynchronous Socket Error, viz obrázek:
Tato výjimka je standardně generována v případě, že dojde k chybě v průběhu navazování spojení, například když se aplikace pokouší změnit parametry již otevřeného socketu, nebo když se objeví chyba při zápisu/čtení do/ze socketu, nebo když socket nemůže být korektně ukončen. A nebo také nemůže-li být socket otevřen, což je náš případ, neboť server dosud neběží (takže se k němu poměrně těžko připojíme).
Rád bych upozornil ještě na jednu věc. Je třeba si uvědomit, že i když to tak nemusí vypadat, naprogramovali jsme ryze síťovou aplikaci. Takže: pokud máte k dispozici síťové prostředí, můžete si později vyzkoušet plnohodnotnou síťovou komunikace: server umístíte na jeden počítač v síti a klienta (klienty) na jiný (na jiné). Klienti budou muset znát IP adresu serveru, aby se k němu mohly připojit.
Pokud však nemáte síť, můžete spustit server i klienty na jednom a tomtéž počítači. Při komunikaci pak zadávejte adresu 127.0.0.1, což je adresa lokálního počítače. Klienti pak budou hledat server přímo na lokálním počítači a budou komunikovat lokálně.
Zůstane však zachován princip síťové komunikace. Vše bude vypadat jako síťová komunikace, protože adresa 127.0.0.1 má zcela analogické použití jako kterákoliv jiná IP adresa (např. 147.228.59.11). Jediný rozdíl je, že cílové umístění této adresy leží vždy tam, kde ji použijete.
Co z toho plyne? Komunikujete-li přes adresu 127.0.0.1, komunikujete sice sami se sebou, ale ze systémového hlediska provozujete korektní síťovou komunikaci. A proto pokud máte např. instalován nějaký firewall nebo jiný program monitorující a kontrolující síťový provoz, zareaguje tento program na pokus o připojení k serveru informačním hlášením, viz následující obrázek (jsem si vědom smutného faktu, že na obrázku je starší verze firewallu, ale snad mi to odpustíte :))
Firewall mě informuje, že aplikace PROJECT1.EXE (tj. klient) se hodlá síťově připojovat na adresu 127.0.0.1 (vůbec přitom nevadí, že jsem to zase já – jde o síťovou komunikaci jako každá jiná) na port 5050 prostřednictvím protokolu TCP. Firewall se mě táže, zda má pokus o toto připojení povolit nebo zakázat.
Komunikujeme se serverem
Pojďme se však konečně podívat na fungující komunikaci. Abychom mohli komunikovat, musíme spustit TCP server vytvořený v minulém díle seriálu. Spustíme tedy příslušný soubor (ať již na lokálním počítači nebo kdekoliv v síti).
Následně spustíme jednoho nebo více klientů vytvořených dnes. Opět nezáleží na tom, zda je pustíme na stejném počítači jako server a nebo zda je rozmístíme po síti. Na principu se vůbec nic nemění. Pro jednoduchost (a pro názornost obrázků) použiji lokální počítač, na němž spustím server a dva klienty, viz obrázek:
Nyní mohu klienty připojit a poslat jim nějaká data; na serveru se budou zobrazovat příslušná hlášení. Navázali jsem fungující TCP/IP spojení, viz obrázek:
Mimochodem, všimněte si, co se stane, když v tuto chvíli vypnete server. Spojení je automaticky ukončeno a klienti dostanou událost OnDisconnect. Nestane se tedy, že by klient komunikoval v dommění, že server poslouchá a server už by byl dávno vypnutý. Vzhledem k tomu, že jsme ošetřili událost klientů OnDisconnect, budou po vypnutí serveru i psrávně nastaveny titulky klientů a jejich tlačítka.
Chcete-li se přesvědčit, že naše aplikace funguje i ve skutečném síťovém prostředí, nejen na lokálním počítači, máte k dispozici následující obrázek:
Zdrojový kód
Pro úplnost si opět uvedeme kompletní zdrojový kód klienta. Můžete znovu posoudit, jak krátký kód stačí k tomu, abychom pomocí Delphi zprovoznili fungující síťovou aplikaci.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ScktComp;
type
TForm1 = class(TForm)
Label1: TLabel;
Label2: TLabel;
Edit1: TEdit;
Edit2: TEdit;
Button1: TButton;
Button2: TButton;
Button3: TButton;
ClientSocket1: TClientSocket;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
procedure ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
Caption := `Klientská aplikace pøes sockety - odpojeno`;
Button1.Caption := `&Pøipoj`;
Button2.Caption := `&Odpoj`;
Button3.Caption := `&Pošli data`;
Label1.Caption := `Zadejte adresu serveru`;
Label2.Caption := `Zadejte data k odeslani`;
Button2.Enabled := False;
Button3.Enabled := False;
Edit1.Text := `127.0.0.1`;
Edit2.Text := ``;
// nastaveni klienta
ClientSocket1.ClientType := ctNonBlocking; // neblokujici spojeni
ClientSocket1.Port := 5050; // pripojujeme se k portu 5050
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if Edit1.Text <> `` then
ClientSocket1.Address := Edit1.Text
else
ClientSocket1.Address := `127.0.0.1`; // nezadano->lokalni pocitac
ClientSocket1.Open; // navazeme spojeni
end;
// Button2 - odpojeni od serveru
procedure TForm1.Button2Click(Sender: TObject);
begin
ClientSocket1.Close; // zrusime spojeni
end;
// Button3 - zaslani dat
procedure TForm1.Button3Click(Sender: TObject);
begin
ClientSocket1.Socket.SendText(Edit2.Text); // posleme text
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ClientSocket1.Close;
end;
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Caption := `Klientská aplikace pøes sockety - pripojeno`;
Button1.Enabled := False;
Button2.Enabled := True;
Button3.Enabled := True;
end;
procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Caption := `Klientská aplikace pøes sockety - odpojeno`;
Button1.Enabled := True;
Button2.Enabled := False;
Button3.Enabled := False;
end;
end.
Na závěr
Dnešní díl seriálu dokončil vytváření ukázkových aplikací demonstrujících síťovou komunikaci přes sockety. Vytvořili jsme jednoduchého TCP klienta, který je schopen připojit se ke vzdálenému serveru na port 5050 a zaslat mu textovou zprávu.
Důležité je, že vytvoření fungujícího TCP klienta a serveru je nesmírně snadnou záležitostí a vystačíme si při něm s několika řádky zdrojového kódu. Výsledné aplikace přitom dokáží komunikovat po síti. V této jednoduchosti je úžasná síla Delphi. Mohlo by být programování síťových aplikací vůbec ještě jednodušší?