Курсовая работа: Проектирование и разработка программы ЧАТ для локальной сети
NikEdit (Edit)
TextEdit (Edit)
ChatMemo (Memo)
ClientBtn (Button)
ServerBtn (Button)
SendBtn (Button)
ServerSocket (ServerSocket)
ClientSocket (ClientSocket)
UserListView (ListView)
ImageList (ImageList)
ServerTimer (Timer)
Компонентыизстандартногопакета Delphi ServerSocket и ClientSocket невсегдамогутбытьотображенывпалитре Internet, иихнужнозагрузитьследующимобразом: выбратьменю: Component - Install Packages… - Add., далеенужноуказатьфайл …\bin\dclsockets70.bpl.
Как правило, разработка любой программы начинается с определения задач, которые она должна выполнять, и определения уже на этом этапе нужных компонентов. Программа представляет собой чат для нескольких пользователей, каждый из которых может быть как сервером, так и клиентом, значит, кидаем в форму компоненты ServerSocket и ClientSocket. Важным параметром для всех является порт. Только при одинаковом значении свойства Port, связь между ними установится. Кинем в форму компонент Edit, чтобы оперативно изменять порт, назовем его PortEdit. Для соединения с сервером необходимо указывать IP сервера или его имя, поэтому кинем еще один Edit, назовем его HostEdit. Так же нам понадобятся еще два Edit’а для указания ника и ввода текста сообщения, назовем их NikEdit и TextEdit, соответственно. Текст принимаемых и отправляемых сообщений будет отображаться в Memo, кинем его в форму и назовем ChatMemo. Установим сразу вертикальную полосу прокрутки: ScrollBars = ssVertical, и свойство ReadOnly = True. Добавим клавиши управления Button: ServerBtn – для создания/закрытия сервера, ClientBtn – для подключения/отключения клиента к серверу, SendBtn - для отправки сообщений. Изменим Caption этих клавиш на “Создать сервер”, “Подключиться” и “Отправить”, соответственно. UserListView предназначен для вывода списка пользователей, который будет динамически обновляться при подключении или отключении пользователей. Сам компонент ListView настраивается как табличный отчет: свойство ViewStyle = vsReport (стиль таблицы), свойство ShowColumnHeaders = False (не показывать имена столбцов), свойство ReadOnly = True (только отображение), свойство SmallImages = ImageList (массив с иконками). Двойным кликом на компоненте ListView выводится редактор Editing UserListView.Columns. Добавляется один столбец (порядковый номер -0). В ImageList через Add закидываются иконки, в нашем случае две, с изображением силуэта пользователя: в белом – пометка сервера, в красном – пометка клиента.
Теперь разберем принцип работы сервера . Традиционно в ServerSocket для приема клиентских пакетов используется OnClientRead, но данный способ не очень удобен, ведь для идентификации пакета (кто прислал) потребуется повозиться со структурой “прием\ответ” и решить каким образом будет происходить синхронизация. Гораздо проще и эффективнее использовать цикл по числу пользователей, в котором ведется “прослушивание” всех каналов и обработка пакета, если он пришел на конкретный канал, закрепленный за конкретным пользователем. Процедура “прослушивания” каналов выполняется в теле таймера, интервал (Interval) работы которого можно изменять по необходимости (для чата нормально 500 мс, для игр нужно существенно меньше). Вот так выглядит общая структура процедуры опроса:
procedure TForm1.Timer1Timer(Sender: TObject);
begin// условие на наличие установленных каналов
if ServerSocket.Socket.ActiveConnections<>0 then begin // циклпосуществующимканалам
for i:=1 to ServerSocket.Socket.ActiveConnections do begin // сохранимпакет (еслиничегонеприслали, попакетпустой)
text:=ServerSocket.Socket.Connections.ReceiveText(); // условие, что пакет не пуст iftext<>” thenbegin{тут обработка строки, выделение составляющих кода команд (com) и пр.} // определение команд casecomofкод: begin{процедура} end; код: begin{процедура} end; ……………………………………. end; end; end; end; // разрешение на выполнение процедур обновления ifUpdDo=Truethenbegin{процедуры} // блокируем разрешение UpdDo:=False; end; end;
Если заметили, что цикл начинается с единицы, а в инициализации канала странное выражение (вместо логичного начала с нуля и инициализации), то такое решение существенным образом облегчает организацию ряда процедур. Например, в списке пользователей, сервер числится под номером “0”, а клиенты - начиная с “1”. Так же удобно совмещать количество каналов (ServerSocket.Socket.ActiveConnections) с процедурами определения активности пользователей. Последнее условие в теле таймера необходимо для задержки выполнения некоторых процедур обновления. Эти процедуры должны выполняться в самом конце “прослушивания” открытых каналов, и не всегда (если будет команда). Данный алгоритм применим практически к любого рода соединениям Клиент-сервер, в том числе и для игр.
Перейдем непосредственно к приложению чата и его процедурам. Проверок на корректность ввода значений в поля не будет. Создадим новый тип, для использования массива объектов, так гораздо удобнее:
Type TUserList = object Status: Byte; // 1 - сервер, 2 - клиент Rec: Boolean; // отметка записи пользователя в список Name: String; // имя (ник) Image: Byte; // индекс иконки end;
Вот переменные, которые понадобятся в программе:
var Form1: TForm1; i, j, com, ContList: Byte; len, pos, x: Word; text, StrUserList: String; UpdDo: Boolean; Buf: array[0..3] of Byte; UserMas: array[0..255] of TUserList; //массивобъектовUItems: TListItem;
Опишемпроцедуру OnCreate формы:
procedure TForm1.FormCreate(Sender: TObject); begin // заголовокформыCaption:='Многопользовательскийчат'; Application.Title:=Caption; // предложенноезначенияпортаPortEdit.Text:='Портсервера'; // адресприпроверкепрограммынаодномПК ("самнасебя") HostEdit.Text:='Адрессервера '; // введемникпо-умолчанию, остальныеполяпростоочистимNikEdit.Text:='Ананим'; TextEdit.Clear; ChatMemo.Lines.Clear; end;
Процедура “ прослушивания ” открытых каналов сервером , выглядиттак:procedure TForm1.ServerTimerTimer(Sender: TObject); begin // условиенаналичиеустановленныхканаловif ServerSocket.Socket.ActiveConnections<>0 then begin // циклпосуществующимканаламfor i:=1 to ServerSocket.Socket.ActiveConnections do begin // сохранимпакет (еслиничегонеприслали, попакетпустой)
text:=ServerSocket.Socket.Connections.ReceiveText(); // условие, что пакет не пуст if text<>” then begin // получим код команды, длину строки com:=StrToInt(Copy(text,1,1)); len:=Length(text)-1; // определение команд case com of // код приема сообщения 0: begin // добавим в ChatMemo сообщение клиента ChatMemo.Lines.Add(Copy(text,2,len)); // разошлем сообщение пользователям (кроме того, кто прислал) for j:=0 to ServerSocket.Socket.ActiveConnections-1 do begin if (j+1)<>i then ServerSocket.Socket.Connections[j].SendText(’0′+Copy(text,2,len)); end; end; // код приема ника клиента 1: begin // запишем в массив полученный ник UserMas.Name:=Copy(text,2,len); // отметим, что пользователь записан в список UserMas.Rec:=True; // обновляем список UpdateUserList; end; end; end; end; end; // разрешение на выполнение процедур обновления if UpdDo=True then begin // обновляем массив пользователей UpdateUserMas; // обновляем список пользователей UpdateUserList; // блокируем разрешение UpdDo:=False; end; end;
Перевод программы в режим сервера осуществляется клавишей “Создать сервер” (ServerBtn). Вот так выглядит процедура на нажатие клавиши ServerBtn (OnClick):