|


Обмін інформацією по TCP/IP-протоколу
Автор: Рудюк С.О.
Internet: rudjuk.kiev.ua
Часто виникає необхідність обмінюватися даними між програмами різних комп'ютерах. Наприклад, це необхідно в чатах, або в програмах, які повинні реагувати одночасно на одну й ту саму подію.
Обмін інформації між комп'ютерами можна реалізувати багато способів. У цій статті я розгляну обмін даними за протоколом TCP/IP.
Компоненти для обміну даними TCP/IP
Для обміну даними за протоколом TCP/IP будемо використовувати три Indy-компоненти: TIdTCPServer , TIdTCPClient , TIdThreadMgrDefault .
Клієнтська компонента призначена для надсилання та прийому повідомлень, а серверна компонента - для прийому повідомлення та розсилки клієнтським компонентам.
Програмна реалізація
Програма складається з двох частин: серверна, на якій стоїть серверна компонента, можна на неї ще поставити і клієнтську компоненту – для тестування клієнтської частини та можливості генерації повідомлень із серверної програми. На клієнтській частині – стоїть лише клієнтська компонента. Ця частина призначена лише для надсилання та прийому повідомлень.
Серверна частина
Встановимо на форму у програмі серверної частини компоненти TIdTCPServer , TIdThreadMgrDefault .
Зв'яжіть властивість ThreadMgr компонентів TIdTCPServer з компонентом TIdThreadMgrDefault.
Для запуску сервера вистачить встановити якість компоненти в True:
Server.Active := True;
Protocol.Lines.Add('=== Запуск сервера ====');
|
Для зупинки сервера - False:
Server.Active := False;
Protocol.Lines.Add('=== Сервер зупинено====');
|
Для реєстрації під'єднаного комп'ютера слід визначити подію OnConnect у компоненті TIdTCPServer.
var
NewClient: PClient;
begin
GetMem(NewClient, SizeOf(TClient)); NewClient.DNS := AThread.Connection.LocalName;
NewClient.Connected := Now;
NewClient.LastAction := NewClient.Connected;
NewClient.Thread := AThread;
AThread.Data:=TObject(NewClient);
try
Clients.LockList.Add(NewClient);
finally
Clients.UnlockList;
end;
Protocol.Lines.Add(TimeToStr(Time)+' З'єднання комп'ютера: "'+NewClient.DNS+'"');
end;
|
Для реєстрації відключення клієнта необхідно визначити подію ServerDisconnect.
var
ActClient: PClient;
ConnN: integer;
begin
ActClient := PClient(AThread.Data);
Protocol.Lines.Add (TimeToStr(Time)+' Від'єднання комп'ютера: "'+ActClient^.DNS+'"');
try
Clients.LockList.Remove(ActClient);
finally
Clients.UnlockList;
end;
FreeMem(ActClient);
AThread.Data := nil;
end;
|
Обробка команд (розсилання) на серверній частині здійснюється за допомогою події OnExecute.
var
ActClient, RecClient: PClient;
CommBlock, NewCommBlock: TCommBlock;
RecThread: TIdPeerThread;
i, ConnN: Integer;
itmp: integer;
begin
if not AThread.Terminated and AThread.Connection.Connected then
begin
AThread.Connection.ReadBuffer (CommBlock, SizeOf (CommBlock));
ActClient := PClient(AThread.Data);
ActClient.LastAction := Now; // update the time of last action
// Реєстрація комп'ютера
if (RusUpperCase(CommBlock.Command) = RusUpperCase(cmRegisterComp)) then
begin
Protocol.Lines.Add(' Реєстрація комп'ютера: '+RusUpperCase(CommBlock.ComputerName));
meConnected.Lines.Add(RusUpperCase(CommBlock.ComputerName));
RefreshConnected;
RefreshConnectedComps;
RefreshGolosProcess;
// AThread.Connection.WriteBuffer (NewCommBlock, SizeOf (NewCommBlock), true); // and there it goes...
end
// Видалення комп'ютера
else if (RusUpperCase(CommBlock.Command) = RusUpperCase(cmUnRegisterComp)) then
begin
Protocol.Lines.Add(' Видалення комп'ютера: '+RusUpperCase(CommBlock.ComputerName));ConnN
:=FindConnComp(RusUpperCase(CommBlock.ComputerName));
if ConnN<>-1
then meConnected.Lines.Delete(ConnN);
RefreshConnected;
RefreshConnectedComps;
RefreshGolosProcess;
// AThread.Connection.WriteBuffer (NewCommBlock, SizeOf (NewCommBlock), true); // and there it goes...
end
// Реєстрація відповідей
else if (RusUpperCase(CommBlock.Command) = RusUpperCase(cmAnswerQuest)) then
begin
if mdGolos.Locate('CompName',RusUpperCase(CommBlock.Msg),[loCaseInsensitive]) then
begin
mdGolos.Edit;
mdGolosCONN.Value:=True;
mdGolos.Post;
end;
RefreshGolosProcess;
// AThread.Connection.WriteBuffer (NewCommBlock, SizeOf (NewCommBlock), true); // and there it goes...
end
// Різні повідомлення
else if (CommBlock.Command = {'MESSAGE'}cmMess) or (CommBlock.Command = 'DIALOG') then
begin // 'MESSAGE': A message was send - forward or broadcast it
// 'DIALOG': A dialog-window shall popup on the recipient's screen
// it's the same code for both commands...
if CommBlock.ReceiverName = '' then
begin // no recipient given - broadcast
Protocol.Lines.Add (TimeToStr(Time)+' Отримання повідомлення від '
+CommBlock.MyUserName+' '+CommBlock.Command+': "'+CommBlock.Msg+'"');
NewCommBlock := CommBlock; // nothing to change ;-))
with Clients.LockList do
try
for i := 0 to Count-1 do // iterate through client-list
begin
RecClient := Items[i]; // get client-object
RecThread := RecClient.Thread; // get client-thread out of it
RecThread.Connection.WriteBuffer(NewCommBlock, SizeOf(NewCommBlock), True); // send the stuff
end;
finally
Clients.UnlockList;
end;
end
else
begin // receiver given - search him and send it to him
NewCommBlock := CommBlock; // again: nothing to change ;-))
Protocol.Lines.Add(TimeToStr(Time)+' Посилання '+CommBlock.Command+' до "'+CommBlock.ReceiverName+'": "'+CommBlock.Msg+'"');
with Clients.LockList do
try
for i := 0 to Count-1 do
begin
RecClient:=Items[i];
if RecClient.DNS=CommBlock.ReceiverName then // we don't have a login function so we have to use the DNS (Hostname)
begin
RecThread:=RecClient.Thread;
RecThread.Connection.WriteBuffer(NewCommBlock, SizeOf(NewCommBlock), True);
end;
end;
finally
Clients.UnlockList;
end;
end;
end
else
begin // unknown command given
Protocol.Lines.Add (TimeToStr(Time)+' Unknown command from "'+CommBlock.MyUserName+'": '+CommBlock.Command);
NewCommBlock.Command := 'DIALOG'; // the message should popup on the client's screen
NewCommBlock.MyUserName := '[Server]'; // the server's username
NewCommBlock.Msg := 'I don''t understand your command: "'+CommBlock.Command+'"'; // the message to show
NewCommBlock.ReceiverName := '[return-to-sender]'; // unnecessary
AThread.Connection.WriteBuffer (NewCommBlock, SizeOf (NewCommBlock), true); // and there it goes...
end;
end;
end;
|
Тут я реалізував додаткову реєстрацію комп'ютера за допомогою команди cmRegisterComp='REGISTER', та додатково посилку повідомлення, що комп'ютер відключився:
cmUnRegisterComp='UNREGISTER'.
При надсиланні повідомлення передається повідомлення типу TCommBlock. Цей тип даних ми можемо змінювати за потребою. У цьому блоці я оголосив змінну для ідентифікації ComputerName комп'ютера.
TCommBlock = record // the Communication Block used in both parts (Server+Client)
Command,
MyUserName, // the sender of the message
Msg, // the message itself
ReceiverName: string[100]; // name of receiver
ComputerName: String[100]; // Назва комп'ютера, що надсилає повідомлення
end;
|
Поле Command - команда, яка надсилається з клієнтського місця.
MyUserName - Ім'я користувача, який надсилає повідомлення.
Msg - текст повідомлення.
ReceiverName - назва комп'ютера-отримувача повідомлення, якщо це поле буде порожнім, повідомлення надсилатиметься всім комп'ютерам.
Клієнтська частина
Через клієнтську компоненту ми можемо надсилати повідомлення, а також отримувати повідомлення з інших повідомлень.
Встановимо на форму клієнтської програми компоненту TIdTCPClient .
Встановимо на форму кнопки Підключитися і Вимкнутись.
Обробник кнопки Підключитися:
IncomingMessages.Lines.Add('===Підключення до сервера===');
Client.Host:=DBInfo.IBaseServerName;
Client.Connect(10000); // in Indy < 8.1 leave the parameter away
ClientHandleThread := TClientHandleThread.Create(True);
ClientHandleThread.Cli:=Client;
ClientHandleThread.EventMest:=FEventMess;
ClientHandleThread.Str:=IncomingMessages.Lines;
ClientHandleThread.FreeOnTerminate:=True;
ClientHandleThread.Resume;
RegComp;
except
on E: Exception do MessageDlg ('Помилка підключення:'+#13+E.Message, mtError, [mbOk], 0);
end; |
У кнопці Вимкнутись прописуємо:
if Client.Connected then
begin
ClientHandleThread.Terminate;
Client.Disconnect;
end;
|
Тип TClientHandleThread призначений для обробки команд із клієнтської сторони.
TEvent_Mesto = procedure(Sender: TObject) of object;
....
TClientHandleThread = class(TThread)
private
procedure HandleInput;
public
Str: TStrings;
Cli: TIdTCPClient;
protected
procedure Execute; override;
public
CB: TCommBlock;
FEventMest: TEvent_Mesto;
published
property EventMest: TEvent_Mesto read FEventMest write FEventMest;
end;
....
var
ClientHandleThread: TClientHandleThread; // variable (type see above)
....
procedure TClientHandleThread.Execute;
begin
while not Terminated do
begin
if not Cli.Connected then
Terminate
else
try
Cli.ReadBuffer(CB, SizeOf (CB));
Synchronize(HandleInput);
except
end;
end;
end;
....
procedure TClientHandleThread.HandleInput;
begin
if Assigned(EventMest) then EventMest(Self);
// Обробка команд
if RusCompare(CB.Command,'MESSAGE') Or (RusCompare(CB.Command,cmdSendPrav)) or (RusCompare(CB.Command, cmdAskPrav)) or
(RusCompare(CB.Command,cmdNewGame)) or (RusCompare(CB.Command,cmdEndGame)) or
(RusCompare(CB.Command,cmdNewTur)) or (RusCompare(CB.Command,cmdEndTur)) or
(RusCompare(CB.Command,cmdRunShellAll)) or (RusCompare(CB.Command,cmdRunShell)) or
(RusCompare(CB.Command,cmdSendActiveWinAll)) or (RusCompare(CB.Command,cmdSendActiveWin)) or
(RusCompare(CB.Command,cmdMinimizeWin)) or (RusCompare(CB.Command,cmdMinimizeWinAll)) or
(RusCompare(CB.Command,cmdCloseWin)) or (RusCompare(CB.Command,cmdCloseWinAll)) or
(RusCompare(CB.Command,cmdSendUserName)) or (RusCompare(CB.Command,cmdSendPassword)) or
(RusCompare(CB.Command,cmdNextGolos)) or (RusCompare(CB.Command,cmdGolosSended)) or
(RusCompare(CB.Command,cmdGolosEkspert)) or (RusCompare(CB.Command,cmdRefreshInfo)) or
(RusCompare(CB.Command,cmdRefreshInfoAll)) or (RusCompare(CB.Command,cmdSendMessage)) or
(RusCompare(CB.Command,cmdSendMessageAll)) or (RusCompare(CB.Command,cmdSendMessageAdmin)) or
(RusCompare(CB.Command,cmdClearMessages)) or (RusCompare(CB.Command,cmdClearMessgesAll)) or
(RusCompare(CB.Command,cmdReconnected)) or (RusCompare(CB.Command,cmdReconnectedAll))
or (RusCompare(CB.Command,cmdSetOcenk))
or RusCompare(CB.Command, cmdRegComp)
then Str.Add (CB.MyUserName + ': ' + CB.Msg)
else
if RusCompare(CB.Command,'DIALOG') then
MessageDlg ('"'+CB.MyUserName+'" посилаємо повідомлення:'+#13+CB.Msg, mtInformation, [mbOk], 0)
else // unknown command
MessageDlg('Команда "'+CB.Command+'" містить це повідомлення:'+#13+CB.Msg, mtError, [mbOk], 0);
end;
... |
У процедурі HandleInput перехоплюються повідомлення. У події EventMest ми можемо визначити процедуру, яка буде виконуватись при отриманні повідомлення.
Поміщаємо на форму кнопку Надіслати, поле введення Повідомлення, та список Команда, де буде перераховано всі доступні команди.
В обробнику клацання кнопки опишемо команду посилки повідомлення:
var
CommBlock : TCommBlock;
begin
inherited;
// Команда, яку ми посилаємо
CommBlock.Command := RusUpperCase(EditCommand.Text);
// Назва комп'ютера
CommBlock.MyUserName := Client.LocalName;
// Текст повідомлення
CommBlock.Msg := EditMessage.Text;
// Назва комп'ютера, якому ми надсилаємо повідомлення
CommBlock.ReceiverName := EditRecipient.Text;
// Назва комп'ютера, який надсилає повідомлення
CommBlock.ComputerName := RusUpperCase(Client.LocalName);
Client.WriteBuffer (CommBlock, SizeOf (CommBlock), true);
end;
|
Обговорити на форумі

|