Работа с сетью, компоненты TcpClient и TcpServer

01.04.2011
Что мы подразумеваем под работой с сетью? В основе это отправка каких либо текстовых сообщений (например чаты). Не будем углубляться и рассматривать как это происходит, а просто напишем два небольших приложения (клиентское и серверное) и через некоторый промежуток будем отправлять данные от клиента к серверу. А помогут нам в этом компоненты TcpClient и TcpServer.

Пример


В качестве примера разберём такую задачу: Есть несколько компьютеров и нам необходимо знать IP, имя компьютера и имя пользователя, каждого включенного ПК. Т.е. создадим такое приложение, которое будет через определенный промежуток времени отправлять эти данные на сервер, на котором эти данные будут отображаться в наглядном виде.

Клиент


Начнём с проектирования клиентского приложения, для начала создадим новую форму и добавим на неё три компонента: TcpClient (вкладка Internet), IdIPWatch (вкладка Indy Misc) и Timer.

И сразу же начнём начальную настройку нашего соединения. А в процедуру FormCreate пишем следующие:
procedure TForm1.FormCreate(Sender: TObject);

begin
//IP на который будем отправлять сообщение
TCPClient1.RemoteHost := '192.168.1.4';
//Порт, через который будем работать
TCPClient1.RemotePort := '1010';
//Активируем соединение
TCPClient1.Active := true;
//Делаем форму невидимой
Application.ShowMainForm := false;
end;

Т.е. по сути мы указываем в качестве сервера компьютер с ip 192.168.1.4, а подключаться к нему будем через порт 1010.
Локальный IP адрес компьютера и его имя мы будем узнавать с помощью компонента IdIPWatch, а имя пользователя с помощью следующей функции.
function TForm1.GetUserFromWindows: string;

var
UserName : string;
UserNameLen : Dword;
begin
//Длина имени пользователя
UserNameLen := 255;
SetLength(userName, UserNameLen);
//Получаем и возвращаем имя
if GetUserName(PChar(UserName), UserNameLen) then
Result := Copy(UserName,1,UserNameLen - 1)
else
Result := '';
end;

Итак, все необходимые данные нам известны. Теперь их надо отправить на сервер, для этого нам и понадобиться компонент Timer, установим у него значение свойства Interval равным 10000.
procedure TForm1.Timer1Timer(Sender: TObject);

begin
if TCPClient1.connect then
begin
TCPClient1.Sendln('PCName="' + IdIPWatch1.LocalName + '"; IP="' + IdIPWatch1.LocalIP + '"; UserName="' + GetUserFromWindows + '".');
TCPClient1.Disconnect;
end;
end;

Подключаемся к серверу и если подключение удалось, то отправляем ему строку в которой содержаться все необходимые нам данные, после чего прерываем соединение. Именно это написано в функции выше.
На этом с клиентским приложением всё.

Сервер


Для начала добавим два компонента на форму, это Timer и TcpServer (вкладка Internet). Так же добавим PageControl (вкладка Win32), на нём сделаем три страницы. На первую добавим две кнопки Button, одну назавём - "Запуск", а вторую - "Остановка". На вторую страницу добавим поле Memo, и на третью StringGrid, соответственно.
В Memo мы будем выводить все "сообщения", которые к нам поступят от клиентов, а в StringGrid эти данные будут наглядно отображаться.
И начнём мы как всегда с процедуры FormCreate.
procedure TForm1.FormCreate(Sender: TObject);

begin
CountPC := 0;
StringGrid1.Cells[0, CountPC] := 'Имя ПК';
StringGrid1.Cells[1, CountPC] := 'IP';
StringGrid1.Cells[2, CountPC] := 'Имя пользователя';
StringGrid1.Cells[3, CountPC] := 'Последний отклик';
StringGrid1.Cells[4, CountPC] := 'Первый отклик';
end;

CountPC, это глобальная переменная в которой хранится количество активных клиентов. А далее мы просто пишем заголовки для StringGrid'a.
Перейдём к нашим кнопкам, начнём с той, которую обозвали "Запуск".
procedure TForm1.Button1Click(Sender: TObject);

begin
//Наш локальный ip
TCPServer1.LocalHost:='192.168.1.4';
//Порт, который будем слушать и ждать сообщения от клиента =)
TCPServer1.LocalPort:='1010';
//Активируем сервер и выводим сообщение об этом в Memo
TCPServer1.Active:=true;
Memo1.Lines.Add('Сервер запущен');
Button1.Enabled := false;
Button2.Enabled := true;
Timer1.Interval := 10000;
end;

Тут практически всё аналогично тому, что мы писали в клиентском приложении. Указываем свой ip, порт, который будем слушать и активируем сервер. Собственно этими действиями мы запустили наш сервер. А теперь, о том - как его отключить, т.е. о кнопке "Остановка".
procedure TForm1.Button2Click(Sender: TObject);

begin
TCPServer1.Active := false;
Memo1.Lines.Add('Сервер остановлен');
Button2.Enabled := false;
Button1.Enabled := true;
Timer1.Interval := 0;
end;

Дезактивируем сервер, выводим сообщение об этом и также выключаем Timer.

А теперь самое интересное - приём данных от сервера. Для этого нам понадобиться событие OnAccept компонента TcpServer. Оно будет срабатывать каждый раз, когда через вышеуказанный порт будет приходить сообщение.
В него мы запишем следующий код
procedure TForm1.TcpServer1Accept(Sender: TObject; ClientSocket: TCustomIpClient);

var
s:string;
PCName, ip, user: string;
i: integer;
new: boolean;
begin
//Для удобства присвоим перемнной s полученое сообщение
s := ClientSocket.receiveln;
//Обработка полученного сообщения
while s <> '' do
begin
new := false;
Memo1.Lines.Add(TimeToStr(Now) + ': ' + s);
PCName := copy(s, 9, pos('"; ', s) - 9);
delete(s, 1, pos('"; ', s) + 3);
ip := copy(s, 4, pos('"; ', s) - 4);
delete(s, 1, pos('"; ', s) + 3);
user := copy(s, 10, pos('".', s) - 10);
with StringGrid1 do
begin
for i := 1 to CountPC do
if (Cells[0, i] = PCName) and (Cells[1, i] = ip) then
begin
Cells[2, i] := user;
Cells[3, i] := TimeToStr(Now);
new := true;
break;
end;
if new = false then
begin
inc(CountPC);
Cells[0, CountPC] := PCName;
Cells[1, CountPC] := ip;
Cells[2, CountPC] := user;
Cells[3, CountPC] := TimeToStr(Now);
Cells[4, CountPC] := TimeToStr(Now);
if CountPC > 1 then
RowCount := RowCount + 1;
end;
end;
s:=ClientSocket.receiveln;
end;
end;

В выше написанном коде мы получаем сообщение от клиента и начинаем цикл в котором:
1) Добавляем всё полученное сообщение в Memo
2) Выделяем из сообщения отдельные значения (Имя ПК, ip и т.д.)
3) Проверяем, есть ли уже в StringGrid запись с таким же ip, если есть, то просто обновляем у неё время, если нет, то добавляем новую запись
4) Снова получаем сообщение и т.д.

А в Timer осталось добавить небольшую процедуру, которая каждые пять минут удаляла бы из StringGrid записи которые не обновлялись 3 минуты.
И напоследок три скриншота серверной части, написанной с применением компонентов TMS Component Pack