Crearea unui chat multi-user borland delphi

Articolul anterior ("Crearea unui client-server") a spus despre dezvoltarea unui chat simplu pentru doi utilizatori. Structura chat-ului "heads-to-head" este destul de simplă, deoarece există un singur canal, pe de o parte a serverului, pe de altă parte - clientul. Structura mai multor utilizatori este oarecum mai complicată. Există un singur server și mulți clienți. Serverul procesează apoi mesajele primite, le transmite către canalele corecte, înregistrează utilizatorii și arată tuturor câte utilizatori comunică în prezent. În acest articol vom încerca să implementăm toate acestea.

Chat pentru mai mulți utilizatori (Multy-user on-line)

Să începem să dezvoltăm o aplicație de chat cu un formular deja gata făcut din articolul precedent sau cu unul nou. Iată ce ar trebui să fie în forma:

Componentele din pachetul standard Delphi ServerSocket și ClientSocket nu pot fi afișate întotdeauna în paleta de Internet. și acestea trebuie descărcate după cum urmează:

Selectați meniul: Componentă - Instalați pachetele ... - Adăugați. În continuare trebuie să specificați fișierul ... \ bin \ dclsockets70.bpl.

Se adaugă noi componente:

Acum, să analizăm principiul serverului. În mod tradițional, ServerSocket utilizează OnClientRead pentru a primi pachete client. dar această metodă nu este foarte convenabilă, deoarece pentru a identifica pachetul (care a trimis-o) va trebui să tinker cu structura "recepție \ răspuns" și să decidă cum va avea loc sincronizarea. Este mult mai ușor și mai eficient să utilizați un ciclu după numărul de utilizatori, în care toate canalele sunt "ascultați" și procesate de pachet, dacă se ajunge la un anumit canal alocat unui anumit utilizator. Procedura de "ascultare" a canalelor este efectuată în corpul unui cronometru, intervalul dintre care poate fi schimbat după cum este necesar (pentru chat este normal 500 ms, pentru jocurile de care aveți nevoie în mod semnificativ mai puțin). Iată structura generală a procedurii de anchetă:

procedura TForm1.Timer1Timer (expeditor: TObject);
începe
// condiție pentru prezența canalelor stabilite
dacă ServerSocket.Socket.ActiveConnections<>0 atunci
începe
// bucla prin canalele existente
pentru i: = 1 la ServerSocket.Socket.ActiveConnections face
începe
// salvați pachetul (dacă nu este trimis nimic, pachetul este gol)
text: = ServerSocket.Socket.Connections [i-1] .ReceiveText ();
// condiția ca pachetul să nu fie gol
dacă textul<>„“ Atunci
începe

// definirea comenzilor
cazul com
cod: începe

se încheie;
cod: începe

se încheie;
...........................................
se încheie;
se încheie;
se încheie;
se încheie;
// permisiunea de a efectua proceduri de actualizare
dacă ActualDo = Adevărat atunci
începe

// blocați permisiunea
Actualizare: = False;
se încheie;
se încheie;

Dacă observați că ciclul începe cu unul, iar în inițierea canalului o expresie ciudată [i-1] (în loc de pornire logică pornind de la zero și inițializare), această soluție facilitează în mod semnificativ organizarea unui număr de proceduri. De exemplu, în lista de utilizatori, serverul este listat sub numărul "0", iar clienții - începând cu "1". De asemenea, este convenabil să combinați numărul de canale (ServerSocket.Socket.ActiveConnections) cu proceduri pentru determinarea activității utilizatorilor.
Ultima condiție din corpul temporizatorului este necesară pentru a întârzia executarea unor proceduri de actualizare. Aceste proceduri trebuie efectuate la sfârșitul "asculării" canalelor deschise, și nu întotdeauna (dacă există o comandă).
Acest algoritm este valabil pentru aproape orice fel de conexiune Client-Server, inclusiv pentru jocuri.

ATENȚIE! Dacă nu sunteți sigur de corectitudinea codului de procesare a comenzii pe care l-ați scris în bucla "asculta" a canalelor deschise, ÎNTOTDEAUNA utilizați funcția Try..Except..End ;. care va evita erorile grave, deoarece repetabilitatea ciclului poate fi foarte rapidă. exemplu:

încerca

cu excepția
// opriți temporizatorul și acțiunile conexe
End;

Să mergem direct la aplicația noastră de chat și la procedurile sale. Ca și înainte, nu vor exista verificări pentru corectitudinea introducerii valorilor în câmpuri.
Creați un tip nou, pentru a utiliza o serie de obiecte, este mult mai convenabil:

tip
TUserList = obiect
Status: Byte; // 1 - server, 2 - client
Rec: Boolean; // marchează intrarea utilizatorului în listă
Nume: String; // nume (porecla)
Imagine: Byte; // index index
se încheie;

Iată variabilele care vor fi necesare în program:

var
Form1: TForm1;
i, j, com, ContList: Byte;
len, pos, x: Cuvânt;
text, StrUserList: String;
Actualizare: Boolean;
Buf: array [0..3] de Byte;
UserMas: matrice [0..255] din TUserList; // array de obiecte
UItems: TListItem;

Să descriem procedura OnCreate pentru formularul:

Procedura pentru "ascultarea" canalelor deschise de către server este următoarea:

Transferați programul în modul server utilizând butonul "Creare server" (ServerBtn). Iată procedura de apăsare a tastei ServerBtn (OnClick):

procedura TForm1.ServerBtnClick (expeditor: TObject);
începe
dacă ServerBtn.Tag = 0 atunci
începe
// cheia ClientBtn și câmpurile HostEdit, PortEdit, NikEdit sunt blocate
ClientBtn.Enabled: = False;
HostEdit.Enabled: = False;
PortEdit.Enabled: = False;
NikEdit.Enabled: = False;
// scrieți portul specificat în ServerSocket
ServerSocket.Port: = StrToInt (PortEdit.Text);
// porniți serverul
ServerSocket.Active:=True;
// adăugați mesajul chatmemo cu timpul de creare
ChatMemo.Lines.Add (Serverul '' 'TimeToStr (Time) +'] este creat. ');
// modificați eticheta
ServerBtn.Tag: = 1;
// modificați titlul cheii
ServerBtn.Caption: = 'Închidere server';
// activați temporizatorul serverului
ServerTimer.Enabled: = Adevărat;
// introduceți parametrii serverului
UserMas [0] .Status: = 1;
UserMas [0] .Rec: = Adevărat;
UserMas [0]. Nume: = NikEdit.Text;
UserMas [0]. Imagine: = 1;
// activați actualizarea
Actualizare: = Adevărat;
capăt
altfel
începe
// dezactivați temporizatorul serverului
ServerTimer.Enabled: = False;
// ștergeți parametrii serverului
UserMas [0] .Status: = 0;
UserMas [0] .Rec: = False;
UserMas [0] .Name: = 'Necunoscut';
UserMas [0] .Image: = 0;
// activați actualizarea
Actualizare: = Adevărat;
// ștergeți lista de clienți
UserListView.Items.Clear;
// cheia ClientBtn și câmpurile HostEdit, PortEdit, NikEdit sunt deblocate
ClientBtn.Enabled: = Adevărat;
HostEdit.Enabled: = Adevărat;
PortEdit.Enabled: = Adevărat;
NikEdit.Enabled: = True;
// închideți serverul
ServerSocket.Active:=False;
// tipăriți mesajul în ChatMemo
ChatMemo.Lines.Add (serverul "['+ TimeToStr (Time) +'] este închis. ');
// returnați valoarea inițială în etichetă
ServerBtn.Tag: = 0;
// returnați eticheta originală a cheii
ServerBtn.Caption: = 'Creare server';
se încheie;
se încheie;

Următoarele sunt evenimentele care ar trebui să apară când starea ServerSocket este sigură. Să scriem procedura când clientul sa conectat la server (OnClientConnect):

procedura TForm1.ServerSocketClientConnect (expeditor: TObject;
Socket: TCustomWinSocket);
începe
// adăugați mesajul chatmemo cu timpul de conectare la client
ChatMemo.Lines.Add ('[' + TimeToStr (Time) + '] Clientul a fost conectat.');
// activați actualizarea
Actualizare: = Adevărat;
se încheie;

Să scriem procedura când clientul este deconectat (OnClientDisconnect):

procedura TForm1.ServerSocketClientDisconnect (Expeditor: TObject;
Socket: TCustomWinSocket);
începe
// adăugați un mesaj la ChatMemo cu timpul de deconectare al clientului
ChatMemo.Lines.Add ('[' + TimeToStr (Time) + '] Clientul a fost deconectat.');
// activați actualizarea
Actualizare: = Adevărat;
se încheie;

Trimiterea mesajelor. La noi se face prin apăsarea unei taste "Send" (SendBtn), dar este necesar să se verifice modul serverului sau clientului programului. Scrie procedura (OnClick):

procedura TForm1.SendBtnClick (expeditor: TObject);
începe
// verificați modul în care se află programul
dacă ServerSocket.Active = True atunci
// trimiteți un mesaj de la server tuturor utilizatorilor
pentru i: = 0 la ServerSocket.Socket.ActiveConnections-1 nu
ServerSocket.Socket.Connections .SendText ('0 [' + TimeToStr (Timp) + ']' + NikEdit.Text + ':' + TextEdit.Text)
altfel
// trimite un mesaj de la client
ClientSocket.Socket.SendText ('0 [' + TimeToStr (Timp) + ']' + NikEdit.Text + ':' + TextEdit.Text);
// afișați mesajul în ChatMemo
ChatMemo.Lines.Add ('[' + TimeToStr (Timp) + ']' + NikEdit.Text + ':' + TextEdit.Text);
// clear TextEdit
TextEdit.Clear;
se încheie;

Modul client. Când apăsați butonul "Connect" (ClientBtn), ServerBtn este blocat și ClientSocket este activat. Iată procedura ClientBtn (OnClick):

Proceduri privind OnConnect. OnDisconnect. Pe clientul client. Citiți mai întâi mesajul de pe server (OnRead):

Mai mult, este simplu, obișnuit adăugarea în ChatMemo a mesajului respectiv:

procedura TForm1.ClientSocketConnect (expeditor: TObject;
Socket: TCustomWinSocket);
începe
// adăugați mesajul ChatMemo despre conectarea la server
ChatMemo.Lines.Add ('[' + TimeToStr (Time) + '] Conectarea la server.');
se încheie;

procedura TForm1.ClientSocketDisconnect (Expeditor: TObject;
Socket: TCustomWinSocket);
începe
// adăugați mesajul de pierderi de chat către ChatMemo
ChatMemo.Lines.Add (Serverul '[' + TimeToStr (Time) + '] nu a fost găsit.');
se încheie;

Custodia informațiilor despre utilizatori este o matrice, procedura de completare și de actualizare a acesteia arată astfel:

procedura TForm1.UpdateUserMas;
începe
// ștergeți matricea cu informații
pentru i: = 1 până la 255
începe
UserMas .Status: = 0;
UserMas .Rec: = False;
UserMas .Name: = 'Necunoscut';
UserMas .Image: = 0;
se încheie;
// completați datele utilizatorului
dacă ServerSocket.Socket.ActiveConnections<>0 atunci
începe
pentru i: = 1 la ServerSocket.Socket.ActiveConnections face
începe
UserMas .Status: = 2;
UserMas .Name: = 'Necunoscut';
UserMas .Image: = 0;
// solicitați numele (porecla) utilizatorului pe canalul său (codul de comandă este 1)
ServerSocket.Socket.Connections [i-1] .SendText ('1');
se încheie;
se încheie;
se încheie;

Lista UserListView este actualizată în următoarea procedură:

procedura TForm1.UpdateUserList;
începe
// ștergeți lista de clienți
UserListView.Items.Clear;
// ștergeți variabila
StrUserList: = '';
// zero la marcajul înregistrării
ContList: = 0;
// a alerga prin gama de canale
pentru i: = 0 până la 255
începe
// dacă înregistrarea nu este goală
dacă UserMas .Status<>0 atunci
începe
// adăugați o linie la UserListView
UItems: = UserListView.Items.Add;
UItems.Caption: = UserMas. Nume;
UItems.ImageIndex: = UserMas .Image;
// dacă utilizatorul nu este înregistrat
dacă UserMas .Rec = False apoi ContList: = 1;
// compune șir de utilizatori
StrUserList: = StrUserList + UserMas. Nume + Chr (152);
se încheie;
se încheie;
// dacă toți utilizatorii sunt verificați și există cel puțin un canal
dacă (ContList = 0) și (ServerSocket.Socket.ActiveConnections<>0) atunci
începe
// rulați prin toate canalele deschise
pentru i: = 0 la ServerSocket.Socket.ActiveConnections-1 nu
începe
// trimite șirul de liste de utilizatori (codul de comandă este 2)
ServerSocket.Socket.Connections .SendText ('2' + StrUserList);
se încheie;
se încheie;
se încheie;

De fapt, asta e tot. Nu vă fie frică să experimentați, dar amintiți-vă regulile de bază ale dezvoltării software în condiții de siguranță. Mult noroc!

Articole similare