Форум программистов
 

Восстановите пароль или Зарегистрируйтесь на форуме, о проблемах и с заказом рекламы пишите сюда - alarforum@yandex.ru, проверяйте папку спам!

Вернуться   Форум программистов > Клуб программистов > Обсуждение статей
Регистрация

Восстановить пароль
Повторная активизация e-mail

Купить рекламу на форуме - 42 тыс руб за месяц

Ответ
 
Опции темы Поиск в этой теме
Старый 12.01.2008, 23:10   #1
Pblog
Бот
Администратор
 
Регистрация: 27.05.2007
Сообщений: 182
Стрелка обновление в блоге - Создание многопользовательского чата

Создание многопользовательского чата

pblog.ruВ предыдущей статье (“Создание клиент-сервера”) рассказывалось о разработке простейшего чата на двоих пользователей. Структура чата “head-to-head” достаточно проста, ведь есть только один канал, с одной стороны которого сервер, с другой – клиент. Multy-user-структура несколько сложнее. Есть один сервер и множество клиентов. Сервер при этом выполняет обработку входящих сообщений, пересылает их по нужным каналам, регистрирует пользователей и показывает всем, сколько пользователей общаются в текущий момент.
Многопользовательский чат (Multy-user on-line)
Начнем разработку приложения чата с уже готовой формы из предыдущей статьи, или с новой. Вот, что должно быть в форме:
PortEdit (Edit)
HostEdit (Edit)
NikEdit (Edit)
TextEdit (Edit)
ChatMemo (Memo)
ClientBtn (Button)
ServerBtn (Button)
SendBtn (Button)
ServerSocket (ServerSocket)
ClientSocket (ClientSocket)
Компоненты из стандартного пакета Delphi ServerSocket и ClientSocket не всегда могут быть отображены в палитре Internet, и их нужно загрузить следующим образом:
выбрать меню: Component - Install Packages… - Add., далее нужно указать файл …\bin\dclsockets70.bpl.
Добавляются новые компоненты:
UserListView (ListView)
ImageList (ImageList)
ServerTimer (Timer)
(more…)
Pblog вне форума Ответить с цитированием
Старый 08.04.2008, 21:29   #2
Beermonza
Инженер ИС
Старожил
 
Аватар для Beermonza
 
Регистрация: 13.12.2006
Сообщений: 2,671
По умолчанию Добавляем приват.

F@iTH Март 28th, 2008 | 11:44
Цитата:
...можно ли в ней сделать приват и каким образом? ...
Приват - есть условие, …вы ставите условие, чтобы сервер посылал сообщение только в определенный канал, …как узнать в какой? Для этого вам сначала нужно добавить еще один тип команды.
Сейчас набор индексов идентифиткации команд (первый байт пакета) таков:
0 - для сообщений;
1 - для идентификации пользователя;
2 - для приема списка пользователей.
…вам нужно добавить еще один индекс;
3 - для приватных сообщений.
После индекса в этой команде будет записано имя получателя, его можно узнать через UserListView.Selected.Caption (на OnClick), …затем идет символ конца имени Chr(152) и текст из TextEdit.
Сервер приняв эту команду, определит индекс, запустит алгоритм считывания имени получателя (это тот же алгоритм, что и в процедуре считывания списка пользователей), пробежится по массиву пользователей , сравнивая полученное имя с UserMas[i].Name, если имя найдется, то должен будет остановить цикл, и отослать текст (содержимое команды после Chr(152)) по каналу i.

Вот так будет выглядеть часть кода на приватные сообщения у сервера:
Код:
// код приема приватного сообщения ---------------------------------------------
                3: Begin
// укажем начальный символ
                     pos:=2;
// обнулим счетчик символов
                     x:=0;
// пробегаем по длине принятой строки
                     For j:=2 to len+1 do
                       Begin
// записываем в счетчик сдвиг
                         x:=x+1;
// если найден ключ (конец части ника в строке)
                         If Copy(text,j,1)=Chr(152) then
                           Begin
// сохраняем ник приватного пользователя
                             PrivateUser:=Copy(text,pos,x-1);
                           end;
                       end;
// если приватный пользователь - "сервер"
                     If PrivateUser=NikEdit.Text then
                       Begin
// добавим в ChatMemo сообщение клиента
                         ChatMemo.Lines.Add(Copy(text,3+Length(PrivateUser),len-Length(PrivateUser)-1));
                       end
                     else
                       Begin
// создаем цикл перебора пользователей
                         For i:=0 to ServerSocket.Socket.ActiveConnections-1 do
                           Begin
// если пользователь найден
                             If UserMas[i+1].Name=PrivateUser then
                               Begin
// отсылаем сообщение в канал приватного пользователя
                                 ServerSocket.Socket.Connections[i].SendText('0'+Copy(text,3+Length(PrivateUser),len-Length(PrivateUser)-1));
// сбрасываем цикл
                                 break;
                               end;
                           end;
                       end;
                   end;
// -----------------------------------------------------------------------------
На форме для приватных дел понадобятся некоторые объекты: PrivateEdit - в нем будет отображаться, кому следует доставить сообщение и кнопочка сброса приватности. Вот так будет выглядеть процедура выбора приватного ника:
Код:
procedure TForm1.UserListViewClick(Sender: TObject);
begin
// если список пользователей не пустой и выделена запись
  If (UserListView.Items.Count>0) And (UserListView.SelCount>0) then
    Begin
// запишем в поле "Кому" приватного пользователя
      PrivateEdit.Text:=UserListView.Selected.Caption;
    end;
end;
А это сброс приватности:
Код:
procedure TForm1.AllBtnClick(Sender: TObject);
begin
// сообщение для всех
  PrivateEdit.Text:='Всем';
end;
Исходники в архиве.

Дальнейшее обсуждение статьи следует вести тут. Модернизация программы и комментарии могут быть полезны всем.
Вложения
Тип файла: zip Multy-user on-line chat (Private).zip (265.9 Кб, 537 просмотров)
Руководитель проекта MMO 2D RPG: Настоящее имя Денис Стрижак (10.05.1981-6.02.2019) Мир духу его
Beermonza вне форума Ответить с цитированием
Старый 15.04.2008, 11:01   #3
InseR
Пользователь
 
Регистрация: 01.06.2007
Сообщений: 59
По умолчанию

Помогите пожалуйста.Если на сервер подключается минимум 2 пользователя,то тот,кто подключился раньше,неможет посылать сообщения,вернее он их посылает,но сервер почему то их не обрабатывает.

Код:
// Процедура прослушывания на сервере
procedure TMainForm.ServerTimerTimer(Sender: TObject);
var
text:string;
user:integer;
begin

if ServerSocket.Socket.ActiveConnections = 0 then
exit
else
begin

 for i:=0 to ServerSocket.Socket.ActiveConnections-1 do
 text:=ServerSocket.Socket.Connections[i].ReceiveText();


 if copy(text,1,2) = '#M' then
 begin
 for j:=0 to ServerSocket.Socket.ActiveConnections-1 do
 ServerSocket.Socket.Connections[j].SendText(text);
 end;

 if copy(text,1,2) = '#N' then
 begin
 Delete(text,1,2);
 UserList.Items.Add(text);
 text:='#U';
 for user:=0 to UserList.Items.Count-1 do
 text:=text+UserList.Items[user]+';';
 for  j:=0 to ServerSocket.Socket.ActiveConnections-1 do
 ServerSocket.Socket.Connections[j].SendText(text);
 end;



end
end;

Код:
// процедура отправки сообщения с клиента
procedure TMainForm.SendBtnClick(Sender: TObject);
begin
ClientSocket.Socket.SendText('#M'+OptionsForm.NickEdit.Text+': '+MessageEdit.Text);
MessageEdit.Clear;
end;
InseR вне форума Ответить с цитированием
Старый 15.04.2008, 15:22   #4
Beermonza
Инженер ИС
Старожил
 
Аватар для Beermonza
 
Регистрация: 13.12.2006
Сообщений: 2,671
По умолчанию Разбор полетов...

InseR, обратите внимание на вашу запись кода цикла приема пакета, она такая (схематично):

Цикл от 0 до количество каналов - 1
начало
прием пакета
конец

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

Код:
 for i:=0 to ServerSocket.Socket.ActiveConnections-1 do
   begin  
     text:=ServerSocket.Socket.Connections[i].ReceiveText();  
   
     if copy(text,1,2) = '#M' then  
       begin  
         for j:=0 to ServerSocket.Socket.ActiveConnections-1 do
           begin  
             ServerSocket.Socket.Connections[j].SendText(text);
           end;
       end;  
   
     if copy(text,1,2) = '#N' then  
       begin  
         Delete(text,1,2);  
         UserList.Items.Add(text);  
         text:='#U';
  
         for user:=0 to UserList.Items.Count-1 do
           begin  
             text:=text+UserList.Items[user]+';';
           end;
  
         for  j:=0 to ServerSocket.Socket.ActiveConnections-1 do
           begin  
             ServerSocket.Socket.Connections[j].SendText(text);
           end;
       end;
   end;
Совет: не ленитесь проставлять полностью операторы begin и end, это вам визуально помогает определять начало и конец цикла, ...в последствии, вы их можете убрать, когда программа будет исправно работать.
Руководитель проекта MMO 2D RPG: Настоящее имя Денис Стрижак (10.05.1981-6.02.2019) Мир духу его
Beermonza вне форума Ответить с цитированием
Старый 15.04.2008, 16:16   #5
InseR
Пользователь
 
Регистрация: 01.06.2007
Сообщений: 59
По умолчанию

Блин,так и знал,что в этом дело. Спасибо вам огромное Beermonza.
InseR вне форума Ответить с цитированием
Старый 16.04.2008, 09:50   #6
InseR
Пользователь
 
Регистрация: 01.06.2007
Сообщений: 59
По умолчанию

Возникла проблема при попытке сделать приват.

Клиент

Код:
// По нажатию на элемент списка UserBox
procedure TMainForm.UserListClick(Sender: TObject);
begin
MessageEdit.Text:=': '+UserList.Items[UserList.ItemIndex]+' : ';
end;
Код:
// Отправка приватного сообщения от клиента
procedure TMainForm.SendBtnClick(Sender: TObject);
var
text:string;
begin
if Copy(MessageEdit.Text,1,2) = ': ' then
begin
text:=MessageEdit.Text;
Delete(text,1,2);
text:='#P'+IntToStr(UserList.ItemIndex)+' [Private] '+OptionsForm.NickEdit.Text+': '+text;
ClientSocket.Socket.SendText(text);
MessageEdit.Clear;
end;
Код:
// Принятие приватного сообщения от сервера
if Copy(text,1,2) = '#P' then
 begin
 Delete(text,1,2);
 MessageMemo.Lines.Add(text);
 end;
------------------------------------------------------------------

Сервер

Код:
// Принятие,редактирование и отправка сообщения на указанный клиент
if copy(text,1,2) = '#P' then
 begin
 Delete(text,1,2);
 if copy (text,2,2)= ' ' then
 begin
 priv:=StrToInt(copy(text,1,1));
 Delete(text,1,2);
 text:='#P'+text;
 ServerSocket.Socket.Connections[priv].SendText(text);
 end
 else
 priv:=StrToInt(copy(text,1,2));
 Delete(text,1,2);
 text:='#P'+text;
 ServerSocket.Socket.Connections[priv].SendText(text);
 end;

Если выбрать первого человека в списке и попытатся отправить ему приватное сообщение,то вылетит ошибка - " 0 - is not a valid integer value",а если попытаться отправить человеку стоящему ниже по списку,то вообще ничего не происходит,вернее сообщение отправляется,но до получателя оно недоходит.
InseR вне форума Ответить с цитированием
Старый 16.04.2008, 14:49   #7
Demone$$a
Новичок
Джуниор
 
Регистрация: 14.04.2008
Сообщений: 1
По умолчанию

Beermonza, вы просто ЧЕЛОВЕЧИЩЕ)))
Чат очень помог в понимании работы отдельных компонентов Delphi, просто огромный вам респект и уважуха ))
F@iTH
Demone$$a вне форума Ответить с цитированием
Старый 16.04.2008, 15:22   #8
Beermonza
Инженер ИС
Старожил
 
Аватар для Beermonza
 
Регистрация: 13.12.2006
Сообщений: 2,671
По умолчанию Приват...

InseR, вот тут у вас проблемы:

Код:
// По нажатию на элемент списка UserBox
procedure TMainForm.UserListClick(Sender: TObject);
begin
  MessageEdit.Text:=': '+UserList.Items[UserList.ItemIndex]+' : ';
end;
По всей видимости вы хотите извлечь индекс элемента списка, но почему-то оставляете UserList.Items[UserList.ItemIndex] в числовом типе не переводя в строковый, ... и сама эта запись неверная. Когда в списке вы хотите выделить элемент, то в коде должен участвовать оператор выделения - свойство Selected списка. Чуть выше я описывал процедуру извлечения данных со списка, попробуйте так:

Код:
// По нажатию на элемент списка UserBox
procedure TMainForm.UserListClick(Sender: TObject);
begin
  // если список не пустой и выделена запись
  if (UserList.Items.Count>0) and (UserList.SelCount>0) then
    begin
      //запишем индекс
      MessageEdit.Text:=': '+IntToStr(UserList.Selected.Index)+' : ';
    end;
end;
Руководитель проекта MMO 2D RPG: Настоящее имя Денис Стрижак (10.05.1981-6.02.2019) Мир духу его
Beermonza вне форума Ответить с цитированием
Старый 16.04.2008, 16:38   #9
InseR
Пользователь
 
Регистрация: 01.06.2007
Сообщений: 59
По умолчанию

Спасибо,попробую.
InseR вне форума Ответить с цитированием
Старый 17.04.2008, 07:33   #10
InseR
Пользователь
 
Регистрация: 01.06.2007
Сообщений: 59
По умолчанию

Цитата:
Сообщение от Beermonza Посмотреть сообщение
InseR, вот тут у вас проблемы:

Код:
// По нажатию на элемент списка UserBox
procedure TMainForm.UserListClick(Sender: TObject);
begin
  MessageEdit.Text:=': '+UserList.Items[UserList.ItemIndex]+' : ';
end;
По всей видимости вы хотите извлечь индекс элемента списка, но почему-то оставляете UserList.Items[UserList.ItemIndex] в числовом типе не переводя в строковый, ... и сама эта запись неверная. Когда в списке вы хотите выделить элемент, то в коде должен участвовать оператор выделения - свойство Selected списка. Чуть выше я описывал процедуру извлечения данных со списка, попробуйте так:

Код:
// По нажатию на элемент списка UserBox
procedure TMainForm.UserListClick(Sender: TObject);
begin
  // если список не пустой и выделена запись
  if (UserList.Items.Count>0) and (UserList.SelCount>0) then
    begin
      //запишем индекс
      MessageEdit.Text:=': '+IntToStr(UserList.Selected.Index)+' : ';
    end;
end;

Ваш способ неработает,при компиляции выдает ошибку,да и при написании строки userList.Selected.index тоже.Я использую не ListView,а ListBox,возможно ошибка из-за этого?
InseR вне форума Ответить с цитированием
Ответ


Купить рекламу на форуме - 42 тыс руб за месяц

Опции темы Поиск в этой теме
Поиск в этой теме:

Расширенный поиск


Похожие темы
Тема Автор Раздел Ответов Последнее сообщение
обновление в блоге - Создание клиент-сервера Pblog Обсуждение статей 0 03.10.2007 17:12
обновление в блоге - Диплом. Создание и продвижение сайта - готовь сани летом, а дипл Pblog Обсуждение статей 0 31.08.2007 20:00
обновление в блоге - USB Холодильник Pblog Обсуждение статей 0 25.06.2007 14:13
обновление в блоге - О ярлыках Pblog Обсуждение статей 0 27.05.2007 03:17