Prosty komunikator
sieciowy
Kurs ten został opracowany
dla środowiska Borland C++ Builder 6 Profesional, do przerobienia tego kursu
niezbędne są komponenty TClientSocket i TServerSocket. Nie będzie to komunikator
współpracujący z komunikatorami Gadu-Gadu, Tlen, itp... Celem tego kursu jest
pokazania jak należy używać komponentów TClientSocket i TServerSocket w
komunikacji sieciowej, nasz komunikator będzie dość prosty i będzie działał
podobnie do innych komunikatorów, z tą jednak różnicą, że do jednego serwera
będzie mogło być podłączonych kilku użytkowników i będą oni mogli komunikować
się ze sobą nawzajem, tak więc nasz komunikator będzie posiadał cechy czatu.
Tworzymy nowy projekt i zapisujemy go pod dowolną nazwą,
następnie umieszczamy na formularzu komponenty według zamieszczonego rysunku:

Właściwości (Properties):
Form1:
BorderStyle |
bsSingle |
+BorderIcons - biMinimize |
false |
User1:
+EditLabel - Caption |
Użytkownik: |
LabelPosition |
lpLeft |
Text |
Nazwa użytkownika |
MemoKomunikaty i
MemoZdarzenia:
DragMode |
dmAutomatic |
ReadOnly |
true |
WordWrap
(tylko MemoKomunikaty) |
false |
SpeedButton1:
SpeedButton2:
Port1:
ServerSocket1:
ClientSocket1:
StatusBar1:
W pliku
nagłówkowym (np. Unit1.h) w sekcji private tworzymy zmienną IsServer
typu bool, ponieważ komunikator będzie mógł pracować jako klient i jako
serwer, więc ta zmienna posłuży jako przełącznik, sprawdzający w jakim trybie
pracuje program:
// Plik nagłówkowy Unit1.h //-------------------------------- private:
bool IsServer; //-------------------------------- |
Przechodzimy do pliku źródłowego (np. Unit1.cpp) i w zdarzeniu OnClick
dla przycisku Button1 (Połącz) umieszczamy instrukcje odczytujące
adres hosta pod który zostanie podłączony klient, a następnie aktywujemy
klienta:
// Plik źródłowy Unit1.cpp //--------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Unit1.h"
//--------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//--------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//--------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
AnsiString tekst = InputBox("Client
- Server","Podaj adres:","127.0.0.1");
ClientSocket1->Active = false;
ClientSocket1->Host = tekst;
ClientSocket1->Active = true;
SpeedButton1->Enabled = false;
} //-------------------------------- |
IntputBox jest to okienko dialogowe, które pobiera adres hosta i przepisuje
go do zmiennej tekst, jeśli nie zostanie podany żaden adres hosta to okienko
zwróci domyślny adres hosta lokalnego, następnie Klient jest wyłączany,
przepisywany jest adres hosta do obiektu ClientSocket1 i klient jest aktywowany.
W zdarzeniu OnClick dla przycisku Button2 (Rozłącz)
dezaktywujemy klienta:
// Plik źródłowy Unit1.cpp //--------------------------------
void __fastcall TForm1::Button2Click(TObject *Sender)
{
ClientSocket1->Active = false;
StatusBar1->Panels->Items[0]->Text =
"Oczekiwanie...";
SpeedButton1->Enabled = true;
} //------------------------------- |
W zdarzeniu OnClick dla przycisku Button3 (Wyślij) umieszczamy instrukcję
wysyłającą wiadomość do serwera, podobną instrukcję umieszczamy w zdarzeniu
OnKeyDown dla obiektu EditKomunikat, chodzi o to żeby po wpisaniu komunikatu i
wciśnięciu przycisku Enter wiadomość została wysłana bez konieczności klikania
na przycisku Button3:
// Plik źródłowy Unit1.cpp //--------------------------------
void __fastcall TForm1::Button3Click(TObject *Sender)
{
String time = " (" +
TimeToStr(Now().FormatString("hh:nn:ss"))
+ ")";
if(IsServer)
{
MemoKomunikaty->Lines->Add(User1->Text + time +
":\n" +
EditKomunikat->Text +
"\n====================");
for(int x = 0; x <
ServerSocket1->Socket->ActiveConnections; x++)
{
ServerSocket1->Socket->Connections[x]->SendText(User1->Text + time
+ ":\n" +
EditKomunikat->Text +
"\n====================");
}
}
else
{
ClientSocket1->Socket->SendText(User1->Text + time +
":\n" +
EditKomunikat->Text +
"\n====================");
}
EditKomunikat->Text = "";
MemoKomunikaty->SetFocus();
Application->ProcessMessages();
EditKomunikat->SetFocus();
}
//--------------------------------
void __fastcall TForm1::EditKomunikatKeyDown(TObject *Sender, WORD &Key,
TShiftState Shift)
{
String time = " (" +
TimeToStr(Now().FormatString("hh:nn:ss"))
+ ")";
if(Key == VK_RETURN)
{
if(IsServer)
{
MemoKomunikaty->Lines->Add(User1->Text + time +
":\n" +
EditKomunikat->Text +
"\n====================");
for(int x = 0; x < ServerSocket1->Socket->ActiveConnections;
x++)
{
ServerSocket1->Socket->Connections[x]->SendText(User1->Text +
time + ":\n" +
EditKomunikat->Text +
"\n====================");
}
}
else
{
ClientSocket1->Socket->SendText(User1->Text + time +
":\n" +
EditKomunikat->Text +
"\n====================");
}
EditKomunikat->Text = "";
MemoKomunikaty->SetFocus();
Application->ProcessMessages();
EditKomunikat->SetFocus();
}
} //-------------------------------- |
Oprócz samej treści wiadomości, wysyłane są jeszcze dwie dodatkowe linie
pierwsza zawierająca nazwę użytkownika pobraną z obiektu User1 + czas wysłania
wiadomości, ostatnia linia jest po prostu rozdzielająca. Funkcja
ActiveConnections sprawdza ilu użytkowników jest podłączonych do serwera, a
pętla for przesyła wszystkie komunikaty wychodzące z serwera, do
wszystkich klientów podłączonych do niego. Widzimy tutaj zastosowanie zmiennej
IsServer, sprawdza ona czy komunikaty wysyła serwer czy klient.
W zdarzeniu OnClientRead dla obiektu ServerSocket1 umieszczamy
instrukcje przechwytujące komunikaty od klientów, a następnie te komunikaty są
przesyłane do wszystkich klientów podłączonych do serwera wewnątrz pętli for:
// Plik źródłowy Unit1.cpp //--------------------------------
void __fastcall TForm1::ServerSocket1ClientRead(TObject *Sender,
TCustomWinSocket *Socket)
{
MemoKomunikaty->Lines->Add(Socket->ReceiveText());
MemoKomunikaty->SetFocus();
if(IsServer)
{
int i = MemoKomunikaty->Lines->Count;
String tekst1 = MemoKomunikaty->Lines->Strings[i - 3];
String tekst2 = MemoKomunikaty->Lines->Strings[i - 2];
String tekst3 = MemoKomunikaty->Lines->Strings[i - 1];
for(int x = 0; x < ServerSocket1->Socket->ActiveConnections;
x++)
{
ServerSocket1->Socket->Connections[x]->SendText(tekst1 + tekst2 +
tekst3);
}
}
} //------------------------------- |
W zdarzeniu OnAccept dla obiektu ServerSocket1 zmieniamy status
zmiennej IsServer, a w zdarzeniu OnClientConnect dla tego samego
obiektu umieszczamy odpowiedni komunikat:
// Plik źródłowy Unit1.cpp //--------------------------------
void __fastcall TForm1::ServerSocket1Accept(TObject *Sender,
TCustomWinSocket *Socket)
{
IsServer = true;
StatusBar1->Panels->Items[0]->Text =
"Podłączono do: " +
Socket->RemoteAddress;
}
//--------------------------------
void __fastcall TForm1::ServerSocket1ClientConnect(TObject *Sender,
TCustomWinSocket *Socket)
{
MemoZdarzenia->Lines->Add("Podłączono
klienta: " + Socket->RemoteAddress +
" " + Socket->RemoteHost);
MemoKomunikaty->Lines->Clear();
} //-------------------------------- |
W zdarzeniu OnError dla obiektu ClientSocket1 umieszczamy
komunikaty o ewentualnych błędach w komunikacji między klientem i serwerem:
// Plik źródłowy Unit1.cpp //--------------------------------
void __fastcall TForm1::ClientSocket1Error(TObject *Sender,
TCustomWinSocket *Socket, TErrorEvent
ErrorEvent, int &ErrorCode)
{
String error;
if(ErrorEvent == eeConnect)
{
SpeedButton1->Enabled = true;
error = "Błąd w połączeniu z
serwerem";
}
if(ErrorEvent == eeSend)
error = "Błąd w wysyłaniu";
if(ErrorEvent == eeReceive)
error = "Błąd w odbieraniu
danych";
if(ErrorEvent == eeDisconnect)
error = "Błąd w rozłączaniu";
if(ErrorEvent == eeAccept)
error = "Błąd przy próbie
akceptacji połączenia klienta";
if(ErrorEvent == eeGeneral)
error = "Wystąpił nieokreślony
błąd w komunikacji";
ShowMessage(error);
MemoZdarzenia->Lines->Add(TimeToStr(Now().FormatString("hh:nn:ss"))
+ " " + error);
ErrorCode = 0;
} //------------------------------- |
W zdarzeniu OnClientConnect dla obiektu ClientSocket1
umieszczamy dwa komunikaty, a w zdarzeniu OnRead umieszczamy instrukcje
przechwytujące komunikaty przychodzące z serwera:
// Plik źródłowy Unit1.cpp //--------------------------------
void __fastcall TForm1::ClientSocket1Connect(TObject *Sender,
TCustomWinSocket *Socket)
{
StatusBar1->Panels->Items[0]->Text =
"Podłączono do: " +
Socket->RemoteHost;
MemoZdarzenia->Lines->Add("Podłączono
klienta: " + Socket->RemoteAddress +
" " + Socket->RemoteHost);
}
//--------------------------------
void __fastcall TForm1::ClientSocket1Read(TObject *Sender,
TCustomWinSocket *Socket)
{
MemoKomunikaty->Lines->Add(Socket->ReceiveText());
MemoKomunikaty->SetFocus();
Application->ProcessMessages();
EditKomunikat->SetFocus();
} //-------------------------------- |
W zdarzeniu OnClick dla przycisków SpeedButton1 i
SpeedButton2 umieszczamy odpowiednio instrukcje włączające i wyłączające
serwer:
// Plik źródłowy Unit1.cpp //--------------------------------
void __fastcall TForm1::SpeedButton1Click(TObject *Sender)
{
try
{
ClientSocket1->Active = false;
ServerSocket1->Active = true;
StatusBar1->Panels->Items[0]->Text =
"Oczekiwanie...";
Button1->Enabled = false;
}
catch(...)
{
SpeedButton1->Down = false;
SpeedButton2->Down = true;
Button1->Enabled = true;
}
}
//--------------------------------
void __fastcall TForm1::SpeedButton2Click(TObject *Sender)
{
ClientSocket1->Active = false;
ServerSocket1->Active = false;
StatusBar1->Panels->Items[0]->Text =
"Rozłączone...";
Button1->Enabled = true;
} //-------------------------------- |
W zdarzeniu OnClick dla przycisku Button5 pobieramy numer
portu, który powinien być zawsze wysoki, natomiast w zdarzeniu OnClick dla
przycisku Button4 sprawdzamy ilu klientów jest podłączonych do serwera:
// Plik źródłowy Unit1.cpp //--------------------------------
void __fastcall TForm1::Button5Click(TObject *Sender)
{
ServerSocket1->Port = StrToInt(Port1->Text);
ClientSocket1->Port = StrToInt(Port1->Text);
}
//--------------------------------
void __fastcall TForm1::Button4Click(TObject *Sender)
{
ShowMessage("Do
serwera jest podłączonych " +
IntToStr(ServerSocket1->Socket->ActiveConnections) +
" klientów.");
} //-------------------------------- |
I to byłoby już wszystko, program jest gotowy do pracy. W przypadku gdy
serwer znajduje się za zaporą klienci mogą mieć problem z podłączeniem się do
niego:
Pliki źródłowe projektu, archiwum RAR - rozmiar 258 kb.
Opracował: Cyfrowy Baron