Программирование гнёзд. Компоненты ТServerSocket и ТClientSocket:
До сегодняшнего дня, я описывал низкоуровневое программирование в сети. Но это не говорит о том, что в Delphi нет компонентов, облегчающих работу с сокетами. Такие компоненты есть, и сейчас я расскажу о двух из них: ТServerSocket и ТClientSocket. В качестве примера я не буду изобретать колесо, а воспользуюсь примером, который поставляется с Delphi.
Рис 1. Главная форма
На рисунке 1 ты можешь познакомиться с главной формой, а в самом конце, можно скачать русифицированные и немного поправленные мною исходники примера.
Для начала о компонентах. ТServerSocket является сервером сокета. К нему могут подключаться клиенты ТClientSocket. Для того, чтобы ТclientSocket мог присоединиться к ТserverSocket у них должны быть одинаковые свойства ServerType (для обучения достаточно stNonBlocking) и Port (номер порта). Номер порта желательно использовать больше чем 1000, это позволит избежать конфликтов с зарезервированными портами для других целей. В примере я использую 1024 для сервера и клиента.
Я тут заметил, что я ещё не рассказывал, как создаётся меню. Сейчас исправлю. Чтобы создать меню, надо поставить на форму компонент TMainMenu. Дважды щёлкаем по нему и получите распишитесь. Перед нами возникает редактор. Просто пишем названия меню в поле Caption и всё. А в это время в Object Inspector можно изменять такие интересные значения, как Bitmap (картинка для пункта меню), ShotCut (быстрый вызов), RadioItem (пункт меню с галочкой, как у TRadioBox) и многое другое. Со всем очень легко разобраться, если есть хоть начальные знания английского. Если таким мама не наградила, то очень даже помогает словарь. Вставлять и удалять пункты меню можно клавишами Ins и Del . Теперь попробуй создать меню сам, и всё станет понятно.
Теперь рассмотрим все пункты меню.
"Включить прослушивание":
FileListenItem.Checked := not FileListenItem.Checked;
if FileListenItem.Checked then
begin
//Надо включить прослушивание
ClientSocket.Active := False;
ServerSocket.Active := True;
Statusbar1.Panels[0].Text := 'Прослушиваем...';
end
else//Иначе надо отключить прослушивание
begin
if ServerSocket.Active then
ServerSocket.Active := False;
Statusbar1.Panels[0].Text := '';
end;
Первая строка изменяет состояние FileListenItem.Checked на противоположное. Это означает: Если уже включено прослушивание FileListenItem. Checked = true, то надо отключить, иначе надо включить.
Для начала, рассмотрим ситуацию, когда надо включить сервер на прослушивание. Здесь я сначала отключаю клиента. ClientSocket.Active := False - делаю клиента не активным. Если у тебя на форме стоит одновременно и клиент и сервер, то они не могут быть оба одновременно включёнными. Одно приложение не может быть одновременно и сервером и клиентом для одного и того же порта. Это возможно только если порты разные, поэтому я на всякий случай отключаю клиента.
ServerSocket.Active := True - включить сервер на прослушивание. После этого ТserverSocket начинает слушать порт на предмет подключения к нему.
Statusbar1.Panels[0].Text := 'Прослушиваем...' - вывожу соответствующее сообщение в строку состояния.
Теперь ситуация, когда надо отключить сервер:
if ServerSocket.Active then ServerSocket.Active := False - эта строка означает: если сервер включён, то отключить его.
Если ты один раз выберешь это меню, то запуститься сервер на прослушивание. При следующем нажатии, сервер отключится.
Теперь о меню подключиться:
procedure TChatForm.FileConnectItemClick(Sender: TObject);
begin
if ClientSocket.Active then ClientSocket.Active := False;
if InputQuery('Присоединиться к...', 'Имя сервера:', Server) then
if Length(Server) > 0 then
with ClientSocket do
begin
Host := Server;
Active := True;
FileListenItem.Checked := False;
end;
end;
В первой строке я проверяю, если клиент уже подключён, то я отключаю его.
Затем я запрашиваю ввод имени сервера (имя компьютера или его IP адрес где запущена такая же прога и включено прослушивание). InputQuery - показывает диалоговое окно, в котором можно производить ввод. В качестве первых двух параметров выступают заголовки. Это строки, которые подсказывают пользователю, что надо ввести. Третий параметр - это переменная типа строки, в которую будет записан результат ввода.
Дальше идёт проверка if Length(Server)>0 then если длинна введённого пользователем текста больше 0 (то есть, если строка не пустая), то выполнить действия. В качестве действий выполняется три оператора.
with ClientSocket do
begin
Host := Server;
Active := True;
FileListenItem.Checked := False;
end;
Обрати внимание на присутствие здесь конструкции with ClientSocket do. Я уже рассказывал о ней, но всё же повторюсь. Она говорит о том, что все действия между последующими BEGIN и END, надо по умолчанию выполнять с ClientSocket. Если какое-то действие нельзя выполнить, то выполнять его с главной формой. Сейчас всё будет видно на примере.
Host := Server. Если бы эта строка не стояла внутри конструкции with, то Host не был бы найден. А так, Delphi находит свойство Host у ClientSocket и присваивает в него введённое имя сервера.
Можно было не использовать конструкцию with, но тогда наша фунция бы выглядела так:
procedure TChatForm.FileConnectItemClick(Sender: TObject);
begin
if ClientSocket.Active then ClientSocket.Active := False;
if InputQuery('Присоединиться к...', 'Имя сервера:', Server) then
if Length(Server) > 0 then
begin
ClientSocket.Host := Server;
ClientSocket.Active := True;
FileListenItem.Checked := False;
end;
end;
Итак, имя сервера ввели, можно его активировать (Active := True;). После этого я на всякий случай отключаю прослушивание (FileListenItem.Checked := False). Я уже говорил, что одно приложение не может быть одновременно и сервером и клиентом для одного порта.
Если имя сервера было введено правильно, то приложение начнёт присоединяться к нему. Присоединились? Хорошо. Теперь надо разорвать связь. Вот процедура, вызываемая при щелчке по пункту меню "Отключиться":
Всё очень просто. Первая строка делает неактивным клиент (разрывает связь). Вторая строка включает сервер на прослушивание. Третья выводит сообщение в строку состояния. Всё это мы уже пережевали немного раньше.
Теперь перейдём к отправке текста. Для этого в примере перехватывается событие OnKeyDown у TMemo:
procedure TChatForm.Memo1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_Return then
if ServerSocket.Active then
ServerSocket.Socket.Connections[0].SendText(
Memo1.Lines[Memo1.Lines.Count - 1])
else
ClientSocket.Socket.SendText(Memo1.Lines[Memo1.Lines.Count - 1]);
end;
Вначале идёт проверка: если нажата клавиша ENTER, то мы продолжаем работать. Дальше проверяется, если это сервер (к нам подключились), то для отправки используется компонент TServerSocket иначе TClientSocket.
Я немного мухлюю. Я проверяю, активен ли сервер, но не проверяю, есть ли к нему присоединившиеся клиенты. Если их нет, то произойдёт ошибка. Если не хочешь, чтоб выскакивала эта ошибка то напиши так:
if (ServerSocket.Active=true) and
(ServerSocket.Socket.ActiveConnections>0) then
ServerSocket.Socket.Connections[0].SendText(
Memo1.Lines[Memo1.Lines.Count - 1])
Теперь будет проверяться активность сервера и количество конектов (ServerSocket.Socket.ActiveConnections), которое должно быть обязательно больше 0. Если хотябы одно из этихз условий не выполнено, то отправки текста не будет.
Сначала разберёмся с клиентом, он проще. У него есть свойство Socket типа TClientWinSocket, которое отвечает за всю сетевую работу. У этого Socket есть интересующие нас методы SendBuf (отправить буфер), SendStream (отправить поток) и непосредственно SendText (отправить текст). Вот последняя нас и интересует. С помощью неё мы отправляем последнюю введённую пользователем строчку (Memo1.Lines[Memo1.Lines.Count - 1]).
У сервера чуточку сложнее. Там тоже есть свойство Socket типа TServerWinSocket. Но тут нельзя сразу отправить текст. Для этого нужно выбрать, какому именно клиенту предназначается текст. Я отправляю всегда первому клиенту ServerSocket. Socket. Connections[0]. SendText, но если ты захочешь отправлять сообщения сразу всем, то это можно сделать так:
for I:=0 to ServerSocket.Socket.ActiveConnections-1 do
begin
ServerSocket.Socket.Connections[i].SendText(
Memo1.Lines[Memo1.Lines.Count - 1])
end;
Весь остальной текст проги - это ответы на сообщения, генерируемые компонентами ТServerSocket и ТClientSocket, поэтому я не будут на них останавливаться. Все эти процедуры чисто информационные. Единственное, что нам надо - это сообщение OnRead у ТClientSocket и OnClientRead у ТServerSocket . Эти сообщения происходят, когда на порт пришли какие-то данные (в нашем случае это текст). В примере, обе функции реализованы одинаковы, поэтому я приведу только одну:
procedure TChatForm.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
Это сообщение возвращает нам Socket типа TCustomWinSocket. Это очень удобно, потому что, вызвав Socket.ReceiveText, мы получаем переданный нам текст. Я его просто добавляю к Memo2.
Программа готова. Запусти одну её копию на одном компьютере, а другую на другом. Теперь в одной из них включи прослушивание, а из другой попробуй подсоединиться, введя имя или IP- адрес первого компьютера. Попробуй напечатать какой-нибудь текст в поле Memo. После нажатия пимпы Enter, текст пересылается на другой компьютер.
Развлекайся, а я пошёл работать дальше. Напоминаю: если с чем-то не разобрался, то не волнуйся. Просто поиграй с примером и всё само придёт. Я даю только основы, а всё самое главное придёт собственным потом. Тренируйся, и всё получится.