Форма входа
Категории раздела
Delphi [24]
Статьи по программированию на Delphi.
html [42]
Статьи и помощь по html
I Love Bashorg
Главная » Статьи » Программирование » Delphi

Пример чата на основе сокетов


Введение

Всем известно, что справочный материал - это хорошо. Но изучение почти любого вопроса гораздо легче, если рассматривать его на примерах. Поэтому целью данной статьи, в отличие от предыдущих на эту тему, будет изучение различных методов и приемов работы с сокетами на наглядном примере.

Здесь мы рассмотрим практический пример приложения-чата для локальной сети.

Чат для локальной сети

Рассмотрим довольно простой пример чата для локальной сети. В этом примере два приложения - чат-сервер и чат-клиент. Чат-клиенты подключаются к чат-серверу и через него обмениваются сообщениями. Чат-сервер может быть запущен и на том компьютере, где запущен один из клиентов. Кроме того, для тестирования Вы можете запустить на своем компьютере сразу чат-сервер и несколько чат-клиентов. Для этого нужно указать localhost в поле Host, а в поле Port у сервера и у клиента должны быть одинаковые значения. Не путайте сервер в понимании программы, принимающей вызовы клиентов, с компьютером-сервером! То же самое и с клиентом.

Исходники обоих приложений (чат-сервера и чат-клиента) Вы можете скачать, нажав здесь. Этот пример сделан на Borland Delphi 5. Однако, код будет работать в любой версии Дельфи, где есть компоненты TServerSocket и TClientSocket. В более ранних версиях возможны проблемы с открытием форм, но их легко сделать самому, т.к. здесь приведены скриншоты этих примеров.

Чтобы посмотреть данный пример, скачайте его исходники, откомпилируйте оба проекта, запустите srv_ex.exe, в его окне нажмите кнопку Start. В появившемся окне запроса нужно указать порт, на котором будет работать сервер. Значение по умолчанию - 1001.

Затем запустите chat_ex.exe (чат-клиент). Откроется главное окно, в котором нужно нажать кнопкуConnect, а затем - ввести необходимые параметры:

  • Host - нужно ввести адрес сервера, с которым нужно установить соединение. Если сервер запущен на том же компьютере, что и клиент, то введите в это поле - localhost
  • Port - нужно ввести порт сервера, с которым нужно установить соединение. Порт сервера и клиента должен совпадать и желательно не должен быть меньше тысячи, т.к. порты в этом диапазоне могут использоваться системой. Значение по-умолчанию - 1001
  • Nickname - нужно ввести свой ник (псевдоним) для отображения в списке пользователей.


Ну а теперь разберем исходный код чат-сервера:

 {Запуск сервера}
 procedure TForm1.Button1Click(Sender: TObject);
 var s: string;
 begin
 {Запрашиваем порт}
 s := InputBox('Start chat server','Enter port:','1001');
 if s = '' then
 Exit;
 {Чистим юзер лист}
 ListBox1.Items.Clear;
 {Устанавливаем порт}
 ServerSocket1.Port := StrToInt(s);
 {Запускаем сервер}
 ServerSocket1.Open;
 end;
 
 procedure TForm1.Button2Click(Sender: TObject);
 begin
 {Чистим юзер лист и останавливаем сервер}
 ListBox1.Items.Clear;
 if ServerSocket1.Active then
 ServerSocket1.Close;
 end;
 
 procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
 Socket: TCustomWinSocket);
 var s: string;
 i: Integer;
 begin
 {сохраняем в s присланную нам строку}
 s := Socket.ReceiveText;
 {Если кто-то прислал нам свое имя}
 if Copy(s,1,2) = '#N' then begin
 Delete(s,1,2);
 {Добавляем его в юзер лист}
 ListBox1.Items.Add(s);
 {Записываем в s команду для посылки нового списка юзеров}
 s := '#U';
 for i := 0 to ListBox1.Items.Count-1 do
 s := s+ListBox1.Items[i]+';';
 {...и рассылаем этот список всем клиентам}
 for i := 0 to ServerSocket1.Socket.ActiveConnections-1 do
 ServerSocket1.Socket.Connections[i].SendText(s);
 Exit;
 end;
 {Если кто-то кинул сообщение - рассылаем его всем клиентам}
 if (Copy(s,1,2) = '#M')or(Copy(s,1,2) = '#P') then begin
 for i := 0 to ServerSocket1.Socket.ActiveConnections-1 do
 ServerSocket1.Socket.Connections[i].SendText(s);
 Exit;
 end;
 end;
 
 procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject;
 Socket: TCustomWinSocket);
 var i: Integer;
 begin
 {Кто-то присоединился или отсоединился? Нет проблем! Запрашиваем у всех
 юзеров их имена}
 ListBox1.Items.Clear;
 for i := 0 to ServerSocket1.Socket.ActiveConnections-1 do
 ServerSocket1.Socket.Connections[i].SendText('#N');
 end;

Итак, что же делает данный код? Button1Click и Button2Click - думаю, понятно, что они запускают и останавливают сервер соответственно. Дополнительного рассмотрения требует ServerSocket1ClientRead.

ServerSocket1ClientRead - обработчик события OnClientRead компонента TServerSocket. Первая строка - сохраняем полученные из сокета данные в s. Далее, функция Copy(s,1,2) возвращает первые два символа строки s, которые затем проверяются на соответствие с условно-введенной нами командой "#N", которая означает, что в строке s после самой команды содержится присланное кем-либо из клиентов имя (ник - псевдоним). Это имя затем добавляется в ListBox1. Таким образом ListBox1 становится списком подключенных клиентов.

Далее, в строку s (информация в которой уже не нужна) записываем команду "#U" и последовательно всех юзеров из списка ListBox1. Затем всю эту строку рассылаем ВСЕМ клиентам.

Затем, если мы получили не "#N", а "#M" или "#P" (простое или приватное сообщение) - рассылаем его всем клиентам (а они уже разберуться, кому это сообщение :-) ).

ServerSocket1ClientDisconnect - обработчик события, возникающего когда кто-либо из клиентов отсоединился от сервера. Здесь мы очищаем список юзеров и посылаем всем клиентам запросы на получение их ников (псевдонимов).

ПРИМЕЧАНИЕ: Данный пример максимально упрощен, чтобы просто была понятна технология создания подобных приложений. Возможности нормального чата должны быть намного шире. Также имейте в виду, что команды типа "#N", "#U", "#M", и т.д. вводятся самим разработчиком просто чтобы определить, что прислали из сокета. Эти команды никак не привязаны непостредственно к сокетам.


Далее приведем исходный текст чат-клиента с необходимыми пояснениями:

 ...
 {Здесь определение формы TForm1}

 var
 Form1: TForm1;
 nickname: string; {Ник (псевдоним)}
 
 implementation
 
 uses conn; {Юнит с диалогом установки соединения}
 
 {$R *.DFM}
 
 procedure TForm1.Button2Click(Sender: TObject);
 var do_connect: Boolean;
 host,port: string;
 begin
 {Показываем окно установки соединения с сервером}
 Form2 := TForm2.Create(Application);
 {do_connect = True, если была нажата кнопка Connect}
 do_connect := (Form2.ShowModal = mrOk);
 {заполнение переменных до того, как мы уничтожим форму}
 host := Form2.Edit1.Text;
 port := Form2.Edit2.Text;
 nickname := Form2.Edit3.Text;
 {Уничтожаем форму}
 Form2.Free;
 {Если была нажата кнопка Cancel, то уходим отсюда}
 if not do_connect then
 Exit;
 {Если соединение уже установлено, то обрываем его}
 if ClientSocket1.Active then
 ClientSocket1.Close;
 {Устанавливаем свойства Host и Port}
 ClientSocket1.Host := host;
 ClientSocket1.Port := StrToInt(port);
 {Пытаемся соединиться}
 ClientSocket1.Open;
 end;
 
 procedure TForm1.Button3Click(Sender: TObject);
 begin
 {Закрываем соединение (если оно установлено)}
 if ClientSocket1.Active then
 ClientSocket1.Close;
 end;
 
 procedure TForm1.ClientSocket1Error(Sender: TObject;
 Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
 var ErrorCode: Integer);
 begin
 {Если произошла ошибка, выводим ее код в Memo1}
 {Insert вставляет строку в указанную позицию (в данном случае - 0 - в начало)}
 Memo1.Lines.Insert(0,'Socket error ('+IntToStr(ErrorCode)+')');
 end;
 
 procedure TForm1.ClientSocket1Lookup(Sender: TObject;
 Socket: TCustomWinSocket);
 begin
 {Сообщаем о том, что идет поиск хоста}
 Memo1.Lines.Insert(0,'Looking up for server...');
 end;
 
 procedure TForm1.ClientSocket1Connecting(Sender: TObject;
 Socket: TCustomWinSocket);
 begin
 {соединяемся...}
 Memo1.Lines.Insert(0,'connecting...');
 end;
 
 procedure TForm1.ClientSocket1Connect(Sender: TObject;
 Socket: TCustomWinSocket);
 begin
 {соединились!}
 Memo1.Lines.Insert(0,'connected!');
 end;
 
 procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
 Socket: TCustomWinSocket);
 begin
 {отсоединились :(}
 Memo1.Lines.Insert(0,'disconnected');
 end;
 
 procedure TForm1.ClientSocket1Read(Sender: TObject;
 Socket: TCustomWinSocket);
 var s,from_,to_: string;
 begin
 {присваиваем s полученную от сервера строку}
 s := Socket.ReceiveText;
 {Если сервер посылает нам User List}
 if Copy(s,1,2) = '#U' then begin
 Delete(s,1,2);
 {Чистим ListBox1}
 ListBox1.Items.Clear;
 {Добавляем по одному юзеру в список. Имена юзеров разделены знаком ";"}
 while Pos(';',s) > 0 do begin
 ListBox1.Items.Add(Copy(s,1,Pos(';',s)-1));
 Delete(s,1,Pos(';',s));
 end;
 Exit;
 end;
 {Если нам прислали общее сообщение (видимое для всех юзеров)}
 if Copy(s,1,2) = '#M' then begin
 Delete(s,1,2);
 {Добавляем его в Memo1}
 Memo1.Lines.Insert(0,Copy(s,1,Pos(';',s)-1)+'> '+
 Copy(s,Pos(';',s)+1,Length(s)-Pos(';',s)));
 Exit;
 end;
 {Если нам прислали запрос на наше имя юзера}
 if Copy(s,1,2) = '#N' then begin
 {Посылаем ответ}
 Socket.SendText('#N'+nickname);
 Exit;
 end;
 {Если нам прислали приватное сообщение (или не нам :) )}
 if Copy(s,1,2) = '#P' then begin
 Delete(s,1,2);
 {Выделяем в to_ - кому оно предназначено}
 to_ := Copy(s,1,Pos(';',s)-1);
 Delete(s,1,Pos(';',s));
 {Выделяем в from_ - кем отправлено}
 from_ := Copy(s,1,Pos(';',s)-1);
 Delete(s,1,Pos(';',s));
 {Если оно для нас, или написано нами - добавляем в Memo1
 (иногда полезно убрать этот оператор if :) )}
 if (to_ = nickname)or(from_ = nickname) then
 Memo1.Lines.Insert(0,from_+' (private) > '+s);
 Exit;
 end;
 end;
 
 procedure TForm1.Button1Click(Sender: TObject);
 var s: string;
 begin
 {Если мы хотим послать приватное сообщение, но не выбрали адресата -
 нас покарают замечанием :) и выгонят из обработчика}
 if (CheckBox1.Checked)and(ListBox1.ItemIndex < 0) then begin
 ShowMessage('At first you should select the user in the User List!');
 Exit;
 end;
 {Если это приватное сообщение}
 if CheckBox1.Checked then
 s := '#P'+ListBox1.Items[ListBox1.ItemIndex]+';' {добавляем спец.команду и адресат}
 else {А если не очень приватное?}
 s := '#M'; {Просто спец.команду}
 {Добавляем наше имя (от кого) и само сообщение}
 s := s+nickname+';'+Edit1.Text;
 {Посылаем все это добро по сокету}
 ClientSocket1.Socket.SendText(s);
 {И снова ждем ввода в уже чистом TEdit-е}
 Edit1.Text := '';
 ActiveControl := Edit1;
 end;
 
 procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word;
 Shift: TShiftState);
 begin
 {Если была нажата Enter (для тех, кто с мышами не дружит) - тоже не
 отказываемся послать сообщение}
 if Key = VK_RETURN then
 Button1.Click;
 end;

Итак, Button2Click - вызывает диалог установки соединения (из второго юнита), а затем устанавливает это соединение. На данном вопросе мы останавливаться не будем. Button3Click и последующие события, добавляемые в Memo1 также довольно просты. Разберем подробнее обработчик события OnRead - ClientSocket1Read.

Сначала мы сохраняем полученные по сокету данные в строку s. Затем, если нам прислали список других подключенных клиентов, то мы выделяем из строки s по одному юзеру и добавляем их последовательно в ListBox1. Таким образом ListBox1 становится списком юзеров.

Далее - если нам прислали команду "#M" - обычное сообщение, то мы выделяем из s отправителя и само сообщение, а затем все это в стандартной для чатов форме выводим в Memo1.

Если же был получен запрос на имя пользователя (ник) - команда "#N", то посылаем серверу свой ник.

А если пришло приватное сообщение ("#P"), то из строки s мы выделяем имя отправителя, адресата и само сообщение. Если сообщение адресовано нам, либо мы же его и отправили - выводим его в Memo1.

Теперь разберем Button1Click - обработчик нажатия кнопки отправки сообщения. Если поставлен флаг CheckBox1 (т.е. сообщение - приватное), а адресат в списке пользователей не выделен - выдаем ошибку. Далее, формируем в s команду для отправки сообщения: сначала тип ("#P" или "#M". Если "#P", то плюс имя адресата из списка юзеров), разделитель (";"), затем - имя отправителя, разделитель, и непосредственно сам текст сообщения. Далее идет отправка строки s по сокету, очистка поля ввода сообщения, и перевод в него фокуса (курсора).

Edit1KeyDown нужен для того, чтобы вместо нажатия кнопки Button1 каждый раз, когда нужно отправить сообщение, просто нажимать Enter.

Эпилог

В этой статье был представлен один из примеров для наглядного изучения сокетов. Если у Вас есть вопросы - скидывайте их мне на E-mail: snick@mailru.com, а еще лучше - пишите в конференции этого сайта (Delphi. Общие вопросы), чтобы и другие пользователи смогли увидеть Ваш вопрос и попытаться на него ответить!

Карих Николай. (Nitro)    Московская область, г.Жуковский



Автор статьи:  Карих Николай

Статья написана для сайта: Дельфи.Вокруг да около.





Источник: http://www.delphimaster.ru/articles/sockadv/index.html
Категория: Delphi | Добавил: Bombers (11.10.2009)
Просмотров: 1002 | Рейтинг: 5.0/1
Всего комментариев: 0
Суббота, 20.04.2024, 15:02
Приветствую Вас Гость
Статистика
  • Онлайн всего: 1
    Гостей: 1
    Пользователей: 0
    Admin icq status
    587643917
    Друзья сайта